Public
Edited
May 20
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
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
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function numberDial(value, dials = 2, base = 10, hideLeadingZeros = false) {
const margin = 5;
const radius =
Math.max(20, Math.min(width / dials / 2 - margin * 2, base*16));
const w = width;
const h = (radius + margin * 2) * 2;
const ctx = DOM.context2d(w, h);
const x0 = 0;
const y0 = h / 2 - radius;

ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = 4;

const circle = (x, y, r, {fill, stroke}) => {
ctx.beginPath();
if (fill) ctx.fillStyle = fill;
if (stroke) ctx.strokeStyle = stroke;
ctx.moveTo(x + r, y);
ctx.arc(x, y, r, 0, Math.PI * 2, false);
if (fill) ctx.fill();
if (stroke) ctx.stroke();
}

let minDial = hideLeadingZeros ? dials-1 : 0;
for (let i = 0; hideLeadingZeros && i < dials; i++) {
const offset = (value / base ** (dials - 1 - i)) % base | 0;
if (offset) {
minDial = i;
break;
}
}

// Dial plates
for (const [r, style] of [
[radius, {fill: "#F8F8F8", stroke: "#111"}],
[radius / 20, {fill: "#444", stroke: "#444"}]
]) {
for (let i = minDial; i < dials; i++) {
circle(x0 + margin + radius * (1 + i * 2), y0 + margin + radius, r, style);
}
}

ctx.beginPath();
// Dial numbers
const dialTextSize = Math.min(
48,
radius / 2,
radius * 0.75 * (base > 2 ? Math.sin((2 * Math.PI) / base) : 1)
);
ctx.font = `${dialTextSize}px sans`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";

for (let i = minDial; i < dials; i++) {
const cx = margin + x0 + radius * ((0.5 + i) * 2);
const cy = margin + y0 + radius;
const offset = (value / base ** (dials - 1 - i)) % base | 0;
for (let j = 0; j < base; j++) {
const x =
cx +
(radius - dialTextSize * 0.65) * Math.sin((j * 2 * Math.PI) / base);
const y =
cy +
(radius - dialTextSize * 0.65) * -Math.cos((j * 2 * Math.PI) / base);
if (j === offset) {
ctx.beginPath()
ctx.strokeStyle = "#444";
ctx.moveTo(cx, cy);
ctx.lineTo((cx * 0.5 + x * 0.5), (cy * 0.5 + y * 0.5));
ctx.stroke();
}
const numberText = b64[j];
ctx.fillStyle = j !== offset ? "#AAA" : "#000";

ctx.strokeStyle = "#FFF";
ctx.strokeText(numberText, x, y);
ctx.fillText(numberText, x, y);
}
}
return ctx.canvas;
}
Insert cell
constrain = (v, lb, ub) => v < lb ? lb : v > ub ? ub : v;
Insert cell
function dots(value, radius, valuesPerColumn) {
const margin = 5;
const totalColumnWidth = 1 + Math.floor(value / valuesPerColumn);
const w = constrain(radius * 3 * totalColumnWidth + 2 * margin, 0, width);
const h = radius * 2.5 * valuesPerColumn + 2 * margin;
const ctx = DOM.context2d(w, h);

const xStep = Math.min((width - 2 * margin) / totalColumnWidth, radius * 3);
const yStep = radius * 2.5;
ctx.lineCap = "round";
ctx.lineJoin = "round";
const lineWidth = ctx.lineWidth = Math.min(4, xStep | 0) / 2;
ctx.fillStyle = "#444";
const hex = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
const cval = Math.round(constrain(xStep/radius, 0, 1) * 255);
const color = hex[Math.floor(cval / 16)] + hex[cval % 16];
console.log({xStep, cval, color})
ctx.strokeStyle = "#" + color + color + color;

const circle = (x, y, r) => {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arc(x, y, r, 0, Math.PI * 2, false);
if (lineWidth) ctx.stroke();
ctx.fill();
}


for (let i = 0; i < value; i++) {
const x = margin + Math.floor(i / valuesPerColumn) * xStep + radius;
const y = margin + (i % valuesPerColumn) * yStep + radius;
circle(x, y, radius);
}

return ctx.canvas;
}
Insert cell
// Not the official ordering, but this order makes it more intuitive while reading
b64 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+=".split(
""
)
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