Public
Edited
Jun 21, 2023
Fork of Color legend
Insert cell
Insert cell
Legend(d3.scaleSequential([0, 100], d3.interpolateViridis), {
title: "Temperature (°F)"
})
Insert cell
Legend(d3.scaleSequentialSqrt([0, 1], d3.interpolateTurbo), {
title: "Speed (kts)"
})
Insert cell
Legend(d3.scaleDiverging([-0.1, 0, 0.1], d3.interpolatePiYG), {
title: "Daily change",
tickFormat: "+%"
})
Insert cell
Legend(d3.scaleDivergingSqrt([-0.1, 0, 0.1], d3.interpolateRdBu), {
title: "Daily change",
tickFormat: "+%"
})
Insert cell
Legend(d3.scaleSequentialLog([1, 100], d3.interpolateBlues), {
title: "Energy (joules)",
ticks: 10
})
Insert cell
Legend(d3.scaleSequentialQuantile(d3.range(100).map(() => Math.random() ** 2), d3.interpolateBlues), {
title: "Quantile",
tickFormat: ".2f"
})
Insert cell
Legend(d3.scaleSqrt([-100, 0, 100], ["blue", "white", "red"]), {
title: "Temperature (°C)"
})
Insert cell
Legend(d3.scaleQuantize([1, 10], d3.schemePurples[9]), {
title: "Unemployment rate (%)"
})
Insert cell
Legend(d3.scaleQuantile(d3.range(1000).map(d3.randomNormal(100, 20)), d3.schemeSpectral[9]), {
title: "Height (cm)",
tickFormat: ".0f"
})
Insert cell
Legend(d3.scaleThreshold([2.5, 3.1, 3.5, 3.9, 6, 7, 8, 9.5], d3.schemeRdBu[9]), {
title: "Unemployment rate (%)",
tickSize: 0
})
Insert cell
Legend(d3.scaleOrdinal(["<10", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69", "70-79", "≥80"], d3.schemeSpectral[10]), {
title: "Age (years)",
tickSize: 0,
tickLabels: ["a","b","c","d","e","f","g","h","j"]
})
Insert cell
Insert cell
Swatches(d3.scaleOrdinal(["blueberries", "oranges", "apples"], d3.schemeCategory10))
Insert cell
Swatches(d3.scaleOrdinal(["Wholesale and Retail Trade", "Manufacturing", "Leisure and hospitality", "Business services", "Construction", "Education and Health", "Government", "Finance", "Self-employed", "Other"], d3.schemeTableau10), {
columns: "180px"
})
Insert cell
Insert cell
<style> @import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital@0;1&display=swap'); </style>
Insert cell
// Copyright 2021, Observable Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/color-legend
function Legend(color, {
title,
tickSize = 8,
width = 320,
height = 100 + tickSize,
width_full = 600,
height_full = 400,
marginTopheader = 25,
marginTop = 70,
marginRight = 0,
marginBottom = 16 + tickSize,
marginLeft = 25,
ticks = width / 64,
tickFormat,
tickValues,
tickLabels
} = {}) {

function ramp(color, n = 256) {
const canvas = document.createElement("canvas");
canvas.width = n;
canvas.height = 1;
const context = canvas.getContext("2d");
for (let i = 0; i < n; ++i) {
context.fillStyle = color(i / (n - 1));
context.fillRect(i, 0, 1, 1);
}
return canvas;
}

const svg = d3.create("svg")
.attr("width", width_full)
.attr("height", height_full)
.attr("viewBox", [0, 0, width_full, height_full])
.style("overflow", "visible")
.style("display", "block");

let tickAdjust = g => g.selectAll(".tick line").attr("y1", marginTop + marginBottom - height);
let x;

// Continuous
if (color.interpolate) {
const n = Math.min(color.domain().length, color.range().length);

x = color.copy().rangeRound(d3.quantize(d3.interpolate(marginLeft, width - marginRight), n));

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.copy().domain(d3.quantize(d3.interpolate(0, 1), n))).toDataURL());
}

// Sequential
else if (color.interpolator) {
x = Object.assign(color.copy()
.interpolator(d3.interpolateRound(marginLeft, width - marginRight)),
{range() { return [marginLeft, width - marginRight]; }});

svg.append("image")
.attr("x", marginLeft)
.attr("y", marginTop)
.attr("width", width - marginLeft - marginRight)
.attr("height", height - marginTop - marginBottom)
.attr("preserveAspectRatio", "none")
.attr("xlink:href", ramp(color.interpolator()).toDataURL());

// scaleSequentialQuantile doesn’t implement ticks or tickFormat.
if (!x.ticks) {
if (tickValues === undefined) {
const n = Math.round(ticks + 1);
tickValues = d3.range(n).map(i => d3.quantile(color.domain(), i / (n - 1)));
}
if (typeof tickFormat !== "function") {
tickFormat = d3.format(tickFormat === undefined ? ",f" : tickFormat);
}
}
}

// Threshold
else if (color.invertExtent) {
const thresholds
= color.thresholds ? color.thresholds() // scaleQuantize
: color.quantiles ? color.quantiles() // scaleQuantile
: color.domain(); // scaleThreshold

const thresholdFormat
= tickFormat === undefined ? d => d
: typeof tickFormat === "string" ? d3.format(tickFormat)
: tickFormat;

x = d3.scaleLinear()
.domain([-1, color.range().length - 1])
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.range())
.join("rect")
.attr("x", (d, i) => x(i - 1))
.attr("y", marginTop)
.attr("width", (d, i) => x(i) - x(i - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", d => d);

tickValues = d3.range(thresholds.length);
tickFormat = i => thresholdFormat(thresholds[i], i);
}

// Ordinal
else {
x = d3.scaleBand()
.domain(color.domain())
.rangeRound([marginLeft, width - marginRight]);

svg.append("g")
.selectAll("rect")
.data(color.domain())
.join("rect")
.attr("x", x)
.attr("y", marginTop)
.attr("width", Math.max(0, x.bandwidth() - 1))
.attr("height", height - marginTop - marginBottom)
.attr("fill", color);

tickAdjust = () => {}
}

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(d3.axisBottom(x)
.ticks(ticks, typeof tickFormat === "string" ? tickFormat : undefined)
.tickFormat(typeof tickFormat === "function" ? tickFormat(function(d,i){ return tickLabels[i] }) : undefined)
.tickSize(tickSize)
.tickValues(tickValues))
//.tickFormat(function(d,i){ return tickLabels[i] })
.call(tickAdjust)
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", marginLeft)
.attr("y", marginTop + marginBottom - height - 6)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "14px")
.attr("class", "title")
.text(title));

// add how to

svg.append("text") // add text for dashed lines
.attr("x", marginLeft)
.attr("y", marginTopheader)
.text("How to read this graph:")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "14px")
.style("font-weight", "bold");

// add NA

svg.append('rect')
.attr('x', width + 8)
.attr('y', marginTop)
.attr('width', 30)
.attr('height', height - marginTop - marginBottom)
.attr('stroke', 'grey')
.attr('fill', 'grey');

svg.append("text") // add label NA
.attr("x", width + 15)
.attr("y", height - 6)
.text("NA")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "10px");

// add manual legend for dashed lines and constitutions

svg.append("text") // add label for cc
.attr("x", width + 88)
.attr("y", marginTop - 6)
.text("Constitutional Change")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "14px");
svg.append("line") // dashed line for legend
.attr("x1", width + 90)
.attr("x2", width + 90)
.attr("y1", marginTop)
.attr("y2", height - 16)
.style("stroke-width", 2)
.style("stroke-dasharray","5,5") // dashed array for line
.style("stroke", "#878787");

svg.append("text") // add text for dashed lines
.attr("x", width + 100)
.attr("y", marginTop + 13)
.text("Amendment")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "14px");

svg.append("line")
.attr("x1", width + 200)
.attr("x2", width + 200)
.attr("y1", marginTop)
.attr("y2", height -16)
.style("stroke-width", 2)
.style("stroke", "#878787");

svg.append("text") // dashed lines are amendments
.attr("x", width + 210)
.attr("y", marginTop + 13)
.text("New")
.style("font-family", "Atkinson Hyperlegible")
.style("font-size", "14px");

svg.append("foreignObject")
.attr("x", 10)
.attr("y", height)
.attr("width", 600)
.attr("height", 200)
.append("xhtml:body")
.style("font", "14px 'Atkinson Hyperlegible'")
.html("<p>The color represents how populist the country's government at the time was according to the V-Party populism score. We calculated the mean populism score per government without considering the strength per party. The score is continuuous and equals 0 if a government was <font color='#FAB869'>not populist</font> at all, and 1 if the party was <font color='#9E3D22'>very populist</font>. The [V-Party dataset's](https://www.v-dem.net/data/v-party-dataset/) populism score is based on their anti-elite and people-centred items. Find out more in their [codebook](https://www.v-dem.net/documents/6/vparty_codebook_v2.pdf)!</p>");


return svg.node();
}
Insert cell
function legend({color, ...options}) {
return Legend(color, options);
}
Insert cell
// Copyright 2021, Observable Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/color-legend
function Swatches(color, {
columns = null,
format,
unknown: formatUnknown,
swatchWidth = 25,
swatchHeight = 2,
marginLeft = 0
} = {}) {
const id = `-swatches-${Math.random().toString(16).slice(2)}`;
const unknown = formatUnknown == null ? undefined : color.unknown();
const unknowns = unknown == null || unknown === d3.scaleImplicit ? [] : [unknown];
const domain = color.domain().concat(unknowns);
if (format === undefined) format = x => x === unknown ? formatUnknown : x;

function entity(character) {
return `&#${character.charCodeAt(0).toString()};`;
}

if (columns !== null) return htl.html`<div style="display: flex; align-items: center; margin-left: ${+marginLeft}px; min-height: 33px; font: 14px; font-family: 'Atkinson Hyperlegible';">
<style>

.${id}-item {
break-inside: avoid;
display: flex;
align-items: center;
padding-bottom: 1px;
}

.${id}-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - ${+swatchWidth}px - 0.5em);
}

.${id}-swatch {
width: ${+swatchWidth}px;
height: ${+swatchHeight}px;
margin: 0 0.5em 0 0;
}

</style>
<div style=${{width: "100%", columns}}>${domain.map(value => {
const label = `${format(value)}`;
return htl.html`<div class=${id}-item>
<div class=${id}-swatch style=${{background: color(value)}}></div>
<div class=${id}-label title=${label}>${label}</div>
</div>`;
})}
</div>
</div>`;

return htl.html`<div style="display: flex; align-items: center; min-height: 33px; margin-left: ${+marginLeft}px; font-family: 'Atkinson Hyperlegible';font-size: 14px;">
<style>

.${id} {
display: inline-flex;
align-items: center;
margin-right: 1em;
}

.${id}::before {
content: "";
width: ${+swatchWidth}px;
height: ${+swatchHeight}px;
margin-right: 0.5em;
background: var(--color);
}

</style>
<div>${domain.map(value => htl.html`<span class="${id}" style="--color: ${color(value)}">${format(value)}</span>`)}</div>`;
}
Insert cell
function swatches({color, ...options}) {
return Swatches(color, options);
}
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