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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more