Public
Edited
Nov 18, 2023
Insert cell
Insert cell
selangor = ({ ...district, features: district.features.filter(f => {
// TODO: focus on PJ and KL with faded sides
return (
// f.properties.district === "Gombak" ||
// f.properties.district === "Ulu Langat" ||
f.properties.district === "Petaling" ||
f.properties.state === "W.P. Kuala Lumpur"
)
})})
Insert cell
projection = d3.geoMercator().fitExtent([[0, 0], [width, 500]], selangor)
Insert cell
district = FileAttachment("district.json").json()
Insert cell
geoGenerator = d3.geoPath().projection(projection);
Insert cell
{
const margin = {
top: 0,
bottom: 0,
left: -100,
right: 0,
}

// const width = 400
const height = 500

const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
// .attr("background-color", "black")
// .attr("background-opacity", 0.75)

const g = svg.append("g")
.attr("width", width)
.attr("height", height)
.attr("transform", `translate(${margin.left}, ${margin.top} )`)

const rScale = d3.scaleRadial(
d3.extent(averageRidershipByStation.map(d => d.average)),
[1, 7]
)

const curve = d3.line()
.x(d => d[0])
.y(d => d[1])
.curve(d3.curveNatural)

// selangor
g.append("g")
.selectAll("path")
.data(selangor.features)
.join("path")
.attr("d", geoGenerator)
.attr("fill", "black")
.attr("stroke", "white")

const kjPath = curve(
stops
.filter(stop => stop.stop_id.includes("KJ"))
.sort()
.reverse()
.map(stop => projection([stop.stop_lon, stop.stop_lat]))
)

const agPath = curve(
stops
.filter(stop => stop.stop_id.includes("AG"))
.sort()
.reverse()
.map(stop => projection([stop.stop_lon, stop.stop_lat]))
)

const spPath = curve(
stops
.filter(stop => stop.stop_id.includes("SP"))
.sort()
.reverse()
.map(stop => projection([stop.stop_lon, stop.stop_lat]))
)

// kj line
g.append("g")
.append("path")
.attr("d", kjPath)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-opacity", 0.5)

// ag line
g.append("g")
.append("path")
.attr("d", agPath)
.attr("fill", "none")
.attr("stroke", "orange")
.attr("stroke-opacity", 0.5)

// sp line
g.append("g")
.append("path")
.attr("d", spPath)
.attr("fill", "none")
.attr("stroke", "yellow")
.attr("stroke-opacity", 0.5)

// stops with average ridership
g.append("g")
.selectAll("circle")
.data(averageRidershipByStation)
.join("circle")
.attr("cx", d => projection([d.longitude, d.latitude])[0])
.attr("cy", d => projection([d.longitude, d.latitude])[1])
.attr("r", d => rScale(d.average))
.attr("fill", d => getLineColor(d.line))
.attr("fill-opacity", 0.5)
.attr("stroke", d => getLineColor(d.line))
.attr("stroke-opacity", 1)
return svg.node()
}
Insert cell
ridershipByDate = FileAttachment("ridership-by-date.json").json()
Insert cell
averageRidershipByStation = ridershipByDate.map(d => ({
station: d.station,
average: Math.round(d3.mean(d.ridership)),
longitude: getStopDataFromStation(d.station).stop_lon,
latitude: getStopDataFromStation(d.station).stop_lat,
line: getStopDataFromStation(d.station).stop_id.includes("KJ")
? "kelana-jaya"
: getStopDataFromStation(d.station).stop_id.includes("AG")
? "ampang"
: "sri-petaling"
}))
Insert cell
state_map = FileAttachment("state_map.json").json()
Insert cell
stops.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
function getStopDataFromStation(station) {
return stops.find(stop => stop.stop_name === station.toUpperCase())
}
Insert cell
function getLineColor(line) {
return line === "kelana-jaya"
? "red"
: line === "ampang"
? "orange"
: "yellow"
}
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