xxxxxxxxxx
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="debounce.js"></script>
<style>
body {
font-family: "avenir next", Arial, sans-serif;
font-size: 12px;
margin: 0;
color: #666;
}
#vis {
min-width: 300px;
max-width: 900px;
margin: 0 auto;
}
.axis path, .axis line {
fill: none;
stroke: #666;
shape-rendering: crispEdges;
}
.line {
fill: none;
stroke-width: 1.5px;
}
#line-Highlight {
stroke-width: 2px;
}
.baseline {
stroke: #666;
}
</style>
</head>
<body>
<div id='vis'></div>
<script>
const margin = { top: 50, right: 70, bottom: 30, left: 50 };
const $chart = d3.select('#vis');
const $svg = $chart.append('svg');
const $plot = $svg.append('g')
.attr('transform', `translate(${margin.left}, ${margin.top})`);
const parseDate = d3.timeParse('%Y');
// set up scales
const x = d3.scaleTime();
const y = d3.scaleLinear();
const colour = d3.scaleOrdinal(d3.schemeCategory10);
const xAxis = d3.axisBottom()
.scale(x)
.ticks(5);
const yAxis = d3.axisLeft()
.scale(y)
.ticks(10);
const line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value))
.curve(d3.curveMonotoneX);
function render() {
const width = parseInt($chart.node().offsetWidth) - margin.left - margin.right;
const height = parseInt(width * 0.6) - margin.top - margin.bottom;
$svg.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
x.range([0, width]);
y.range([height, 0]);
$plot.select('.axis.x')
.attr('transform', `translate(0, ${height})`)
.call(xAxis)
.select('.domain').remove();
$plot.select('.axis.y')
.call(yAxis)
.call(g => g.select('.tick:last-of-type text').clone()
.attr('x', 3)
.attr('text-anchor', 'start')
.attr('font-weight', 600)
.text('$ billion'))
.select('.domain').remove();
$plot.select('.baseline')
.attr('x1', 0)
.attr('x2', width)
.attr('y1', y(0))
.attr('y2', y(0))
.attr('fill', 'none')
.attr('stroke', '#000')
.attr('stroke-width', '1px')
.attr('shape-rendering', 'crispEdges')
.attr('stroke-dasharray', '3, 3')
const path = $plot.selectAll('path')
.attr('d', d => line(d.values))
.attr('stroke', d => colour(d.name))
.attr('opacity', d => d.name == 'Highlight' ? 1 : 0.5)
.attr('id', (d, i) => `line-${d.name}`)
path.each((d, i) => {
const sel = d3.select(`#line-${d.name}`);
const length = sel.node().getTotalLength();
sel.attr('stroke-dasharray', `${length} ${length}`)
.attr('stroke-dashoffset', length)
.transition()
.duration(5000)
.attr('stroke-dashoffset', 0)
})
$plot.selectAll('.line-label')
.attr('transform', d => {
return `translate(${x(d.value.date)}, ${y(d.value.value)})`;
})
.attr('x', 5)
.attr('dy', '.35em')
.attr('fill', d => colour(d.name))
.attr('font-weight', d => d.name == 'Highlight' ? 700 : 400)
.text(d => d.name)
.attr('opacity', 0)
.transition()
.delay(4000)
.duration(200)
.attr('opacity', 1)
}
function bindData(rawdata) {
// column headings, for each line
const keys = rawdata.columns.filter(key => key != 'year');
rawdata.forEach(d => {
d.year = parseDate(d.year);
})
const data = keys.map(name => {
return {
name,
values: rawdata.map(d => {
return {date: d.year, value: +d[name]};
})
}
});
colour.domain(keys);
x.domain(d3.extent(rawdata, d => d.year));
y.domain([
d3.min(data, c => d3.min(c.values, v => v.value)),
d3.max(data, c => d3.max(c.values, v => v.value))
]).nice();
// bind data to DOM elements
const $lines = $plot.append('g')
.attr('class', 'lines')
.selectAll('.line')
.data(data)
.enter()
.append('g')
.attr('class', 'line')
$lines.append('path')
.attr('class', 'path')
$lines.append('text')
.datum(d => {
return {
name: d.name,
value: d.values[d.values.length - 1]
}
})
.attr('class', 'line-label')
.attr('opacity', 0)
$plot.append('g')
.attr('class', 'axis x');
$plot.append('g')
.attr('class', 'axis y');
$plot.append('line')
.attr('class', 'baseline')
window.addEventListener('resize', debounce(render, 200));
render();
}
function init() {
d3.csv('dummy-data.csv').then(bindData);
}
init();
</script>
</body>
https://d3js.org/d3.v5.min.js