Built with blockbuilder.org
forked from areologist's block: stock data (line)
license: mit |
Built with blockbuilder.org
forked from areologist's block: stock data (line)
{ | |
"all": [ | |
{ | |
"year": 2017, | |
"months": [ | |
{ | |
"month": 1, | |
"totalNet": 130000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 2, | |
"totalNet": 128000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 3, | |
"totalNet": 145000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 4, | |
"totalNet": 142000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 5, | |
"totalNet": 163899, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 6, | |
"totalNet": 158000, | |
"totalIn": 0, | |
"totalOut": 0 | |
} | |
] | |
}, | |
{ | |
"year": 2016, | |
"months": [ | |
{ | |
"month": 1, | |
"totalNet": 142000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 2, | |
"totalNet": 141000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 3, | |
"totalNet": 130000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 4, | |
"totalNet": 152000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 5, | |
"totalNet": 150000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 6, | |
"totalNet": 153000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 7, | |
"totalNet": 149000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 8, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 9, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 10, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 11, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 12, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
} | |
] | |
}, | |
{ | |
"year": 2015, | |
"months": [ | |
{ | |
"month": 1, | |
"totalNet": 123000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 2, | |
"totalNet": 123300, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 3, | |
"totalNet": 130000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 4, | |
"totalNet": 130400, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 5, | |
"totalNet": 125000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 6, | |
"totalNet": 122000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 7, | |
"totalNet": 138000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 8, | |
"totalNet": 146000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 9, | |
"totalNet": 144000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 10, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 11, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
}, | |
{ | |
"month": 12, | |
"totalNet": 140000, | |
"totalIn": 0, | |
"totalOut": 0 | |
} | |
] | |
} | |
] | |
} |
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
svg { | |
box-shadow: 1px 1px 10px 2px #ccc; | |
} | |
.y-label { | |
text-anchor: 'middle'; | |
font-size: 13px; | |
fill: #000; | |
fill-opacity: 0.5; | |
font-family: Helvetica; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
const margin = { top: 80, right: 65, bottom: 80, left: 65, }; | |
const fullWidth = 520; | |
const fullHeight = 300; | |
const width = fullWidth - margin.right - margin.left; | |
const height = fullHeight - margin.top - margin.bottom; | |
const svg = d3.select("body").append("svg") | |
.attr("width", fullWidth) | |
.attr("height", fullHeight) | |
//.call(responsivefy) | |
.append('g') | |
.attr('transform', `translate(${margin.left}, ${margin.top})`); | |
const parseTime = d3.timeParse('%Y-%m'); | |
let yScale, xScale; | |
const minMonth = 1; | |
const maxMonth = 8; | |
d3.json('data.json', (err, rawData) => { | |
const data = rawData.all | |
.map(y => { | |
const year = y.year; | |
const values = y.months | |
.map(m => ({ | |
month: m.month, | |
value: m.totalNet | |
})) | |
.filter(m => m.month >= minMonth && m.month <= maxMonth); | |
return { | |
year, | |
values | |
}; | |
}); | |
setup(data); | |
}); | |
function setup(data) { | |
// build y scale and axis | |
let min = d3.min(data, | |
year => d3.min(year.values, | |
d => d.value)); | |
let max = d3.max(data, | |
year => d3.max(year.values, | |
d => d.value)); | |
yScale = d3.scaleLinear() | |
.domain([min, max]) | |
.range([height, 0]); | |
const yAxis = d3.axisLeft(yScale) | |
.ticks(2); | |
//svg.append('g') | |
// .call(yAxis); | |
// build the x scale and axis | |
xScale = d3.scaleLinear() | |
.domain([minMonth, maxMonth]) | |
.range([0, width]); | |
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | |
const xAxis = d3.axisBottom(xScale) | |
.ticks(8) | |
.tickSize(0) | |
.tickFormat(m => months[m - 1]); | |
svg.append('g') | |
.attr('transform', `translate(0, ${height + 40})`) | |
.call(xAxis) | |
.attr('font-size', 12) | |
.attr('fill-opacity', 0.5) | |
.select('.domain').remove(); | |
update(data); | |
} | |
function update(data) { | |
const line = d3.line() | |
.x(d => xScale(d.month)) | |
.y(d => yScale(d.value)); | |
const colors = [ | |
{ fill: '#1170a7', stroke: '#004f9a' }, | |
{ fill: '#29b258', stroke: '#00ab3b' }, | |
{ fill: '#dadcde', stroke: '#bdbebd' } | |
]; | |
const lines = svg.selectAll('.line') | |
.data(data.reverse()) | |
.enter() | |
.append('g') | |
.attr('class', 'line'); | |
lines | |
.append('path') | |
.attr('d', d => line(d.values)) | |
.style('stroke-width', 2) | |
.style('stroke', (d, i) => colors[2 - i].stroke) | |
.style('fill', 'none'); | |
lines | |
.attr('transform', (d, i) => | |
`translate(0, ${fullHeight + 500 * i})`) | |
.transition() | |
.duration(1000) | |
.ease(d3.easeCubicInOut) | |
.attr('transform', `translate(0, 0)`); | |
data.forEach((d, i) => { | |
scatterPlot(d.values, colors[2 - i]); | |
checkbox(d.year, colors[2 - i]) | |
.attr('transform', `translate(${80 * i}, -100)`) | |
.style('opacity', 0) | |
.transition() | |
.delay(900) | |
.duration(1000) | |
.attr('transform', `translate(${80 * i}, -70)`) | |
.style('opacity', 1); | |
}); | |
// y-axis labels | |
const container = svg.append('g'); | |
container | |
.selectAll('text') | |
.data(yScale.domain().reverse()) | |
.enter() | |
.append('text') | |
.text(d => d3.format('$,.0f')(d)) | |
.attr('class', 'y-label') | |
.attr('text-anchor', 'middle') | |
.attr('transform', (d, i) => `translate(-20, ${(height + 45) * i - 20})`) | |
.style('opacity', 0) | |
.transition() | |
.delay(800) | |
.duration(1600) | |
.style('opacity', 1); | |
} | |
function scatterPlot(data, colors) { | |
const container = svg.append('g'); | |
container.selectAll('circle') | |
.data(data) | |
.enter() | |
.append('circle') | |
.attr('cx', 0) | |
.attr('cy', 0) | |
.attr('fill', colors.fill) | |
.attr('stroke', colors.stroke) | |
.attr('stroke-width', 1) | |
.attr('transform', d => `translate(${xScale(d.month)}, ${yScale(d.value)})`) | |
.attr('r', 0) | |
.style('opacity', 0.5) | |
.transition() | |
.delay(1000) | |
.duration(1000) | |
.attr('r', 6) | |
.style('opacity', 1); | |
} | |
function checkbox(data, color) { | |
const container = svg.append('g'); | |
const icon = container | |
.append('svg') | |
.attr('x', 0) | |
.attr('y', 0) | |
.attr('viewBox', '0 0 1000 1000') | |
.attr('width', 18) | |
.attr('height', 18) | |
.style('opacity', 0.2); | |
icon.append('g') | |
.append('path') | |
.attr('d', 'M924.7,10H75.3C39.2,10,10,39.2,10,75.3v849.3c0,36.1,29.2,65.3,65.3,65.3h849.3c36.1,0,65.3-29.2,65.3-65.3V75.3C990,39.2,960.8,10,924.7,10z M407.1,765.4L141.5,504.9l81.7-80.5l182.8,180l371.7-365.9l81.7,80.4L407.1,765.4z'); | |
container.append('text') | |
.text(`${data}`) | |
.style('font-family', 'Helvetica') | |
.style('font-size', 14) | |
.style('font-weight', 'bold') | |
.style('fill', `${color.stroke}`) | |
.attr('transform', 'translate(26, 14)'); | |
return container; | |
} | |
///// | |
function responsivefy(svg) { | |
// get container + svg aspect ratio | |
var container = d3.select(svg.node().parentNode), | |
width = parseInt(svg.style("width")), | |
height = parseInt(svg.style("height")), | |
aspect = width / height; | |
// add viewBox and preserveAspectRatio properties, | |
// and call resize so that svg resizes on inital page load | |
svg.attr("viewBox", "0 0 " + width + " " + height) | |
.attr("preserveAspectRatio", "xMinYMid") | |
.call(resize); | |
// to register multiple listeners for same event type, | |
// you need to add namespace, i.e., 'click.foo' | |
// necessary if you call invoke this function for multiple svgs | |
// api docs: https://github.com/mbostock/d3/wiki/Selections#on | |
d3.select(window).on("resize." + container.attr("id"), resize); | |
// get width of container and resize svg to fit it | |
function resize() { | |
var targetWidth = parseInt(container.style("width")); | |
svg.attr("width", targetWidth); | |
svg.attr("height", Math.round(targetWidth / aspect)); | |
} | |
} | |
</script> | |
</body> |