Public
Edited
Jun 1
Insert cell
Insert cell
import { vl } from "@vega/vega-lite-api"
Insert cell
metadata = FileAttachment("metadata.json").csv({ typed: true })
Insert cell
data = FileAttachment("756_F8E084C_Dataset_2024-07-15.csv").csv({ typed: true })
Insert cell
data_dictionary = FileAttachment("DDS - Data Dictionary 2024-12-12.csv").csv({
typed: true
})
Insert cell
metadata1 = FileAttachment("metadata@1.json").json()
Insert cell
DDS - Data Dictionary 2024-12-12@1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
756_77D059C_Dataset_2024-07-22.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
data2 = FileAttachment("756_77D059C_Dataset_2024-07-22.csv").csv({ typed: true })
Insert cell
data_dictionary_2 = FileAttachment("DDS - Data Dictionary 2024-12-12@1.csv").csv({ typed: true })
Insert cell
combinedData = [
...data.map(d => ({
DIM_TIME: d.DIM_TIME,
RATE_PER_100_N: d.RATE_PER_100_N, // Keep original field name
indicator: "DTP3 Coverage",
date: new Date(d.DIM_TIME, 0, 1)
})),
...data2.map(d => ({
DIM_TIME: d.DIM_TIME,
RATE_PER_100_N: d.RATE_PER_1000_N / 10, // Map to same field name
indicator: "HIV Rate",
date: new Date(d.DIM_TIME, 0, 1)
}))
]

Insert cell
allValues = combinedData
.map(d => d.RATE_PER_100_N)
.filter(val => val != null && !isNaN(val) && typeof val === 'number')

Insert cell
minVal = Math.min(...allValues)

Insert cell
maxVal = Math.max(...allValues)

Insert cell
padding = (maxVal - minVal) * 0.05 // 5% padding
Insert cell
vl.layer(
// DTP3 Coverage (left axis)
vl.markLine({color: "steelblue", strokeWidth: 2})
.data(data.map(d => ({...d, date: new Date(d.DIM_TIME, 0, 1)})))
.encode(
vl.x().fieldT("date").title("Year"),
vl.y().fieldQ("RATE_PER_100_N")
.scale({domain: "unaggregated"})
.axis({titleColor: "steelblue", title: "DTP3 Coverage (%)"})
),
// HIV Rate (right axis)
vl.markLine({color: "red", strokeWidth: 2})
.data(data2.map(d => ({...d, date: new Date(d.DIM_TIME, 0, 1)})))
.encode(
vl.x().fieldT("date"),
vl.y().fieldQ("RATE_PER_1000_N")
.scale({domain: "unaggregated"})
.axis({titleColor: "red", title: "HIV Rate (per 1000)", orient: "right"})
)
)
.resolve({scale: {y: "independent"}})
.width(600)
.height(400)
.render()

Insert cell
normalizeData = (data, field) => {
const values = data.map(d => d[field])
const min = Math.min(...values)
const max = Math.max(...values)
return data.map(d => ({
...d,
normalized: (d[field] - min) / (max - min)
}))
}
Insert cell
// Calculate normalized values
normalizedCombined = [
...normalizeData(data, "RATE_PER_100_N").map(d => ({
DIM_TIME: d.DIM_TIME,
normalized_rate: d.normalized,
indicator: "DTP3 Coverage (normalized)"
})),
...normalizeData(data2, "RATE_PER_1000_N").map(d => ({
DIM_TIME: d.DIM_TIME,
normalized_rate: d.normalized,
indicator: "HIV Rate (normalized)"
}))
]

Insert cell
vl.layer(
// Line layer
vl.markLine({strokeWidth: 2})
.data(normalizedCombined)
.encode(
vl.x().fieldO("DIM_TIME").title("Year"),
vl.y().fieldQ("normalized_rate").scale({domain: [0, 1]}).title("Normalized Rate"),
vl.color().fieldN("indicator").title("Health Indicator")
),
// Point layer
vl.markPoint({size: 60, filled: true})
.data(normalizedCombined)
.encode(
vl.x().fieldO("DIM_TIME"),
vl.y().fieldQ("normalized_rate"),
vl.color().fieldN("indicator"),
vl.tooltip([
{field: "DIM_TIME", type: "ordinal", title: "Year"},
{field: "normalized_rate", type: "quantitative", title: "Normalized Rate", format: ".3f"},
{field: "indicator", type: "nominal", title: "Indicator"}
])
)
)
.width(600)
.height(400)
.render()

Insert cell
vl.markLine()
.data(normalizedCombined)
.encode(
vl.x().fieldO("DIM_TIME").title("Year"),
vl.y().fieldQ("normalized_rate")
.scale({domain: [0, 1]})
.title("Normalized Rate"),
vl.color().fieldN("indicator").title("Health Indicator"),
vl.facet().fieldN("indicator").columns(2)
)
.width(280)
.height(200)
.render()

Insert cell
// Create standardized data for both datasets
standardizeData = (data, field) => {
const values = data.map(d => d[field])
const mean = values.reduce((a, b) => a + b) / values.length
const std = Math.sqrt(values.reduce((a, b) => a + (b - mean) ** 2, 0) / values.length)
return data.map(d => ({
...d,
standardized: (d[field] - mean) / std
}))
}
Insert cell


standardizedCombined = [
...standardizeData(data, "RATE_PER_100_N").map(d => ({
DIM_TIME: d.DIM_TIME,
standardized_rate: d.standardized,
indicator: "DTP3 Coverage (standardized)",
date: new Date(d.DIM_TIME, 0, 1)
})),
...standardizeData(data2, "RATE_PER_1000_N").map(d => ({
DIM_TIME: d.DIM_TIME,
standardized_rate: d.standardized,
indicator: "HIV Rate (standardized)",
date: new Date(d.DIM_TIME, 0, 1)
}))
]

Insert cell
vl.markLine()
.data(standardizedCombined) // Use the correct variable name
.encode(
vl.x().fieldO("DIM_TIME").title("Year"),
vl.y().fieldQ("standardized_rate")
.scale({domain: [-3, 3], zero: true})
.title("Standardized Rate (Z-Score)"),
vl.color().fieldN("indicator").title("Health Indicator"),
vl.tooltip([
{field: "DIM_TIME", type: "ordinal", title: "Year"},
{field: "standardized_rate", type: "quantitative", title: "Z-Score", format: ".2f"},
{field: "indicator", type: "nominal", title: "Indicator"}
])
)
.width(600)
.height(400)
.render()

Insert cell
Insert cell
// Define the percentage change calculation function
calculatePercentChange = (data, field) => {
return data.map((d, i) => {
if (i === 0) return {...d, pct_change: 0}
const prevValue = data[i-1][field]
const pctChange = ((d[field] - prevValue) / prevValue) * 100
return {...d, pct_change: pctChange}
})
}
Insert cell
// Calculate percentage changes for both datasets
dtp3PercentChange = calculatePercentChange(data, "RATE_PER_100_N")

Insert cell
hivPercentChange = calculatePercentChange(data2, "RATE_PER_1000_N")
Insert cell
percentChangeCombined = [
...dtp3PercentChange.map(d => ({
DIM_TIME: d.DIM_TIME,
pct_change: d.pct_change,
indicator: "DTP3 Coverage % Change",
date: new Date(d.DIM_TIME, 0, 1)
})),
...hivPercentChange.map(d => ({
DIM_TIME: d.DIM_TIME,
pct_change: d.pct_change,
indicator: "HIV Rate % Change",
date: new Date(d.DIM_TIME, 0, 1)
}))
]
Insert cell
vl.layer(
// Zero reference line
vl.markRule({color: "black", strokeDash: [2,2], opacity: 0.7})
.data([{zero: 0}])
.encode(vl.y().fieldQ("zero")),
// Percentage change lines
vl.markLine({strokeWidth: 2})
.data(percentChangeCombined)
.encode(
vl.x().fieldO("DIM_TIME").title("Year"),
vl.y().fieldQ("pct_change")
.scale({zero: true})
.title("Percentage Change from Previous Year (%)"),
vl.color().fieldN("indicator").title("Health Indicator"),
vl.tooltip([
{field: "DIM_TIME", type: "ordinal", title: "Year"},
{field: "pct_change", type: "quantitative", title: "% Change", format: ".2f"},
{field: "indicator", type: "nominal", title: "Indicator"}
])
),
// Add points for better visibility
vl.markPoint({size: 60, filled: true})
.data(percentChangeCombined)
.encode(
vl.x().fieldO("DIM_TIME"),
vl.y().fieldQ("pct_change"),
vl.color().fieldN("indicator"),
vl.tooltip([
{field: "DIM_TIME", type: "ordinal", title: "Year"},
{field: "pct_change", type: "quantitative", title: "% Change", format: ".2f"},
{field: "indicator", type: "nominal", title: "Indicator"}
])
)
)
.width(600)
.height(400)
.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