var newDatasets = [ { meta: { id: 1, name: 'A' }, data: [ {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}, {x: 50, y: 50} ] }, { meta: { id: 3, name: 'C' }, data: [ {x: 100, y: 10}, {x: 200, y: 20}, {x: 300, y: 30}, {x: 400, y: 40}, {x: 500, y: 50} ] } ]; var firstDatasets = [ { meta: { id: 1, name: 'A' }, data: [ {x: 10, y: 10}, {x: 20, y: 20}, {x: 30, y: 30}, {x: 40, y: 40}, {x: 50, y: 50} ] }, { meta: { id: 2, name: 'B' }, data: [ {x: 10, y: 1}, {x: 20, y: 2}, {x: 30, y: 3}, {x: 40, y: 4}, {x: 50, y: 5} ] } ]; var __width = 500; var __height = 500; var _margin = {top: 50, right: 50, bottom: 50, left: 50}; var getId = function(dataset) { return dataset.meta.id; }; var getLabel = function(dataset) { return dataset.meta.name; }; var getData = function(dataset) { return dataset.data; }; var getLastData = function(dataset) { return dataset.data[dataset.data.length - 1]; }; var getX = function(data) { return data.x; }; var getY = function(data) { return data.y; }; var _width = __width - _margin.left - _margin.right; var _height = __height - _margin.top - _margin.bottom; var xScale = d3.scale.linear().range([0, _width]); var yScale = d3.scale.linear().range([_height, 0]); var line = d3.svg.line().interpolate('linear') .x(function(data) { return xScale(getX(data)); }) .y(function(data) { return yScale(getY(data)); }) var dataTooltip = d3.select('#container') .append('div') .classed('data-tooltip', true); var svg = d3.select('#container') .append('svg') .attr('width', __width) .attr('height', __height); var g = svg.append('g') .attr('transform', 'translate(' + _margin.left + ',' + _margin.top + ')'); render(firstDatasets); function render(datasets) { var xs = _.chain(datasets).map(getData) .flatten().map(getX).value(); var ys = _.chain(datasets).map(getData) .flatten().map(getY).value(); xScale.domain(d3.extent(xs)); yScale.domain(d3.extent(ys)); var xAxis = d3.svg.axis().scale(xScale).orient('bottom'); var yAxis = d3.svg.axis().scale(yScale).orient('left'); if (g.selectAll('g.x.axis').empty()) { g.append('g') .classed('x axis', true) .attr('transform', 'translate(0,' + _height + ')') .call(xAxis); } else { g.selectAll('g.x.axis') .transition().duration(1000) .call(xAxis); } if (g.selectAll('g.y.axis').empty()) { g.append('g') .classed('y axis', true) .call(yAxis); } else { g.selectAll('g.y.axis') .transition().duration(1000) .call(yAxis); } // bind dataset var datasetContainers = g.selectAll('g.dataset') .data(datasets, getId); // enter datasetContainers .enter().append('g') .classed('dataset', true) .attr('id', function(dataset) { return getId(dataset); }) .on('mouseover', function(dataset) { var self = this; var gs = d3.selectAll('g.dataset'); gs.filter(function() { return self.id !== this.id; }) .transition().duration(1000) .style('opacity', 0.2); }) .on('mouseout', function(dataset) { var self = this; var gs = d3.selectAll('g.dataset'); gs.filter(function() { return self.id !== this.is; }) .transition().duration(1000) .style('opacity', 1.0); }) .style('opacity', 0.0) .transition().duration(1000) .style('opacity', 1.0); datasetContainers.selectAll('path.line') .data(function(dataset) { return [dataset]; }) .enter().append('path') .classed('line', true) .attr('d', function(dataset) { return line(getData(dataset)); }); datasetContainers.selectAll('circle.point') .data(function(dataset) { return getData(dataset); }) .enter().append('circle') .classed('point', true) .attr('cx', function(data) { return xScale(getX(data)); }) .attr('cy', function(data) { return yScale(getY(data)); }) .attr('r', 4) .on('mouseover', function(data) { d3.select(this) .transition().duration(1000) .attr('r', 5) .style('stroke-width', '3px'); var dataTooltipContainer = dataTooltip.data([data]); dataTooltipContainer.append('div') .classed('x', true) .html(getX); dataTooltipContainer.append('div') .classed('y', true) .html(getY); dataTooltipContainer.append('div') .classed('data-tooltip-arrow', true); dataTooltip .transition().duration(1000) .style('left', (+d3.select(this).attr('cx') + _margin.left) + 'px') .style('top', (+d3.select(this).attr('cy') + _margin.top) + 'px') .style('opacity', 1) .style('display', 'block'); }) .on('mouseout', function(data) { d3.select(this) .transition().duration(1000) .attr('r', 4) .style('stroke-width', '2px'); // hide tooltip dataTooltip .transition().duration(1000) .style('opacity', 0) .style('display', 'none'); dataTooltip.selectAll('.x').remove(); dataTooltip.selectAll('.y').remove(); dataTooltip.selectAll('.data-tooltip-arrow').remove(); }); datasetContainers.selectAll('text.label') .data(function(dataset) { return [dataset]; }) .enter().append('text') .classed('label', true) .attr('x', function(dataset) { return xScale(getX(getLastData(dataset))); }) .attr('y', function(dataset) { return yScale(getY(getLastData(dataset))); }) .text(getLabel); // exit datasetContainers .exit() .transition().duration(1000) .style('opacity', 0.0) .remove(); // update datasetContainers .selectAll('path.line') .transition().duration(1000) .attr('d', function(dataset) { return line(getData(dataset)); }); datasetContainers .selectAll('circle.point') .transition().duration(1000) .attr('cx', function(data) { return xScale(getX(data)); }) .attr('cy', function(data) { return yScale(getY(data)); }); datasetContainers .selectAll('text.label') .transition().duration(1000) .attr('x', function(dataset) { return xScale(getX(getLastData(dataset))); }) .attr('y', function(dataset) { return yScale(getY(getLastData(dataset))); }); }