block by tophtucker 67ae8d1faa58567945cd5bbca8cc08eb

Swizzle

Full Screen

Swizzling! (Or pandas DataFrame “reshaping” / “pivoting” / “(un)stacking”.)

Click and drag the i,j,k in the upper right to reorder. Refresh for random dimensions.

One can imagine more spreadsheety stuff, extending to 4D arrays and higher, and aggregating/slicing/collapsing/folding along different dimensions.

No, I’m not at all sure I’m using the term “swizzle” correctly. I don’t understand matrices.

index.html

<!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>