var margin = { left: 30, top: 20, right: 20, bottom: 20 }; var width = Math.min($('#vis').width(), 800) - margin.left - margin.right; var height = width * 2 / 3; var vis = d3.select('#vis').insert('svg', ':first-child') .attr({ width: width + margin.left + margin.right, height: height + margin.top + margin.bottom }); var wrapper = vis.append('g') .attr('class', 'chordWrapper') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); var opacityCircles = 0.7; var color = d3.scale.ordinal() .domain(['Deposit', 'Receive', 'Spend', 'Withdrawal']) .range(['#00986F', '#41A5D1', '#695998', '#CD5053']); var xScale = d3.scale.log() .domain(d3.extent(countries, function(d) { return d.create_date; })) .range([0, width]) .nice(); var xAxis = d3.svg.axis() .orient('bottom') .ticks(2) .tickFormat(function (d) { return xScale.tickFormat(8, function(d) { var prefix = d3.formatPrefix(d); return "$" + prefix.scale(d) + prefix.symbol; })(d); }) .scale(xScale); wrapper.append('g') .attr('class', 'x axis') .attr('transform', 'translate(' + 0 + ',' + height + ')') .call(xAxis); var yScale = d3.scale.linear() .range([height, 0]) .domain(d3.extent(countries, function(d) { return d.usd_value; })) .nice(); var yAxis = d3.svg.axis() .orient('left') .ticks(6) .scale(yScale); wrapper.append('g') .attr('class', 'y axis') .attr('transform', 'translate(' + 0 + ',' + 0 + ')') .call(yAxis); var circleGroup = wrapper.append('g') .attr('class', 'circleWrapper'); circleGroup.selectAll('countries') .data(countries.sort(function(a, b) { return b.risk_score > a.risk_score; })) .enter().append('circle') .attr('class', function(d, i) { return 'countries ' + d.id; }) .style('opacity', opacityCircles) .style('fill', function(d) { return color(d.activity); }) .attr('cx', function(d) { return xScale(d.create_date); }) .attr('cy', function(d) { return yScale(d.usd_value); }) .attr('r', 9); var voronoi = d3.geom.voronoi() .x(function(d) { return xScale(d.create_date); }) .y(function(d) { return yScale(d.usd_value); }) .clipExtent([[0, 0], [width, height]]); var voronoiGroup = wrapper.append('g') .attr('class', 'voronoiWrapper'); voronoiGroup.selectAll('path') .data(voronoi(countries)) .enter().append('path') .attr('d', function(d, i) { return 'M' + d.join('L') + 'Z'; }) .datum(function(d, i) { return d.point; }) .attr('class', function(d, i) { return 'voronoi ' + d.id; }) .style('fill', 'none') .style('pointer-events', 'all') .on('mouseover', showTooltip) .on('mouseout', removeTooltip); wrapper.append('g') .append('text') .attr('class', 'x title') .attr('text-anchor', 'end') .style('font-size', '12px') .attr('transform', 'translate(' + width + ',' + (height - 10) + ')') .text('Date'); wrapper.append('g') .append('text') .attr('class', 'y title') .attr('text-anchor', 'end') .style('font-size', '12px') .attr('transform', 'translate(18, 0) rotate(-90)') .text('Transaction Amount'); var legendMargin = { left: 5, top: 10, right: 5, bottom: 10 }; var legendWidth = 145; var legendHeight = 270; var svgLegend = d3.select('#legend').append('svg') .attr('width', (legendWidth + legendMargin.left + legendMargin.right)) .attr('height', (legendHeight + legendMargin.top + legendMargin.bottom)); var legendWrapper = svgLegend.append('g') .attr('class', 'legendWrapper') .attr('transform', 'translate(' + legendMargin.left + ',' + legendMargin.top +')'); var rectSize = 15; var rowHeight = 20; var maxWidth = 144; var legend = legendWrapper.selectAll('.legendSquare') .data(color.range()) .enter().append('g') .attr('class', 'legendSquare') .attr('transform', function(d, i) { return 'translate(' + 0 + ',' + (i * rowHeight) + ')'; }) .style('cursor', 'pointer') .on('mouseover', selectLegend(0.02)) .on('mouseout', selectLegend(opacityCircles)) .on('click', clickLegend); legend.append('rect') .attr('width', maxWidth) .attr('height', rowHeight) .style('fill', 'white'); legend.append('rect') .attr('width', rectSize) .attr('height', rectSize) .style('fill', function(d) { return d; }); legend.append('text') .attr('transform', 'translate(' + 22 + ',' + (rectSize / 2) + ')') .attr('class', 'legendText') .style('font-size', '10px') .attr('dy', '.35em') .text(function(d, i) { return color.domain()[i]; }); function bubbleLegend(wrapperVar, scale, sizes, titleName) { var legendSize1 = sizes[0]; var legendSize2 = sizes[1]; var legendSize3 = sizes[2]; var legendCenter = 0; var legendBottom = 50; var legendLineLength = 25; var textPadding = 5; var numFormat = d3.format(','); wrapperVar.append('text') .attr('class','legendTitle') .attr('transform', 'translate(' + legendCenter + ',' + 0 + ')') .attr('x', 0 + 'px') .attr('y', 0 + 'px') .attr('dy', '1em') .text(titleName); wrapperVar.append('circle') .attr('r', scale(legendSize1)) .attr('class','legendCircle') .attr('cx', legendCenter) .attr('cy', (legendBottom-scale(legendSize1))); wrapperVar.append('circle') .attr('r', scale(legendSize2)) .attr('class','legendCircle') .attr('cx', legendCenter) .attr('cy', (legendBottom-scale(legendSize2))); wrapperVar.append('circle') .attr('r', scale(legendSize3)) .attr('class','legendCircle') .attr('cx', legendCenter) .attr('cy', (legendBottom-scale(legendSize3))); wrapperVar.append('line') .attr('class','legendLine') .attr('x1', legendCenter) .attr('y1', (legendBottom - 2 * scale(legendSize1))) .attr('x2', (legendCenter + legendLineLength)) .attr('y2', (legendBottom - 2 * scale(legendSize1))); wrapperVar.append('line') .attr('class','legendLine') .attr('x1', legendCenter) .attr('y1', (legendBottom - 2 * scale(legendSize2))) .attr('x2', (legendCenter + legendLineLength)) .attr('y2', (legendBottom - 2 * scale(legendSize2))); wrapperVar.append('line') .attr('class','legendLine') .attr('x1', legendCenter) .attr('y1', (legendBottom - 2 * scale(legendSize3))) .attr('x2', (legendCenter + legendLineLength)) .attr('y2', (legendBottom - 2 * scale(legendSize3))); wrapperVar.append('text') .attr('class','legendText') .attr('x', (legendCenter + legendLineLength + textPadding)) .attr('y', (legendBottom - 2 * scale(legendSize1))) .attr('dy', '0.25em') .text('$ ' + numFormat(Math.round(legendSize1 / 1e9)) + ' B'); wrapperVar.append('text') .attr('class','legendText') .attr('x', (legendCenter + legendLineLength + textPadding)) .attr('y', (legendBottom - 2 * scale(legendSize2))) .attr('dy', '0.25em') .text('$ ' + numFormat(Math.round(legendSize2 / 1e9)) + ' B'); wrapperVar.append('text') .attr('class','legendText') .attr('x', (legendCenter + legendLineLength + textPadding)) .attr('y', (legendBottom - 2 * scale(legendSize3))) .attr('dy', '0.25em') .text('$ ' + numFormat(Math.round(legendSize3 / 1e9)) + ' B'); } function selectLegend(opacity) { return function(d, i) { var chosen = color.domain()[i]; wrapper.selectAll('.countries') .filter(function(d) { return d.activity !== chosen; }) .transition() .style('opacity', opacity); }; } function clickLegend(d, i) { event.stopPropagation(); d3.selectAll('.legendSquare') .on('mouseover', null) .on('mouseout', null); var chosen = color.domain()[i]; wrapper.selectAll('.countries') .style('opacity', opacityCircles) .style('visibility', function(d) { if (d.activity !== chosen) { return 'hidden'; } else { return 'visible'; } }); wrapper.selectAll('.voronoi') .on('mouseover', function(d, i) { if(d.activity !== chosen) { return null; } else { return showTooltip.call(this, d, i); } }) .on('mouseout', function(d, i) { if(d.activity !== chosen) { return null; } else { return removeTooltip.call(this, d, i); } }); } function resetClick() { d3.selectAll('.legendSquare') .on('mouseover', selectLegend(0.02)) .on('mouseout', selectLegend(opacityCircles)); wrapper.selectAll('.countries') .style('opacity', opacityCircles) .style('visibility', 'visible'); wrapper.selectAll('.voronoi') .on('mouseover', showTooltip) .on('mouseout', function (d, i) { removeTooltip.call(this, d, i); }); } d3.select('body').on('click', resetClick); function removeTooltip (d, i) { var element = d3.selectAll('.countries.' + d.id); element.style('opacity', opacityCircles); $('.popover').each(function() { $(this).remove(); }); d3.selectAll('.guide') .transition().duration(200) .style('opacity', 0) .remove(); } function showTooltip (d, i) { var element = d3.selectAll('.countries.' + d.id); $(element).popover({ placement: 'auto top', container: '#vis', trigger: 'manual', html : true, content: function() { return '' + d.activity + ": $" + d.usd_value + ''; } }); $(element).popover('show'); element.style('opacity', 1); wrapper.append('g') .attr('class', 'guide') .append('line') .attr('x1', element.attr('cx')) .attr('x2', element.attr('cx')) .attr('y1', element.attr('cy')) .attr('y2', (height)) .style('stroke', element.style('fill')) .style('opacity', 0) .style('pointer-events', 'none') .transition().duration(200) .style('opacity', 0.5); wrapper.append('g') .attr('class', 'guide') .append('line') .attr('x1', +element.attr('cx')) .attr('x2', 0) .attr('y1', element.attr('cy')) .attr('y2', element.attr('cy')) .style('stroke', element.style('fill')) .style('opacity', 0) .style('pointer-events', 'none') .transition().duration(200) .style('opacity', 0.5); }