Published
Edited
Dec 2, 2021
Importers
Insert cell
# Spurious correlations graph
Insert cell
Insert cell
Insert cell
correlationChart(dataA, dataB)
Insert cell
Insert cell
function correlationChart(dataA, dataB, {
height = 500,
strokeLinecap = "round", // stroke line cap of the line
strokeLinejoin = "round", // stroke line join of the line
strokeWidth = 1.5, // stroke width of line, in pixels
strokeOpacity = 1, // stroke opacity of line
x = ([x]) => x, // given d in data, returns the (temporal) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
title, // given d in data, returns the title text
yFormat,
colorA = "#ba0f27",
colorB = "black",
margin = ({top: 110, right: 110, bottom: 25, left: 110}),
curve = d3.curveCatmullRom,
xDomain = [1998.5, 2009.5]
} = {}) {
const xScale = d3.scaleLinear()
.domain(xDomain)
.range([margin.left, width - margin.right]);

const xAxis = (g, x) => g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.attr("color", "gray")
.call(d3.axisBottom(x).ticks(width / 100).tickFormat(xStringFormat).tickSizeOuter(0))
.selectAll("text").attr("color", colorB);

const xStringFormat = d => String(d);

const x2Axis = (g, x) => g
.attr("transform", `translate(0, ${margin.top})`)
.attr("color", "gray")
.call(d3.axisTop(x).ticks(width / 100).tickFormat(xStringFormat).tickSizeOuter(0))
.selectAll("text").attr("color", colorA);
const yScale = d3.scaleLinear()
.domain(dataA.domain)
.range([height - margin.bottom, margin.top]);

const yStringFormat = d => d + " " + dataA.valueLabel;

const yAxis = (g, y) => g
.attr("transform", `translate(${margin.left},0)`)
.attr("color", colorA)
.call(d3.axisLeft(y).ticks(3).tickFormat(yStringFormat).tickSizeOuter(10))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - margin.right -100)
.attr("stroke", "#444")
.attr("stroke-opacity", 0.1))
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.style('transform', `translate(-100px, ${height / 2 - 50}px) rotate(-90deg)`)
.style('font-size', '1.5em')
.attr("text-anchor", "middle")
.text(dataA.label));

const y2Scale = d3.scaleLinear()
.domain(dataB.domain)
.range([height - margin.bottom, margin.top]);

const y2StringFormat = d => d + " " + dataB.valueLabel;

const yAxis2 = (g, y2) => g
.attr("transform", `translate(${width - margin.right},0)`)
.attr("color", colorB)
.call(d3.axisRight(y2).ticks(3).tickFormat(y2StringFormat).tickSizeOuter(10))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.style('transform', `translate(100px, ${height / 2 - 50}px) rotate(90deg)`)
.style('font-size', '1.5em')
.attr("text-anchor", "middle")
.text(dataB.label))
const line = d3.line()
.curve(curve)
.x(d => xScale(d.year))
.y(d => yScale(d.value));

const line2 = d3.line()
.curve(curve)
.x(d => xScale(d.year))
.y(d => y2Scale(d.value));

// function getVoronoiData(dataA, dataB) {
// let array = [];
// for (let index = 0; index < dataA.length; ++index) {
// let elemA = dataA[index];
// let elemB = dataB[index];
// array.push([xScale(elemA.year), d3.mean([yScale(elemA.value), y2Scale(elemB.value)])])
// }
// return array;
// }

// const voronoiData = getVoronoiData(dataA, dataB);
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const clip = DOM.uid("clip");

svg.append("clipPath")
.attr("id", clip.id)
.append("rect")
.attr("x", margin.left)
.attr("y", margin.top)
.attr("width", width - margin.left - margin.right)
.attr("height", height - margin.top - margin.bottom);

svg.append("path")
.attr("fill", "none")
.attr("stroke", colorA)
.attr("stroke-width", strokeWidth)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-opacity", strokeOpacity)
.attr("d", line(dataA.data));

svg.append("path")
.attr("fill", "none")
.attr("stroke", colorB)
.attr("stroke-width", strokeWidth)
.attr("stroke-linecap", strokeLinecap)
.attr("stroke-linejoin", strokeLinejoin)
.attr("stroke-opacity", strokeOpacity)
.attr("d", line2(dataB.data));
const gx = svg.append("g")
.call(xAxis, xScale);

const gx2 = svg.append("g")
.call(x2Axis, xScale);

const gy1 = svg.append("g")
.call(yAxis, yScale);

const gy2 = svg.append("g")
.call(yAxis2, y2Scale);

svg.append("g")
.attr("id", "red-circles")
.attr("fill", colorA)
.attr("stroke", colorA)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(dataA.data)
.join("circle")
.attr("cx", d => xScale(d.year))
.attr("cy", d => yScale(d.value))
.attr("r", 3);

svg.append("g")
.attr("id", "black-circles")
.attr("fill",colorB)
.attr("stroke", colorB)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(dataB.data)
.join("circle")
.attr("cx", d => xScale(d.year))
.attr("cy", d => y2Scale(d.value))
.attr("r", 3);

const mainTitle = svg.append("g").attr("id", "title");

mainTitle.append("text")
.attr("x", width / 2)
.attr("y", 20)
.style("fill", colorA)
.style('font-size', '1.3em')
.style('font-weight', 'bold')
.attr("text-anchor", "middle")
.text(dataA.title)

mainTitle.append("text")
.attr("x", width / 2)
.attr("y", 45)
.style("fill", "darkgray")
.style('font-size', '1em')
.attr("text-anchor", "middle")
.text("se correlaciona con")

mainTitle.append("text")
.attr("x", width / 2)
.attr("y", 70)
.style("fill", colorB)
.style('font-size', '1.3em')
.style('font-weight', 'bold')
.attr("text-anchor", "middle")
.text(dataB.title)

return svg.node();
}
Insert cell
<span>Data sources: </span><a href=`${dataA.sourceUrl}`>${dataA.sourceLabel} </a> <span> and </span> <a href=`${dataB.sourceUrl}`>${dataB.sourceLabel} </a>
Insert cell
https://observablehq.com/@didoesdigital/9-may-2020-d3-scatterplot-with-voronoi-tooltips
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