|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<meta name="viewport" content="width=device-width"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/2.24.0/d3-legend.min.js"></script> |
|
<title>Pedestrian and cyclists injured or killed in collisions</title> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css?family=Roboto'); |
|
body { |
|
margin: 0px; |
|
font-family: Roboto; |
|
} |
|
.tick { |
|
font-size: 2em; |
|
} |
|
.axis line { |
|
stroke: #ccc; |
|
} |
|
.axis text { |
|
fill: #666; |
|
} |
|
.axis-label { |
|
fill: #666; |
|
font-size: 2.5em; |
|
} |
|
.comet { |
|
opacity: 0.4; |
|
} |
|
.comet:hover { |
|
opacity: 1; |
|
fill: #ffa251; |
|
} |
|
.schwassman { |
|
opacity: 0.7; |
|
} |
|
.long { |
|
opacity: 0.5; |
|
} |
|
.plotTitle { |
|
font-size: 1.5em; |
|
fill: #666; |
|
} |
|
.legend-label { |
|
fill: #666; |
|
} |
|
.color-legend, .r-legend { |
|
font-size: 1.2em; |
|
fill: #666; |
|
} |
|
.line { |
|
fill: none; |
|
stroke-width: 2px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<svg width="960" height="500"></svg> |
|
<script> |
|
const xValue = d => d.MONTH_NUM; |
|
const y1Value = d => d.PERCENT_CYCLIST; |
|
const y2Value = d => d.PERCENT_PEDESTRIAN; |
|
const colorValue = d => d.YEAR; |
|
|
|
const xLabel = 'Month'; |
|
const yLabel = '% with injury or death to ...'; |
|
|
|
const margin = { left: 100, right: 140, top: 60, bottom: 120 }; |
|
|
|
const svg = d3.select('svg'); |
|
const width = svg.attr('width'); |
|
const height = svg.attr('height'); |
|
const innerWidth = width - margin.left - margin.right; |
|
const innerHeight = height - margin.top - margin.bottom; |
|
const g = svg.append('g') |
|
.attr('transform', `translate(${margin.left},${margin.top})`); |
|
|
|
g.append("text") |
|
.attr("x", (innerWidth / 2)) |
|
.attr("y", -20) |
|
.attr("text-anchor", "middle") |
|
.attr('class', 'plotTitle') |
|
.text("% collisions with injury or death to a ..."); |
|
|
|
const xAxisG = g.append('g') |
|
.attr('transform', `translate(0, ${innerHeight})`) |
|
.attr('class', 'axis'); |
|
const yAxisG = g.append('g') |
|
.attr('class', 'axis'); |
|
const colorLegendG = g.append('g') |
|
.attr('transform', `translate(${innerWidth+40}, ${innerHeight/3})`); |
|
const shapeLegendG = g.append('g') |
|
.attr('transform', `translate(${innerWidth+20}, 20)`); |
|
|
|
xAxisG.append('text') |
|
.attr('class', 'axis-label') |
|
.attr('x', innerWidth/2) |
|
.attr('y', 60) |
|
.text(xLabel); |
|
|
|
const xScale = d3.scaleLinear(); |
|
const yScale = d3.scaleLinear(); |
|
const colorScale = d3.scaleOrdinal(d3["schemeCategory10"]) |
|
.domain([2013, 2014, 2015, 2016]) |
|
|
|
const months = {1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun", 7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"}; |
|
|
|
const xAxis = d3.axisBottom() |
|
.scale(xScale) |
|
.tickPadding(10) |
|
.tickSize(-innerHeight) |
|
.tickFormat(d => months[d]); |
|
|
|
const yAxis = d3.axisLeft() |
|
.scale(yScale) |
|
.ticks(5) |
|
.tickPadding(20) |
|
.tickSize(-innerWidth); |
|
|
|
const colorLegend = d3.legendColor() |
|
.scale(colorScale) |
|
.shapePadding(10) |
|
.shape('circle'); |
|
|
|
const row = d => { |
|
d.YEAR = +d.YEAR; |
|
d.MONTH_NUM = +d.MONTH_NUM; |
|
d.PERCENT_CYCLIST = +d.PERCENT_CYCLIST; |
|
d.PERCENT_PEDESTRIAN = +d.PERCENT_PEDESTRIAN; |
|
return d; |
|
}; |
|
|
|
d3.csv('collision_damage_fractions_comparison.csv', row, data => { |
|
xScale |
|
.domain(d3.extent(data, xValue)) |
|
.range([0, innerWidth]) |
|
.nice(); |
|
|
|
yScale |
|
.domain([0, d3.max(data, y2Value)]) |
|
.range([innerHeight, 0]) |
|
.nice(); |
|
|
|
const circle_r = 8; |
|
const square_side = 14; |
|
const opacity = 0.6; |
|
|
|
g.selectAll('.cyclists').data(data) |
|
.enter().append('circle') |
|
.attr('cx', d => xScale(xValue(d))) |
|
.attr('cy', d => yScale(y1Value(d))) |
|
.attr('r', circle_r) |
|
.attr('opacity', opacity) |
|
.attr('fill', d => colorScale(colorValue(d))); |
|
|
|
g.selectAll('.pedestrians').data(data) |
|
.enter().append('rect') |
|
.attr('x', d => xScale(xValue(d)) - square_side/2) |
|
.attr('y', d => yScale(y2Value(d)) - square_side/2) |
|
.attr('width', square_side) |
|
.attr('height', square_side) |
|
.attr('opacity', opacity) |
|
.attr('fill', d => colorScale(colorValue(d))); |
|
|
|
xAxisG.call(xAxis); |
|
yAxisG.call(yAxis); |
|
colorLegendG.call(colorLegend) |
|
.attr('class', 'color-legend'); |
|
|
|
// add symbol legend by hand |
|
g.append('rect') |
|
.attr('x', d => xScale(10) - square_side/2 + 30) |
|
.attr('y', d => yScale(7.8)- square_side/2) |
|
.attr('width', square_side) |
|
.attr('height', square_side) |
|
.attr('opacity', opacity); |
|
|
|
g.append('circle') |
|
.attr('cx', d => xScale(10) + 30) |
|
.attr('cy', d => yScale(7.3)) |
|
.attr('r', circle_r) |
|
.attr('opacity', opacity); |
|
|
|
g.append('text') |
|
.attr('x', d => xScale(10) + 45) |
|
.attr('y', d => yScale(7.8) + 5) |
|
.text('pedestrian'); |
|
|
|
g.append('text') |
|
.attr('x', d => xScale(10) + 45) |
|
.attr('y', d => yScale(7.3) + 5) |
|
.text('cyclist'); |
|
|
|
|
|
}); |
|
</script> |
|
</body> |
|
</html> |