|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
|
|
<style> |
|
|
|
* { |
|
box-sizing: border-box; |
|
} |
|
|
|
html, body { |
|
width: 100%; |
|
height: 100%; |
|
margin: 0; |
|
} |
|
|
|
body { |
|
font-family: sans-serif; |
|
font-size: 20px; |
|
} |
|
|
|
.order { |
|
position: fixed; |
|
right: .5em; |
|
top: .5em; |
|
border: 2px solid black; |
|
background: white; |
|
padding: 10px; |
|
font-weight: bold; |
|
display: inline-block; |
|
font-size: 2em; |
|
width: 284px; |
|
height: 104px; |
|
} |
|
|
|
.order .dim { |
|
position: absolute; |
|
display: inline-block; |
|
width: 80px; |
|
height: 80px; |
|
padding: .25em; |
|
text-align: center; |
|
border: 1px solid black; |
|
} |
|
|
|
.order .dim:hover { |
|
cursor: -webkit-grab; |
|
cursor: grab; |
|
background: rgba(0,0,0,.1); |
|
} |
|
|
|
.order .dim.active { |
|
cursor: -webkit-grabbing; |
|
cursor: grabbing; |
|
background: black; |
|
color: white; |
|
z-index: 2; |
|
} |
|
|
|
.table { |
|
position: absolute; |
|
} |
|
|
|
.table div.header { |
|
position: absolute; |
|
text-align: center; |
|
padding: .5em; |
|
font-weight: bold; |
|
} |
|
|
|
.table div.cell { |
|
position: absolute; |
|
text-align: right; |
|
border-bottom: 1px solid black; |
|
border-right: 1px solid white; |
|
border-right-color: white; |
|
} |
|
|
|
.table div.bar { |
|
position: absolute; |
|
background: rgba(0,0,0,.1); |
|
width: 100%; |
|
} |
|
|
|
.table div.value { |
|
padding: .5em; |
|
} |
|
|
|
</style> |
|
|
|
<body> |
|
<div class="table"></div> |
|
<div class="order"></div> |
|
</body> |
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
|
|
var w = 45, |
|
h = 45, |
|
order = ['i', 'j', 'k'], |
|
dim = { |
|
i: 2 + (Math.random() * 10 | 0), |
|
j: 2 + (Math.random() * 10 | 0), |
|
k: 2 + (Math.random() * 10 | 0) |
|
}, |
|
value = (i,j,k) => i + 5 + 5 * Math.sin(j) + d3.randomNormal(5)(), |
|
rainbow = d3.scaleSequential(d3.interpolateRainbow) |
|
.domain([0, Math.max(dim.i, dim.j, dim.k)]), |
|
data = d3.merge( |
|
d3.range(dim.i).map( |
|
(i) => d3.merge(d3.range(dim.j).map( |
|
(j) => d3.range(dim.k).map( |
|
(k) => ({i, j, k, x: Math.round(value(i,j,k))}) |
|
) |
|
)) |
|
) |
|
), |
|
headerData = [].concat( |
|
d3.range(dim.i).map(i => ({dim: "i", val: i})), |
|
d3.range(dim.j).map(j => ({dim: "j", val: j})), |
|
d3.range(dim.k).map(k => ({dim: "k", val: k})) |
|
), |
|
barScale = d3.scaleLinear() |
|
.domain(d3.extent(data.map(d => d.x))) |
|
.range([0,100]) |
|
|
|
var drag = d3.drag() |
|
.on("start", dragstarted) |
|
.on("drag", dragged) |
|
.on("end", dragended) |
|
|
|
var label = d3.select(".order").selectAll("div.dim") |
|
.data(order) |
|
.enter() |
|
.append("div") |
|
.attr("class", "dim") |
|
.text(d => d) |
|
.call(renderDim) |
|
.call(drag) |
|
|
|
var table = d3.select(".table") |
|
.style("left", w + "px") |
|
.style("top", h * 2 + "px") |
|
|
|
var header = table.selectAll("div.header") |
|
.data(headerData) |
|
.enter() |
|
.append("div") |
|
.attr("class", "header") |
|
.html(d => d.dim + "<sub>" + d.val + "</sub>") |
|
.call(renderHeader) |
|
|
|
var cell = table.selectAll("div.cell") |
|
.data(data) |
|
.enter() |
|
.append("div") |
|
.attr("class", "cell") |
|
.call(renderCell) |
|
|
|
cell.append("div") |
|
.attr("class", "value") |
|
.text(d => d.x) |
|
|
|
cell.append("div") |
|
.attr("class", "bar") |
|
.style("bottom", "0") |
|
.style("height", d => barScale(d.x) + "%") |
|
|
|
var interval = d3.interval(swizzle, 3000) |
|
|
|
function swizzle() { |
|
d3.shuffle(order) |
|
d3.transition() |
|
.duration(2000) |
|
.call(render) |
|
} |
|
|
|
function render(t) { |
|
label.transition(t) |
|
.call(renderDim) |
|
header.transition(t) |
|
.call(renderHeader) |
|
cell.transition(t) |
|
.call(renderCell) |
|
} |
|
|
|
function renderDim(selection) { |
|
selection |
|
.filter(function() { return !d3.select(this).classed("active") }) |
|
.style("left", d => 10 + order.indexOf(d) * 90 + "px") |
|
} |
|
|
|
function renderHeader(selection) { |
|
selection |
|
.style("width", w + "px") |
|
.style("height", h + "px") |
|
.style("left", d => { |
|
if(d.dim == order[0]) { |
|
return w * dim[order[2]] * d.val + "px" |
|
} else if(d.dim == order[1]) { |
|
return -w + "px" |
|
} else if(d.dim == order[2]) { |
|
return w * d.val + "px" |
|
} |
|
}) |
|
.style("top", d => { |
|
if(d.dim == order[0]) { |
|
return -2 * h + "px" |
|
} else if(d.dim == order[1]) { |
|
return h * d.val + "px" |
|
} else if(d.dim == order[2]) { |
|
return -h + "px" |
|
} |
|
}) |
|
} |
|
|
|
function renderCell(selection) { |
|
selection |
|
// .style("color", d => rainbow(d[order[2]])) |
|
.style("width", w + "px") |
|
.style("height", h + "px") |
|
.style("border-right-color", d => d[order[2]] === dim[order[2]] - 1 ? 'rgba(0,0,0,1)' : 'rgba(0,0,0,0)') |
|
.style("left", (d) => (w * dim[order[2]]) * d[order[0]] + w * d[order[2]] + "px") |
|
.style("top", (d) => h * d[order[1]] + "px") |
|
} |
|
|
|
|
|
function dragstarted(d) { |
|
interval.stop() |
|
d3.select(this).classed("active", true) |
|
} |
|
|
|
function dragged(d) { |
|
d3.select(this) |
|
.style("left", d3.event.x - 40 + "px") |
|
|
|
order = label.nodes() |
|
.sort( |
|
(a,b) => a.offsetLeft - b.offsetLeft) |
|
.map( |
|
el => d3.select(el).datum() |
|
) |
|
|
|
d3.transition() |
|
.duration(100) |
|
.call(render) |
|
} |
|
|
|
function dragended(d) { |
|
d3.select(this).classed("active", false) |
|
label.transition() |
|
.duration(100) |
|
.call(renderDim) |
|
} |
|
|
|
// do good |
|
// ok, 12 march 2021, removing this. interesting record of my feeling of helpless desperation though! |
|
/* |
|
d3.select("body").on("click", () => { |
|
if(!d3.select(d3.event.target).classed("dim")) { |
|
window.open(Math.random() > .5 ? "https://action.aclu.org/donate-aclu" : "https://www.cair.com/donations/general-donation/campaign/#/donation") |
|
} |
|
}) |
|
*/ |
|
|
|
</script> |