Public
Edited
Jan 2, 2023
1 fork
Insert cell
md`# Weight Chart`
Insert cell
renderWeightChart()
Insert cell
d3 = require('d3')
Insert cell
import { range } from '@nuuuwan/list-utils'
Insert cell
import { getSVG, drawLine, drawText } from '@nuuuwan/svg-utils'
Insert cell
import { DAY_LIST, MONTH_LIST } from '@nuuuwan/time-utils'
Insert cell
CHART_WIDTH = 1000
Insert cell
CHART_HEIGHT = 773
Insert cell
PADDING = 72
Insert cell
MIN_WEIGHT = CONFIG[PERSON_NAME].MIN_WEIGHT
Insert cell
WEIGHT_RANGE = 8
Insert cell
WEIGHT_INCR = 0.2
Insert cell
TIME_RANGE_DAYS = 4
Insert cell
TIME_INCR = 1 / 7
Insert cell
LBS_IN_KG = 2.20462
Insert cell
HEIGHT_M = CONFIG[PERSON_NAME].HEIGHT_M
Insert cell
PERSON_NAME = "Kanchana"
Insert cell
CONFIG = {
const config = {
Nuwan: {
HEIGHT_M: 1.93,
MIN_WEIGHT: 84
},
Kanchana: {
HEIGHT: 1.524,
MIN_WEIGHT: 58
}
};
return config;
}
Insert cell
function renderWeightChart() {
const svg = getSVG({ width: CHART_WIDTH, height: CHART_HEIGHT });
const INNER_WIDTH = CHART_WIDTH - PADDING * 2;
const INNER_HEIGHT = CHART_HEIGHT - PADDING * 2;
const MAX_WEIGHT = MIN_WEIGHT + WEIGHT_RANGE;
const TICK_DIM = 12;
const STROKE_WIDTH_NORMAL = 0.5;
const STROKE_WIDTH_THICK = 1;

const FONT_SIZE_NORMAL = 24;
const FONT_SIZE_SMALL = 12;
const FONT_SIZE_LARGE = 36;

const MIN_WEIGHT_LB = MIN_WEIGHT * LBS_IN_KG;
const WEIGHT_RANGE_LB = WEIGHT_RANGE * LBS_IN_KG;
const WEIGHT_RANGE_BMI = WEIGHT_RANGE / (HEIGHT_M * HEIGHT_M);

const nIncrWeight = parseInt(WEIGHT_RANGE / WEIGHT_INCR);
const nDarkWeight = parseInt(1 / WEIGHT_INCR);
let weightLbSet = new Set();
let bmiSet = new Set();
range(0, nIncrWeight + 1).forEach(function(i) {
const [x1, x2] = [PADDING - TICK_DIM, PADDING + INNER_WIDTH + TICK_DIM];
const y = PADDING + (INNER_HEIGHT * i) / nIncrWeight;
const isDark = i % nDarkWeight === 0;

const strokeWidth = isDark ? STROKE_WIDTH_THICK : STROKE_WIDTH_NORMAL;
drawLine(svg, [x1, y], [x2, y], { stroke: 'black', strokeWidth });

if (isDark) {
const weight = MAX_WEIGHT - i * WEIGHT_INCR;
drawText(svg, [PADDING * 0.25, y], weight, {
textAnchor: 'start',
fill: 'black',
fontSize: FONT_SIZE_LARGE
});

const weightLbActual = weight * LBS_IN_KG;
const weightLb = parseInt(weightLbActual / 5) * 5;
if (!weightLbSet.has(weightLb)) {
const y0 =
y + (INNER_HEIGHT * (weightLbActual - weightLb)) / WEIGHT_RANGE_LB;
drawText(svg, [PADDING * 0.01, y0], weightLb, {
textAnchor: 'start',
fill: 'black',
fontSize: FONT_SIZE_SMALL
});
weightLbSet.add(weightLb);
}

const bmiActual = weight / (HEIGHT_M * HEIGHT_M);
const bmi = parseInt(bmiActual / 0.5) * 0.5;
if (!bmiSet.has(weightLb)) {
const y0 = y + (INNER_HEIGHT * (bmiActual - bmi)) / WEIGHT_RANGE_BMI;
drawText(
svg,
[INNER_WIDTH + PADDING + PADDING / 2 + TICK_DIM, y0],
d3.format('.1f')(bmi),
{
textAnchor: 'end',
fill: 'black',
fontSize: FONT_SIZE_NORMAL
}
);
weightLbSet.add(weightLb);
}
}
});

const nIncrTime = parseInt(TIME_RANGE_DAYS / TIME_INCR);
const nDarkTime = parseInt(1 / TIME_INCR);
const dateNow = new Date();
const startTimeUnixTime =
parseInt(dateNow.getTime() / (7 * 86400 * 1000)) * 7 * 86400 * 1000 +
3 * 86400 * 1000;

range(0, nIncrTime + 1).forEach(function(i) {
const [y1, y2] = [PADDING - TICK_DIM, PADDING + INNER_HEIGHT + TICK_DIM];
const x = PADDING + (INNER_WIDTH * i) / nIncrTime;
const isDark = i % nDarkTime === 0;

const strokeWidth = isDark ? STROKE_WIDTH_THICK : STROKE_WIDTH_NORMAL;
drawLine(svg, [x, y1], [x, y2], { stroke: 'black', strokeWidth });

drawText(svg, [x, PADDING * 0.75], DAY_LIST[i % 7].substring(0, 1), {
textAnchor: 'middle',
fill: 'black',
fontSize: FONT_SIZE_SMALL
});

if (isDark) {
const date = new Date(startTimeUnixTime + i * 86400 * 1000);
const dateStr = `${MONTH_LIST[date.getMonth()].substring(
0,
3
)} ${date.getDate()}`;
drawText(svg, [x, PADDING * 0.25], dateStr, {
textAnchor: 'middle',
fill: 'black',
fontSize: FONT_SIZE_NORMAL
});
}
});

drawText(
svg,
[CHART_WIDTH / 2, CHART_HEIGHT - PADDING / 2],
`${PERSON_NAME}'s Weight Chart`,
{
textAnchor: 'middle',
fill: 'black'
}
);

return svg.node();
}
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