119 lines
3.2 KiB
JavaScript
119 lines
3.2 KiB
JavaScript
(function () {
|
|
function punchCard() {
|
|
var margin = { top: 20, right: 20, bottom: 30, left: 150 };
|
|
var data;
|
|
var updateData;
|
|
|
|
const tooltip = d3.select('body').append('div')
|
|
.classed('tooltip', true)
|
|
.attr('id', 'pCardTooltip')
|
|
.style("opacity", 0);
|
|
;
|
|
|
|
function chart(svg) {
|
|
let width = svg.attr('width');
|
|
let height = svg.attr('height');
|
|
|
|
width -= margin.left + margin.right;
|
|
height -= margin.top + margin.bottom;
|
|
|
|
const g = svg.append('g')
|
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
|
|
|
const gAxis = g.append('g')
|
|
.classed('axis', true);
|
|
|
|
|
|
updateData = function () {
|
|
const maxValue = _(data).values().map(e => _(e).values().value()).map(e => _(e).max()).max();
|
|
const x = d3.scaleLinear()
|
|
.domain([0, 23])
|
|
.range([0, width])
|
|
;
|
|
|
|
const y = d3.scaleLinear()
|
|
.domain([0, d3.keys(data).length])
|
|
.range([0, height])
|
|
;
|
|
|
|
const r = d3.scaleSqrt()
|
|
.domain([1, maxValue])
|
|
.range([3, 11])
|
|
;
|
|
|
|
let rows = g.selectAll('g.row').data(d3.entries(data), d => d.key);
|
|
|
|
let erows = rows.enter().append('g')
|
|
.classed('row', true)
|
|
.attr('opacity', 1)
|
|
|
|
erows
|
|
.append('text')
|
|
.attr('x', -10)
|
|
.attr('y', 3)
|
|
.attr('text-anchor', 'end')
|
|
.text(d => d.key);
|
|
|
|
rows.exit().remove();
|
|
|
|
rows = erows.merge(rows);
|
|
|
|
rows.transition().duration(25).attr('transform', (d, i) => `translate(0, ${y(i)})`);
|
|
|
|
const circles = rows.selectAll('circle.punch').data(d => d3.entries(d.value), d => d.key);
|
|
circles.enter().append('circle')
|
|
.classed('punch', true)
|
|
.attr('cx', d => x(+d.key))
|
|
.attr('fill', 'orange')
|
|
.attr('r', 0)
|
|
.on("mouseover", function (d) {
|
|
tooltip.transition()
|
|
.duration(200)
|
|
.style("opacity", .9);
|
|
tooltip.html(d.value);
|
|
|
|
// We calculate the bounding rects after setting the html
|
|
let rect = d3.select(this).node().getBoundingClientRect();
|
|
let t_rect = tooltip.node().getBoundingClientRect();
|
|
tooltip
|
|
.style("left", (rect.left + rect.width/2 - t_rect.width/2) + "px")
|
|
.style("top", (rect.top - t_rect.height - 5) + "px");
|
|
})
|
|
.on("mouseout", _ => {
|
|
tooltip.transition()
|
|
.duration(500)
|
|
.style("opacity", 0);
|
|
})
|
|
.merge(circles)
|
|
.transition()
|
|
.duration(25)
|
|
.attr('r', d => r(d.value))
|
|
;
|
|
|
|
circles.exit().transition()
|
|
.attr('r', 0)
|
|
.remove();
|
|
|
|
rows.selectAll('circle.punch').attr('r', d => r(d.value));
|
|
|
|
const axis = d3.axisBottom(x).ticks(24);
|
|
|
|
gAxis
|
|
.attr('transform', `translate(0, ${y.range()[1]})`)
|
|
.call(axis);
|
|
}
|
|
updateData();
|
|
}
|
|
|
|
chart.data = function (value) {
|
|
if (!arguments.length) return data;
|
|
data = value;
|
|
if (typeof updateData === 'function') updateData();
|
|
return chart;
|
|
}
|
|
|
|
return chart;
|
|
}
|
|
|
|
window['punchCard'] = punchCard;
|
|
})();
|