Insert cell
Insert cell
Insert cell
geoJson = await FileAttachment("Philippines_provinces@1.geojson").json()
Insert cell
geoJson.features.filter(function(item){
return item.properties.income_class == undefined;
});
Insert cell
Insert cell
{
// create svg html element
const svg = d3.create('svg')
.attr("viewBox", [0, 0, chart_dimensions.width, chart_dimensions.height]);

// merge geojson and budget data for convenient access of income class code and income group in tooltip
mergeGeoJsonAndBudgetData()
const clippedWidth = chart_dimensions.width - chart_dimensions.margin * 2;
const clippedHeight = chart_dimensions.height - chart_dimensions.margin * 2;

const legend_title = svg.append("g")
legend_title.attr("class", "legend")
.style('font-size', 12)
.style('font-family', 'sans-serif')
.attr("transform", `translate(${centerScale(7)},50)`)
.append("text")
.attr("font-weight", "bold")
.text("Income Class Code")
const legend_group = svg.append("g")
legend_group.attr("transform", `translate(${centerScale(7)},70)`)
.call(legend)
// initialize geoMercator
const geoMercator = d3
.geoMercator()
// the center uses longtitude and latitude
// get Long/Lat data from google maps
.center([199, 36])
.fitSize([clippedWidth, clippedHeight], geoJson);

// initialize geoPath with the help of the mercator
// This will be our data later
const pathGen = d3.geoPath(geoMercator);

// initialize zoom
const zoom = d3.zoom()
.scaleExtent([1, 8])
.on('zoom', zoomed);
// initialize stage
// stage is basically just a group appended to the main svg
const stage = svg
.append('g')
.attr('transform', `translate(${chart_dimensions.margin},${chart_dimensions.margin})`);

// initialize Hover text on the upper left
const textX = 10;
const textY = 10;
const infoText = stage
.append('g')
.attr('transform', `translate(${textX},${textY})`)
.append('text')
.style("font", "20px sans-serif");
infoText.text('no data');

// setup onMouseHover, onMouseMove, onMouseLeave on the polugons
const onMouseHover = d => {
let income_class_text = d.properties.income_class;
let income_group_text = d.properties.income_group;
if (d.properties.income_class == undefined)
{
income_class_text = `<span style="color:red">No information available</span>`
income_group_text = `<span style="color:red">No information available</span>`
}
stage
.selectAll('.geopath')
.filter(td => td.properties.ADM2_EN === d.properties.ADM2_EN)
.attr('fill', 'yellow');
infoText.text(d.properties.ADM2_EN);
tooltip.html(`<div> <strong>Province: </strong>${d.properties.ADM2_EN}</div>
<div> <strong>Region: </strong>${d.properties.ADM1_EN}</div>
<div> <strong>Income Code: </strong>${income_class_text}</div>
<div> <strong>Income Class: </strong>${income_group_text}</div>`)
.style('visibility', 'visible');
};
const onMouseMove = d => {
tooltip
.style('top', d3.event.pageY - 10 + 'px')
.style('left', d3.event.pageX + 10 + 'px');
}

const onMouseLeave = d => {
stage
.selectAll('.geopath')
.filter(td => td.properties.ADM2_EN === d.properties.ADM2_EN)
.attr('fill', function(td) {
if (typeof td.properties.income_class === "number"){
return colorScale(td.properties.income_class)
}
else{
return "white"
}});
infoText.text('Hover on a province');
tooltip.html(``).style('visibility', 'hidden');
};

// initialize tEnter (polygons that composes the map)
// initialize tUpdate, tExit (optional)
const tEnter = enter =>
enter
.append('path')
.attr('d', pathGen)
.attr('stroke', 'purple')
.attr('fill', function(td) {
if (typeof td.properties.income_class === "number"){
return colorScale(td.properties.income_class)
}
else{
return "white"
}})
.classed('geopath', true)
.on('mouseenter', onMouseHover)
.on('mousemove', onMouseMove)
.on('mouseleave', onMouseLeave);
const tUpdate = null;
const tExit = null;
// setup stage
stage
.selectAll('.geopath')
.data(geoJson.features)
.join(tEnter, tUpdate, tExit);

// call zoom
svg.call(zoom);
// zoomed functio nfor zoom
function zoomed() {
stage
.selectAll('path') // To prevent stroke width from scaling
.attr('transform', d3.event.transform);
}
return svg.node();
}
Insert cell
Insert cell
budget_data = {
const text = await FileAttachment("local_gov_finance_data@2.csv").text();
return d3.csvParse(text, (d) => ({
lgu_type: d.lgu_type,
region: d.region,
province: d.province,
lgu_name: d.lgu_name,
income_classification_code: +d.income_classification_code,
population: d.population,
income_group: d.income_group
}));
}
Insert cell
Insert cell
function mergeGeoJsonAndBudgetData (){
var income_classsification_dict = {};
var income_group_dict = {};
budget_data_province.map((e,i) => { income_classsification_dict[e.province]=e.income_classification_code});
budget_data_province.map((e,i) => { income_group_dict[e.province]=e.income_group});
console.log(income_classsification_dict);
for (var property in geoJson.features) {
if (geoJson.features.hasOwnProperty(property)) {
geoJson.features[property].properties.income_class = income_classsification_dict[geoJson.features[property].properties.ADM2_EN];
geoJson.features[property].properties.income_group = income_group_dict[geoJson.features[property].properties.ADM2_EN];
//console.log(geoJson.features[property].properties);
}
}
}
Insert cell
budget_data_province = await get_budget_data_province();
Insert cell
function get_budget_data_province() {
const budget_data_province_temp = budget_data.filter(function(item){
return item.lgu_type == "PROVINCE";
});
budget_data_province_temp.forEach(function(item){
item.province = item.province.toLowerCase().replace(/(^\w{1})|(\s{1}\w{1})/g, match => match.toUpperCase());
item.province = item.province.replace("Del", "del");
console.log(item);
});
//console.log(budget_data_province_temp)
return budget_data_province_temp;
}
Insert cell
uniqueIncomeClassificationCode = [...new Set(budget_data_province.map(({ income_classification_code }) => income_classification_code))]
Insert cell
Insert cell
Insert cell
chart_dimensions = ({
width: 850,
height: 1100,
margin: 50,
})
Insert cell
d3 = require("d3@v5")
Insert cell
color = '#69b3a2'
Insert cell
tooltip = d3
.select('body')
.append('div')
.attr('class', 'tooltip')
.style('position', 'absolute')
.style('z-index', '10')
.style('visibility', 'hidden')
.style('padding', '10px')
.style('background-color', 'rgba(255,255,255,0.90)')
.style('border-radius', '10px')
.style('-webkit-border-radius', '10px')
.style('box-shadow', '0 0 2px 0px white inset, 0 0 1px 0px black')
.style('color', "purple")
.style('font-family','sans-serif')
.text('a simple tooltip');
Insert cell
colorScale = d3.scaleSequential([0, d3.max(budget_data_province, d => d.income_classification_code)], d3.interpolatePurples)
Insert cell
legend = svg => {
const g = svg
.attr("text-anchor", "end")
.attr("font-size", 15)
.selectAll("g")
.data(["1", "2", "3", "4", "5"])
.join("g")
.attr("transform", (d, i) => `translate(${i * 25}, 0)`);

g.append("rect")
.attr("width", 19)
.attr("height", 19)
.attr("fill", colorScale);

g.append("text")
.attr("x", 10)
.attr("y", -10)
.attr("dy", "0.35em")
.text(d => d);
}
Insert cell
centerScale = d3.scaleLinear()
.domain([0,10])
.range([0, chart_dimensions.width]);
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