makeIsotype = function({
inputData,
shapeDomain = ["Lebensmittel", "Wohnen", "Energie", "Mobilität", "Güter", "Dienstleistungen"],
shapeRange = [path1, path2, path3, path4, path5, path6],
shapeRangeHalf = [path1_half, path2_half, path3_half, path4_half, path5_half, path6_half],
colorRange = colors_new,
quantileSort = ["Arme", "Untere Mittelschicht", "Mittelschicht", "Obere Mittelschicht", "Reiche"],
quantileLabels = {},
facetLabelExpr = null,
categoryLabelExpr = null,
xDomain = [-0.5, 12],
height = 250,
size = 5,
unit = "€",
divisor = 1000,
showHeader = true
}) {
const poorLabel = quantileLabels["Arme"] || "Arme";
const lowerMiddleLabel = quantileLabels["Untere Mittelschicht"] || "Untere Mittelschicht";
const middleLabel = quantileLabels["Mittelschicht"] || "Mittelschicht";
const upperMiddleLabel = quantileLabels["Obere Mittelschicht"] || "Obere Mittelschicht";
const richLabel = quantileLabels["Reiche"] || "Reiche";
const labelExprString = `datum.value === 'Arme' ? '${poorLabel}' : datum.value === 'Untere Mittelschicht' ? '${lowerMiddleLabel}' : datum.value === 'Mittelschicht' ? '${middleLabel}' : datum.value === 'Obere Mittelschicht' ? '${upperMiddleLabel}' : datum.value === 'Reiche' ? '${richLabel}' : datum.value`;
return vl.layer(
vl.markPoint({
size: size,
stroke: "black",
strokeOpacity: 1,
strokeWidth: 0.8,
opacity: 0.811,
clip: false,
})
.encode(
vl.y()
.fieldN("name")
.sort(shapeDomain)
.axis({
domain: false,
ticks: false,
title: null,
labelOffset: 18,
minExtent: width >= 860 ? 128 : 16,
labels: width >= 860,
labelExpr: `
datum.label === 'Lebensmittel' ? '${t("food")}' :
datum.label === 'Wohnen' ? '${t("housing")}' :
datum.label === 'Energie' ? '${t("energy")}' :
datum.label === 'Mobilität' ? '${t("mobility")}' :
datum.label === 'Güter' ? '${t("goods")}' :
datum.label === 'Dienstleistungen' ? '${t("services")}' :
datum.label
`
}),
vl.x()
.fieldQ("positions")
.scale({
domain: xDomain,
nice: false,
zero: false,
})
.axis({
title: null,
labels: false,
ticks: false,
domain: false,
grid: false,
clip: false,
}),
vl.fill()
.fieldN("name")
.scale({
range: colorRange,
})
.legend(false),
vl.shape()
.fieldN("shapeKey")
.scale({
domain: shapeDomain.concat(shapeDomain.map(d => d + "_half")),
range: shapeRange.concat(shapeRangeHalf)
})
.legend(false)
)
)
.transform(
vl.calculate(`datum.value / ${divisor}`).as("exactValue"),
vl.calculate(`floor(datum.exactValue)`).as("fullShapes"),
vl.calculate(`datum.exactValue - datum.fullShapes`).as("decimal"),
vl.calculate(`datum.decimal >= 0.25 && datum.decimal < 0.75 ? datum.fullShapes + 1 : round(datum.exactValue)`).as("totalShapes"),
vl.calculate(`sequence(0, datum.totalShapes)`).as("positions"),
vl.flatten(["positions"]),
vl.calculate(`datum.positions === datum.totalShapes - 1 && datum.decimal >= 0.25 && datum.decimal < 0.75 ? 1 : 0`).as("isHalfShape"),
vl.calculate(`datum.isHalfShape === 1 ? datum.name + '_half' : datum.name`).as("shapeKey"),
vl.calculate(`datum.isHalfShape === 1 ? datum.positions - 0.3 : datum.positions`).as("adjustedPositions")
)
.width(makeWidth(width))
.height(height)
.facet(
width < 860 ? {
row: {
field: "Quantile",
type: "nominal",
title: null,
sort: quantileSort,
header: showHeader ? {
labelFontSize: 15,
labelOrient: "top",
labelPadding: -8,
labelAlign: "left",
labelAnchor: "start",
labelExpr: labelExprString
} : {
labels: false
}
}
}
: {
column: {
field: "Quantile",
type: "nominal",
title: null,
sort: quantileSort,
header: showHeader ? {
labelFontSize: 15,
labelOrient: "top",
labelPadding: -8,
labelAlign: "left",
labelAnchor: "start",
labelExpr: labelExprString
} : {
labels: false
},
},
}
)
.resolve({scale: {x: "shared"}})
.data(inputData)
.config(config);
}
ISOTYPE
config = ({
view: { stroke: null },
background: "transparent",
font: "Jost",
fontSize: 15,
padding: {
top: width >= 860 ? 0 : 20,
right: 0,
bottom: 22,
left: 0,
},
title: {
offset: 0,
fontSize: 16,
subtitleFontSize: 15,
},
facet: {
fontSize: 15,
labelFontSize: 15,
titleFontSize: 15,
titleFontWeight: "normal",
},
axis: {
labelFontSize: 15,
titleFontSize: 15,
titleFontWeight: "normal",
},
axisX: {
grid: false,
labelPadding: 10,
},
axisY: {
domain: false,
ticks: false,
labelPadding: 10,
},
legend: {
labelFontSize: 15,
titleFontSize: 15,
titleFontWeight: "normal",
orient: "top",
title: false,
},
mark: {
fontSize: 15
},
locale: {
number: {
decimal: ",",
thousands: " ",
format: ",.0f",
grouping: [3]
},
time: {
dateTime: "%A %e %B %Y, %X",
date: "%d/%m/%Y",
time: "%H:%M:%S",
periods: ["AM", "PM"],
days: [
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag",
"Sonntag"
],
shortDays: [
"Mo",
"Di",
"Mi",
"Do",
"Fr",
"Sa",
"So"
],
months: [
"Jänner",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember"
],
shortMonths: [
"Jan",
"Feb",
"Mar",
"Apr",
"Mai",
"Jun",
"Jul",
"Aug",
"Sep",
"Okt",
"Nov",
"Dez"
]
}
},
})makeWidth = function(inputWidth) {
if (inputWidth >= 860) {
return 368
}
else {
return (width - 36)
}
}germanLocale = d3.formatLocale({
decimal: ",",
thousands: "",
grouping: [3],
currency: ["€", " "],
})
hexToRgb = function(hex) {
const bigint = parseInt(hex.replace(/^#/, ""), 16)
const r = (bigint >> 16) & 255
const g = (bigint >> 8) & 255
const b = bigint & 255
return `(${r}, ${g}, ${b})`
}
rotatePath = function(pathStr, angleDeg) {
const angle = angleDeg * Math.PI / 180;
const cos = Math.cos(angle);
const sin = Math.sin(angle);
const cx = 200, cy = 200;
return pathStr.replace(/([0-9.]+)\s+([0-9.]+)/g, (match, x, y) => {
const xNum = parseFloat(x);
const yNum = parseFloat(y);
const xNew = cos * (xNum - cx) - sin * (yNum - cy) + cx;
const yNew = sin * (xNum - cx) + cos * (yNum - cy) + cy;
return `${xNew.toFixed(3)} ${yNew.toFixed(3)}`;
});
}colors_new = [
"#198737",
"#F5EBDC",
"#FFAA00",
"#3E6F8E",
"#D62300",
]
// Lebensmittel (Food - Meal / Cloche)
path1 = "M7.5,15.75 m-1.5,0 a1.5,1.5 0 1,0 3,0 a1.5,1.5 0 1,0 -3,0 M3,12 v7.5 h9.075 c0-0.15-0.075-0.225-0.075-0.375 c0-1.05,0.825-1.875,1.875-1.875 s1.875,0.825,1.875,1.875 c0,0.15,0,0.225-0.075,0.375 H21 V12 M21,12 c0-4.125-3.375-7.5-7.5-7.5 h0 L3,12 h10.5 M15.75,12 m-2.25,0 a2.25,2.25 0 1,0 4.5,0 a2.25,2.25 0 1,0 -4.5,0"
// Wohnen (Housing)
path2 = "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
// Energie (Energy)
path3 = "M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7z"
// Mobilität (Mobility)
path4 = "M18.92 6.01C18.72 5.42 18.16 5 17.5 5h-11c-.66 0-1.21.42-1.42 1.01L3 12v8c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-1h12v1c0 .55.45 1 1 1h1c.55 0 1-.45 1-1v-8l-2.08-5.99zM6.5 16c-.83 0-1.5-.67-1.5-1.5S5.67 13 6.5 13s1.5.67 1.5 1.5S7.33 16 6.5 16zm11 0c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zM5 11l1.5-4.5h11L19 11H5z"
// Güter (Goods) - Gift box
path5 = "M12,21 L12,7.5 M16.275,4.5 H18.75 c0.825,0 1.5,0.675 1.5,1.5 v13.5 c0,0.825 -0.675,1.5 -1.5,1.5 H5.25 c-0.825,0 -1.5,-0.675 -1.5,-1.5 V6 c0,-0.825 0.675,-1.5 1.5,-1.5 h2.475 M14.25,12.75 L18,12.75 M12,3 L7.5,3 L7.5,9.75 L12,7.5 L16.5,9.75 L16.5,3 Z M7.5,3 L12,7.5 L16.5,3"
// Dienstleistungen (Services) - Wrench
path6 = "M1 19.894a3.1 3.1 0 0 0 3.098 3.102 3.149 3.149 0 0 0 2.232-.933l10.268-11.938a1.451 1.451 0 0 1 .535-.343.898.898 0 0 1 .088 0 3.932 3.932 0 0 0 3.668-.573 6.235 6.235 0 0 0 2.106-3.958.621.621 0 0 0-.108-.442l-.113-.141-.11-.06a.647.647 0 0 0-.704.06l-2.88 2.239-1.244-.927-.6-1.531 2.889-2.245a.652.652 0 0 0 .237-.644l-.045-.17-.073-.096a.638.638 0 0 0-.42-.241 6.047 6.047 0 0 0-4.32 1.032 4.209 4.209 0 0 0-1.222 4.789 6.976 6.976 0 0 1-.44.593L1.91 17.697A3.085 3.085 0 0 0 1 19.895z"
// Emissionen (Emissions - Factory with smoke)
path7 = "M10.27 11.3525A2.48 1.86 0 017.8 9.5a2.49 1.8675 0 01.77-1.35 3.92 2.94 0 01.33-.2025A3.63 2.7225 0 019.9 5.57a3.68 2.76 0 011.86-.75 4 3 0 011.17-1.9425A4 3 0 1116 8 3.46 2.595 0 0115 9.41a3.55 2.6625 0 01-2.49.795 2.42 1.815 0 01-.51.57 2.46 1.845 0 01-1.73.5475ZM7 16a1.26.945 0 01-1-1v-1a1.26.945 0 011-1H14a1.25.9375 0 011 1v1A1.25.9375 0 0114 16V27H7Z"
// Half shapes (simplified left-half representations)
// Food - left half of cloche
path1_half = "M7.5,15.75 m-1.5,0 a1.5,1.5 0 1,0 3,0 a1.5,1.5 0 1,0 -3,0 M3,12 v7.5 h9.075 L12,19.5 L12,12 M12,12 c0,-4.125 -1.5,-7.5 -6,-7.5 h0 L3,12 h9"
// Housing - left half of house
path2_half = "M12 3L2 12H5V20H10V14H12V12V3Z"
// Energy - left half of lightbulb
path3_half = "M12 2C8.14 2 5 5.14 5 9C5 11.38 6.19 13.47 8 14.74V17C8 17.55 8.45 18 9 18H12V22H12V18H12V14.74V9V2Z"
// Mobility - left half of car
path4_half = "M12 5H6.5C5.67 5 5.11 5.42 4.91 6.01L3 12V20C3 20.55 3.45 21 4 21H5C5.55 21 6 20.55 6 20V19H12V20V12V5ZM6.5 16C5.67 16 5 15.33 5 14.5S5.67 13 6.5 13S8 13.67 8 14.5S7.33 16 6.5 16ZM5 11L6.5 6.5H12V11H5Z"
// Goods - left half of gift box
path5_half = "M 12 21 L 12 7.5 M 7.725 4.5 H 5.25 c -0.825 0 -1.5 0.675 -1.5 1.5 v 13.5 c 0 0.825 0.675 1.5 1.5 1.5 h 6.75 v -18 M 7.5 3 L 7.5 9.75 L 12 7.5 L 12 3 Z M 7.5 3 L 12 7.5"
// Services - left half of wrench
path6_half = "M 1 19.894 a 3.1 3.1 0 0 0 3.098 3.102 a 3.149 3.149 0 0 0 2.232 -0.933 l 10.268 -11.938 a 6.976 6.976 0 0 1 -2.598 -3.125 L 1.91 17.697 A 3.085 3.085 0 0 0 1 19.895 z"
// Emissions - left half of factory
path7_half = "M7 27ZM7 21a1.26.945 0 01-1-1v-1a1.26.945 0 011-1H14a1.25.9375 0 011 1v1A1.25.9375 0 0114 21V27H7Z"
// Person (Human figure - Neurath style)
path_neurath = "M89 1.2l-12 6-4.8 6-3.6 12 1.2 18 4.8 30h-42l-6 4.8L23 85.2l-2.4 18v120L21.8 240 29 254.4l9.6 8.4 6 2.4L36.2 396 35 438l-9.6 1.2-4.8 7.2v4.8h6 54l1.2-1.2 3.6-43.2 8.4-96 2.4-9.6 2.4 9.6L101 336l7.2 75.6 2.4 38.4 1.2 1.2h58.8v-4.8l-6-6-8.4-1.2-1.2-36-4.8-106.8V267.6l4.8-2.4 7.2-6 6-10.8 3.6-13.2V88.8l-3.6-8.4-9.6-6-18-1.2H119l1.2-4.8L125 39.6l-1.2-19.2-2.4-6L114.2 6l-8.4-4.8L95 0Z"
// Person with Lederhose (Austrian/German traditional outfit)
path_en = "M7.5 327.5c3-3 3.5-7 5.5-10.5 3-4 7-7 11-10 5.5-4.5 11.5-8.5 17.5-13 2-1 5-4 7.5-4s4 3.5 5 5.5L64 312c1 2 3.5 4.5 2.5 7-1 2-4.5 3-6.5 4-5 2.5-10 4.5-15 7-2 .5-4.5 1.5-5.5 3-2.5 3.5 4.5 6.5 4.5 10-.5 2-3.5 3-5 3.5-4 2-13.5 8-18 6.5-10.5-3.5-10-15.5-13.5-24-1.5-3.5-5-5.5-3.5-10 2.5-6 10-8.5 11.5-14 1-3-1.5-6-2-9-.5-5 1-10.5 2.5-15.5 4.5-15 11-30 16.5-45 2.5-7.5 7-15 7.5-23 1-13-.5-26.5-1-39.5s0-26.5-.5-39.5c0-10-3-23.5 1-33 5-10 16.5-8 25.5-8 5 0 10-.5 15-.5 4 0 12 .5 14.5-3C98 84 94 73 93.5 68 91 50.5 83.5 26 98 12c15-15 43.5-7.5 49 13 1.5 7.5.5 14-.5 21.5-.5 2.5-.5 6-1.5 8-2 3-5.5 3.5-7 7.5-3.5 8.5-2 17-12 21.5-2.5 1-5 1-7.5 1-19 0-18-19.5-20-33.5-1-6.5-4.5-16 1-21.5 1.5-1.5 4-1 6-1H120h13.5c1.5 0 4-.5 5.5 1 5 7.5-1.5 19 2 27 6 6.5 2 13 2 21 0 3.5-.5 7.5 2.5 9.5 3.5 2.5 8.5 2 12.5 2l23 1c6 0 12 1 15.5 6.5 4 7.5 2.5 21 1.5 29-1.5 17.5-1 35.5-2.5 53.5-1.5 25-1.5 50-1.5 75 0 13 2 28.5-1.5 41-2 6-7.5 11.5-12.5 15-2.5 1.5-6.5 2.5-8 5-4 7.5 3.5 18.5 1.5 26.5-1.5 4-7.5.5-9 4-2 4-1.5 10.5-2.5 15-2 10-5.5 20-6 30-1 17.5 1 35-1 52.5-2 15.5-8.5 33.5-5.5 49 2.5 11 19 10 26 17 2 2.5 5.5 7.5 4.5 11-1 2.5-11.5 2-14 2-12.5 0-25.5.5-38 .5-2.5 0-11 1.5-12.5-1-2-2.5-.5-11-.5-14V471 383 358c0-3 1-8-.5-11-1-2.5-5-.5-6-3-2.5-5.5 1-12.5-1.5-18-5.5 1.5-11.5 2.5-17.5 4-3 1-7 3-10.5 3-4.5-.5-4-7.5-9.5-6-4.5 1-9.5 4-14 6-1.5.5-4 1-4.5 3-1 2.5 2 5.5 3.5 7.5 3 4.5 5.5 9.5 7 15 5.5 18.5 3 38 8.5 56.5 2 5.5 4 10.5 6.5 15.5 1 1.5 3 4.5 2 6.5-.5 1.5-3 2-4.5 3-4.5 1.5-9.5 5.5-14 6M104 89c2-1 6 2 8 2.5 5 2 9.5 2 14.5 0 2-1 5.5-3.5 8-2.5 1 .5 1.5 2.5 2.5 3.5 1 1.5 3 2.5 3.5 4.5 1.5 7.5-8.5 16.5-15 18.5-9.5 3-19-.5-26-8-1.5-2-5.5-6-4.5-9 1-2 4-3.5 5.5-5.5 1-1 2-3.5 3.5-4M72 228c-6 6-4.5 21-4.5 29.5 0 3 .5 9-1.5 11.5-4.5 5-12 8.5-17 12.5-6 4.5-12.5 9-18.5 13.5-1.5 1-4.5 4.5-7 3.5-2-1-2-5-2-7 0-6.5 3-13 5-19 4.5-11.5 8.5-23 13-34.5 3-7 7-14.5 8-22 1.5-7-.5-15-.5-22.5 0-10-.5-20.5-1-30.5 0-2.5-1-9 2-9.5s12.5-1 14.5 1.5c2.5 3 2 11.5 2.5 15.5 1 13 3 26 3.5 39 0 4.5-.5 14 2.5 17.5 3.5 3.5 7.5.5 11-.5 3-.5 7 0 10.5 0H140c6.5 0 15.5 1.5 21.5 0 3.5-1.5 3-13.5 3.5-17 1.5-12.5 2-24.5 3.5-37 .5-4.5-.5-12 2-16.5.5-1.5 2.5-1 4-1 3 0 12-1.5 14 .5 1.5 1 .5 7 .5 8.5 0 8.5-1.5 17-1.5 25.5 0 12.5-1.5 24.5-2 37-.5 19 .5 37.5.5 56.5 0 9-1.5 25-14 25.5-2 0-3-2-3-3.5-.5-4.5-.5-9.5-1-14-1.5-14.5-2.5-29-3.5-43.5 0-3 .5-13-2.5-15-3-2.5-11.5 0-15.5-.5-15.5-1.5-31.5-1-47-1-6 0-15 1.5-20-2.5M122 346c2.5-2 11-.5 14.5-.5H150c1.5 0 4-.5 5 .5 2 1.5.5 9 .5 11 0 12-7 22-7.5 34-.5 6.5-.5 13.5-.5 20 0 2 1 5.5-.5 7-2.5 1.5-8.5.5-11.5.5s-11.5 1.5-13.5-1c-2.5-2-1-9.5-1-12.5V369 352c0-1.5-.5-5 1-6M14 348c-4 5 2 14 4 19 6.5 17 15.5 34 20 51.5.5 3.5.5 6-1.5 9-5.5 8.5-15 12-18.5 22.5-1 2-2.5 9.5.5 10.5 4 .5 9-3 12.5-4.5 3.5-1 6.5-1 29-10m8-2c3.5 3 8.5 2.5 12.5 4 8.5 3.5 16.5 9 21 17.5 9 17.5 3.5 40.5-13 51-19 12-49 6-56.5-17-1.5-4 0-9-.5-13.5 0-2-2-3-2.5-4.5-.5-3 .5-7 1.5-10 2.5-6 9-11 10-17z"
path_de = "M149 543c-3.5 3-7 2-10-.5Zm5 0c7.5 7 0 18 4 26 2 4.5 11.5 13 16 14.5 3 .5 13.5-1 14.5 3 1 4.5-4 9.5-8 10.5-13 2.5-26.5.5-39.5 1.5-3.5.5-13.5 2.5-16 0-2-1.5-1-7-1-9 1-8 3-16 3-24 0-6.5-2.5-18 2-23m-29 1c2 4.5 1 10 1 15 0 9 1 17.5 2.5 26.5 0 3 2 11-1 13-3.5 2-12 .5-16 .5-12 0-27.5 2.5-39-1C43 596 37.5 589 38 584c.5-3 7-2 9.5-2.5 3 0 6 .5 9-.5 4-1 10.5-6.5 13.5-10 1.5-2 1-5.5 1-8 0-6-2-15.5 1-21m74.5-5h-5M22 596.5c-2 2-9.5 4-11 .5-2.5-5-.5-14.5-.5-20V529.5c0-6-2-16.5 1.5-21.5m2.5-3c-4.5 3 .5 6.5 4.5 3.5m2.5 0L16 505m117 11.5c2.5 5 4 10 6 15.5 1 3 2.5 5 1.5 8s-4 3-6.5 3c-5.5 0-12-4-14.5-9-4.5-9.5-2-23.5-2-34 0-5 1-10.5-2.5-14.5M22.5 477.5c0-33.5.5-66.5.5-100 0-7.5-.5-15.5 0-23 .5-3.5 1.5-7.5-2-9m-4.5 1-4-7M21 329l.5-2m-1 13c4-5 9.5-7 5.5-14.5M171.5 348l-3-29m-3.5-3.5 2.5 3 1-5m-4 1.5 3.5-2m-4 0c1.5 1.5 2.5-1 3.5-2M3 324.5l6-15M28 319c-.5 2-4 11.5-6.5 6-1-1.5-.5-4-.5-5.5-.5-6-4-17-11.5-10.5l-3-3m109 91c0 22-.5 43.5-.5 65.5 0 16.5-2 31.5-2.5 48 0 7 1.5 17.5-2 23.5-2 3.5-7 7-11 8.5-3 1.5-6.5 0-9.5 0-9.5 0-20 1-26-8-4-7-3.5-14-3.5-22V480 355c0-19-1-41 14.5-54.5m73.5 1.5 10 3.5L164 313c4-4 .5-11.5-2.5-16M34 325V271m34.5 11.5c4.5 6.5-1 13 7 17.5 4.5 2.5 13 1 18 1 16 .5 31.5.5 47.5.5 5 0 16.5 0 19.5-4.5 3.5-5 2-16.5 2.5-22.5 2-19.5 1.5-39.5 5-58.5M67 213c-2 16.5 1 34.5 1 51 0 5.5 2 12.5 0 17.5-1 4-5.5 8-7.5 11.5-6.5 9-12.5 18-19 27-2 2.5-8 5-9 7.5-1.5 5.5 0 12.5 0 18V385c0 41-.5 82-.5 123v70c0 6 3.5 23-6.5 20-4-1-3.5-4-3.5-7.5V569c0-19.5-1.5-40 .5-59 1.5-11.5 9-22 0-32.5-4.5 5-2 15-3 21-.5 2-2 6.5-4.5 6-1.5-.5-1.5-3-2.5-4-3-5-2-10.5-2-16.5 0-21.5-1-43 .5-64.5s-1-43 0-64.5c0-2 0-4.5 1.5-6 2.5-2 9-3 8-7.5-1.5-5.5-8-1.5-11.5-3-4-2.5-4-9-5.5-12.5-.5-1.5-2-3-2-5-.5-5.5 1-10.5 4.5-15 1.5-2 4-3.5 5-6 2.5-7 .5-18 .5-25.5V215c0-7.5-2-19 1.5-25.5m3.5-4.5 2.5-3.5M16 185l3 2c3-3 0-3.5 0-7 0-5.5 1-16.5 4.5-21m115-51.5c-8.5 2-15.5 7.5-24.5 6.5-4-.5-8-2-11.5-4-1.5-.5-3.5-2.5-5.5-1.5-4 2 0 14.5.5 18 1 9 0 21.5 6 29 6 6.5 19 6.5 25.5 1 2.5-2.5 3.5-6.5 4.5-10 1.5-6 2-12 3-18s4-15 2-21M30.5 60 30 55m3.5 2.5-3-4M34 56l-3-4m3.5 2.5-3-3.5M35 53.5 32.5 49m3 3-3-3.5M36 51l-2.5-4.5m3 3-3-3.5M37 48.5 34.5 44m3 3-3-3.5M38 46l-2.5-4.5m3 3-3-3.5M39 43.5c-7-7 6.5-20.5 9-28.5m-1-1.5-.5-.5m17 0-3-3M49 9.5l-4.5 3m-21 176c-4-2-4.5 1.5-8 2-4 0-.5-4.5.5-5.5-6.5-7.5-5-17-4.5-26 .5-6.5 0-13 0-19.5 0-22-1-46 4-67.5 5.5-22.5 16-40.5 29-59.5L48 15l1.5-6M149 530c-3-5.5 0-10 5-12.5 1 2.5 2 7 .5 9.5-1 1.5-3.5 2-5 3-2.5 3-3.5 10-.5 12.5 3.5 2.5 10-2.5 12.5-4.5 10.5-7.5 8-25.5 8-37V384.5 357c0-1.5-1-8.5 2-8s2.5 7 2.5 9c0 8.5-2 20 .5 28 20-6 19.5-28 19.5-45 0-39.5 1-79 1-118.5 0-12.5 1-25-.5-37.5-.5-4 0-8.5-1.5-12-3-5.5-8.5-6-13.5-6.5-8.5-.5-17.5-.5-26-.5-2.5 0-13 .5-14-1.5s0-4.5 0-6.5c.5-6 1.5-11.5 2.5-17.5 1.5-11.5 3.5-23.5 3.5-35 0-5.5 0-12-3-17-4-6.5-9.5-8-15-12-2-1.5-1.5-4-2.5-6-2-3-7-4-10.5-3.5-5 1.5-4 6.5-7 9-2 1.5-5 2-7 3C97 81 94 84.5 92 88c-9 16-2 40 0 57.5 1 4.5 3.5 13.5 2 18-1 3.5-11 1-13.5 1-8.5 0-16.5 0-25 .5-5 0-10.5 0-13.5 5-5 7.5-3.5 16.5-3.5 25 0 16.5-.5 32.5-.5 49 0 8 2 20.5-4 27-3-4.5-1.5-13-1.5-18.5v-47c0-28.5-.5-57.5 1-86 .5-13.5-.5-27 2-40C39.5 63.5 45 48 52 33c4-8 10.5-15.5 12.5-24L64 8.5C57.5 10 53 18.5 49.5 24 43 34.5 38.5 45 34 56.5c-1 2-3 3-4 5-2 4.5-2.5 10-3.5 15-3 18-2.5 36.5-2.5 54.5 0 8.5-1 17.5-.5 26 .5 4 3.5 7.5 3.5 12 0 5-.5 11-1.5 16-.5 2-2 3.5-2 5.5-1.5 37 0 74.5 0 111.5Z"
path_en2 = "M101.76 452.715c-.9-.315-1.8-.72-2.025-.9-.18-.225-.495-3.645-.675-7.65-.45-11.7-1.71-29.205-2.61-36.315-.225-1.845-.63-6.3-.9-9.9-.27-3.6-.81-9.945-1.215-14.175-.405-4.185-.81-9.315-.9-11.34-.135-2.07-.36-3.735-.585-3.735-.18 0-.45 1.485-.585 3.285-.18 2.925-.81 9.45-2.115 23.04-.675 6.615-1.305 13.68-1.8 20.25-.225 2.97-.855 9.045-1.35 13.5-.495 4.455-.9 10.755-.9 14.04 0 6.525-.405 8.055-2.205 8.595-.63.18-5.31.27-10.395.225-5.085-.045-18.765-.135-30.42-.225-16.965-.135-21.375-.27-21.96-.765-1.125-.945-.945-4.725.405-7.65 1.575-3.33 4.455-6.615 6.66-7.65 3.195-1.395 3.87-2.52 4.41-7.065.225-2.205.495-8.235.585-13.455.18-9.72.765-17.91 1.665-23.4.27-1.71.675-4.14.9-5.4.18-1.215.36-4.365.405-6.975 0-2.61.45-7.74.945-11.475.495-3.69 1.08-10.17 1.305-14.4.225-4.185.585-9.36.855-11.475.27-2.115.675-6.165.945-9 .225-2.835.675-6.975.945-9.225.225-2.205.63-6.39.855-9.225.63-7.605 1.215-12.825 1.935-16.875.81-4.5 1.575-9.54 2.205-14.985.585-5.085.45-5.265-4.815-7.74-1.98-.9-4.815-2.7-6.3-3.96-1.485-1.305-2.835-2.34-2.97-2.34-.675 0-4.635-5.67-5.13-7.335-.315-1.035-1.26-3.285-2.07-5.04-1.35-2.88-1.485-3.465-1.485-7.875-.045-5.715.54-37.935.945-54.45.36-13.995.36-13.995-.315-18.675-.495-3.735-.405-7.38.36-11.16.225-1.215.27-3.735.045-5.85-.495-5.085-.45-13.95.045-20.025.225-3.06.225-6.525 0-8.775-.225-2.025-.45-8.145-.495-13.59-.135-9.27-.045-10.08.855-12.42 1.26-3.195 3.33-5.58 6.885-8.01l2.835-1.89 18.495.045c10.125.045 19.53.045 20.835.045l2.385-.045-2.295-4.275c-1.215-2.385-2.25-4.77-2.25-5.31 0-.585-.54-2.52-1.17-4.365-1.665-4.77-3.375-16.245-3.24-21.825.09-6.03-.18-6.525-3.825-6.525-5.265-.045-8.865-2.925-8.865-7.065 0-4.095 2.88-5.985 8.955-5.985 2.385 0 3.69-.18 4.005-.585.27-.315.81-3.015 1.215-5.985 1.125-8.775 2.43-13.41 5.13-18.45 3.645-6.75 6.12-8.73 10.845-8.73 3.555 0 5.085.72 8.1 3.78 2.61 2.7 3.825 3.105 5.175 1.665.405-.495 2.025-1.665 3.555-2.565 2.25-1.395 3.285-1.71 5.76-1.89 2.655-.18 3.24-.045 5.625 1.215 1.44.765 2.925 1.845 3.285 2.43 3.96 6.255 6.615 13.68 8.145 22.815.63 3.87 1.26 6.3 1.71 6.66.36.315 1.935.54 3.6.54 6.525.045 9.495 1.71 9.495 5.355 0 1.575-.36 2.475-1.35 3.825-1.98 2.61-2.655 2.925-7.38 3.24-3.015.225-4.5.54-4.905.99-.36.45-.495 1.935-.405 4.455.045 2.07-.09 4.095-.315 4.59-.27.45-.675 2.745-.945 5.04-.495 4.455-1.305 7.875-3.825 15.705-2.16 6.66-2.565 7.65-3.735 9.45l-.99 1.575 5.445.315c3.015.18 5.895.135 6.435-.045 1.215-.45 27.855-.27 30.195.18 2.385.495 5.985 4.275 8.055 8.505l1.53 3.195-.135 32.625c-.09 17.955-.405 36.99-.675 42.3-.27 5.31-.45 14.625-.36 20.7.225 11.925-.72 47.43-1.395 53.415-.45 4.095-1.8 7.425-4.14 10.35-.855 1.035-2.205 2.835-3.015 3.96-.855 1.215-2.475 2.61-3.915 3.42-1.35.765-2.565 1.575-2.7 1.8-.405.585-5.715 3.42-7.065 3.735-2.295.54-2.745 1.44-2.475 5.175.135 1.89.45 5.13.72 7.245.225 2.115.675 8.28.9 13.725.27 5.445.675 12.15.945 14.85.405 4.545 1.215 16.02 2.25 31.95.225 3.465.63 11.25.9 17.325.27 6.075.675 13.275.9 15.975.855 10.08 1.755 23.265 2.25 34.38.63 13.68 1.035 15.435 4.185 17.145 2.97 1.665 5.985 4.185 8.01 6.75 1.71 2.16 1.89 2.7 2.115 5.4.18 2.97.18 3.06-1.08 3.87-1.35.945-1.53.945-34.38 1.35-7.29.09-16.02.27-19.35.36-3.915.135-6.66 0-7.74-.315z"
path_de2 = "M84.55 20.64l-11.4 5.7-4.56 5.7-3.42 11.4 1.14 17.1 4.56 28.5h-39.9l-5.7 4.56L21.85 100.44l-2.28 17.1v114L20.71 247.5 27.55 261.18l9.12 7.98 5.7 2.28L34.39 395.7 33.25 435.6l-9.12 1.14-4.56 6.84v4.56h5.7 51.3l1.14-1.14 3.42-41.04 7.98-91.2 2.28-9.12 2.28 9.12L95.95 338.7l6.84 71.82 2.28 36.48 1.14 1.14h55.86v-4.56l-5.7-5.7-7.98-1.14-1.14-34.2-4.56-101.46V273.72l4.56-2.28 6.84-5.7 5.7-10.26 3.42-12.54V103.86l-3.42-7.98-9.12-5.7-17.1-1.14H113.05l1.14-4.56L118.75 57.12l-1.14-18.24-2.28-5.7L108.49 25.2l-7.98-4.56L90.25 19.5Z"i18n = ({
de: {
title: "DIE EMISSIONEN DER WENIGEN <br> DIE LASTEN DER VIELEN",
poor: "Arme",
lower_middle: ["Untere", "Mittelschicht"],
middle: "Mittelschicht",
upper_middle: ["Obere", "Mittelschicht"],
rich: "Reiche",
legend_500: "≈ 500 kg CO₂ pro Person und Jahr",
legend_250: "≈ 250 kg CO₂ pro Person und Jahr",
unit_person_year: "pro Person und Jahr",
compare_label: "Vergleich",
compare_with: "mit Reiche",
lower_middle_full: "Untere Mittelschicht",
upper_middle_full: "Obere Mittelschicht",
background_title: "HINTERGRUND",
authors_title: "AUTOREN",
data_title: "DATEN",
food: "Lebensmittel",
housing: "Wohnen",
energy: "Energie",
mobility: "Mobilität",
goods: "Güter",
services: "Dienstleistungen",
total_emissions: "Emissionen Gesamt",
budget_text_line1: "CO₂-Budget",
budget_text_line2: "max. 3 t"
},
en: {
title: "EMISSIONS OF THE FEW <br> BURDENS OF THE MANY",
poor: "Poor",
lower_middle: ["Lower", "middle class"],
middle: "Middle class",
upper_middle: ["Upper", "middle class"],
rich: "Rich",
legend_500: "≈ 500 kg CO₂ per person per year",
legend_250: "≈ 250 kg CO₂ per person per year",
unit_person_year: "per person per year",
compare_label: "Compare",
compare_with: "with Rich",
lower_middle_full: "Lower middle class",
upper_middle_full: "Upper middle class",
background_title: "BACKGROUND",
authors_title: "AUTHORS",
data_title: "DATA",
food: "Food",
housing: "Housing",
energy: "Energy",
mobility: "Mobility",
goods: "Goods",
services: "Services",
total_emissions: "Total Emissions",
budget_text_line1: "CO₂ Budget",
budget_text_line2: "max. 3 t"
}
})
t = key => i18n[language][key]makeChimneytype = function({
inputData,
shapeDomain = ["Emissionen Gesamt"],
shapeRange = [path7],
shapeRangeHalf = [path7_half],
colorRange = ["#474A51"],
quantileSort = ["Arme", "Untere Mittelschicht", "Mittelschicht", "Obere Mittelschicht", "Reiche"],
quantileLabels = {},
budgetText = ["CO₂-Budget", "max. 3 t"],
xDomain = [-0.5, 12],
height = 60,
size = 5,
unit = "€",
divisor = 1000,
showHeader = false,
iconsPerRow = 10
}) {
const poorLabel = quantileLabels["Arme"] || "Arme";
const lowerMiddleLabel = quantileLabels["Untere Mittelschicht"] || "Untere Mittelschicht";
const middleLabel = quantileLabels["Mittelschicht"] || "Mittelschicht";
const upperMiddleLabel = quantileLabels["Obere Mittelschicht"] || "Obere Mittelschicht";
const richLabel = quantileLabels["Reiche"] || "Reiche";
const labelExprString = `datum.value === 'Arme' ? '${poorLabel}' : datum.value === 'Untere Mittelschicht' ? '${lowerMiddleLabel}' : datum.value === 'Mittelschicht' ? '${middleLabel}' : datum.value === 'Obere Mittelschicht' ? '${upperMiddleLabel}' : datum.value === 'Reiche' ? '${richLabel}' : datum.value`;
return vl.layer(
vl.markPoint({
size: size,
stroke: "black",
strokeOpacity: 1,
strokeWidth: 0.8,
opacity: 0.811,
clip: false,
})
.encode(
vl.y()
.fieldN("name")
.sort(shapeDomain)
.scale({
domain: shapeDomain,
})
.axis({
domain: false,
ticks: false,
title: null,
labelOffset: 18,
minExtent: width >= 860 ? 128 : 16,
labels: width >= 860,
// Combined translation and splitting logic
labelExpr: width >= 860
? `datum.label === 'Emissionen Gesamt' ? split('${t("total_emissions")}', ' ') : split(datum.label, ' ')`
: `datum.label === 'Emissionen Gesamt' ? '${t("total_emissions")}' : datum.label`,
grid: false,
}),
vl.yOffset()
.fieldQ("rowOffset")
.scale({ domain: [0, 100], range: [0, 100] }),
vl.x()
.fieldQ("xPosition")
.scale({
domain: xDomain,
nice: false,
zero: false,
})
.axis({
title: null,
labels: false,
ticks: false,
domain: false,
grid: false,
clip: false,
}),
vl.fill()
.fieldN("colorCategory")
.scale({
domain: ["light", "dark"],
range: ["#d3d3d3", "#474A51"]
})
.legend(false),
vl.shape()
.fieldN("shapeKey")
.scale({
domain: shapeDomain.concat(shapeDomain.map(d => d + "_half")),
range: shapeRange.concat(shapeRangeHalf)
})
.legend(false)
),
vl.markText({
align: "center",
dy: 24,
dx: 80,
fontSize: 14,
clip: false,
color: "gray",
})
.transform(
vl.filter(`datum.positions === 0 && datum.Quantile == "${quantileSort[0]}"`)
)
.encode(
vl.y()
.fieldN("name")
.sort(shapeDomain),
vl.x()
.datum(0.5),
vl.text()
.datum(budgetText)
)
)
.transform(
vl.calculate(`datum.value / ${divisor}`).as("exactValue"),
vl.calculate(`floor(datum.exactValue)`).as("fullShapes"),
vl.calculate(`datum.exactValue - datum.fullShapes`).as("decimal"),
vl.calculate(`datum.decimal >= 0.25 && datum.decimal < 0.75 ? datum.fullShapes + 1 : round(datum.exactValue)`).as("totalShapes"),
vl.calculate(`sequence(0, datum.totalShapes)`).as("positions"),
vl.flatten(["positions"]),
vl.calculate(`datum.positions === datum.totalShapes - 1 && datum.decimal >= 0.25 && datum.decimal < 0.75 ? 1 : 0`).as("isHalfShape"),
vl.calculate(`datum.isHalfShape === 1 ? datum.name + '_half' : datum.name`).as("shapeKey"),
vl.calculate(`floor(datum.positions / ${iconsPerRow})`).as("row"),
vl.calculate(`datum.positions % ${iconsPerRow}`).as("xPosition"),
vl.calculate(`datum.isHalfShape === 1 ? datum.xPosition - 0.5 : datum.xPosition`).as("adjustedXPosition"),
vl.calculate(`datum.row * 40`).as("rowOffset"),
vl.calculate(`datum.positions < 6 ? 'light' : 'dark'`).as("colorCategory")
)
.width(makeWidth(width))
.height(height)
.facet(
width < 860 ? {
row: {
field: "Quantile",
type: "nominal",
title: null,
sort: quantileSort,
header: showHeader ? {
labelFontSize: 15,
labelOrient: "top",
labelPadding: -8,
labelAlign: "left",
labelAnchor: "start",
labelExpr: labelExprString
} : {
labels: false
}
},
} : {
column: {
field: "Quantile",
type: "nominal",
title: null,
sort: quantileSort,
header: showHeader ? {
labelFontSize: 15,
labelOrient: "top",
labelPadding: -8,
labelAlign: "left",
labelAnchor: "start",
labelExpr: labelExprString
} : {
labels: false
},
},
}
)
.resolve({scale: {x: "shared"}})
.data(inputData)
.config(config);
}makeDeciletype = function({
decileData,
labelData,
arrowLeftHigh,
arrowLeft,
arrowLeftMedium,
arrowRight,
vizWidth = 500,
vizHeight = 40
}) {
return vl.layer(
vl.markPoint({
shape: path_neurath,
size: 0.08,
stroke: "black",
strokeWidth: 0.8,
filled: true
})
.data(decileData)
.encode(
vl.x()
.fieldQ("position")
.scale({ domain: width >= 860 ? [-1.2, 10.7] : [-0, 10], nice: false })
.axis(null),
vl.opacity()
.fieldN("isHighlighted")
.scale({ domain: [false, true], range: [0.33, 1] })
.legend(null),
vl.fill()
.fieldN("isHighlighted")
.scale({ domain: [false, true], range: ["url(#hatch_left)", "url(#hatch_right)"] })
.legend(null)
),
vl.markText({
dy: 76,
dx: 14,
align: "center",
anchor: "middle",
baseline: "top",
fontSize: 15,
})
.data(labelData)
.encode(
vl.x()
.fieldQ("position")
.axis(null),
vl.text()
.fieldN("label"),
vl.opacity()
.fieldN("isHighlighted")
.scale({ domain: [false, true], range: [0.33, 1] })
.legend(null)
),
...(width >= 860 ? [
vl.markPoint({
size: 0.04,
stroke: "#333333",
strokeWidth: 1,
filled: false
})
.data(labelData)
.encode(
vl.x()
.fieldQ("xArrow")
.axis(null),
vl.y()
.fieldQ("yArrow")
.scale({ domain: [-15, 5] })
.axis(null),
vl.shape()
.fieldN("arrow")
.scale({ domain: ["leftHigh", "left", "leftMedium", "right"], range: [arrowLeftHigh, arrowLeft, arrowLeftMedium, arrowRight] })
.legend(null),
vl.size()
.fieldN("isHighlighted")
.scale({ domain: [false, true], range: [0, 0.04] })
.legend(null),
vl.opacity()
.fieldN("isHighlighted")
.scale({ domain: [false, true], range: [0.33, 1] })
.legend(null)
)
] : [])
)
.width(width >= 860 ? vizWidth : (width - 24))
.height(vizHeight)
.config({ ...config, padding: { bottom: 13 } });
}viewof language = {
const div = html`
<div class="lang-switch">
<div class="lang-btn" data-lang="de">
<svg viewBox="-5 -5 210 470">
<path d="${path_de2}" transform="translate(10, 0)"/>
</svg>
<span>DE</span>
</div>
<div class="lang-btn" data-lang="en">
<svg viewBox="-5 -5 210 470">
<path d="${path_en2}" transform="translate(7, 0)"/>
</svg>
<span>EN</span>
</div>
</div>
`;
const buttons = div.querySelectorAll(".lang-btn");
// 1. Detect browser language
const browserLang = navigator.language?.slice(0, 2);
const supported = ["de", "en"];
// 2. Set default language
div.value = supported.includes(browserLang) ? browserLang : "en";
// 3. Update active state
const updateActive = () => {
buttons.forEach(b => {
b.classList.toggle("active", b.dataset.lang === div.value);
});
};
updateActive();
// 4. Click handling
buttons.forEach(btn => {
btn.onclick = () => {
div.value = btn.dataset.lang;
updateActive();
div.dispatchEvent(new Event("input"));
};
});
return div;
}arrowRightBase = "M35 138C160.529 259.062 328.006 192.715 361 184.482 M343.69 257C355.23 209.711 361 185.319 361 183.823C361 181.579 327.488 165.87 312 142"
arrowLeftBase = "M365 138C239.471 259.062 71.994 192.715 39 184.482 M56.31 257C44.77 209.711 39 185.319 39 183.823C39 181.579 72.512 165.87 88 142"
arrowRight = rotatePath(arrowRightBase, 125) // pointing up-right
arrowLeftHigh = rotatePath(arrowLeftBase, -125) // pointing up-left (symmetric to right)
arrowLeft = rotatePath(arrowLeftBase, -125) // pointing up-left (symmetric to right)
arrowLeftMedium = rotatePath(arrowLeftBase, -125)baseData = [
{ decile: 1, group: t("poor"), position: 0, label: t("poor"), arrow: "right", xArrow: -1, yArrow: -28 },
{ decile: 2, group: "na", position: 1 },
{ decile: 3, group: t("lower_middle_full"), position: 2, label: t("lower_middle"), arrow: "right", xArrow: 1, yArrow: -28 },
{ decile: 4, group: "na", position: 3 },
{ decile: 5, group: t("middle"), position: 4, label: t("middle"), arrow: "right", xArrow: 3, yArrow: -28 },
{ decile: 6, group: "na", position: 5 },
{ decile: 7, group: t("upper_middle_full"), position: 6, label: t("upper_middle"), arrow: "right", xArrow: 5, yArrow: -28 },
{ decile: 8, group: "Andere", position: 7 },
{ decile: 9, group: "Andere", position: 8 },
{ decile: 10, group: t("rich"), position: 9, label: t("rich"), arrow: "leftHigh", xArrow: 9.7, yArrow: -28 }
]
addHighlight = d => ({ ...d, isHighlighted: d.group === selectedQuantile.label || d.group === t("rich") })
labelData = baseData.filter(d => d.label).map(addHighlight)
decileData = baseData.map(addHighlight)html`<svg width="0" height="0" class="svg-defs">
<defs>
<pattern id="hatch_right" patternUnits="userSpaceOnUse" width="3" height="3">
<path d="M0,3 L3,0" stroke="#333333" stroke-width="0.6" fill="none" />
</pattern>
<pattern id="hatch_left" patternUnits="userSpaceOnUse" width="3" height="3">
<path d="M0,0 L3,3" stroke="#333333" stroke-width="0.6" fill="none" />
</pattern>
</defs>
</svg>`makeDeciletype({
decileData: decileData,
labelData: labelData,
arrowLeftHigh: arrowLeftHigh,
arrowLeft: arrowLeft,
arrowLeftMedium: arrowLeftMedium,
arrowRight: arrowRight,
vizWidth: 600,
vizHeight: 10
})
.render({ renderer: "svg" })data = FileAttachment("data/data.csv").csv({ typed: true })
filteredData = data
.filter(d => d.name != "Emissionen Gesamt")
.filter(d => d.Quantile === selectedQuantile.value || d.Quantile === "Reiche")shapeDomainTranslated = [t("food"), t("housing"), t("energy"), t("mobility"), t("goods"), t("services")]
quantileSortTranslated = [t("poor"), t("lower_middle_full"), t("middle"), t("upper_middle_full"), t("rich")]width >= 860 ?
html`<div class="legend-container">
<div class="legend-item">
<svg width="34" height="34" viewBox="0 0 24 21">
<path d="${path2}" fill="${colors_new[4]}" stroke="black" stroke-width="0.649" opacity="0.811"/>
</svg>
<span>${t("legend_500")}</span>
</div>
<div class="legend-item">
<svg width="20" height="34" viewBox="0 0 16 20">
<path d="${path2_half}" fill="${colors_new[4]}" stroke="black" stroke-width="0.649" opacity="0.811"/>
</svg>
<span>${t("legend_250")}</span>
</div>
</div>`
:
html`<div class="legend-container legend-container--vertical">
<div class="legend-item">
<svg width="34" height="34" viewBox="0 0 24 21">
<path d="${path2}" fill="${colors_new[4]}" stroke="black" stroke-width="0.649" opacity="0.811"/>
</svg>
<span>${t("legend_500")}</span>
</div>
<div class="legend-item">
<svg width="20" height="34" viewBox="0 0 16 20">
<path d="${path2_half}" fill="${colors_new[4]}" stroke="black" stroke-width="0.649" opacity="0.811"/>
</svg>
<span>${t("legend_250")}</span>
</div>
</div>`{
language;
const viz = await makeIsotype({
inputData: filteredData,
shapeDomain: ["Lebensmittel", "Wohnen", "Energie", "Mobilität", "Güter", "Dienstleistungen"],
quantileSort: ["Arme", "Untere Mittelschicht", "Mittelschicht", "Obere Mittelschicht", "Reiche"],
// Add these new parameters with current translations
quantileLabels: {
"Arme": t("poor"),
"Untere Mittelschicht": t("lower_middle_full"),
"Mittelschicht": t("middle"),
"Obere Mittelschicht": t("upper_middle_full"),
"Reiche": t("rich")
},
spacing: -50,
height: 200,
size: 8,
divisor: 500,
unit: "t CO₂",
colorRange: colors_new,
showHeader: width >= 860 ? false : true
})
.config(config)
.render({ renderer: "svg" });
return html`<div class="viz-container">${viz}</div>`;
}filteredEmissionen = data
.filter(d => d.name == "Emissionen Gesamt")
.filter(d => d.Quantile === selectedQuantile.value || d.Quantile === "Reiche"){
language;
const viz = await makeChimneytype({
inputData: filteredEmissionen,
shapeDomain: ["Emissionen Gesamt"],
quantileSort: [
"Arme",
"Untere Mittelschicht",
"Mittelschicht",
"Obere Mittelschicht",
"Reiche"
],
quantileLabels: {
"Arme": t("poor"),
"Untere Mittelschicht": t("lower_middle_full"),
"Mittelschicht": t("middle"),
"Obere Mittelschicht": t("upper_middle_full"),
"Reiche": t("rich")
},
budgetText: [t("budget_text_line1"), t("budget_text_line2")],
shapeRange: [path7],
shapeRangeHalf: [path7_half],
colorRange: ["#474A51"],
height: 100,
size: 8,
unit: "t CO₂",
divisor: 500,
showHeader: width < 860,
iconsPerRow: 10
})
.config(config)
.render({ renderer: "svg" });
return html`<div class="viz-container">${viz}</div>`;
}
quantileOptions = [
{ value: "Arme", label: t("poor") },
{ value: "Untere Mittelschicht", label: t("lower_middle_full") },
{ value: "Mittelschicht", label: t("middle") },
{ value: "Obere Mittelschicht", label: t("upper_middle_full") }
]viewof selectedQuantile = Inputs.select(
quantileOptions,
{
label: t("compare_label") + " ",
value: d => d.value, // ← what gets returned
format: d => d.label // ← what gets displayed
}
)
html`${language === "de" ? html`
<div>
<h3>HINTERGRUND</h3>
<p>Wirksamer Klimaschutz muss soziale Gerechtigkeit mitdenken. Neuen <a href="https://doi.org/10.1111/jiec.70074" target="_blank"><strong>Daten</strong></a> zufolge verursachen die nach Pro-Kopf-Haushaltseinkommen <strong>reichsten 10%</strong> der Bevölkerung in Österreich pro Kopf <strong>mehr als dreimal</strong> so hohe CO₂-Emissionen wie die <strong>ärmsten 10%</strong>.<sup>1</sup> Diese Ungleichheit ist besonders bei <strong class="highlight-mobility">Mobilität</strong>, <strong class="highlight-housing">Wohnen</strong> und <strong class="highlight-services">Dienstleistungen</strong> ausgeprägt. Diese Emissionen führen zu hohen gesellschaftlichen Kosten infolge steigender Temperaturen, etwa durch extreme Wetterereignisse, Produktivitätsverluste und zusätzlichen gesundheitlichen Belastungen. Diese Kosten werden von allen getragen, unabhängig davon, ob eine einzelne Person mehr oder weniger Emissionen verursacht. Um die Auswirkungen zumindest zu begrenzen, dürfte 2025 eine Person nur etwa 3 Tonnen an CO₂-Äquivalenten emittieren<sup>2</sup>. Ein Wert den alle Einkommensdezile in Österreich überschreiten — allen voran Personen mit höherem Einkommen.</p>
<p>Die <strong>Wiener Methode der Bildstatistik</strong> (Isotype) macht diese abstrakte Verteilungsfrage unmittelbar erfassbar: Jedes Symbol steht für eine konkrete Menge CO₂. Was <strong>Marie</strong> und <strong>Otto Neurath</strong> in den 1920er-Jahren für wirtschaftliche und soziale Zusammenhänge entwickelten, zeigt auf einen Blick, wie drastisch die Emissionen zwischen Arm und Reich auseinanderklaffen.</p>
<h3>AUTOREN</h3>
<p><a href="https://www.soziologie.lmu.de/de/das-institut/personen/kontaktseite/fabian-kalleitner-e069e205.html" target="_blank"><strong>Fabian Kalleitner</strong></a> — <a href="https://www.wifo.ac.at/person/lukas-schmoigl/" target="_blank"><strong>Lukas Schmoigl</strong></a></p>
<h3>DATEN</h3>
<p>Dorninger, Christian, Simone Gingrich, Willi Haas, Alina Brad, Etienne Schneider, and Dominik Wiedenhofer. 2025. "Slow and Unequal Reduction in Austrian Household GHG Footprints Between 2000 and 2020." <em>Journal of Industrial Ecology</em> 29 (5): 1651–65. <a href="https://doi.org/10.1111/jiec.70074" target="_blank"><strong>Link</strong></a>.</p>
<hr class="footnote-separator">
<p class="footnote"><sup>1</sup> Diese Schätzungen beziehen sich auf das Jahr 2020 und basieren auf einer Datenkombination aus multiregionalen Input-Output-Tabellen, Umfragedaten der österreichischen Konsumerhebung, und Daten aus der österreichische Luftschadstoff- und Treibhausgas-Inventur (THGI) (Dorninger et al. 2025). Die Daten inkludieren andere Treibhausgase und beziehen sich auf CO₂-Äquivalente.</p>
<p class="footnote"><sup>2</sup> Schätzwert für 2025 berechnet anhand des österreichischen Anteils am global maximalen Emissionswert zur Erreichung des +1,5°C Ziels und einer konstanten prozentualen Abnahme der Emissionen ab 2022. Siehe: <a href="https://ccca.ac.at/fileadmin/00_DokumenteHauptmenue/02_Klimawissen/Papiere/THG-Budget_Hintergrundpapier_CCCA.pdf" target="_blank">Climate Change Center Austria (CCCA) 2022</a>: 1,5° C: Wieviel Treibhausgase dürfen wir noch emittieren? Hintergrundpapier zu globalen und nationalen Treibhausgasbudgets.</p>
</div>
` : html`
<div>
<h3>BACKGROUND</h3>
<p>Effective climate protection must consider social justice. According to new <a href="https://doi.org/10.1111/jiec.70074" target="_blank"><strong>data</strong></a>, the <strong>richest 10%</strong> of the population in Austria by per capita household income cause <strong>more than three times</strong> as much CO₂ emissions per capita as the <strong>poorest 10%</strong>.<sup>1</sup> This inequality is particularly pronounced in <strong class="highlight-mobility">mobility</strong>, <strong class="highlight-housing">housing</strong>, and <strong class="highlight-services">services</strong>. These emissions lead to high societal costs due to rising temperatures, such as extreme weather events, productivity losses, and additional health burdens. These costs are borne by everyone, regardless of whether an individual emits more or fewer emissions. To at least limit the impacts, a person should emit only about 3 tons of CO₂ equivalents in 2025<sup>2</sup>. A value that all income deciles in Austria exceed — especially those with higher incomes.</p>
<p>The <strong>Vienna Method of Pictorial Statistics</strong> (Isotype) makes this abstract distribution question immediately comprehensible: Each symbol represents a concrete amount of CO₂. What <strong>Marie</strong> and <strong>Otto Neurath</strong> developed in the 1920s for economic and social relationships shows at a glance how drastically emissions diverge between rich and poor.</p>
<h3>AUTHORS</h3>
<p><a href="https://www.soziologie.lmu.de/en/our-department/faculty-and-staff/contact-page/fabian-kalleitner-e069e205.html" target="_blank"><strong>Fabian Kalleitner</strong></a> — <a href="https://www.wifo.ac.at/person/lukas-schmoigl/" target="_blank"><strong>Lukas Schmoigl</strong></a></p>
<h3>DATA</h3>
<p>Dorninger, Christian, Simone Gingrich, Willi Haas, Alina Brad, Etienne Schneider, and Dominik Wiedenhofer. 2025. "Slow and Unequal Reduction in Austrian Household GHG Footprints Between 2000 and 2020." <em>Journal of Industrial Ecology</em> 29 (5): 1651–65. <a href="https://doi.org/10.1111/jiec.70074" target="_blank"><strong>Link</strong></a>.</p>
<hr class="footnote-separator">
<p class="footnote"><sup>1</sup> These estimates refer to the year 2020 and are based on a data combination of multi-regional input-output tables, survey data from the Austrian household budget survey, and data from the Austrian air pollutant and greenhouse gas inventory (THGI) (Dorninger et al. 2025). The data includes other greenhouse gases and refers to CO₂ equivalents.</p>
<p class="footnote"><sup>2</sup> Estimate for 2025 calculated based on Austria's share of the global maximum emission value to achieve the +1.5°C target and a constant percentage decrease in emissions from 2022. See: <a href="https://ccca.ac.at/fileadmin/00_DokumenteHauptmenue/02_Klimawissen/Papiere/THG-Budget_Hintergrundpapier_CCCA.pdf" target="_blank">Climate Change Center Austria (CCCA) 2022</a>: 1,5° C: Wieviel Treibhausgase dürfen wir noch emittieren? Hintergrundpapier zu globalen und nationalen Treibhausgasbudgets.</p>
</div>
`}`