Public
Edited
Feb 24
Insert cell
Insert cell
Insert cell
Insert cell
mutable selectedID = null
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataPath = "https://www.web.statistik.zh.ch/ogd/daten/ressourcen/KTZH_00001362_00006004.csv"
Insert cell
// Wir arbeiten vorerst mit Dummy-Daten, welche die Gemeindefusion von 2023 bereits enthält
data = d3 // FileAttachment("Netzproben@6.txt").csv({ typed: true })
.csv(
"https://www.web.statistik.zh.ch/ogd/daten/ressourcen/KTZH_00001362_00006004.csv",
// "https://www.web.statistik.zh.ch/ogd/data/gesundheitsdirektion-kanton-zuerich/kl/twq-data/Trinkwasserqualitaetsexport.csv",
{ typed: true }
)
.then(function (data) {
data.forEach(function (d) {
d.druckzone = d.verteilzone;
d.messwert_kategorie = d.kategorie + " " + d.einheit;
d.kategorie_sortierung = +d.kategorie_sortierung;
d.date = d3.timeFormat("%Y-%m-%d")(new Date(d.datum));
});
return data;
})
Insert cell
Insert cell
Insert cell
// Es gibt eine eigene OGD-Ressource, in welcher die erfassten Analyten sowie deren zulässigen Wertebereiche erfasst sind
konzentrationsbereiche = d3
.csv(
"https://www.web.statistik.zh.ch/ogd/daten/ressourcen/KTZH_00001362_00006003.csv",
// "https://www.web.statistik.zh.ch/ogd/data/gesundheitsdirektion-kanton-zuerich/kl/twq-data/Konzentrationsbereiche.csv",
{ typed: true }
)
.then(function (data) {
data.forEach(function (d) {
d.gruppe_id = +d.gruppe_id;
d.analyt_id = +d.analyt_id;
d.kategorie_sortierung = +d.kategorie_sortierung;
d.messwert_kategorie = d.kategorie + " " + d.einheit;
d.farbcode_legende = d.farbcode.concat("90"); // changing hexcolor code to match fill opacity 0.4 in the map (considering the impact of the background)
});
return data;
})
Insert cell
// Daten mit den Farbcodes aus dem CSV "Bewertungskriterien" anreichern
timeseries = tidy(
// data,
mergedData,
leftJoin(konzentrationsbereiche, { by: ["analyt", "kategorie_sortierung"] })
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dateRange = d3.extent(mergedData, (d) => d.date)
Insert cell
xDomain = selectZeitraum == "Letzten 2 Jahre"
? [subtractYears(new Date(dateRange[1]), 2), dateRange[1]]
: dateRange
Insert cell
Insert cell
subtractYears(new Date(dateRange[1]), 2)
Insert cell
// Function to subtract years from a Date
// Used to adjust the domain of the x-axis
function subtractYears(date, years) {
date.setFullYear(date.getFullYear() - years);
// return d3.timeFormat("%Y-%m-%d")(new Date(date));
return d3.timeFormat("%Y-%m-%d")(date.setMonth(date.getMonth(), 1)); // To get the first day of the month
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof gruppeSelect = Inputs.select(gruppe, {
value: "Alle Gruppen"
})
Insert cell
gruppe = [
...new Set(
konzentrationsbereiche
.sort((a, b) => a.gruppe_id - b.gruppe_id)
.map((d) => d.gruppe)
.concat("Alle Gruppen")
)
]
Insert cell
Insert cell
Insert cell
filteredAnalytGruppe = {
const filteredAnalyt =
gruppeSelect !== "Alle Gruppen"
? konzentrationsbereiche
.filter((d) => d.gruppe === gruppeSelect)
.sort((a, b) => a.analyt_id - b.analyt_id) // sort ascending
.map((d) => d.analyt)
: konzentrationsbereiche
.sort((a, b) => a.analyt_id - b.analyt_id) // sort ascending
.map((d) => d.analyt);

return [...new Set(filteredAnalyt)];
}
Insert cell
Insert cell
Insert cell
Insert cell
plotDataFilter = (function () {
if (selectZeitraum == "Letzten 2 Jahre") {
return (d) =>
d.analyt == analytSelect &&
d.gemeinde_nummer == selectedID &&
d.date >= xDomain[0];
} else {
return (d) => d.analyt == analytSelect && d.gemeinde_nummer == selectedID;
}
})()
Insert cell
// function to draw plots based on index for select industry
function drawPlot(index_druckzonenInGemeindeSelect) {
let data = plotDataByDruckzone[index_druckzonenInGemeindeSelect];
let druckzone = [
...new Set(
d3.map(
plotDataByDruckzone[index_druckzonenInGemeindeSelect],
(d) => d.druckzone
)
)
];
let source = [
...new Set(
d3.map(
plotDataByDruckzone[index_druckzonenInGemeindeSelect],
(d) => d.quelle
)
)
];
const initDate = new Date(
d3.min(plotDataByDruckzone[index_druckzonenInGemeindeSelect], (d) => d.date)
); // letzte Messung pro Druckzone
// The selection
const hover = vl
.selectSingle("hover")
.on("mouseover")
.encodings("x")
.nearest(true)
.clear("moouseout")
.init({
x: {
year: +d3.timeFormat("%Y")(initDate),
month: +d3.timeFormat("%m")(initDate),
date: +d3.timeFormat("%d")(initDate)
}
});

// predicate to test if a point is hover-selected
// return false if the selection is empty
const isHovered = hover.empty(false);

// The line and point marks. Notice how we filter the points on hover
const lineAndPoint = vl
.data(data)
.layer(
vl
.markPoint({
size: 100,
filled: false,
legend: null,
color: { value: "#949494" }
})
.transform(vl.filter(hover)),
// .encode(
// vl
// .color()
// .field("quelle")
// .scale({
// domain: colorDomainPlot,
// range: colorRangePlot,
// nice: true
// })
// .legend("top")
// .title("Quelle")
// ),
vl.markCircle({ size: 75, stroke: "white", strokeWidth: 1.5 }).encode(
vl
.color()
.field("quelle")
.scale({
domain: colorDomainPlot,
range: colorRangePlot,
nice: true
})
.legend("top")
.title("Quelle"),
vl.tooltip([
{ field: "tipDatum", title: "Datum" },
{ field: "messwert_kategorie", title: "Messwert" },
{ field: "quelle", title: "Quelle" }
])
)
)
.encode(
vl.x().fieldT("date").scale({ domain: xDomain }),
vl
.y()
.fieldN("messwert_kategorie")
.scale({ domain: yDomain, reverse: true })
);

// The rule helps as a proxy for the hover. We draw rules all over the chart
// so we can easily find the nearest one. We then hide them using opacity 0
const rule = vl
.markRule({
stroke: "#666666",
strokeWidth: 1,
tooltip: true,
strokeDash: [8, 8]
})
.data(data)
.encode(
vl.x().fieldT("date").scale({ domain: xDomain }),
vl.opacity().value(0).if(hover, vl.value(1)).value(0),
vl.tooltip([
{ field: "tipDatum", title: "Datum" },
{ field: "messwert_kategorie", title: "Messwert" },
{ field: "quelle", title: "Quelle" }
])
)
.select(hover);

let plot = vl
.layer(lineAndPoint, rule)
.title({
// adding facette title
text: `Verteilzone: ${druckzone}`,
subtitle: `Quelle: ${source}`,
anchor: "start",
color: "black",
fontSize: 14
})
.width(widthNow * 0.825)
.height(height);

return plot;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof plotDruckzonen = {
// Aubauen der nach Verteilzonen gruppierten Daten je Gemeinde
const all_plots = [];
// Info, für welche Druckzonen keine Daten vorhanden sind

if (delta_druckzonenInGemeindeSelect.length > 0) {
for (
let i = 0;
i < [delta_druckzonenInGemeindeSelect.join(", ")].length;
i++
) {
var plot_i = drawNoPlot(delta_druckzonenInGemeindeSelect.join(", "));
all_plots.push(plot_i);
}
}

for (var i = 0; i < plotDataByDruckzone.length; i++) {
var plot_i = drawPlot(i);
all_plots.push(plot_i);
}

return vl
.vconcat(all_plots)
.autosize({ resize: true, type: "fit" })
.config({
locale: { number: localeNumber, time: localeTime },
view: { stroke: null },
// scale: { bandPaddingInner: 0.2, bandPaddingOuter: 0.2, stroke: 2 },
background: "#ffffff",
concat: { spacing: 35 },
axis: {
labelFont: ZHFonts.regular,
labelFontSize: 14,
labelColor: "black",
// labelPadding: 7,
titleFont: ZHFonts.black,
titleFontSize: 16,
titleFontColor: "black",
tickColor: "lightgrey",
domainColor: "#949494",
gridColor: "lightgrey"
},
axisX: {
title: null,
labelAngle: 0,
labelAlign: "left",
tickCount:
selectZeitraum == "Letzten 2 Jahre"
? Math.ceil(width / 240)
: Math.ceil(widthNow / 120),
labelExpr:
"[timeFormat(datum.value, '%b'), timeFormat(datum.value, '%m') == '01' ? timeFormat(datum.value, '%Y') : '']",
labelOffset: 4,
labelPadding: -24,
tickSize: 30,
grid: true,
gridDash: {
condition: {
test: { field: "value", timeUnit: "month", equal: 1 },
value: []
},
value: [2, 2]
},
tickDash: {
condition: {
test: { field: "value", timeUnit: "month", equal: 1 },
value: []
},
value: [2, 2]
}
},
axisY: {
title: null,
labelLimit: 300,
tickSize: 10,
grid: true
},
title: {
font: ZHFonts.black,
fontSize: 16,
offset: 15,
anchor: "start",
lineHeight: 5,
padding: 40,
subtitleFont: ZHFonts.regular,
subtitleFontSize: 14
},
header: {
labelOrient: "top",
labelPadding: 5,
lineHeight: 3,
labelFontSize: 14,
labelFont: ZHFonts.black,
labelColor: "black"
},
legend: {
orient: "top",
symbolSize: 200,
titleFont: ZHFonts.black,
titleFontSize: 14,
labelFontSize: 14,
labelFont: ZHFonts.regular,
labelColor: "black",
labelLimit: 300,
// columns: 2, // responsive legend width
rowPadding: 10,
title: null
},
text: {
font: ZHFonts.regular,
align: "center",
fill: "black",
fontSize: 12,
dx: 0
}
})
.render();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// css = htl.html`<link href="https://bitbucket.statistik.zh.ch/projects/ZW/repos/lib/raw/zhweb-fonts.min.css?at=refs%2Fheads%2Fmaster" type="text/css" rel="stylesheet" />
// <link href="https://bitbucket.statistik.zh.ch/projects/ZW/repos/lib/raw/zhweb-text.css?at=refs%2Fheads%2Fmaster" type="text/css" rel="stylesheet" />
// `
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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