const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
let x = d3.scaleLinear()
.range([0 + margin.left, width - margin.right]);
let y = d3.scaleBand()
.range([0, height - margin.top - margin.bottom])
.padding(0.1);
let xAxisSpecs = d3.axisTop(x)
.ticks(6)
.tickFormat(x => Math.abs(x))
let yAxisSpecs = d3.axisLeft(y)
.tickSize(0)
let xAxisGrid = d3.axisTop(x)
.ticks(6)
.tickSize(-(height - margin.top - margin.bottom))
.tickFormat("")
let xAxis = g => g
.attr("transform", `translate(0,${margin.top})`)
.call(xAxisSpecs)
.call(g => g.selectAll(".domain").remove())
const xoffset = -40
let yAxis = g => g
.attr("transform", `translate(${margin.left},${margin.top})`)
.call(yAxisSpecs)
.call(g => g.selectAll(".domain").remove())
.call(g => g.selectAll(".tick text")
.attr('x', xoffset-10)
.attr('class', 'station-name'))
let maxRider = d3.max([d3.max(data, d => -d.pickup), d3.max(data, d => d.dropoff)])
x.domain([-maxRider, maxRider])
y.domain(data.map(d => d.station))
let colorScale = d3.scaleOrdinal()
.domain(['pickup','dropoff','waited'])
.range(d3.schemePaired.slice(0,3))
// add the x-axis gridlines
svg.append("g")
.attr("class", "grid")
.attr("transform", `translate(0,${margin.top})`)
.call(xAxisGrid)
// add subway line
const linewidth = 8
svg.append('g')
.attr("transform", `translate(${margin.left},${margin.top})`)
.append('line')
.attr('class', 'shuttle')
.attr('x1', xoffset)
.attr('x2', xoffset)
.attr('y1', y('Fire Station') + y.bandwidth()/2)
.attr('y2', y('Seward 9N') + y.bandwidth()/2)
.attr('stroke', '#319244')
.attr('stroke-width', linewidth)
// create group for each shuttle stop
let rects = svg.selectAll('.shuttle-stop')
.data(data)
.join('g')
.attr('class', 'shuttle-stop')
.attr("transform", `translate(0,${margin.top})`)
// add elements for each shuttle stop
rects.append('circle')
.attr('class', 'station')
.attr("transform", `translate(${margin.left},0)`)
.attr('cx', xoffset)
.attr('cy', d => y(d.station) + y.bandwidth()/2)
.attr('r', linewidth/2 + 1)
.attr('stroke-width', 2)
rects.append('text')
.attr('class', 'headway')
.attr("transform", `translate(${margin.left},0)`)
.attr('x', xoffset + 45)
.attr('y', d => y(d.station) + y.bandwidth()/2)
.attr('dy', '0.35em')
.text(d => `${Math.floor(d.headway/60)}:${d3.format('>02')(d.headway%60)}`)
rects.append('rect')
.attr('class', 'pickup pdw')
.attr('x', d => x(-d.pickup))
.attr('y', d => y(d.station))
.attr('width', d => x(d.pickup)-x(0))
.attr('height', y.bandwidth())
.attr("data-tippy-content", d => `${d.station}: ${d.pickup} pickups`)
.attr('fill', colorScale('pickup'))
rects.append('rect')
.attr('class', 'dropoff pdw')
.attr('x', d => x(0))
.attr('y', d => y(d.station))
.attr('width', d => x(d.dropoff)-x(0))
.attr('height', y.bandwidth())
.attr("data-tippy-content", d => `${d.station}: ${d.dropoff} dropoffs`)
.attr('fill', colorScale('dropoff'))
rects.append('rect')
.attr('class', 'waited pdw')
.attr('x', d => x(-d.waited))
.attr('y', d => y(d.station))
.attr('width', d => x(d.waited)-x(0))
.attr('height', y.bandwidth())
.attr("data-tippy-content", d => `${d.station}: ${d.waited} waited`)
.attr('fill', colorScale('waited'))
// add axes
svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${margin.top})`)
.call(xAxis);
svg.append("g")
.attr("class", "y-axis")
.call(yAxis);
// headers
let headers = svg.append('g')
.attr('class', 'header')
.attr("transform", `translate(0,${margin.top})`)
// title
headers.append('text')
.attr('transform', `translate(${x(0)},${-50})`)
.text(`${d3.sum(data, d => d.dropoff)} riders`)
.style('font-size', '2.0em')
// x-axis headers
headers.append('text')
.attr('transform', `translate(${x(-maxRider/2)},${-30})`)
.text('pickup')
headers.append('text')
.attr('transform', `translate(${x(maxRider/2)},${-30})`)
.text('dropoff')
function updateChart(day) {
-
const T = 750
svg.selectAll('rect.pickup').transition().duration(T)
.attr('x', d => x(-d.pickup))
.attr('width', d => x(d.pickup)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.pickup} pickups`)
svg.selectAll('rect.dropoff').transition().duration(T)
.attr('width', d => x(d.dropoff)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.dropoff} dropoff`)
svg.selectAll('rect.waited').transition().duration(T)
.attr('x', d => x(-d.waited))
.attr('width', d => x(d.waited)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.waited} waited`)
+
rects.data(rawdata.filter(d => +d.date == +new Date(day)))
.transition()
.duration(750)
.call(g => g.select("rect.pickup")
.attr('x', d => x(-d.pickup))
.attr('width', d => x(d.pickup)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.pickup} pickups`))
.call(g => g.select("rect.dropoff")
.attr('width', d => x(d.dropoff)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.dropoff} dropoff`))
.call(g => g.select("rect.waited")
.attr('x', d => x(-d.waited))
.attr('width', d => x(d.waited)-x(0))
.attr("data-tippy-content", d => `${d.station}: ${d.waited} waited`))
}
return Object.assign(svg.node(), {updateChart});
}