Published
Edited
Nov 11, 2019
1 fork
2 stars
Insert cell
Insert cell
viewof date = {
var count = 0;
const svg = d3.select(DOM.svg(width, height))
.style("-webkit-tap-highlight-color", "transparent");
const lines = svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.append("line")
.attr("transform", d => "translate(0,"+ (y(1) - y(d.values[0].value / d.values[0].value)) + y(d.values[0].value)+")")
.attr("x1", margin.left)
.attr("x2", width - margin.right)
.attr("stroke", d => z(d.key))
.attr("stroke-width", "1")
.style("opacity", "0.5");
const ruleanchor = svg.append("g");
const rule = ruleanchor.append("g")
.append("line")
.attr("y1", height)
.attr("y2", 0)
.attr("stroke", "black")
.attr("stroke-width", "2");
const rulehorizontal = ruleanchor.append("g")
.append("line")
.attr("x1", 0)
.attr("x2", width)
.attr("stroke", "grey")
.attr("stroke-width", "2");

const serie = svg.append("g")
.style("font", "arial 13px sans-serif")
.selectAll("g")
.data(series)
.join("g");
const path = serie.append("path")
.attr("fill", "none")
.attr("stroke-width", 5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke", d => z(d.key))
.attr("d", d => line(d.values));
svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);
const box = serie.append("g")
.attr("display", "none");
box.append("rect")
.attr("x", -45)
.attr("y", -30)
.attr("width", 90)
.attr("height", 20)
.attr("fill", "white")
.style("opacity", "0.66");
const dot = box.append("g");
dot.append("circle")
.attr("r", 5);

dot.append("text")
.style("font", "bold 14px sans-serif")
.attr("text-anchor", "middle")
.attr("x", 0)
.attr("y", -15);
const increasenumbers = serie.append("text")
.attr("x", x.range()[1] + 3)
.attr("y", d => y(d.values[d.values.length - 1].value)*1.2)
.attr("dy", "0.35em")
.text(d => d.values[d.values.length - 1].valueabs)
.attr("fill", d => z(d.key));
const yeartext = serie.append("text")
.attr("x", x.range()[1] - 330)
.attr("y", 135)
.attr("dy", "0.35em")
.attr("width", 100)
.attr("fill", "grey")
.text("Since 1960 we saw an increase in");
const increasetext = serie.append("text")
.attr("x", x.range()[1] + 97)
.attr("y", d => y(d.values[d.values.length - 1].value)*1.2)
.attr("dy", "0.35em")
.attr("width", 100)
.attr("fill", "grey")
.attr("text-anchor", "end")
.text(d => d.key +":")
.attr("fill", d => z(d.key));
var legendHeight = 12,
interLegend = 4,
colorWidth = legendHeight*3;
var legendContainer = svg.append("g")
.classed("legend", true)
.attr("transform", "translate("+[width-136, height-50]+")");
var legends = legendContainer.selectAll(".legend")
.data(series)
.enter();
var legend = legends.append("g")
.classed("legend", true)
.attr("transform", function(d,i){
return "translate("+[0, -i*(legendHeight+interLegend)]+")";
})
legend.append("rect")
.classed("legend-color", true)
.attr("y", -legendHeight)
.attr("width", colorWidth)
.attr("height", legendHeight)
.style("fill", d=> z(d.key));
legend.append("text")
.style("font-size", "11px")
.attr("transform", "translate("+[-5, -2]+")")
.attr("text-anchor", "end")
.text(d => d.values[0].seriesname);


d3.transition()
.ease(d3.easeLinear)
.duration(4000)
.tween("date", () => {
const i = d3.interpolateDate(x.domain()[1], x.domain()[0]);
return t => update(i(t));
});
function update(date) {
date = d3.timeDay.round(date);
const i = bisect(series[0].values, date, 0, series[0].values.length - 1);
var toplineposition = 1000;
series.forEach(function(element) {
const i = bisect(element.values, date, 0, element.values.length - 1);
const lineposition = (y(1) - y(element.values[i].value / element.values[0].value)) + y(element.values[element.values.length-1].value);
if (lineposition < toplineposition) toplineposition = lineposition;
});

ruleanchor.attr("transform", `translate(${x(date) + 0.5},${toplineposition})`);

serie.attr("transform", ({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return `translate(0,${y(1) - y(values[i].value / values[0].value)})`;
});
box.attr("transform", ({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return `translate(${x(date) + 0.5},${y(values[i].value)})`;
});
dot.attr("fill",d=>z(d.key));
dot.select("text").text(d=>d.values[i].valueabs+" "+d.values[i].unit);
yeartext.attr("transform", ({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return `translate(-100,${-(y(1) - y(values[i].value / values[0].value))+toplineposition-148})`;
});
yeartext.text(({values}) => {
return "Compared to "+date.getFullYear()+" we saw an increase in";
});
increasetext.attr("transform", ({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return `translate(-100,${-(y(1) - y(values[i].value / values[0].value))+toplineposition-186})`;
});
increasenumbers.attr("transform", ({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return `translate(0,${-(y(1) - y(values[i].value / values[0].value))+toplineposition-186})`;
});
increasenumbers.text(({values}) => {
const i = bisect(values, date, 0, values.length - 1);
return "+"+(values.slice(-1)[0].valueabs-values[i].valueabs).toFixed(1)+" "+ values[0].unit;
});
svg.property("value", date).dispatch("input");
}
function moved() {
update(x.invert(d3.mouse(this)[0]));
/*
const ym = y.invert(d3.mouse(this)[1]);
const date = d3.timeDay.round(x.invert(d3.mouse(this)[0]));
const i = bisect(series[0].values, date, 0, series[0].values.length - 1);
//const s = series.reduce((a, b) => Math.abs((y(1) - y(a.values[i].value / a.values[0].value)) + y(a.values[i].value)) - y(ym) < Math.abs((y(1) - y(b.values[i].value / b.values[0].value)) + y(b.values[i].value) - y(ym)) ? a : b);
const s = series.reduce((a, b) => Math.abs((y(1) - y(a.values[i].value / a.values[0].value)) + y(a.values[i].value)-y(ym)) < Math.abs((y(1) - y(b.values[i].value / b.values[0].value)) + y(b.values[i].value)-y(ym)) ? a : b);
//path.attr("stroke", d => d === s ? null : "#ddd").filter(d => d === s).raise();
//dot.attr("transform", `translate(${x(date) + 0.5},${(y(1) - y(s.values[i].value / s.values[0].value)) + y(s.values[i].value)})`);
//box.attr("transform", `translate(${x(date) + 0.5},${(y(1) - y(s.values[i].value / s.values[0].value)) + y(s.values[i].value)})`);
box.attr("transform", d=>`translate(${x(date) + 0.5},0)`);
dot.attr("fill",d=>z(d.key));
dot.select("text").text(d=>d.values[i].valueabs+" "+d.values[i].unit);
*/
d3.event.preventDefault();
}
update(x.domain()[0]);
function entered() {
//path.style("mix-blend-mode", null).attr("stroke", "#ddd");
box.attr("display", null);
}

function left() {
//path.style("mix-blend-mode", "multiply").attr("stroke", null);
box.attr("display", "none");
}
if ("ontouchstart" in document) svg
.style("-webkit-tap-highlight-color", "transparent")
.on("touchmove", moved)
.on("touchstart", entered)
.on("touchend", left)
else svg
.on("mousemove", moved)
.on("mouseenter", entered)
.on("mouseleave", left);



return svg.node();
}
Insert cell
height = 700
Insert cell
margin = ({top: 40, right: 120, bottom: 40, left: 60})
Insert cell
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
.clamp(true)
Insert cell
y = {
/*const k = d3.nest()
.key(d => d.name)
.rollup(data => d3.max(data, d => d.value) / d3.min(data, d => d.value))
.entries(data)
.reduce((p, d) => Math.max(p, d.value), 0);*/
return d3.scaleLog()
.domain([0.25, 2.4])
.range([height - margin.bottom, margin.top]);
}
Insert cell
z = d3.scaleOrdinal(d3.schemeCategory10).domain(data.map(d => d.name))
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.style("font-size", "15px")
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
.call(g => g.select(".domain").remove())
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
//.call(d3.axisLeft(y).ticks(10))
.call(g => g.selectAll(".tick line")
.attr("stroke-opacity", d => d === 1 ? null : 0.2)
.attr("x2", width - margin.left - margin.right))
.call(g => g.select(".domain").remove())
Insert cell
parseDate = d3.timeParse("%Y-%m-%d")
Insert cell
parseDate2 = d3.timeParse("%Y")
Insert cell
formatDate = d3.timeFormat("%B, %Y")
Insert cell
line = d3.line()
.x(d => x(d.date))
.y(d => y(d.value))
Insert cell
bisect = d3.bisector(d => d.date).left
Insert cell
series = d3.nest().key(d => d.name).entries(data).map(({key, values}) => {
console.log(values);
const v = values[0].value;
const vmax = values.slice(-1)[0].value;
const offset = 1-values[0].series*0.12;
console.log(offset);
return {key, values: values.map(({date, value, unit, seriesname}) => ({date, value: ((((value-v)/(vmax-v)))+(offset)), valueabs: value, unit: unit, seriesname: seriesname}))};
})
Insert cell
data = d3.merge(await Promise.all(["Emissions","Concentration","Temperature"].map(name => d3.csv(`https://gist.githubusercontent.com/Jfriedrich/348756b3bb41705e7e71b23bdc622372/raw/64ea070fa22f4bab88b04643b1e5676f6c81cad9/${name}.csv`, d => {
const date = parseDate2(d["yr"]);
return {name, date, value: +d["value"], unit: d["unit"], seriesname:d["name"], series:d["series"]};
}))))
Insert cell
d3 = require("d3@5")
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more