Published
Edited
Jan 23, 2021
Insert cell
md`# Task A - D3.js Submission 01`
Insert cell
md`Student: **Zhao Yue**, Matric Number: **A0137801L**`
Insert cell
md`This submission discussed about the data source provided by [data.gov.sg](https://data.gov.sg/) regarding the weekly infections disease recorded from Jan.2012 to Sep.2020. The purpse of the visualisation is to examine:

- The top 20 categories of disease with highest total infection cases from Jan.2012 to Sep.2020.
- The overall trend of the top categories of disease accross different years.
- The trend of each of the disease among the top category diseases.
- The the occurances of each disease in different seasons.
`
Insert cell
import {table} from "@tmcw/tables/2"
Insert cell
d3 = require("d3@6")
Insert cell
raw = d3.csv("https://samuelzhaoy.static.observableusercontent.com/files/c363d0a38fd775ba0914f1d8f91e55e4acfa7b3539411d5038495f11dd31bad9987963ebd84f7d372986bcf93fba7f6fcf6e33aace3ca664eecaa9d8d6dd462b?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27weekly-infectious-disease-bulletin-cases.csv&Expires=1611446400000&Key-Pair-Id=APKAJCHFJLLLU4Y2WVSQ&Signature=CIfmvXN9Ggu9IRGEi8OSajcWt-NwQywSaHWwQkE-02YADz5~vbI5Nvt1RjwQwai-lasoxr-Xw0aWgK9C1R-rxNKfSW8uasJlSUaED34FjQNBCpzSP2RrylpWZ~b00DUNnd9JnwE89UJHy9UdXtxQSwZSdVPaYj9BCEdDGMjLqY5A9oFl7vxURlc9~w8t-FUzEU1d-KrcfQZdJJrugSTYWB80jqeKIWGvh4iM-vHjU5m-k76sp6uiJgofr9FcL0UqcCSMHEBHGT-qmvP2B8LAOPn5ECiLvP6AyqMixg2lh4InAnnB9XmbgSSxELfdx7PVH-eKPYThUgdQYlPN-5DVZg__")
Insert cell
md`### Query 1: identify the top 20 disease accross 2012 - 2020`
Insert cell
topDisease = new Promise(resolve => {

var diseaseCount = {};
// accumulating count
raw.forEach(function (item, index) {
if (!diseaseCount.hasOwnProperty(item.disease)) {
diseaseCount[item.disease] = parseInt(item["no._of_cases"]);
} else {
diseaseCount[item.disease] += parseInt(item["no._of_cases"]);
}
});
// transform into array
var diseaseCountArray = [];
Object.keys(diseaseCount).forEach(function (key, index) {
diseaseCountArray.push(
{
"name":key,
"count":diseaseCount[key]
}
);
});
// sort in descending order
diseaseCountArray = diseaseCountArray.sort((a, b) => {
return b.count - a.count;
}).slice(0,20)
// return the top 20 diseases
resolve(diseaseCountArray);
})
Insert cell
md`now we have the table contents of the top 20 diseases across the recent years, lets present them in a table`
Insert cell
viewof topDiseaseTable = table(topDisease, { title: "top diseases from 2012 - 2020" })
Insert cell
md`### Query 2: identify the trend of total occurence of Top 20 diseases across 2012 - 2020`
Insert cell
md`### Query 3: identify the trend of each of the disease among the top category diseases.`
Insert cell
md`Now we need further filter out the raw data, combine weekly data into month and year.`
Insert cell
filteredRecords = new Promise(resolve => {
const topDiseaseNames = topDisease.map( d => d.name );
var filteringData = raw.filter( r => topDiseaseNames.indexOf(r.disease) >= 0 )
// accumulating count
filteringData.forEach(function (item, index) {
item.year = item.epi_week.split('-')[0];
item.week = parseInt(item.epi_week.split('-W')[1]);
item.month = Math.ceil(item.week / 4.35);
});
//
filteringData.forEach(function (item, index) {
if (item.disease == 'HFMD') {
item.disease = 'Hand, Foot Mouth Disease';
}
});
resolve(filteringData);
});
Insert cell
occurenceByYear = new Promise(resolve => {

var yearlyRecord = {};
const topDiseaseNames = topDisease.map( d => d.name );
// accumulating count
filteredRecords.forEach(function (item, index) {
if (!yearlyRecord.hasOwnProperty(item.year)) {
yearlyRecord[item.year] = {name:item.year};
topDiseaseNames.forEach(function (name, index) {
yearlyRecord[item.year][name] = 0;
})
yearlyRecord[item.year][item.disease] = parseInt(item["no._of_cases"]);
} else {
if (yearlyRecord[item.year].hasOwnProperty(item.disease) == false) {
yearlyRecord[item.year][item.disease] = parseInt(item["no._of_cases"]);
} else {
yearlyRecord[item.year][item.disease] += parseInt(item["no._of_cases"]);
}
}
});
// here we found that actually HFMD is Hand Foot Mouth Disease, hence we should combine the two.
Object.values(yearlyRecord).forEach(function (record, index) {
record["Hand, Foot Mouth Disease"] += record["HFMD"];
})
resolve(yearlyRecord);
})
Insert cell
md`now let's plot them out`
Insert cell
import {swatches} from "@d3/color-legend"
Insert cell
data = new Promise ( resolve => {
var topDiseaseNames = topDisease.map( d => d.name );
topDiseaseNames = topDiseaseNames.filter(e => e !== 'HFMD')
var renderData = Object.values(occurenceByYear);
renderData.columns = topDiseaseNames;
renderData.columns.splice( 0, 0, "name" )
resolve(renderData);
})
Insert cell
series = d3.stack()
.keys(data.columns.slice(1))
(data)
.map(d => (d.forEach(v => v.key = d.key), d))
Insert cell
color = d3.scaleOrdinal()
.domain(series.map(d => d.key))
.range(series.map(d => d3.interpolateSpectral((series.indexOf(d)+1)/series.length)))
.unknown("#ccc")
Insert cell
key = swatches({color, title: "name", columns: "180px"})
Insert cell
chart = {
var height = 600
var margin = ({top: 10, right: 10, bottom: 20, left: 40})
var x = d3.scaleBand()
.domain(data.map(d => d.name))
.range([margin.left, width - margin.right])
.padding(0.1)
var y = d3.scaleLinear()
.domain([0, d3.max(series, d => d3.max(d, d => d[1]))])
.rangeRound([height - margin.bottom, margin.top])
var xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
var yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())
var formatValue = x => isNaN(x) ? "N/A" : x.toLocaleString("en")
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

svg.append("g")
.selectAll("g")
.data(series)
.join("g")
.attr("fill", d => color(d.key))
.selectAll("rect")
.data(d => d)
.join("rect")
.attr("x", (d, i) => x(d.data.name))
.attr("y", d => y(d[1]))
.attr("height", d => y(d[0]) - y(d[1]))
.attr("width", x.bandwidth())
.append("title")
.text(d => `${d.data.name} ${d.key} ${formatValue(d.data[d.key])}`);

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

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

return svg.node();
}
Insert cell
md`### Query 4: identify the correlation between seasons and each of the disease`
Insert cell
import {select} from "@jashkenas/inputs"
Insert cell
import { vl } from "@vega/vega-lite-api"
Insert cell
topDiseaseNames = topDisease.map( d => d.name ).filter(e => e !== 'HFMD');

Insert cell
selectionValueLabels = topDiseaseNames.map(function(name) {
return {
'value': topDiseaseNames.indexOf(name),
'label': name
}
})
Insert cell
getSeasonalData = function(index) {
let disease = topDiseaseNames[index]
let formData = {};
filteredRecords.forEach(record => {
var monthYear = record.year+'_'+record.month
if (record.month <= 3) {
monthYear = record.year+'_spring'
} else if (record.month <= 6) {
monthYear = record.year+'_summer'
} else if (record.month <= 9) {
monthYear = record.year+'_autum'
} else if (record.month <= 13) {
monthYear = record.year+'_winter'
}
if (record.disease == disease) {
if (formData.hasOwnProperty(monthYear) == false) {
formData[monthYear] = {
timeline: monthYear,
count: parseInt(record['no._of_cases'])
};
} else {
formData[monthYear].count += parseInt(record['no._of_cases']);
}
}
})
return Object.values(formData);
}
Insert cell
getSeasonalData(0)
Insert cell
viewof value = select({
title: "Disease Name",
description: "Please select the disease",
options: selectionValueLabels,
})
Insert cell
viewof simpleBar = vl.markBar()
.data(getSeasonalData(value))
.encode(
vl.x().fieldO("timeline"),
vl.y().fieldQ("count")
)
.render()
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