Published
Edited
Sep 25, 2020
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// the bars of the top graph
const bars = vl.markBar()
.encode(
vl.x().fieldT("DATE"), // one bar per day along the x-axis
vl.y().sum("NEW_IN"), // for each day, sum the number of new cases
vl.opacity().value(0.2), // make the bars less prominent
vl.tooltip([ // add a tooltip to the bars
{field: "DATE", type: "temporal", title: 'Date'},
{field: "NEW_IN", aggregate: "sum", title: "New hospitalisations" }
])
)
.width(800);
// the average line on the top graph
const average = vl.markLine({interpolate: "monotone"}) // draw a slightly smoother line
// first calculate some secondary data points
.transform(
// calculate a rolling average with a window of the 3 previous days and the 3 next days
vl.groupby("PROVINCE").window(vl.mean("NEW_IN").as("AVG_IN")).frame([-3,3]),
// sum the provinces for each day
vl.groupby("DATE").aggregate(vl.sum("AVG_IN").as("AVG_IN_BY_DATE"))
)
.encode(
vl.x()
.fieldT("DATE")
.title(null), // disable the title on the x-axis
vl.y().sum("AVG_IN_BY_DATE").title('New hospitalisations per day')
);
// the bottom graph
const diff = vl.markBar()
// first calculate some secondary data points
.transform(
// rolling average, same as above
vl.groupby("PROVINCE").window(vl.mean("NEW_IN").as("AVG_IN")).frame([-3,3]),
vl.groupby("DATE").aggregate(vl.sum("AVG_IN").as("AVG_IN_BY_DATE")),
// for each day, add a value containing the value of the previous day
vl.window(vl.lag("AVG_IN_BY_DATE").as("PREV_AVG_IN_BY_DATE")).frame([-1,0]),
// calculate the percentual difference between 2 days
vl.calculate('datum.PREV_AVG_IN_BY_DATE == null ? 0 : (datum.AVG_IN_BY_DATE / (datum.PREV_AVG_IN_BY_DATE)) - 1').as('DIFF')
)
.encode(
// draw the bars as before
vl.x().fieldT("DATE").title(null),
vl.y().sum("DIFF")
.title('Percentual difference')
.axis({format: ".0%"}), // format the values as percentage
// color the bars according to a condition
vl.color()
.condition([
// last 3 in gray
{ test: "dayofyear(now()) - dayofyear(datum.DATE) < 4", value: "lightgray" },
// positive in orange
{ test: "datum.DIFF > 0", value: "orange" }
])
.value("green"), // others in green
vl.tooltip([
{field: "DATE", type: "temporal", title: 'Date'},
{field: "DIFF", format: ".2%", title: 'Change'}
])
)
.width(800)
.height(150);
// concat the graphs and render
return vl.vconcat(vl.layer(bars, average), diff)
.data(hospitalisations_data)
.render()
}
Insert cell
Insert cell
{
// this is exactly the same as before with 2 changes
const dots = vl.markCircle() // use circles instead of bars
.encode(
vl.x().fieldT("DATE"),
vl.y().sum("NEW_IN"),
vl.opacity().value(0.4),
vl.tooltip([
{field: "DATE", type: "temporal", title: 'Date'},
{field: "NEW_IN", aggregate: "sum", title: "New hospitalisations" }
])
)
.width(800)
.height(400);
const average = vl.markLine({interpolate: "monotone"})
.transform(
vl.groupby("PROVINCE").window(vl.mean("NEW_IN").as("AVG_IN")).frame([-3,3]),
vl.groupby("DATE").aggregate(vl.sum("AVG_IN").as("AVG_IN_BY_DATE"))
)
.encode(
vl.x().fieldT("DATE").title(null),
vl.y()
.sum("AVG_IN_BY_DATE")
.title('New hospitalisations per day')
.scale({type:"log"}) // use a log scale instead of a linear one
);
return vl.layer(dots, average)
.data(hospitalisations_data)
.render()
}
Insert cell
Insert cell
{
// here we use the facet feature of vega to create subplots of each of the values of a data field
const dots = vl.markCircle()
.encode(
vl.x().fieldT("DATE"),
vl.y().sum("NEW_IN"),
vl.opacity().value(0.3)
);
const average = vl.markLine({interpolate: "monotone"})
.transform(
vl.groupby("PROVINCE").window(vl.mean("NEW_IN").as("ROLLING")).frame([-3,3])
)
.encode(
vl.x().fieldT("DATE").title(null),
vl.y().sum("ROLLING").title('New hospitalisations per day')
);
return vl.layer(dots, average)
.width(150).height(150)
.facet({row: vl.field('AGEGROUP'), column: vl.field('PROVINCE')})
.resolve({scale: {x: 'independent'}}) // plot an x-axis on each graph instead of only at the bottom
.data(hospitalisations_data)
.render()
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// here we'll use geoshapes to draw the objects from the topojson file
vl.markGeoshape({stroke: '#fff', strokeWidth: 1})
// our data here is the municipalities from the topo json file
.data(vl.topojson(belgium).feature('municipalities'))
// next, some data transforms
.transform(
// based on the nis code, look up the number of cases for that muni in the sciensano data
vl.lookup('properties.nis').default(0).from(vl.data(case_data).key('NIS5').fields('CASES')),
// numbers between 1 and 4 are not reported, so ignore them
// I'm looking at you, Herstappe
vl.calculate('datum.CASES == "<5" ? 1 : datum.CASES').as('Cases'),
// calculate the number of cases per inhabitant
vl.calculate('1000 * datum["Cases"] / datum.properties.population').as('per_1000'),
)
.encode(
vl.tooltip([
{field: "properties.name_nl", type: "nominal", title: 'Municipality'},
{field: "per_1000", type: "quantitative", title: 'Cases per 1000 inhabitants', format:".2f"},
{field: "CASES", type: "nominal", title: 'Total number of cases'},
]),
// color the municipalities according to the number of cases per 1000 inhabitants
vl.color()
.fieldQ('per_1000')
.legend({title: "Cases per 1000 inhabitants", orient: "bottom"}) // draw the legend at the bottom
)
.width(850).height(600)
.config({view: {stroke: null}})
.render();
Insert cell
cases = (await fetch('https://epistat.sciensano.be/Data/COVID19BE_CASES_AGESEX.json')).json()
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