|
<!DOCTYPE html> |
|
<meta charset='utf-8'> |
|
<style> |
|
|
|
svg { |
|
font: 10px sans-serif; |
|
padding: 12px; |
|
} |
|
|
|
.axis, |
|
.frame { |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.axis line { |
|
stroke: #e8e8e8; |
|
} |
|
|
|
.axis path { |
|
display: none; |
|
} |
|
|
|
.axis text { |
|
fill: #999; |
|
} |
|
|
|
.cell text { |
|
font-weight: bold; |
|
text-transform: capitalize; |
|
font-size: 15px; |
|
fill: #222; |
|
} |
|
|
|
.frame { |
|
fill: none; |
|
stroke: #aaa; |
|
} |
|
|
|
.diagonal { |
|
stroke: none; |
|
fill: #fff; |
|
fill-opacity: 0.8; |
|
} |
|
|
|
circle.hidden { |
|
fill: #ccc !important; |
|
} |
|
|
|
.extent { |
|
fill: #000; |
|
fill-opacity: .125; |
|
stroke: #fff; |
|
} |
|
|
|
</style> |
|
<body> |
|
<script src='https://d3js.org/d3.v4.0.0-alpha.35.js'></script> |
|
<script src='https://npmcdn.com/babel-core@5.8.34/browser.min.js'></script> |
|
<script lang='babel' type='text/babel'> |
|
const traits = ['assist86', 'hits86', 'homer86', 'runs86', 'rbi86', 'walks86']; |
|
const n = traits.length; |
|
|
|
const width = 960; |
|
const size = (width / n) - 12; |
|
const padding = 24; |
|
|
|
// setup the page |
|
d3.select('body').append('div') |
|
.attr('width', size * n + padding) |
|
.attr('height', size * n + padding) |
|
.attr('id', 'vis') |
|
.style('top', -20) |
|
.style('position', 'relative'); |
|
|
|
let x = d3.scaleLinear() |
|
.range([padding / 2, size - padding / 2]); |
|
|
|
let y = d3.scaleLinear() |
|
.range([size - padding / 2, padding / 2]); |
|
|
|
let xAxis = d3.axisBottom() |
|
.scale(x) |
|
.ticks(5); |
|
|
|
let yAxis = d3.axisLeft() |
|
.scale(y) |
|
.ticks(5); |
|
|
|
const color = d3.scaleCategory20(); |
|
|
|
d3.csv('Baseball.csv', (error, data) => { |
|
if (error) throw error; |
|
|
|
data.forEach(d => { |
|
traits.forEach(trait => { |
|
return d[trait] = +d[trait]; |
|
}); |
|
}); |
|
|
|
let domainByTrait = {}; |
|
|
|
traits.forEach(trait => { |
|
domainByTrait[trait] = d3.extent(data, d => d[trait]); |
|
}); |
|
|
|
xAxis.tickSize(size * n); |
|
yAxis.tickSize(-size * n); |
|
|
|
let svg = d3.select('div#vis').append('svg') |
|
.attr('width', size * n + padding) |
|
.attr('height', size * n + padding) |
|
.datum({ |
|
x: width / 2, |
|
y: 960 |
|
}) |
|
.append('g') |
|
.attr('transform', `translate(${padding}, ${padding / 2})`); |
|
|
|
svg.selectAll('.x.axis') |
|
.data(traits) |
|
.enter().append('g') |
|
.attr('class', 'x axis') |
|
.attr('transform', (d, i) => 'translate(' + (n - i - 1) * size + ',0)') |
|
.each(function (d) { |
|
x.domain(domainByTrait[d]).nice(); |
|
d3.select(this).call(xAxis); |
|
}); |
|
|
|
svg.selectAll('.y.axis') |
|
.data(traits) |
|
.enter().append('g') |
|
.attr('class', 'y axis') |
|
.attr('transform', (d, i) => `translate(0, ${i * size})`) |
|
.each(function (d) { |
|
y.domain(domainByTrait[d]); |
|
d3.select(this).call(yAxis); |
|
}); |
|
|
|
let cell = svg.selectAll('.cell') |
|
.data(cross(traits, traits)) |
|
.enter().append('g') |
|
.attr('class', 'cell') |
|
.attr('transform', d => `translate(${(n - d.i - 1) * size}, ${d.j * size})`) |
|
.each(plot); |
|
|
|
// Titles for the diagonal. |
|
cell.filter(d => d.i === d.j).append('text') |
|
.attr('x', size/2) |
|
.attr('y', size/2) |
|
.attr('text-anchor', 'middle') |
|
.text(d => d.x); |
|
|
|
//cell.call(brush); |
|
|
|
function plot(p) { |
|
let cell = d3.select(this); |
|
|
|
x.domain(domainByTrait[p.x]); |
|
y.domain(domainByTrait[p.y]); |
|
|
|
cell.append('rect') |
|
.attr('class', 'frame') |
|
.classed('diagonal', d => d.i === d.j) |
|
.attr('x', padding / 2) |
|
.attr('y', padding / 2) |
|
.attr('width', size - padding) |
|
.attr('height', size - padding); |
|
|
|
cell.filter(d => d.i !== d.j) // hide diagonal marks |
|
.selectAll('circle') |
|
.data(data) |
|
.enter().append('circle') |
|
.classed('marks', true) |
|
.attr('cx', d => x(d[p.x])) |
|
.attr('cy', d => y(d[p.y])) |
|
.attr('r', 2.5) |
|
.style('fill', d => color(d.posit86)) |
|
.style('fill-opacity', 0.356); |
|
} |
|
}); |
|
|
|
function cross(a, b) { |
|
let c = [], n = a.length, m = b.length, i, j; |
|
for (i = -1; ++i < n;) for (j = -1; ++j < m;) c.push({x: a[i], i: i, y: b[j], j: j}); |
|
return c; |
|
} |
|
|
|
// slider |
|
d3.select('body').append('input') |
|
.attr('type', 'range') |
|
.attr('min', 0) |
|
.attr('max', 1) |
|
.attr('value', 0.356) |
|
.attr('step', 0.001) |
|
.style('top', '0px') |
|
.style('left', `${padding}px`) |
|
.style('height', '36px') |
|
.style('width', `${915 - padding}px`) |
|
.style('position', 'relative') |
|
.attr('id', 'slider'); |
|
|
|
d3.select('#slider') |
|
.on('input', function() { |
|
update(+this.value); |
|
}); |
|
|
|
function update(sliderValue) { |
|
// adjust the text on the range slider |
|
d3.select('#nRadius-value').text(sliderValue); |
|
d3.select('#nRadius').property('value', sliderValue); |
|
|
|
// update the circle radius |
|
d3.selectAll('.marks') |
|
.style('fill-opacity', sliderValue); |
|
} |
|
</script> |