function tooltipper(){ var config = { left: 0, top: 0, text: '', isVisible: false }; var tooltip = d3.select('body') .append('div') .attr({ 'class': 'tooltip' }) .style({ position: 'absolute', 'pointer-events': 'none' }); var render = function(){ tooltip.style({ left: config.left + 'px', top: (config.top - 15) + 'px', display: config.isVisible ? 'block' : 'none' }) .html(config.text); return this; }; var exports = {}; exports.show = function(){ config.isVisible = true; render(); return this; }; exports.hide = function(){ config.isVisible = false; render(); return this; }; exports.text = function(text){ config.text = text; return this; }; exports.position = function(position){ config.left = position[0]; config.top = position[1]; return this; }; return exports } function scatterPlot() { var margin = {top: 40, right: 60, bottom: 60, left: 40}, width = 600, height = 400, emphasizedCountries = [], xScale = d3.scale.log(), yScale = d3.scale.linear(), sizeScale = d3.scale.sqrt(), colorScale = d3.scale.category10(), xAxis = d3.svg.axis().scale(xScale).orient('bottom').tickSize(6,1).ticks(12, d3.format('s')), yAxis = d3.svg.axis().scale(yScale).orient('left').tickSize(6, 1), labelY = '', valueName = '', dataMinX = null, dataMaxX = null, dataMinY = null, dataMaxY = null, year = null, dataMaxSize = null, tooltip = tooltipper(), chartTitle = '', dispatch = d3.dispatch('info'), showBackgroundLines = false; function chart(selection) { selection.each(function(data) { var line = d3.svg.line() .x(function(d) { return xScale(d[0]); }) .y(function(d) { return yScale(d[1]); }); var chartHeight = height - margin.top - margin.bottom, chartWidth = width - margin.left - margin.right; xScale .domain([dataMinX, dataMaxX]) .range([0, chartWidth]); yScale .domain([dataMinY, dataMaxY]) .range([chartHeight, 0]); var svg = d3.select(this).selectAll('svg').data([data.map(function(d, i){ return d[year - 1990]; })]); var svgEnter = svg.enter().append('svg'); var gEnter = svgEnter.append('g'); gEnter.append('g').attr({'class': 'lines'}); gEnter.append('g').attr({'class': 'bg-lines'}); gEnter.append('g').attr({'class': 'points'}); gEnter.append('g').attr({'class': 'x axis'}); gEnter.append('g').attr({'class': 'y axis'}); gEnter.append('g').attr({'class': 'point-labels'}); gEnter.append('text').attr({'class': 'title'}); gEnter.append('g').attr({'class': 'axis-labels'}); svg.attr({ width: width, height: height }); var g = svg.select('g') .attr({transform: 'translate(' + margin.left + ',' + margin.top + ')'}); var points = g.select('g.points') .selectAll('circle.point') .data(function(d){ return d; }); points.enter().append('circle') .attr({ 'class': 'point' }) .style({ display: 'none' }) .on('mousemove', function(d, i){ var mouse = d3.event; tooltip.position([mouse.x, mouse.y]) .text( 'Name: ' + d[2] + '
' + 'Region: ' + d[3] + '
' + 'GDP: ' + d[0] + '
' + valueName + ': ' + d[1] + '
') .show(); dispatch.info(d); }) .on('mouseout', function(d){ tooltip.hide(); }) .on('click', function(d){ var line = g.select('path.' + formatName(d[3])); line.classed('highlighted', !line.classed('highlighted')); var point = d3.select(this); point.classed('highlighted', !point.classed('highlighted')); var label = g.select('text.' + formatName(d[3])); label.classed('highlighted', !label.classed('highlighted')); }); points.transition().duration(100) //.attr({r: function(d) { return sizeScale(d[2]); }}) .attr({r: function(d) { return 2; }}) .style({ display: 'none' }) .filter(function(d){ return d && d[0] !== null && d[1] !== null; }) .attr({transform: function(d) { return 'translate(' + xScale(d[0]) + ',' + yScale(d[1]) + ')'; }}) .style({ display: 'block' }); var lines = g.select('g.lines') .selectAll('path.line') .data(data); lines.enter().append('path') .attr({ 'class': function(d){ var highlightClass = emphasizedCountries.indexOf(d[0][3]) > -1 ? ' highlighted' : ''; return formatName(d[0][3]) + ' line' + highlightClass; }, d: line, stroke: function(d, i){ return colorScale(d[0][2], i); }, fill: 'none' }); if(showBackgroundLines){ var lines = g.select('g.bg-lines') .selectAll('path.bg-line') .data(data); lines.enter().append('path') .attr({ 'class': function(d){ var highlightClass = emphasizedCountries.indexOf(d[0][3]) > -1 ? ' highlighted' : ''; return formatName(d[0][3]) + ' bg-line' + highlightClass; }, d: line, stroke: function(d, i){ return colorScale(d[0][2], i); }, fill: 'none' }); } var labels = g.select('g.point-labels') .selectAll('text.point-label') .data(data); labels.enter().append('text') .attr({ 'class': function(d){ var highlightClass = emphasizedCountries.indexOf(d[0][3]) > -1 ? ' highlighted' : ''; return formatName(d[0][3]) + ' point-label' + highlightClass; }, dx: 8, transform: function(d) { var lastPos = d[d.length-1]; return 'translate(' + xScale(lastPos[0]||1) + ',' + yScale(lastPos[1]||1) + ')'; } }) .html(function(d){ return d[0][3]; }); g.select('.x.axis') .attr({transform: 'translate(0,' + chartHeight + ')'}) .call(xAxis); g.select('.y.axis') .call(yAxis); g.select('text.title') .html(chartTitle) .attr({ x: function(){ return (chartWidth - this.getBBox().width) / 2; }, y: function(){ return -this.getBBox().height; } }); var axisLabels = gEnter.select('g.axis-labels'); axisLabels.append('text') .html(labelY) .attr({ 'class': 'y-axis-title', x: -30, y: -5 }); axisLabels.append('text') .html('$ per capita') .attr({ 'class': 'x-axis-title', x: chartWidth - margin.right + 35, y: chartHeight + 18 }); }); } function X(d) { return xScale(d[0]); } function Y(d) { return yScale(d[1]); } function formatName(name){ return name.replace(/[^a-zA-Z]/g, '_'); } chart.margin = function(_) { if (!arguments.length) return margin; margin = _; return chart; }; chart.width = function(_) { if (!arguments.length) return width; width = _; return chart; }; chart.height = function(_) { if (!arguments.length) return height; height = _; return chart; }; chart.labelY = function(_) { if (!arguments.length) return labelY; labelY = _; return chart; }; chart.valueName = function(_) { if (!arguments.length) return valueName; valueName = _; return chart; }; chart.dataMinX = function(_) { if (!arguments.length) return dataMinX; dataMinX = _; return chart; }; chart.dataMaxX = function(_) { if (!arguments.length) return dataMaxX; dataMaxX = _; return chart; }; chart.dataMinY = function(_) { if (!arguments.length) return dataMinY; dataMinY = _; return chart; }; chart.dataMaxY = function(_) { if (!arguments.length) return dataMaxY; dataMaxY = _; return chart; }; chart.dataMaxSize = function(_) { if (!arguments.length) return dataMaxSize; dataMaxSize = _; return chart; }; chart.year = function(_) { if (!arguments.length) return year; year = _; return chart; }; chart.chartTitle = function(_) { if (!arguments.length) return chartTitle; chartTitle = _; return chart; }; chart.highlights = function(_){ if (!arguments.length) return emphasizedCountries; emphasizedCountries = _; return chart; }; chart.showBackgroundLines = function(_){ showBackgroundLines = _; return chart; }; d3.rebind(chart, dispatch, 'on'); return chart; }