Public
Edited
Mar 3
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
TEST_SIZE = 24
Insert cell
RENDER_HEIGHT_FACTOR = 0.8
Insert cell
TIME_LABEL_VERTICAL_GAP = 0.15
Insert cell
// Color selection function
function getChangeIndicatorColor(indicator) {
const direction = indicator?.toLowerCase() || "";
if (direction.includes("increases as worse (red)")) {
return "#BC2B3C";
} else if (direction.includes("increases as better (green)")) {
return "#2A7E41";
}
return "#1C2B34"; // default color
}
Insert cell
// Modified getTypeSizes function to handle change indicator
function getTypeSizes(
width,
height,
primaryText,
secondaryText,
changeIndicator
) {
const MIN_FONT_SIZE = 12;
const fontBins = [
{
primarySize: MIN_FONT_SIZE,
secondaryMaxRatio: 0.9,
ciMaxRatio: 0.9,
timeLabelRatio: 0 // 12px
},
{
primarySize: 18,
secondaryMaxRatio: 0.9,
ciMaxRatio: 0.9,
timeLabelRatio: 0 // 18px
},
{
primarySize: 24,
secondaryMaxRatio: 0.9,
ciMaxRatio: 0.75,
timeLabelRatio: 0 // 24px
},
{
primarySize: 36,
secondaryMaxRatio: 0.75,
ciMaxRatio: 0.6,
timeLabelRatio: 0 // 36px
},
{
primarySize: 42,
secondaryMaxRatio: 0.6,
ciMaxRatio: 0.5,
timeLabelRatio: 0.25 // 42px
},
{
primarySize: 64,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.2 // 64px
},
{
primarySize: 80,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.175 // 80px
},
{
primarySize: 96,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.15 // 96px
},
{
primarySize: 130,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.15 // 130px
},
{
primarySize: 260,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.15 // 260px
},
{
primarySize: 320,
secondaryMaxRatio: 0.5,
ciMaxRatio: 0.4,
timeLabelRatio: 0.125 // 320px
}
];
const binFontSize = (fontSize) => {
const nextLargerIndex = fontBins.findIndex((b) => b.primarySize > fontSize);
if (nextLargerIndex === 0 || Number.isNaN(fontSize)) return fontBins[0];
if (nextLargerIndex === -1) return fontBins[fontBins.length - 1];
return fontBins[nextLargerIndex - 1];
};

const ONE_LINE_MARGIN = 3;
const FONT_FAMILY =
"'NotoSans', 'Lucida Grande', 'Lucida Sans Unicode', sans-serif";

// Get text measurements
const primaryWidth = d3
.select(svg2)
.select("text.pText")
.node()
.getBBox().width;
const secondaryWidth = secondaryText
? d3.select(svg2).select("text.sText").node().getBBox().width
: 0;
const changeIndicatorWidth = changeIndicator
? d3.select(svg2).select("text.ciText").node().getBBox().width
: 0;

// Padding calculations
const pX = Math.min(40, width * 0.2);
const pY = height * 0.3;

// Always use two-line layout when change indicator is present
const layoutTwoLines = changeIndicator ? true : false;

// Calculate sizes for primary + secondary (first line)
const firstLineFactor = Math.min(
(height - pY) / TEST_SIZE / (1.5 * RENDER_HEIGHT_FACTOR + 0.25),
(width - pX) / (primaryWidth + secondaryWidth + ONE_LINE_MARGIN)
);

const binnedSize = binFontSize(firstLineFactor * TEST_SIZE);
const { primarySize, secondaryMaxRatio, ciMaxRatio } = binnedSize;

// Calculate secondary size
const secondaryWidthAvail =
width - pX - ONE_LINE_MARGIN - (primarySize * primaryWidth) / TEST_SIZE;
const secondaryHeightAvail = height - pY;
const secondaryFactor = Math.min(
secondaryWidthAvail / secondaryWidth,
secondaryHeightAvail / (TEST_SIZE * 0.5 * RENDER_HEIGHT_FACTOR)
);
const secondarySize = Math.max(
MIN_FONT_SIZE,
Math.min(primarySize * secondaryMaxRatio, secondaryFactor * TEST_SIZE * 0.5)
);

// Calculate change indicator size
let changeIndicatorSize = 0;
if (changeIndicator) {
const ciWidthAvail = width - pX;
const ciHeightAvail =
height - pY - (RENDER_HEIGHT_FACTOR + 0.25) * primarySize;

const ciFactor = Math.min(
ciWidthAvail / changeIndicatorWidth,
ciHeightAvail / (TEST_SIZE * 0.5 * RENDER_HEIGHT_FACTOR)
);
changeIndicatorSize = Math.max(
MIN_FONT_SIZE,
Math.min(primarySize * ciMaxRatio, ciFactor * TEST_SIZE * 0.5)
// Math.min(primarySize * ciMaxRatio, ciFactor * TEST_SIZE)
);
}

// Add time label size calculation
let timeLabelSize = 0;
const { timeLabelRatio } = binnedSize;
timeLabelSize = primarySize * timeLabelRatio;

return {
primarySize,
secondarySize,
changeIndicatorSize,
timeLabelSize,
numLines: layoutTwoLines ? 2 : 1
};
}
Insert cell
// Updated measurement SVG
svg2 = {
var svg = d3.create("svg").attr("width", 1000).attr("height", 500);

svg
.append("text")
.attr("class", "pText")
.attr("x", 0)
.attr("y", 480)
.attr("font-weight", "bold")
.attr("font-size", TEST_SIZE)
.attr("font-family", "Noto Sans")
.text(primaryText);

svg
.append("text")
.attr("class", "sText")
.attr("x", 0)
.attr("y", 480)
.attr("font-weight", "normal")
.attr("font-size", TEST_SIZE * 0.5)
.attr("font-family", "Noto Sans")
.text(secondaryText);

svg
.append("text")
.attr("class", "ciText")
.attr("x", 0)
.attr("y", 480)
.attr("font-weight", "normal")
.attr("font-size", TEST_SIZE * 0.5)
.attr("font-family", "Noto Sans")
.text(changeIndicator);

return svg.node();
}
Insert cell
// google font
style = html`
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<style>
svg {
font-family: '${"Noto Sans"}', sans-serif;
}
* {
box-sizing: inherit;
}
div {
display: block;
}
.textContainer {
align-items: center;
display: flex;
flex-direction: column;
font-family: NotoSans,Lucida Grande,Lucida Sans Unicode,sans-serif;
height: 100%;
justify-content: center;
overflow: hidden;
position: relative;
white-space: nowrap;
width: 100%;
}
.textContainerWrapped {
align-items: center;
flex-direction: column;
display: flex;
justify-content: center;
line-height: normal;
}
.textContainerNotwrapped {
align-items: baseline;
flex-direction: row;
display: flex;
justify-content: center;
line-height: normal;
}
</style>
`
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