Skip to content

Commit

Permalink
feat(option): Intent to ship boost.useCssRule
Browse files Browse the repository at this point in the history
Implement boost.useCssRule, which avoid setting element's
style as inlined.

Ref naver#2716
  • Loading branch information
netil committed Jun 14, 2022
1 parent 80cc3b7 commit 3bdb124
Show file tree
Hide file tree
Showing 13 changed files with 170 additions and 18 deletions.
22 changes: 22 additions & 0 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5078,6 +5078,28 @@ d3.select(".chart_area")
}
},
},
Boost: {
useCssRule: {
description: "Each data point's color styles are set by CSS rule, not as inlined styles.<br>*Checkout from devtools.",
options: {
boost: {
useCssRule: true
},
data: {
columns: [
["data1", 30, 31.8, 33, 33.8, 34.4, 34.8, 35, 35, 34.6, 34.4, 34.2, 33.8, 33.4, 33.4, 33.6, 33.6, 33.8, 34, 34.2, 34, 34, 34, 34, 34, 34, 33.6, 33, 32.4, 31.8, 31.2, 30.8, 30.8, 31, 31.2, 31.4, 31.8, 32, 32, 32.2, 32.4, 32.4, 32.2, 32, 31.6, 31.2, 31, 31, 31.4, 31.8, 32.2, 32.6, 33, 33, 31.2],
["data2", 32.8, 32.6, 32.4, 32.2, 31.8, 31.6, 31.4, 31.2, 31, 31, 31.2, 31.4, 31.6, 31.8, 32, 32, 32, 32.2, 32.2, 32.4, 32.4, 32.4, 32.2, 32.2, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32.2, 32.4, 32.4, 32.4, 32.4, 32.2, 32, 32, 32, 32.2, 32.2, 32.2, 32, 31.8, 31.4, 31.2, 31, 31, 31.2, 31.4],
["data3", 31, 31, 30.8, 31, 31.6, 32.2, 32.6, 33, 32.8, 32.4, 32, 32, 31.8, 31.8, 31.8, 31.6, 31, 30.8, 30.8, 31, 31.6, 32.4, 33, 33.4, 33.8, 34, 34, 34, 33.8, 33.6, 33.4, 33.2, 33, 33, 33, 32.8, 32.6, 32.4, 32.2, 32, 32, 31.8, 31.6, 31.4, 31.2, 30.8, 30.6, 30.4, 30.2, 30, 30, 30, 30, 30],
["data4", 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19.1, 19.1, 19.1, 19.4, 19.6, 19.6, 19.8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10.1, 10.6, 11.1, 11, 11, 14, 14.8, 15.4, 15.8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]
],
type: "line",
types: {
data4: "bar"
}
},
}
}
},
API: {
AxisLabel: {
options: {
Expand Down
5 changes: 4 additions & 1 deletion src/Chart/api/chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default {
*/
destroy(): null {
const $$ = this.internal;
const {$el: {chart, svg}} = $$;
const {$el: {chart, style, svg}} = $$;

if (notEmpty($$)) {
$$.callPluginHook("$willDestroy");
Expand All @@ -119,6 +119,9 @@ export default {
.selectChildren()
.remove();

// remove <style> element added by boost.useCssRule option
style && style.parentNode.removeChild(style);

// releasing own references
Object.keys(this).forEach(key => {
key === "internal" && Object.keys($$).forEach(k => {
Expand Down
37 changes: 34 additions & 3 deletions src/ChartInternal/ChartInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,33 @@ export default class ChartInternal {
init(): void {
const $$ = <any> this;
const {config, state, $el} = $$;
const useCssRule = config.boost_useCssRule;

checkModuleImport($$);

state.hasAxis = !$$.hasArcType();
state.hasRadar = !state.hasAxis && $$.hasType("radar");

// datetime to be used for uniqueness
state.datetimeId = `bb-${+new Date() * (getRandom() as number)}`;

if (useCssRule) {
// append style element
const styleEl = document.createElement("style");

// styleEl.id = styleId;
styleEl.type = "text/css";
document.head.appendChild(styleEl);

state.style = {
rootSelctor: `.${state.datetimeId}`,
sheet: styleEl.sheet
};

// used on .destroy()
$el.style = styleEl;
}

// when 'padding=false' is set, disable axes and subchart. Because they are useless.
if (config.padding === false) {
config.axis_x_show = false;
Expand Down Expand Up @@ -211,6 +232,7 @@ export default class ChartInternal {

$el.chart.html("")
.classed(bindto.classname, true)
.classed(state.datetimeId, useCssRule)
.style("position", "relative");

$$.initToRender();
Expand Down Expand Up @@ -253,13 +275,22 @@ export default class ChartInternal {
const $$ = <any> this;
const {config, format, state} = $$;
const isRotated = config.axis_rotated;
const useCssRule = config.boost_useCssRule;

// datetime to be used for uniqueness
state.datetimeId = `bb-${+new Date() * (getRandom() as number)}`;

// color settings
$$.color = $$.generateColor();
$$.colorByRule = $$.color;
$$.colorTextByRule = $$.updateTextColor.bind($$);
$$.levelColor = $$.generateLevelColor();

if (useCssRule) {
state.colorRule = {};

// to not apply inline color setting
$$.colorByRule = null;
$$.colorTextByRule = null;
}

if ($$.hasPointType()) {
$$.point = $$.generatePoint();
}
Expand Down
27 changes: 26 additions & 1 deletion src/ChartInternal/internals/color.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {scaleOrdinal as d3ScaleOrdinal} from "d3-scale";
import {document, window} from "../../module/browser";
import {$ARC, $COLOR, $SHAPE} from "../../config/classes";
import {KEY} from "../../module/Cache";
import {notEmpty, isFunction, isObject, isString} from "../../module/util";
import {addCssRules, notEmpty, isFunction, isObject, isString} from "../../module/util";
import {IArcData, IDataRow} from "../data/IData";
import {d3Selection} from "../../../types";

/**
* Set pattern's background color
Expand Down Expand Up @@ -40,6 +41,30 @@ const colorizePattern = (pattern, color, id: string) => {
const schemeCategory10 = ["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"];

export default {
/**
* Add props color css rule to given selector
* @param {Function} fn Color function
* @param {string} selector CSS selector
* @param {Array} props CSS props list
* @returns {Function}
* @private
*/
setColorByRule(fn: Function, selector: string, props: string[]): Function {
const $$ = this;
const {config, state: {colorRule, style}} = $$;
const colorFn = fn || $$.color;

return config.boost_useCssRule ? (selection: d3Selection) => {
selection.each((d: IDataRow) => {
const color = colorFn.call($$, d);
const shapeSelector = `${$SHAPE.shapes}${$$.getTargetSelectorSuffix(d.id)} .${selector}`;

(shapeSelector in colorRule) && style.sheet.deleteRule(colorRule[shapeSelector]);
$$.state.colorRule[shapeSelector] = addCssRules(style, shapeSelector, props.map(v => `${v}: ${color}`));
});
} : () => {};
},

/**
* Get color pattern from CSS file
* CSS should be defined as: background-image: url("#00c73c;#fa7171; ...");
Expand Down
5 changes: 3 additions & 2 deletions src/ChartInternal/internals/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export default {
const mainTextEnter = mainTextUpdate.enter().append("g")
.style("opacity", "0")
.attr("class", classChartText)
.call($$.setColorByRule($$.updateTextColor, $TEXT.text, ["fill"]))
.style("pointer-events", "none");

mainTextEnter.append("g")
Expand Down Expand Up @@ -166,7 +167,7 @@ export default {

return (config.axis_rotated ? (isEndAnchor ? "end" : "start") : "middle");
})
.style("fill", $$.updateTextColor.bind($$))
.style("fill", $$.colorTextByRule)
.style("fill-opacity", "0")
.each(function(d, i, texts) {
const node = d3Select(this);
Expand Down Expand Up @@ -266,7 +267,7 @@ export default {
const rotateString = angle ? `rotate(${angle})` : "";

$$.$el.text
.style("fill", $$.updateTextColor.bind($$))
.style("fill", $$.colorTextByRule)
.attr("filter", $$.updateTextBacgroundColor.bind($$))
.style("fill-opacity", forFlow ? 0 : $$.opacityForText.bind($$))
.each(function(d: IDataRow, i: number) {
Expand Down
7 changes: 4 additions & 3 deletions src/ChartInternal/shape/bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export default {
// Bars for each data
mainBarEnter.append("g")
.attr("class", classBars)
.style("cursor", d => (isSelectable?.bind?.($$.api)(d) ? "pointer" : null));
.style("cursor", d => (isSelectable?.bind?.($$.api)(d) ? "pointer" : null))
.call($$.setColorByRule(null, $BAR.bar, ["fill"]));
},

/**
Expand All @@ -83,7 +84,7 @@ export default {

$root.bar = bar.enter().append("path")
.attr("class", classBar)
.style("fill", $$.color)
.style("fill", $$.colorByRule)
.merge(bar)
.style("opacity", initialOpacity);
},
Expand All @@ -102,7 +103,7 @@ export default {
return [
$$.$T(bar, withTransition, getRandom())
.attr("d", d => (isNumber(d.value) || $$.isBarRangeType(d)) && drawFn(d))
.style("fill", $$.color)
.style("fill", $$.colorByRule)
.style("opacity", null)
];
},
Expand Down
20 changes: 12 additions & 8 deletions src/ChartInternal/shape/point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export default {
enterNode.append("g")
.attr("class", classCircles)
.style("cursor", d => (isFunction(isSelectable) && isSelectable(d) ? "pointer" : null))
.call($$.setColorByRule(null, $CIRCLE.circle, ["fill", "stroke"]))
.style("opacity", function() {
const parent = d3Select(this.parentNode);

Expand Down Expand Up @@ -138,10 +139,10 @@ export default {

circles.enter()
.filter(Boolean)
.append($$.point("create", this, $$.pointR.bind($$), $$.color));
.append($$.point("create", this, $$.pointR.bind($$), $$.colorByRule));

$root.circle = $root.main.selectAll(`.${$CIRCLE.circles} .${$CIRCLE.circle}`)
.style("stroke", $$.color)
.style("stroke", $$.colorByRule)
.style("opacity", $$.initialOpacityForCircle.bind($$));
}
},
Expand All @@ -156,10 +157,10 @@ export default {
return [];
}

const fn = $$.point("update", $$, cx, cy, $$.color, withTransition, flow, selectedCircles);
const fn = $$.point("update", $$, cx, cy, $$.colorByRule, withTransition, flow, selectedCircles);
const posAttr = $$.isCirclePoint() ? "c" : "";

const t: any = getRandom();
const t = getRandom();
const opacityStyleFn = $$.opacityForCircle.bind($$);
const mainCircles: any[] = [];

Expand Down Expand Up @@ -194,7 +195,7 @@ export default {
const cx = (hasRadar ? $$.radarCircleX : $$.circleX).bind($$);
const cy = (hasRadar ? $$.radarCircleY : $$.circleY).bind($$);
const withTransition = toggling || isUndefined(d);
const fn = $$.point("update", $$, cx, cy, $$.color, resizing ? false : withTransition);
const fn = $$.point("update", $$, cx, cy, $$.colorByRule, resizing ? false : withTransition);

if (d) {
circle = circle
Expand Down Expand Up @@ -297,8 +298,11 @@ export default {

circles.attr("r", r);

!$$.isCirclePoint() &&
circles.attr("transform", `scale(${r(circles) / $$.config.point_r})`);
if (!$$.isCirclePoint()) {
const scale = r(circles) / $$.config.point_r;

circles.attr("transform", scale !== 1 ? `scale(${scale})` : null);
}
},

pointR(d): number {
Expand Down Expand Up @@ -485,7 +489,7 @@ export default {
},

custom: {
create(element, id, sizeFn, fillStyleFn) {
create(element, id, fillStyleFn) {
return element.append("use")
.attr("xlink:href", `#${id}`)
.attr("class", this.updatePointClass.bind(this))
Expand Down
2 changes: 2 additions & 0 deletions src/config/Options/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/
// common
import main from "./common/main";
import boost from "./common/boost";
import data from "./data/data";
import color from "./common/color";
import interaction from "./interaction/interaction";
Expand All @@ -30,6 +31,7 @@ export default class Options {
constructor() {
return deepClone(
main,
boost,
data,
color,
interaction,
Expand Down
25 changes: 25 additions & 0 deletions src/config/Options/common/boost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2017 ~ present NAVER Corp.
* billboard.js project is licensed under the MIT license
*/
/**
* boost config options
*/
export default {
/**
* Set boost options
* @name boost
* @memberof Options
* @type {object}
* @property {object} boost boost object
* @property {boolean} [boost.useCssRule=false] Avoid setting inline styles for each shape elements.
* - **NOTE:**
* - Will append &lt;style> to the head tag and will add shpes' CSS rules dynamically.
* - For now, covers colors related properties (fill, stroke, etc.) only.
* @example
* boost: {
* useCssRule: true
* }
*/
boost_useCssRule: false
};
3 changes: 3 additions & 0 deletions src/config/Store/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export default class State {
hasAxis: false,
hasRadar: false,

// for data CSS rule index (used when boost.useCssRule is true)
colorRule: {},

current: {
// chart whole dimension
width: 0,
Expand Down
24 changes: 24 additions & 0 deletions src/module/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {d3Selection} from "../../types/types";
import {document, window} from "./browser";

export {
addCssRules,
asHalfPixel,
brushEmpty,
callFn,
Expand Down Expand Up @@ -254,6 +255,7 @@ function getPathBox(
};
}


/**
* Get event's current position coordinates
* @param {object} event Event object
Expand Down Expand Up @@ -457,6 +459,28 @@ function camelize(str: string, separator = "-"): string {
*/
const toArray = (v: CSSStyleDeclaration | any): any => [].slice.call(v);

/**
* Add CSS rules
* @param {object} style Style object
* @param {string} selector Selector string
* @param {Array} prop Prps arrary
* @returns {number} Newely added rule index
* @private
*/
function addCssRules(style, selector: string, prop: string[]): number {
const {rootSelctor, sheet} = style;
const getSelector = s => s
.replace(/\s?(bb-)/g, ".$1")
.replace(/\.+/g, ".");

const rule = `${rootSelctor} ${getSelector(selector)} {${prop.join(";")}}`;

return sheet[sheet.insertRule ? "insertRule" : "addRule"](
rule,
sheet.cssRules.length
);
}

/**
* Get css rules for specified stylesheets
* @param {Array} styleSheets The stylesheets to get the rules from
Expand Down
1 change: 1 addition & 0 deletions test/assets/module/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {window} from "../../src/module/browser";
import * as orgUtil from "../../test/assets/module/fake";

export const {
addCssRules,
asHalfPixel,
brushEmpty,
callFn,
Expand Down
10 changes: 10 additions & 0 deletions types/options.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ export interface ChartOptions {
imgUrl?: string;
};

boost?: {
/**
* Avoid setting inline styles for each shape elements.
* - **NOTE:**
* - Will append <style> to the head tag and will add shpes' CSS rules dynamically.
* - For now, covers colors related properties (fill, stroke, etc.) only.
*/
useCssRule?: boolean;
};

size?: {
/**
* The desired width of the chart element.
Expand Down

0 comments on commit 3bdb124

Please sign in to comment.