Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/8055_change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Memoize color specifier parsing, reducing the time to draw marker-heavy SVG `scatter` traces by roughly a third [[#8055](https://github.com/plotly/plotly.js/pull/8055)]
37 changes: 35 additions & 2 deletions src/components/color/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,14 +271,46 @@ const contrast = (cstr, lightAmount, darkAmount) => {
}
};

// `stroke` and `fill` below run once per data point, and every point of a trace
// normally repeats the same specifier, so re-deriving the styles each time is
// pure overhead. Cache both values a specifier yields, keyed on the specifier
// itself, and take them from a single `parse` so that a miss costs no more than
// it has to. Only strings are cached, since they are the only specifiers that
// repeat by value rather than by identity.
const MAX_MEMO_SIZE = 1000;

const styleCache = new Map();

const computeStyle = (cstr) => {
const c = parse(cstr);
// Force alpha to 1 in the color so that it gets dropped from the string.
return [formatRgb({ ...c, alpha: 1 }), c.alpha];
};

const styleOf = (cstr) => {
if (typeof cstr !== 'string') return computeStyle(cstr);

let value = styleCache.get(cstr);
if (value === undefined) {
value = computeStyle(cstr);
// Stop growing rather than evicting: a graph only ever uses a handful
// of distinct colors, so a full cache means array-valued colors, which
// repeat too little to be worth tracking.
if (styleCache.size < MAX_MEMO_SIZE) styleCache.set(cstr, value);
}

return value;
};

/**
* Apply `stroke` and `stroke-opacity` styles to a D3 selection.
*
* @param {Selection} s - D3 selection
* @param {*} cstr - Color specifier
*/
const stroke = (s, cstr) => {
s.style({ stroke: rgb(cstr), 'stroke-opacity': parse(cstr).alpha });
const style = styleOf(cstr);
s.style({ stroke: style[0], 'stroke-opacity': style[1] });
};

/**
Expand All @@ -288,7 +320,8 @@ const stroke = (s, cstr) => {
* @param {*} cstr - Color specifier
*/
const fill = (s, cstr) => {
s.style({ fill: rgb(cstr), 'fill-opacity': parse(cstr).alpha });
const style = styleOf(cstr);
s.style({ fill: style[0], 'fill-opacity': style[1] });
};

/**
Expand Down
Loading