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))); }); | |
} |