This repository has been archived by the owner on Dec 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 271
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: optimize functions for getting text dimension (#199)
* feat: add function for getting multiple text dimensions * feat: lazy deletion * feat: use lazy factory * fix: comments * fix: rename variable
- Loading branch information
Showing
17 changed files
with
646 additions
and
80 deletions.
There are no files selected for viewing
66 changes: 66 additions & 0 deletions
66
packages/superset-ui-dimension/src/getMultipleTextDimensions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { TextStyle, Dimension } from './types'; | ||
import getBBoxCeil from './svg/getBBoxCeil'; | ||
import { hiddenSvgFactory, textFactory } from './svg/factories'; | ||
import updateTextNode from './svg/updateTextNode'; | ||
|
||
/** | ||
* get dimensions of multiple texts with same style | ||
* @param input | ||
* @param defaultDimension | ||
*/ | ||
export default function getMultipleTextDimensions( | ||
input: { | ||
className?: string; | ||
container?: HTMLElement; | ||
style?: TextStyle; | ||
texts: string[]; | ||
}, | ||
defaultDimension?: Dimension, | ||
): Dimension[] { | ||
const { texts, className, style, container } = input; | ||
|
||
const cache = new Map<string, Dimension>(); | ||
// for empty string | ||
cache.set('', { height: 0, width: 0 }); | ||
let textNode: SVGTextElement | undefined; | ||
let svgNode: SVGSVGElement | undefined; | ||
|
||
const dimensions = texts.map(text => { | ||
// Check if this string has been computed already | ||
if (cache.has(text)) { | ||
return cache.get(text) as Dimension; | ||
} | ||
|
||
// Lazy creation of text and svg nodes | ||
if (!textNode) { | ||
svgNode = hiddenSvgFactory.createInContainer(container); | ||
textNode = textFactory.createInContainer(svgNode); | ||
} | ||
|
||
// Update text and get dimension | ||
updateTextNode(textNode, { className, style, text }); | ||
const dimension = getBBoxCeil(textNode, defaultDimension); | ||
// Store result to cache | ||
cache.set(text, dimension); | ||
|
||
return dimension; | ||
}); | ||
|
||
// Remove svg node, if any | ||
if (svgNode && textNode) { | ||
// The nodes are added to the DOM briefly only to make getBBox works. | ||
// (If not added to DOM getBBox will always return 0x0.) | ||
// After that the svg nodes are not needed. | ||
// We delay its removal in case there are subsequent calls to this function | ||
// that can reuse the svg nodes. | ||
// Experiments have shown that reusing existing nodes | ||
// instead of deleting and adding new ones can save lot of time. | ||
setTimeout(() => { | ||
textFactory.removeFromContainer(svgNode); | ||
hiddenSvgFactory.removeFromContainer(container); | ||
// eslint-disable-next-line no-magic-numbers | ||
}, 500); | ||
} | ||
|
||
return dimensions; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
export default class LazyFactory<T extends HTMLElement | SVGElement> { | ||
private activeNodes = new Map< | ||
HTMLElement | SVGElement, | ||
{ | ||
counter: number; | ||
node: T; | ||
} | ||
>(); | ||
|
||
private factoryFn: () => T; | ||
|
||
constructor(factoryFn: () => T) { | ||
this.factoryFn = factoryFn; | ||
} | ||
|
||
createInContainer(container: HTMLElement | SVGElement = document.body) { | ||
if (this.activeNodes.has(container)) { | ||
const entry = this.activeNodes.get(container)!; | ||
entry.counter += 1; | ||
|
||
return entry.node; | ||
} | ||
|
||
const node = this.factoryFn(); | ||
container.appendChild(node); | ||
this.activeNodes.set(container, { counter: 1, node }); | ||
|
||
return node; | ||
} | ||
|
||
removeFromContainer(container: HTMLElement | SVGElement = document.body) { | ||
if (this.activeNodes.has(container)) { | ||
const entry = this.activeNodes.get(container)!; | ||
entry.counter -= 1; | ||
if (entry.counter === 0) { | ||
container.removeChild(entry.node); | ||
this.activeNodes.delete(container); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// eslint-disable-next-line import/prefer-default-export | ||
export const SVG_NS = 'http://www.w3.org/2000/svg'; |
10 changes: 10 additions & 0 deletions
10
packages/superset-ui-dimension/src/svg/createHiddenSvgNode.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { SVG_NS } from './constants'; | ||
|
||
export default function createHiddenSvgNode() { | ||
const svgNode = document.createElementNS(SVG_NS, 'svg'); | ||
svgNode.style.position = 'absolute'; // so it won't disrupt page layout | ||
svgNode.style.opacity = '0'; // and not visible | ||
svgNode.style.pointerEvents = 'none'; // and not capturing mouse events | ||
|
||
return svgNode; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { SVG_NS } from './constants'; | ||
|
||
export default function createTextNode() { | ||
return document.createElementNS(SVG_NS, 'text'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import LazyFactory from './LazyFactory'; | ||
import createHiddenSvgNode from './createHiddenSvgNode'; | ||
import createTextNode from './createTextNode'; | ||
|
||
export const hiddenSvgFactory = new LazyFactory(createHiddenSvgNode); | ||
export const textFactory = new LazyFactory(createTextNode); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Dimension } from '../types'; | ||
|
||
const DEFAULT_DIMENSION = { height: 20, width: 100 }; | ||
|
||
export default function getBBoxCeil( | ||
node: SVGGraphicsElement, | ||
defaultDimension: Dimension = DEFAULT_DIMENSION, | ||
) { | ||
const { width, height } = node.getBBox ? node.getBBox() : defaultDimension; | ||
|
||
return { | ||
height: Math.ceil(height), | ||
width: Math.ceil(width), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { TextStyle } from '../types'; | ||
|
||
const STYLE_FIELDS: (keyof TextStyle)[] = [ | ||
'font', | ||
'fontWeight', | ||
'fontStyle', | ||
'fontSize', | ||
'fontFamily', | ||
'letterSpacing', | ||
]; | ||
|
||
export default function updateTextNode( | ||
node: SVGTextElement, | ||
{ | ||
className, | ||
style = {}, | ||
text, | ||
}: { | ||
className?: string; | ||
style?: TextStyle; | ||
text?: string; | ||
} = {}, | ||
) { | ||
const textNode = node; | ||
|
||
if (textNode.textContent !== text) { | ||
textNode.textContent = typeof text === 'undefined' ? null : text; | ||
} | ||
if (textNode.getAttribute('class') !== className) { | ||
textNode.setAttribute('class', className || ''); | ||
} | ||
|
||
// clear style | ||
STYLE_FIELDS.forEach((field: keyof TextStyle) => { | ||
textNode.style[field] = null; | ||
}); | ||
|
||
// apply new style | ||
// Note that the font field will auto-populate other font fields when applicable. | ||
STYLE_FIELDS.filter( | ||
(field: keyof TextStyle) => typeof style[field] !== 'undefined' && style[field] !== null, | ||
).forEach((field: keyof TextStyle) => { | ||
textNode.style[field] = `${style[field]}`; | ||
}); | ||
|
||
return textNode; | ||
} |
16 changes: 3 additions & 13 deletions
16
packages/superset-ui-dimension/test/computeMaxFontSize.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.