|
<!DOCTYPE html> |
|
<html> |
|
<head> |
|
<title>Solution</title> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src='https://unpkg.com/d3@4'></script> |
|
<script src='https://unpkg.com/d3-component@3'></script> |
|
<script src='https://unpkg.com/redux@3/dist/redux.min.js'></script> |
|
<script src='https://unpkg.com/d3-tip@0.7.1'></script> |
|
<script src='https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.23.1/babel.min.js'></script> |
|
<link rel='stylesheet' href='https://unpkg.com/bootstrap@4.0.0-alpha.6/dist/css/bootstrap.min.css'> |
|
<style> |
|
body { |
|
margin: 0px; |
|
} |
|
.point { |
|
fill: currentColor; |
|
} |
|
|
|
/* Tooltip styles copied from |
|
http://bl.ocks.org/Caged/6476579 */ |
|
.d3-tip { |
|
line-height: 1; |
|
padding: 12px; |
|
background: rgba(0, 0, 0, 0.8); |
|
color: #fff; |
|
border-radius: 2px; |
|
} |
|
.axis .tick line { |
|
stroke-width: 1px; |
|
stroke: #dddddd; |
|
} |
|
.axis .tick text { |
|
font-size: 20px; |
|
fill: #8E8883; |
|
} |
|
.axis .domain { |
|
display: none; |
|
} |
|
.axis__label { |
|
text-anchor: middle; |
|
font-size: 30px; |
|
fill: #635F5D; |
|
} |
|
.legend .tick text { |
|
font-size: 20px; |
|
fill: #8E8883; |
|
font-family: sans-serif; |
|
alignment-baseline: middle; |
|
} |
|
.legend__label { |
|
font-size: 30px; |
|
fill: #635F5D; |
|
font-family: sans-serif; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<svg width="1200" height="500"></svg> |
|
<script> |
|
const xValue = d => d.battingAverage; |
|
const yValue = d => d.numRuns; |
|
const sizeValue = d => d.salary; |
|
const sizeMax = 13; |
|
const xLabel = "Batting Average"; |
|
const yLabel = "Number of Runs"; |
|
const margin = {top: 30, right: 500, bottom: 100, left: 100}; |
|
|
|
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 xScale = d3.scaleLinear().range([0, innerWidth]); |
|
const yScale = d3.scaleLinear().range([innerHeight, 0]); |
|
const sizeScale = d3.scaleSqrt() |
|
.range([0, sizeMax]); |
|
|
|
const xAxis = d3.axisBottom() |
|
.scale(xScale) |
|
.tickSizeInner(-innerHeight) |
|
.tickPadding(15); |
|
const yAxis = d3.axisLeft() |
|
.scale(yScale) |
|
.tickSizeInner(-innerWidth) |
|
.ticks(5) |
|
.tickPadding(10); |
|
|
|
const g = svg.append("g") |
|
.attr("transform", `translate(${margin.left}, ${margin.top})`); |
|
const xAxisG = g.append("g") |
|
.attr("class", "axis") |
|
.attr("transform", `translate(0, ${innerHeight})`); |
|
const yAxisG = g.append("g") |
|
.attr("class", "axis"); |
|
|
|
xAxisG |
|
.append("text") |
|
.attr("class", "axis__label") |
|
.attr("x", innerWidth / 2) |
|
.attr("y", 85) |
|
.text(xLabel); |
|
|
|
yAxisG |
|
.append("text") |
|
.attr("class", "axis__label") |
|
.attr("transform", "rotate(-90)") |
|
.attr("x", -innerHeight / 2) |
|
.attr("y", -45) |
|
.text(yLabel); |
|
|
|
function sizeLegend(selection, props){ |
|
const sizeScale = props.sizeScale; |
|
const positionX = props.positionX; |
|
const positionY = props.positionY; |
|
const ticksCount = props.ticks; |
|
const tickFill = props.tickFill; |
|
const tickSpacing = props.tickSpacing; |
|
const tickPadding = props.tickPadding; |
|
const label = props.label; |
|
const labelX = props.labelX; |
|
const labelY = props.labelY; |
|
|
|
let legendG = selection |
|
.selectAll(".legend--size") |
|
.data([null]); |
|
legendG = legendG |
|
.enter().append("g") |
|
.attr("class", "legend legend--size") |
|
.merge(legendG) |
|
.attr("transform", `translate(${positionX}, ${positionY})`); |
|
|
|
const legendLabel = legendG |
|
.selectAll(".legend__label") |
|
.data([null]); |
|
legendLabel |
|
.enter().append("text") |
|
.attr("class", "legend__label") |
|
.merge(legendLabel) |
|
.attr("x", labelX) |
|
.attr("y", labelY) |
|
.text(label); |
|
|
|
const ticks = legendG |
|
.selectAll(".tick") |
|
.data(sizeScale.ticks(ticksCount).filter(d => d)); |
|
const ticksEnter = ticks |
|
.enter().append("g") |
|
.attr("class", "tick"); |
|
ticksEnter |
|
.merge(ticks) |
|
.attr("transform", (d, i) => `translate(0, ${i * tickSpacing})`); |
|
ticks.exit().remove(); |
|
|
|
ticksEnter |
|
.append("circle") |
|
.merge(ticks.select("circle")) |
|
.attr("r", sizeScale) |
|
.attr("fill", tickFill); |
|
|
|
ticksEnter |
|
.append("text") |
|
.merge(ticks.select("text")) |
|
.attr("x", tickPadding) |
|
.text(d => d); |
|
} |
|
|
|
function render(data){ |
|
xScale |
|
.domain(d3.extent(data, xValue)) |
|
.nice(); |
|
yScale |
|
.domain(d3.extent(data, yValue)) |
|
.nice(); |
|
sizeScale |
|
.domain([0, d3.max(data, sizeValue)]); |
|
|
|
xAxisG.call(xAxis); |
|
yAxisG.call(yAxis); |
|
|
|
const circles = g.selectAll("circle").data(data); |
|
circles.exit().remove(); |
|
circles |
|
.enter().append("circle") |
|
.attr("r", 5) |
|
.merge(circles) |
|
.attr("cx", d => xScale(xValue(d))) |
|
.attr("cy", d => yScale(yValue(d))) |
|
.attr("r", d => sizeScale(sizeValue(d))) |
|
.attr("stroke", "white") |
|
.attr("opacity", 0.6); |
|
|
|
sizeLegend(svg, { |
|
sizeScale: sizeScale, |
|
positionX: 722, |
|
positionY: 200, |
|
ticks: 5, |
|
tickFill: "black", |
|
tickSpacing: 35, |
|
tickPadding: 16, |
|
label: "Salary (thousands)", |
|
labelX: -20, |
|
labelY: -30 |
|
}); |
|
|
|
} |
|
|
|
function type(d){ |
|
d.salary = +d.salary; |
|
d.battingAverage = +d.battingAverage; |
|
d.onBase = +d.onBase; |
|
d.numRuns=+d.NumberRuns; |
|
return d; |
|
} |
|
|
|
d3.csv("baseball.csv", type, render); |
|
|
|
</script> |
|
</body> |
|
</html> |