Published
Edited
Apr 12, 2021
Importers
Insert cell
Insert cell
Insert cell
b = stack(trunk)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
trunk = pack(crate)
Insert cell
stack = (crate, height = width * 9 / 16) => {

const sheet = d3.create("svg")
.attr("name", "sheet")
.attr("width", width)
.attr("height", height)
// .attr("viewBox", `0 0 ${width/1} ${height/1}`)
// .attr("preserveAspectRatio", "xMidYMid meet")

drawBoxes(sheet, crate)

return sheet.node()
}
Insert cell
drawBoxes = (sheet, crate) => {

const container = sheet.append("svg")
.attr("name", "crate")
.attr("viewBox", `0 0 ${crate.max.w} ${crate.total.h}`)
.attr("preserveAspectRatio", "xMidYMin meet") // change this to move the stack around min/mid/max
.attr("fill", "white")
.style("font-family", "monospace")
.style("font-size", ".09em")
.style("text-anchor", "middle")
.style("alignment-baseline", "central")

const box = container.selectAll("_")
.data(crate.boxes)
.enter()
.append("svg")
.attr("name", (d, i) => `box-${i}`)
.attr("y", d => d.framed.total.h)
.attr("x", d => (crate.max.w - d.framed.w) / 2)
box
.append("rect")
.attr("width", d => d.framed.w)
.attr("height", d => d.framed.h)
.attr("fill", "dimgrey")
// .attr("opacity", .7)

box
.append("rect")
.attr("x", d => d.framed.x)
.attr("y", d => d.framed.y)
.attr("width", d => d.normalized.w)
.attr("height", d => d.normalized.h)
.attr("fill", "red")

const text = box
.append("text")
.attr("x", d => d.framed.w / 2)
.attr("y", d => d.framed.y + d.normalized.h / 2)

text.append("tspan")
.text(d => `${fix(d.original.w)}:${fix(d.original.h)} = ${fix(d.normalized.a)}`)

text.append("tspan")
.attr("x", d => d.framed.w / 2)
.attr("dy", 1.5)
.text(d => `×${fix(d.normalized.scale)} = ${fix(d.normalized.w)}:${fix(d.normalized.h)}`)
}
Insert cell
Insert cell
selectedBoxes =
aspects
// bboxes
Insert cell
Insert cell
Insert cell
boxes = take(grab)(selectedBoxes)
Insert cell
Insert cell
crate = ({
equalizer,
sizing,
round,
norm,
padding,
isoframe,
boxes,
})
Insert cell
Insert cell
Insert cell
i = 0
Insert cell
box = boxes[i]
Insert cell
Insert cell
addAspect = curry((box) => ({...box, a: box.w / box.h}))
Insert cell
Insert cell
addAspect(box)
Insert cell
map(addAspect)(boxes)
Insert cell
Insert cell
boxer = curry(
(crate, original, i) => {
const pimped = addAspect(original),
normalized = normalize(crate, original),
framed = frame(crate, normalized, i)

return ({
i, // remember the index; use later e.g. to create unique name for box or check if first or last box
original,
pimped,
normalized,
framed,
})

}
)
Insert cell
Insert cell
boxer(crate, box, i)
Insert cell
mapIndexed(boxer(crate))(boxes)
Insert cell
Insert cell
pack = (crate) => {
const boxes = pipe(mapIndexed(boxer(crate)), addSum("framed"))(crate.boxes),
total = last(boxes).framed.exit, // hoist the exit values for the last box; just for convenience
max = maxOf("framed", ["w", "h"])(boxes)

return ({
...crate,
boxes: map(fraction(total.h))(boxes),
total,
max: {...max, a: max.w / max.h },
aspect: max.w / total.h,
})
}
Insert cell
Insert cell
maxOf = curry(
(kind, props, list) =>
reduce(
(acc, k) => ({
...acc,
[k]: max(kind)(k)(list),
}),
{}
)(props)
)
Insert cell
maxOf("framed", ["w", "h"])(trunk.boxes)
Insert cell
max = curry(
(p1, p2, list) =>
Math.max(
...map(
k => k[p1][p2],
list
)
)
)
Insert cell
Insert cell
fraction = curry(
(h, box) => ({
...box,
framed: {
...box.framed,
fraction: box.framed.init.h / h
}
})
)
Insert cell
Insert cell
fraction(trunk.total.h, trunk.boxes[2]).framed.fraction
Insert cell
Insert cell
addSum = property => running(R.add, {w: 0, h: 0, a: 0}, {subPropertyName: property})
Insert cell
Insert cell
total = curry((kind, prop, boxes) => sum(map(k => k[kind][prop])(boxes)))
Insert cell
Insert cell
total("framed", "w", trunk.boxes)
Insert cell
Insert cell
totalizer = (kind, properties, boxes) =>
reduce(
(collector, k) => ({
...collector,
[k]: total(kind, k)(boxes)
}),
{},
)
(properties)
Insert cell
Insert cell
totalizer("framed", ["w", "h"], trunk.boxes)
Insert cell
Insert cell
normalize = curry(
(crate, original) => {
const scale = crate.round(crate.norm / original[crate.equalizer]) || 1,
{w, h} = map(x => scale * x)(pick(["w", "h"])(original))

return ({scale, ...original, w, h, a: w / h})

}
)
Insert cell
Insert cell
frame = curry(
(crate, normalized, i) => {
const isoframe = crate?.isoframe ?? 1
const padding = curry((crate, n) => (crate?.padding ?? 0) * n)(crate)
// returns function that takes n and returns right amount of padding for this crate

// isFirst === 1 for first item in list, 0 otherwise;
// if-less way of saying: isFirst = i === 0 ? 1 : 0 // just for fun; useful here?
const isFirst = (1 / (i + 1)) | 0

// isLast === 1 for last item in list, 0 otherwise;
// if-less way of saying: isLast = i === list.length ? 1 : 0 // just for fun; useful here?
const isLast = ((i + 1) / crate.boxes.length) | 0
const w = normalized.w + padding(2 + isoframe * 2)
const h = normalized.h + padding(2 + isoframe * (isFirst + isLast))

return ({
...box,
w,
h,
a: w / h,
x: padding(1 + isoframe),
y: padding(1 + isoframe * isFirst),
z: padding(0 + isoframe * (isFirst - isLast) / 2), // z is start position for svg with path
})

}
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more