{
const margin = {
top: 25,
bottom: 25,
left: 50,
right: 25,
}
const width = 750
const height = 400
const svg = d3.create("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
const g = svg.append("g")
.attr("width", width)
.attr("height", height)
.attr("transform", `translate(${margin.left}, ${margin.top} )`)
const xScale = d3.scaleUtc(
[new Date("2023-09-01"), new Date("2023-09-30")],
[0, width]
)
const maxY = d3.max([
...kjAverage,
...agAverage,
...spAverage,
].flatMap(d => d.ridership))
const yScale = d3.scaleLinear(
[0, maxY],
[height, 0]
).nice()
const path = d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
const bisect = d3.bisector(function(d) { return d.date }).center
function mouseover(event) {
circleScrubber.style("opacity", 1)
yLineScrubber.style("opacity", 1)
}
function mousemove(event) {
const offset = event.clientX - event.offsetX
const mouseX = event.clientX - margin.left - offset
const x0 = xScale.invert(mouseX);
const i = bisect(kjAverage, x0, 1);
const selectedData = kjAverage[i]
// circle scrubber
circleScrubber
.transition()
.ease(d3.easeLinear)
.delay(0)
.duration(25)
.attr("cx", xScale(selectedData.date))
.attr("cy", yScale(selectedData.ridership))
// line scrubber
yLineScrubber
.attr("y1", yScale(0))
.attr("y2", yScale(maxY))
.transition()
.ease(d3.easeLinear)
.delay(0)
.duration(50)
.attr("x1", xScale(selectedData.date))
.attr("x2", xScale(selectedData.date))
}
function mouseout(event) {
circleScrubber.style("opacity", 0)
yLineScrubber.style("opacity", 0)
}
// circle scrubber
const circleScrubber = g.append("g")
.append("circle")
.attr("fill", "none")
.attr("stroke", "black")
.attr("r", 5)
.style("opacity", 0)
// x-axis line scrubber
const xScrubber = g.append("g")
.append("line")
.attr("height", height)
.attr("width", 1)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-dasharray", 4)
.style("opacity", 0)
// y-axis scrubber
const yLineScrubber = g.append("g")
.append("line")
.attr("height", height)
.attr("width", 1)
.attr("fill", "none")
.attr("stroke", "black")
.attr("stroke-dasharray", 4)
.style("opacity", 0)
// x-axis
g.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale)
.ticks(30)
.tickFormat(d3.utcFormat('%d'))
)
// y-axis
g.append("g")
.call(d3.axisLeft(yScale).tickFormat(d3.format(".0s")))
// KJ daily average
g.append("g")
.append("path")
.datum(kjAverage)
.attr("d", d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.ridership))
)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
// KJ all lines
lineGroups.get("kelana-jaya").forEach((station) => {
const data = []
for (let i = 0; i < 30; i++) {
data.push({ x: station.dates[i], y: station.ridership[i] })
}
g.append("g")
.append("path")
.datum(data)
.attr("d", d3.line()
.x(d => xScale(d.x))
.y(d => yScale(d.y))
)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
})
// KJ min max
// g.append("g")
// .append("path")
// .datum(kjAverage)
// .attr("d", d3.area()
// .x(d => xScale(d.date))
// .y0(d => yScale(d.min))
// .y1(d => yScale(d.max))
// )
// .attr("fill", "steelblue")
// .attr("fill-opacity", 0.15)
// AG daily average
g.append("g")
.append("path")
.datum(agAverage)
.attr("d", d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.ridership))
)
.attr("fill", "none")
.attr("stroke", "orange")
.attr("stroke-width", 1.5)
// SP daily average
g.append("g")
.append("path")
.datum(spAverage)
.attr("d", d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.ridership))
)
.attr("fill", "none")
.attr("stroke", "green")
.attr("stroke-width", 1.5)
// KL Sentral ridership
// g.append("g")
// .append("path")
// .datum(klSentralRidership)
// .attr("d", d3.line()
// .x(d => xScale(d.date))
// .y(d => yScale(d.ridership))
// )
// .attr("fill", "none")
// .attr("stroke", "red")
// .attr("stroke-width", 1.5)
// invisble area to track mouse movement
g.append("g")
.append('rect')
.attr('width', width)
.attr('height', height)
.style("fill", "none")
.style("pointer-events", "all")
.on('mouseover', mouseover)
.on('mousemove', mousemove)
.on('mouseout', mouseout);
return svg.node()
}