Published
Edited
Jul 27, 2022
Importers
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
regions_table(regions_incidence, 'REGIJE (urejene po projekciji 14d inc.)', (a,b) =>
b.incidenceLastWk + b.incidenceWkNextS - a.incidenceLastWk - a.incidenceWkNextS)
Insert cell
regions_incidence
Insert cell
d3.csvFormat(regions_incidence)
Insert cell
Insert cell
Insert cell
pop_norm = 100000;
Insert cell
function phaseChartInc(incdata, opts_param) {
const options = {
hist_lookback: 0,
hist_len: 7,
ref_offset: 0,
smooth: 2,
smooth_growth: 2,
postproc: (data => data),
filter: (a => true),
...opts_param
};
const data = incdata.sort((a,b) => b.population - a.population).map(reg => {
let srs = reg.series.map(a => a.c7/7);
if (options.hist_lookback > 0) {
srs = srs.slice(0, -options.hist_lookback);
}
for (let ii = 1; ii <= options.smooth; ii++) {
srs = srs.map((a, idx, arr) => idx<arr.length-1 ? geomgauss3(arr, idx) : a);
}
let srsGrowth = srs;
for (let ii = 1; ii <= options.smooth_growth; ii++) {
srsGrowth = srsGrowth.map((a, idx, arr) => idx<arr.length-1 ? geomgauss3(arr, idx) : a);
}
const series_inc = srs;
const series = series_inc.map((a, idx, arr) => {
const aPr = srsGrowth[idx-1];
const aN = idx < srsGrowth.length-1 ? srsGrowth[idx+1] : srsGrowth[idx]*srsGrowth[idx]/aPr;
return {
x: a * pop_norm,
y: index_from_ratio(Math.pow(aN / aPr, 7/2))
};
}).slice(-options.hist_len-1);
const refIdx = series.length - 1 - options.ref_offset;
return {
name: reg.name,
region: reg.region,
x: series[refIdx].x,
y: series[refIdx].y,
series: series,
r: /*reg.population //*/series[refIdx].x * reg.population / pop_norm
};
});
return phaseChart(options.postproc(data).filter(options.filter), options);
}
Insert cell
function index_from_ratio(ratio) {
const idx = (ratio - 1)/(ratio + 1);
return idx;//Math.pow(Math.abs(idx), 1/2) * Math.sign(idx);
}
Insert cell
function ratio_from_index(index) {
const idx = index;// Math.pow(index, 2);
return (1 + idx) / (1 - idx);
}
Insert cell
indexTicks=[0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1,1.1,1.2,1.3,1.4,1.5,1.6,1.75,2.0,2.5,3, 3.5, 6, 11,61,Number.POSITIVE_INFINITY].map(index_from_ratio);
Insert cell
function phaseChart(data, opts_param) {
const minX = d3.min(data.map(a => d3.min(a.series.map(s => s.x).filter(a => a > 0))));
const maxX = d3.max(data.map(a => d3.max(a.series.map(s => s.x).filter(a => a >= 0))));

const minY = d3.min(data.map(a => d3.min(a.series.map(s => s.y).filter(a => a>=-1))));
const maxY = d3.max(data.map(a => d3.max(a.series.map(s => s.y).filter(a => a<=100))));
const maxR = d3.max(data.map(a => a.r));
//return [minX, maxX, minY, maxY];

const options = {
bndsX: [Math.max(0.01, minX * 0.95), Math.min(1000, maxX * 1.2)],
bndsY: [Math.max(0, (minY+1)*0.95)-1, Math.min(6, (maxY+1)*1.1)-1],
w: width,
h: width*9/16,
scaleR: 1,
clampX: true,
clampY: true,
...opts_param
};
const margin = ({top: 10 + (options.title ? 10 : 0), right: 5, bottom: 35, left: 45});
const nTicksX = options.w / 40;
const smoothW = Math.round(7 + Math.sqrt(options.smooth));
const scaleX = d3.scaleLog(options.bndsX, [margin.left, options.w - margin.right]);
scaleX.clamp(options.clampX);
const scaleXAxis = d3.axisBottom(scaleX).ticks(nTicksX, ",").tickFormat(d3.format('.2s'));
const xAxis = g => g
.attr("font-family", "'IBM Plex Sans',sans-serif")
.attr("transform", `translate(0,${options.h - margin.bottom})`)
.call(scaleXAxis)
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", options.w)
.attr("y", margin.bottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text("Primeri na " + (pop_norm/1000) + "k preb. (" + smoothW + "d glajenje)→"));
const yTicks = indexTicks.filter(a => inRange(a, options.bndsY));
const scaleY = d3.scaleLinear(options.bndsY, [options.h - margin.bottom, margin.top]).domain(options.bndsY);
scaleY.clamp(options.clampY);
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(scaleY).tickValues(yTicks).tickFormat(idx => d3.format('.00%')( ratio_from_index(idx) - 1 )))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -margin.left)
.attr("y", 7)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ Tedenska rast (" + smoothW + "d glajenje)"));
const radius = d3.scaleSqrt([0, 314], [0, options.w / 20 * options.scaleR]);
const grid = g => g
.attr("stroke", "black")
.attr("stroke-width", 0.05)
.call(g => g.append("g")
.selectAll("line")
.data(scaleX.ticks(nTicksX))
.join("line")
.attr("x1", d => 0.5 + scaleX(d))
.attr("x2", d => 0.5 + scaleX(d))
.attr("y1", margin.top)
.attr("y2", options.h - margin.bottom))
.call(g => g.append("g")
.selectAll("line")
.data(yTicks)
.join("line")
.attr("y1", d => 0.5 + scaleY(d))
.attr("y2", d => 0.5 + scaleY(d))
.attr("x1", margin.left)
.attr("x2", options.w - margin.right));
const svg = d3.create("svg")
.attr("viewBox", [0, 0, options.w, options.h])
.attr("font-family", "'IBM Plex Sans',sans-serif");

svg.append("g").append("line") // attach a line
.style("stroke", "black") // colour the line
.style("stroke-width", 0.2)
.attr("x1", scaleX(0)) // x position of the first end of the line
.attr("y1", scaleY(0)) // y position of the first end of the line
.attr("x2", scaleX(100000)) // x position of the second end of the line
.attr("y2", scaleY(0));

svg.append("g")
.selectAll("rect")
.data([
{x1: 0.01, x2: 14.5, c: 'green'},
{x1: 14.5, x2: 29, c: 'yellow'},
{x1: 29, x2: 48, c: 'orange'},
{x1: 48, x2: 65, c: 'red'},
{x1: 65, x2: 1000, c: 'darkgray'}])
.join("rect")
.attr("opacity",0.2)
.attr("x", d => scaleX(d.x1))
.attr("y", options.h - margin.bottom)
.attr("width", d => scaleX(d.x2) - scaleX(d.x1))
.attr("height", 20)
.attr("fill", d => d.c);

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

svg.append("g")
.call(grid);
const line = d3.line()
.defined(d => d && (d.x || isFinite(d.x)) && (d.y || isFinite(d.y)))
.x((d,i) => scaleX(d.x || 0) || 0)
.y((d,i) => scaleY(d.y || 1) || 0);
svg.append("g")
.selectAll("path")
.data(data)
.join("path")
.attr("fill", "none")
.attr("stroke", d => reg_colors[d.region])
.attr("stroke-width", d => radius(d.r)/8)
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.attr("opacity", '0.4')
//.style("mix-blend-mode", "multiply")
.attr("d", d => line(d.series));
svg.append("g")
.attr("stroke", "none")
.selectAll("circle")
.data(data)
.join("circle")
.attr("fill", d => reg_colors[d.region])
.attr("opacity", "0.65")
.attr("cx", d => scaleX(d.x))
.attr("cy", d => scaleY(d.y))
.attr("r", d => radius(d.r))
.append("title")
.text(d => d.name);

if (options.labels) {
svg.append("g")
.attr("font-size", 10)
.selectAll("text")
.data(data)
.join("text")
.attr("fill", "black")
.attr("dy", "0.35em")
.attr("x", d => scaleX(d.x) + 7)
.attr("y", d => scaleY(d.y) - radius(d.r))
.text(d => d.name);
}
svg.append("g")
.append("text")
.attr("font-size", 10)
.attr("text-anchor", "end")
.attr("x", options.w)
.attr("y", margin.top)
.text(strFromDate(d3.timeDay.offset(last_data_update, -options.hist_lookback)));

const title_text = (typeof options.title === 'string' || options.title instanceof String)
? options.title
: options.title(data);
if (title_text) {
svg.append("g")
.attr("font-size", 20)
.attr("font-weight", "bold")
.attr("text-anchor", "middle")
.append("text")
.attr("x", 0.5 * (options.w + margin.left - margin.right))
.attr("y", margin.top-2)
.text(title_text);
}
return svg.node();
}
Insert cell
d3.timeDay.range(d3.isoParse('2020-03-04'), new Date())
Insert cell
regions_diagram = phaseChartInc(
regions_incidence,
{title: 'Regije', labels: true, h: width*9/16, hist_len: 14, smooth: 21, hist_lookback: date_back, ref_offset: 8, scaleR: 2, bndsX: [0.3, 250], bndsY: [-1, 1]})
Insert cell
Insert cell
Insert cell
munis_diagram = phaseChartInc(
muni_incidence.filter(m => true/*5000 <= m.population*/),
{
title: 'Občine', hist_len: 7, smooth: 3, ref_offset: 3,
scaleR: 4, bndsX:[0.1, 100], bndsY: [-1, 1]
});
Insert cell
d3.isoParse(last_data_update)
Insert cell
date_back=d3.timeDay.count(date, last_data_update);
Insert cell
reg_colors = ({
lj: 'rgb( 69, 120, 68)',
mb: 'rgb(249, 93, 106)',
ce: 'rgb(102, 81, 145)',
kr: 'rgb(255, 166, 0)',
nm: 'rgb(175, 165, 63)',
ng: 'rgb(219, 165, 29)',
kp: 'rgb(112, 164, 113)',
ms: 'rgb( 2, 74, 102)',
kk: 'rgb(212, 80, 135)',
za: 'rgb( 16, 130, 154)',
sg: 'rgb(119, 124, 41)',
po: 'rgb(160, 81, 149)'
});
Insert cell
Insert cell
Insert cell
regions_table(muni_incidence.slice(1,5),"AA", ((a,b) => b.incidenceWkNext4 - a.incidenceWkNext4))
Insert cell
Insert cell
Insert cell
function inc_eq_str(prev, last) {
const frmt = d3.format('.2d');
return frmt(+prev) + '&nbsp;+&nbsp;' + frmt(+last) + '&nbsp;=&nbsp;' + frmt((last + prev) || 0)
}
Insert cell
function inc_eqgrow_str(prev, last) {
return inc_eq_str(prev, last) + '&nbsp;' + grow_str2(prev, last);
}
Insert cell
function grow_str2(prev, last) {
return grow_str(last/prev);
}
Insert cell
function grow_str(ratio) {
return '(' + (+(ratio > 1.0) ? '+' : '') + d3.format('.0%')(+ratio - 1) + ')';
}
Insert cell
d3.sum(regions_incidence.map(ri => ri.population))
Insert cell
regions_incidence = d3.groups(muni_incidence, m => m.region).map(reg => {
const obj = {
...regions_dict[reg[0]],
population_munis: 0,
cases_todate: []
};
reg[1].forEach(ob => {
ob.cases_todate.forEach((c, idx) => {
if (obj.cases_todate.length <= idx) {
obj.cases_todate.push(0);
}
obj.cases_todate[idx] += (c || 0);
})
obj.population_munis += ob.population;
});
const series_data = produce_series(obj.cases_todate, obj.population, reg[1][0].series.map(s => s.date));
return {...obj, ...series_data, region: obj.id};
});
Insert cell
muni_incidence.filter(a => a.incidence14d>0.02)
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

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