Published
Edited
Oct 13, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
htl.svg`<svg width=${width} height=70>
${d3axis(x)}
${createScale(x, {
width: barWidth,
displayWidth: minWidth,
maxDepth,
height: 20,
unit: "m",
format: d3.format("~s")
})}
<line x1=${barWidth} x2=${barWidth} y1=0 y2=50 stroke=red></line>`
Insert cell
getTicks(x, { width: barWidth, displayWidth: minWidth, maxDepth })
Insert cell
function getTicks(
scale,
{ width = 300, displayWidth = 40, maxDepth = 5 } = {}
) {
const [range, prettydepth] = getClosestNice(scale.invert(width));
maxDepth = Math.min(maxDepth, prettydepth);
function ticking(acc, range, width, depth) {
if (width <= displayWidth || depth >= maxDepth) {
acc.push(0);
return acc;
}

acc.push(range);
return ticking(acc, range / 2, width / 2, ++depth);
}

return ticking([], range, scale(range), 0).sort(d3.ascending);
}
Insert cell
function createScale(
x,
{
width = 500,
maxDepth = 0,
displayWidth = 40,
height = 20,
unit = "",
format = (i) => i
} = {}
) {
const ticks = getTicks(x, { width, displayWidth, maxDepth });
const last = ticks[ticks.length - 1];
const tickPairs = d3.pairs(ticks);

const half = height / 2;
const $scale = d3.create("svg:g");

$scale
.selectAll("text")
.data(ticks)
.join("text")
.call(attrs, {
x,
y: 12,
"text-anchor": (v, i) =>
i === 0 ? "start" : v === last ? "end" : "middle"
})
.text(format);

const $bar = $scale
.append("g")
.attr("transform", "translate(0, 18)")
.call(
build({
append: "rect",
x: x(0),
y: 0,
width: x(last) - 10,
height,
fill: "white",
"stroke-width": 2,
stroke: "black"
})
)
.call(
build({
append: "text",
x: x(last) + 5,
y: half,
"dominant-baseline": "middle",
call: (g) => g.text(unit)
})
)
.call((g) =>
g
.append("g")
.selectAll("rect")
.data(tickPairs)

.join("rect")
.call(attrs, {
x: (v) => x(v[0]),
y: (v, i) => (i % 2) * half,
width: (v) => x(v[1]) - x(v[0]),
height: half
})
);

return $scale.node();
}
Insert cell
Insert cell
htl.svg`<svg width=${width} height=50>${createScaleHtl(x, {
width: barWidth,
displayWidth: minWidth,
maxDepth,
unit: "m",
format: d3.format("~s")
})}`
Insert cell
function createScaleHtl(
x,
{
width = 500,
maxDepth = 5,
displayWidth = 40,
height = 20,
unit = "",
format = (i) => i
} = {}
) {
const ticks = getTicks(x, { width, displayWidth, maxDepth });
const last = ticks[ticks.length - 1];
const tickPairs = d3.pairs(ticks);

const half = height / 2;

return htl.svg`
${ticks.map(
(v, i) =>
htl.svg`<text x=${x(v)} y=12
text-anchor=${i == 0 ? "start" : v === last ? "end" : "middle"}>${format(v)}`
)}
<g transform="translate(0, 18)">
<rect x=${x(0)} y=0 width=${x(last) - 10} height=${height}
fill=white stroke-width=2 stroke=black></rect>
<text x=${x(last) + 5} y=${half} dominant-baseline=middle>${unit}</text>
${tickPairs.map(
(v, i) => htl.svg`<rect x=${x(v[0])} y=${(i % 2) * half}
width=${x(v[1]) - x(v[0])} height=${half}>`
)}`;
}
Insert cell
x = d3.scaleLinear([0, range], [10, domainMax + 10])
Insert cell
d3axis = (scale) =>
d3
.create("svg:g")
.attr("transform", "translate(0,50)")
.call(d3.axisBottom(x))
.node()
Insert cell
function getClosestNice(value) {
const pow10 = Math.pow(
10,
Math.floor(Math.fround(Math.log(value) / Math.log(10)))
);
if (5 * pow10 <= value) return [5 * pow10, 3];
if (4 * pow10 <= value) return [4 * pow10, 16];
if (3 * pow10 <= value) return [3 * pow10, 3];
if (2 * pow10 <= value) return [2 * pow10, 8];
return [pow10, 4];
}
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