Public
Edited
Jan 17
Insert cell
Insert cell
Insert cell
Insert cell
parteispenden_chart = Plot.plot({
height: 950,
width,
marginTop: 20,
marginLeft: 50,
marginRight: 20,
y: {
axis: null
},

r: {
domain: [0, 2100000],
range: [2, 32]
},
marks: [
// Achse Monate
Plot.axisY({
ticks: "2 months",
tickSize: 0,
dx: 6,
fill: "#99AFC2",
tickFormat: (d) =>
d.toLocaleString("de", { month: "short", year: "2-digit" }),
textAnchor: "end"
}),

// Gridlines
Plot.gridY({ interval: "2 months", stroke: "#99AFC2", strokeOpacity: 0.5 }),

// Dots
Plot.dotY(
parteispenden,
Plot.dodgeX({
y: "Datum",
anchor: "middle",
stroke: "#fff",
strokeWidth: isDarkMode() ? 1 : 0,
fillOpacity: 0.9,
title: "Spender",
fill: (d) =>
Array.from(hochburgen.keys()).includes(d.Partei)
? hochburgen.get(d.Partei)
: "#99AFC2",
r: "Spende"
})
),

// Label
Plot.text(
parteispenden,
Plot.dodgeX({
y: "Datum",
r: "Spende",
fillOpacity: (d) => (d.Spende > 300000 ? 1 : 0),
anchor: "middle",
text: (d) =>
d.Partei.replace("Freie Wähler", "FW")
.replace("Grüne", "Grü")
.replace("MLPD", ""),
fill: "#fff"
})
),

// Tooltip
Plot.tip(
parteispenden,
Plot.pointer(
Plot.dodgeX({
y: "Datum",
fill: "#4f80ff",
r: "Spende",
fill: isDarkMode() ? "#293845" : "#ffffff",
fillOpacity: 0.8,
anchor: "middle",
frameAnchor: "bottom",
fontSize: 12,
strokeOpacity: 0.5,
title: (d) =>
`${d.Partei}\nSpende: ${d.Spende.toLocaleString("de")} Euro\n${
d.Spender
}`
})
)
)
]
})
Insert cell
Insert cell
Insert cell
Insert cell
hochburgen = new Map([
["CDU", isDarkMode() ? "#99AFC2" : "#333"],
["CSU", isDarkMode() ? "#99AFC2" : "#333"],
["SPD", "#f44647"],
["Grüne", "#6abf4a"],
["FDP", "#ffd600"],
["Linke", "#c72767"],
["AfD", "#5fb1bf"],
["Volt", "#502379"],
["BSW", "#312783"],
["Freie Wähler", "#E99939"],
["WerteUnion", "#99AFC2"],
["MLPD", "#99AFC2"]
])
Insert cell
import { getLabelLength } from "@bayre/svg-swatches"
Insert cell
// adapted from https://observablehq.com/@mbostock/fit-text-to-circle
getLines = (text, targetWidth, textStyle = "12px sans-serif") => {
const words = text.toString().split(/\s+/g); // To hyphenate: /\s+|(?<=-)/
if (!words[words.length - 1]) words.pop();
if (!words[0]) words.shift();
let line;
let lineWidth0 = Infinity;
const lines = [];
for (let i = 0, n = words.length; i < n; ++i) {
let lineText1 = (line ? line.text + " " : "") + words[i];
let lineWidth1 = getLabelLength(lineText1, textStyle);
if ((lineWidth0 + lineWidth1) / 2 < targetWidth) {
line.width = lineWidth0 = lineWidth1;
line.text = lineText1;
} else {
lineWidth0 = getLabelLength(words[i], textStyle);
line = { width: lineWidth0, text: words[i] };
lines.push(line);
}
}
return lines;
}
Insert cell
function circleLegend({
scale,
fill = "none",
stroke = "#99AFC2",
marginTop = 5,
marginRight = 50,
marginBottom = 5,
marginLeft = 5,
ticks = 3,
tickSize = 5,
tickFormat = (d) => d,
tickValues,
color = "#99AFC2",
tickStroke = "#99AFC2",
tickStrokeDash = "4, 2",
tickFont = "12px sans-serif",
tickWrap = false,
tickWrapSpacing = 12
} = {}) {
ticks = tickValues || scale.ticks(ticks);
const r = scale(d3.max(ticks));
const width = r * 2 + marginRight;
const height = r * 2 + marginBottom;
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("fill", color)
.attr("viewBox", [-marginLeft, -marginTop, width, height])
.attr("overflow", "visible");

svg
.selectAll("circle")
.data(ticks)
.join("circle")
.sort((a, b) => b - a)
.attr("fill", fill)
.attr("stroke", stroke)
.attr("cx", r)
.attr("cy", scale)
.attr("r", scale);

svg
.selectAll("line")
.data(ticks)
.join("line")
.attr("stroke", tickStroke)
.attr("stroke-dasharray", tickStrokeDash)
.attr("x1", r)
.attr("x2", tickSize + r * 2)
.attr("y1", (d) => scale(d) * 2)
.attr("y2", (d) => scale(d) * 2);

const ticksFormatted = ticks
.map((d, i, g) => ({ d, text: tickFormat(d, i, g) }))
.map((d) => ({
...d,
lines: getLines(d.text, marginRight - (tickSize + 3), tickFont)
}));

const tickLabels = svg
.selectAll("text")
.data(ticksFormatted)
.join("text")
.attr(
"transform",
({ d }) => `translate(${r * 2 + tickSize + 3}, ${scale(d) * 2})`
)
.style("font", tickFont);

if (tickWrap) {
tickLabels
.selectAll("tspan")
.data((d) => d.lines)
.join("tspan")
.attr("x", 0)
.attr("y", (d, i, g) => (i - g.length / 2 + 0.8) * tickWrapSpacing)
.text((d) => d.text);
} else {
tickLabels.attr("dominant-baseline", "middle").text((d) => d.text);
}

return svg.node();
}
Insert cell
isDarkMode = () =>
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
Insert cell
style = html`

<div></div>

<style>

figure {
margin-left: 0px;
margin-right: 0px;
padding: 0px;
}

.mode {
color: #0f151a;
background-color: #ffffff;
}

@font-face {
font-family: Source Serif Pro;
src: url(https://static.rndtech.de/share/rnd/jchrist/SourceSerifPro-Regular.ttf);
}

@font-face {
font-display: swap;
font-family: 'DIN Next LT Pro';
font-style: normal;
font-weight: 700;
src: url(https://static.rndtech.de/share/rnd/jchrist/DINNextLTPro-Bold.woff2) format("woff2"),
url(https://static.rndtech.de/share/rnd/jchrist/DINNextLTPro-Bold.woff) format("woff"),
url(https://static.rndtech.de/share/rnd/jchrist/DINNextLTPro-Bold.ttf) format("truetype")
}
@font-face {
font-display: swap;
font-family: 'InterRegular';
font-style: normal;
font-weight: 400;
src: url(https://static.rndtech.de/share/rnd/jchrist/Inter-Regular.woff2) format("woff2"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Regular.woff) format("woff"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Regular.ttf) format("truetype")
}

@font-face {
font-display: swap;
font-family: 'InterMedium';
font-style: normal;
font-weight: 500;
src: url(https://static.rndtech.de/share/rnd/jchrist/Inter-Medium.woff2) format("woff2"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Medium.woff) format("woff"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Medium.ttf) format("truetype")
}

@font-face {
font-display: swap;
font-family: 'InterBold';
font-style: normal;
font-weight: 700;
src: url(https://static.rndtech.de/share/rnd/jchrist/Inter-Bold.woff2) format("woff2"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Bold.woff) format("woff"),
url(https://static.rndtech.de/share/rnd/jchrist/Inter-Bold.ttf) format("truetype")
}

h2 {
color: rgb(15, 21, 26);
font-family: "DIN Next LT Pro", sans-serif;
font-weight: 700;
font-size: 22px;
margin: 0px 0px 0px 0px;
}

.title {
color: rgb(15, 21, 26);
font-family: "DIN Next LT Pro", sans-serif;
font-weight: 700;
font-size: 22px;
margin: 0px 0px 0px 0px;
}

.subtitle {
font-family: "Source Serif Pro", serif;
font-size: 17px;
font-weight: 400;
color: rgb(41, 56, 69);
}

.credits {
font-size: 12px;
color: #99AFC2;
font-family: InterRegular, sans-serif;
font-weight: 400;
position: relative;
}

body {
display: block;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
margin-top: 0px;
}

svg {
background-color: white !important;
color: #0f151a !important;
}

g:not([font-size], [font-weight]) text {
font-size: 12px;
line-height: 125%;
font-weight: 400;
font-family: InterRegular;
color: #0f151a;
background-color: #ffffff;
}

@media (prefers-color-scheme: dark) {

.legend {
bottom: 3px;
position: relative;
margin-right: 5px;
width: 20px;
height: 1px;
background-color: #ffffff;
display: inline-block;"
}

.mode {
color: #ffffff;
background-color: #293845;
}

.subtitle {
font-family: "Source Serif Pro", serif;
font-size: 17px;
font-weight: 400;
color: #ffffff;
}

g:not([font-size], [font-weight]) text {
font-size: 12px;
font-weight: 400;
font-family: InterRegular;
color: #ffffff;
background-color: #293845;
}

svg {
background-color: #293845 !important;
color: white !important;
}

.subtitle {
font-family: "Source Serif Pro", serif;
font-size: 17px;
font-weight: 400;
color: #ffffff;
}

g:not([font-size], [font-weight]) text {
font-size: 12px;
line-height: 125%;
font-weight: 400;
font-family: InterRegular;
color: #ffffff;
background-color: #293845;
}
}

</style>`
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