Enter, exit and update by datasets. Using as a component (Angular, React, ...).
forked from hironow's block: D3 linepoints chart by datasets
Enter, exit and update by datasets. Using as a component (Angular, React, ...).
forked from hironow's block: D3 linepoints chart by datasets
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <title>Title</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script> | |
| <link rel="stylesheet" type="text/css" href="main.css"> | |
| </head> | |
| <body> | |
| <div id="container"></div> | |
| <script src="main.js"></script> | |
| </body> | |
| </html> | 
| #container { | |
| background-color: #EEE; | |
| } | |
| .data-tooltip { | |
| position: absolute; | |
| color: #fff; | |
| background-color: #333; | |
| } | |
| .data-tooltip-arrow { | |
| width: 0; | |
| height: 0; | |
| border-right: 5px solid transparent; | |
| border-left: 5px solid transparent; | |
| border-top: 10px solid #000; | |
| position: absolute; | |
| bottom: -10px; | |
| } | |
| .axis path, | |
| .axis line { | |
| fill: none; | |
| stroke: #333; | |
| shape-rendering: crispEdges; | |
| } | |
| .axis text { | |
| font-family: sans-serif; | |
| font-size: 11px; | |
| } | |
| path.line { | |
| fill: none; | |
| stroke: #333; | |
| stroke-width: 2px; | |
| } | |
| text.label { | |
| fill: #333; | |
| stroke: none; | |
| } | 
| 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))); }); | |
| } |