Skip to content

Commit

Permalink
Merge pull request #48 from bprusinowski/fix/async-font-load
Browse files Browse the repository at this point in the history
fix: Async font load
  • Loading branch information
bprusinowski authored Jul 10, 2023
2 parents ace19ed + 9dca5a5 commit 79e523c
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 37 deletions.
46 changes: 46 additions & 0 deletions src/components/Div.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { select } from "d3-selection";

export const prepareDiv = (div: HTMLDivElement): HTMLDivElement => {
return select(div).style("position", "relative").node() as HTMLDivElement;
};

/** Watches font family changes.
*
* Super important as it impacts the text dimensions calculations (and fonts
* are often loaded asynchronously), so we need to wait for them to load.
*/
export const createFontLoadObserver = (
div: HTMLDivElement,
callback: () => void
): ResizeObserver => {
const node = select(div)
.append("div")
.attr("aria-hidden", "true")
.style("z-index", -1)
.style("position", "absolute")
.style("top", 0)
.style("left", 0)
.style("width", "fit-content")
.style("height", "fit-content")
.style("opacity", 0)
.style("pointer-events", "none")
.style("white-space", "nowrap")
.style("overflow", "hidden")
.text("Font load trigger")
.node() as HTMLDivElement;

const observer = new ResizeObserver(callback);
observer.observe(node);

return observer;
};

export const createResizeObserver = (
div: HTMLDivElement,
callback: () => void
): ResizeObserver => {
const observer = new ResizeObserver(callback);
observer.observe(div);

return observer;
};
15 changes: 10 additions & 5 deletions src/components/Svg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type SVGSelection = Selection<
undefined
>;

export const makeSvg = (div: HTMLDivElement, options: StoryOptions): Svg => {
const { svgBackgroundColor, fontFamily } = options;
export const createSvg = (div: HTMLDivElement, options: StoryOptions): Svg => {
const { svgBackgroundColor } = options;
const selection = select(div)
.selectAll("svg")
.data([null])
Expand All @@ -35,7 +35,6 @@ export const makeSvg = (div: HTMLDivElement, options: StoryOptions): Svg => {
.style("transform", "translate3d(0, 0, 0)")
.style("border-left", "3px solid transparent")
.style("background", svgBackgroundColor)
.style("font-family", fontFamily)
.style("transition", "border-left 0.3s ease") as SVGSelection;

const measure = (): DOMRect => {
Expand All @@ -49,8 +48,15 @@ export const makeSvg = (div: HTMLDivElement, options: StoryOptions): Svg => {
): DOMRect => {
const { width } = measure();
const { paddingLeft = 0, paddingRight = 0 } = options ?? {};
const root = select(document.body)
const root = select(div)
.append("div")
.attr("aria-hidden", "true")
.style("z-index", -1)
.style("position", "absolute")
.style("top", 0)
.style("left", 0)
.style("opacity", 0)
.style("pointer-events", "none")
.style("box-sizing", "border-box")
.style("max-width", `${width}px`)
.style("padding-left", `${paddingLeft}px`)
Expand All @@ -62,7 +68,6 @@ export const makeSvg = (div: HTMLDivElement, options: StoryOptions): Svg => {
.style("line-height", 1.5)
.style("font-size", `${FONT_SIZE[textType]}px`)
.style("font-weight", FONT_WEIGHT[textType])
.style("font-family", fontFamily)
.text(text)
.node() as HTMLDivElement;
const rect = node.getBoundingClientRect();
Expand Down
5 changes: 1 addition & 4 deletions src/components/Tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { select } from "d3-selection";
import { StoryOptions } from "../types";

export type Tooltip = ReturnType<typeof makeTooltip>;

export const makeTooltip = (options: StoryOptions) => {
const { fontFamily } = options;
export const makeTooltip = () => {
const root = select(document.body)
.selectAll(".plotteus-tooltip")
.data([null])
Expand All @@ -18,7 +16,6 @@ export const makeTooltip = (options: StoryOptions) => {
.style("padding", "8px")
.style("background", "#fcfcfcea")
.style("font-size", "12px")
.style("font-family", fontFamily)
.style("transform", "translate(-50%, calc(-100% - 16px))")
.style("pointer-events", "none")
.style("box-shadow", "0px 0px 48px 0px rgba(0, 0, 0, 0.15)")
Expand Down
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * as Axis from "./Axis";
export * as AxisTick from "./AxisTick";
export * as ColorLegend from "./ColorLegend";
export * from "./Div";
export * as Step from "./Step";
export * from "./Svg";
export * as Text from "./Text";
Expand Down
58 changes: 42 additions & 16 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { makeSvg, makeTooltip, Step, Svg } from "./components";
import {
Step,
Svg,
createFontLoadObserver,
createResizeObserver,
createSvg,
makeTooltip,
prepareDiv,
} from "./components";
import {
InputStep,
InputStoryOptions,
StoryOptions,
TextTypeDims,
} from "./types";
import {
DEFAULT_FONT_FAMILY,
TextDims,
deriveSubtlerColor,
getDataValues,
getTextDims,
getTextTypeDims,
max,
TextDims,
unique,
} from "./utils";

Expand Down Expand Up @@ -54,7 +61,7 @@ export const info = (inputSteps: InputStep[], svg: Svg): Info => {
* @returns`Story` object.
*/
const makeStory = (
div: HTMLDivElement,
inputDiv: HTMLDivElement,
inputSteps: InputStep[],
inputOptions?: InputStoryOptions
): {
Expand All @@ -71,21 +78,21 @@ const makeStory = (
indicateProgress?: boolean
) => void;
} => {
const { svgBackgroundColor = "#FFFFFF", fontFamily = DEFAULT_FONT_FAMILY } =
inputOptions ?? {};
const { svgBackgroundColor = "#FFFFFF" } = inputOptions ?? {};
const options: StoryOptions = {
svgBackgroundColor,
fontFamily,
};
const svg = makeSvg(div, options);
const tooltip = makeTooltip(options);
const div = prepareDiv(inputDiv);
const svg = createSvg(div, options);
const tooltip = makeTooltip();
const progressBarColor = deriveSubtlerColor(svgBackgroundColor);
const storyInfo = info(inputSteps, svg);
let storyInfo = info(inputSteps, svg);

// Previous key.
let _key: string | null | undefined;
// Previous progress.
let _t = 0;
let _width = 0;
let _height = 0;
let initialFontLoaded = false;

let intsMap: Step.IntsMap | undefined;

Expand All @@ -94,14 +101,33 @@ const makeStory = (
const { width, height } = svg.measure();
prepareStepsIntsMap(width, height);
render();

_width = width;
_height = height;
});
};

new ResizeObserver(() => {
createFontLoadObserver(div, () => {
if (initialFontLoaded) {
storyInfo = info(inputSteps, svg);
prepareStepsIntsMap(_width, _height);
render();
} else {
initialFontLoaded = true;
}
});

createResizeObserver(div, () => {
const { width, height } = svg.measure();
prepareStepsIntsMap(width, height);
render();
}).observe(div);

if (width !== _width || height !== _height) {
prepareStepsIntsMap(width, height);
render();

_width = width;
_height = height;
}
});

const prepareStepsIntsMap = (width: number, height: number): void => {
const getters = Step.getters({
Expand Down
2 changes: 0 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import { PaletteName } from "./colors";
// External.
export type InputStoryOptions = {
svgBackgroundColor?: string;
fontFamily?: string;
};

export type StoryOptions = {
svgBackgroundColor: string;
fontFamily: string;
};

type BaseInputStep = {
Expand Down
5 changes: 1 addition & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ import { HALF_FONT_K } from "./charts/utils";
import { Svg } from "./components";
import { InputStep, State, TextType, TextTypeDims } from "./types";

export const DEFAULT_FONT_FAMILY =
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif';

export const unique = <T>(array: T[]): T[] => {
return Array.from(new Set(array));
};
Expand Down Expand Up @@ -48,7 +45,7 @@ export const FONT_WEIGHT: Record<TextType, number> = {
export const getTextTypeDims = (svg: Svg): TextTypeDims => {
return Object.fromEntries(
Object.entries(FONT_SIZE).map(([textType]) => {
const { height } = svg.measureText("Ag", textType as TextType);
const { height } = svg.measureText("Text", textType as TextType);
return [textType, { height, yShift: -height * HALF_FONT_K }];
})
) as TextTypeDims;
Expand Down
8 changes: 2 additions & 6 deletions tests/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { makeSvg } from "../src/components";
import { createSvg } from "../src/components";
import { Dimensions } from "../src/dims";
import { DEFAULT_FONT_FAMILY } from "../src/utils";

export const setup = () => {
const div = document.createElement("div");
const svg = makeSvg(div, {
svgBackgroundColor: "#FFFFFF",
fontFamily: DEFAULT_FONT_FAMILY,
});
const svg = createSvg(div, { svgBackgroundColor: "#FFFFFF" });
const dims = new Dimensions(800, 550);

return { svg, dims };
Expand Down

0 comments on commit 79e523c

Please sign in to comment.