Skip to content

Commit

Permalink
Merge pull request #999 from ecomfe/ssr
Browse files Browse the repository at this point in the history
feat(ssr): add emphasis style in ssr css apache/echarts#18334
  • Loading branch information
Ovilia authored Nov 16, 2023
2 parents 401e314 + a78b088 commit 78f84fd
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 9 deletions.
16 changes: 16 additions & 0 deletions src/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,22 @@ class Element<Props extends ElementProps = ElementProps> {
}
}

/**
* Return if el.silent or any ancestor element has silent true.
*/
isSilent() {
let isSilent = this.silent;
let ancestor = this.parent;
while (!isSilent && ancestor) {
if (ancestor.silent) {
isSilent = true;
break;
}
ancestor = ancestor.parent;
}
return isSilent;
}

/**
* Update animation targets when reference is changed.
*/
Expand Down
16 changes: 12 additions & 4 deletions src/svg/Painter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,11 @@ class SVGPainter implements PainterBase {
}

renderToVNode(opts?: {
animation?: boolean
willUpdate?: boolean
animation?: boolean,
willUpdate?: boolean,
compress?: boolean,
useViewBox?: boolean
useViewBox?: boolean,
emphasis?: boolean
}) {

opts = opts || {};
Expand All @@ -140,6 +141,7 @@ class SVGPainter implements PainterBase {
scope.animation = opts.animation;
scope.willUpdate = opts.willUpdate;
scope.compress = opts.compress;
scope.emphasis = opts.emphasis;

const children: SVGVNode[] = [];

Expand Down Expand Up @@ -173,7 +175,12 @@ class SVGPainter implements PainterBase {
* If add css animation.
* @default true
*/
cssAnimation?: boolean
cssAnimation?: boolean,
/**
* If add css emphasis.
* @default true
*/
cssEmphasis?: boolean,
/**
* If use viewBox
* @default true
Expand All @@ -183,6 +190,7 @@ class SVGPainter implements PainterBase {
opts = opts || {};
return vNodeToString(this.renderToVNode({
animation: retrieve2(opts.cssAnimation, true),
emphasis: retrieve2(opts.cssEmphasis, true),
willUpdate: false,
compress: true,
useViewBox: retrieve2(opts.useViewBox, true)
Expand Down
12 changes: 10 additions & 2 deletions src/svg/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const SVGNS = 'http://www.w3.org/2000/svg';
export const XLINKNS = 'http://www.w3.org/1999/xlink';
export const XMLNS = 'http://www.w3.org/2000/xmlns/';
export const XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace';
export const META_DATA_PREFIX = 'ecmeta_';

export function createElement(name: string) {
return document.createElementNS(SVGNS, name);
Expand Down Expand Up @@ -128,8 +129,11 @@ export interface BrushScope {

cssNodes: Record<string, CSSSelectorVNode>
cssAnims: Record<string, Record<string, Record<string, string>>>
/**
* Cache for css style string, mapping from style string to class name.
*/
cssStyleCache: Record<string, string>

cssClassIdx: number
cssAnimIdx: number

shadowIdx: number
Expand All @@ -141,6 +145,10 @@ export interface BrushScope {
* If create animates nodes.
*/
animation?: boolean,
/**
* If create emphasis styles.
*/
emphasis?: boolean,

/**
* If will update. Some optimization for string generation can't be applied.
Expand All @@ -164,8 +172,8 @@ export function createBrushScope(zrId: string): BrushScope {

cssNodes: {},
cssAnims: {},
cssStyleCache: {},

cssClassIdx: 0,
cssAnimIdx: 0,

shadowIdx: 0,
Expand Down
3 changes: 2 additions & 1 deletion src/svg/cssAnimation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Animator from '../animation/Animator';
import CompoundPath from '../graphic/CompoundPath';
import { AnimationEasing } from '../animation/easing';
import { createCubicEasingFunc } from '../animation/cubicEasing';
import { getClassId } from './cssClassId';

export const EASING_MAP: Record<string, string> = {
// From https://easings.net/
Expand Down Expand Up @@ -355,7 +356,7 @@ export function createCSSAnimation(
}

if (cssAnimations.length) {
const className = scope.zrId + '-cls-' + scope.cssClassIdx++;
const className = scope.zrId + '-cls-' + getClassId();
scope.cssNodes['.' + className] = {
animation: cssAnimations.join(',')
};
Expand Down
5 changes: 5 additions & 0 deletions src/svg/cssClassId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let cssClassIdx = 0;

export function getClassId() {
return cssClassIdx++;
}
73 changes: 73 additions & 0 deletions src/svg/cssEmphasis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import Displayable from '../graphic/Displayable';
import { liftColor } from '../tool/color';
import { BrushScope, SVGVNodeAttrs } from './core';
import { getClassId } from './cssClassId';

export function createCSSEmphasis(
el: Displayable,
attrs: SVGVNodeAttrs,
scope: BrushScope
) {
if (!el.ignore) {
if (el.isSilent()) {
// If el is silent, it can not be hovered nor selected.
// So set pointer-events to pass through.
const style = {
'pointer-events': 'none'
};
setClassAttribute(style, attrs, scope, true);
}
else {
const emphasisStyle = el.states.emphasis && el.states.emphasis.style
? el.states.emphasis.style
: {};
let fill = emphasisStyle.fill;
if (!fill) {
// No empahsis fill, lift color
const normalFill = el.style && el.style.fill;
const selectFill = el.states.select
&& el.states.select.style
&& el.states.select.style.fill;
const fromFill = el.currentStates.indexOf('select') >= 0
? (selectFill || normalFill)
: normalFill;
if (fromFill) {
fill = liftColor(fromFill);
}
}
let lineWidth = emphasisStyle.lineWidth;
if (lineWidth) {
// Symbols use transform to set size, so lineWidth
// should be divided by scaleX
const scaleX = (!emphasisStyle.strokeNoScale && el.transform)
? el.transform[0]
: 1;
lineWidth = lineWidth / scaleX;
}
const style = {
cursor: 'pointer', // TODO: Should this be customized?
} as any;
if (fill) {
style.fill = fill;
}
if (emphasisStyle.stroke) {
style.stroke = emphasisStyle.stroke;
}
if (lineWidth) {
style['stroke-width'] = lineWidth;
}
setClassAttribute(style, attrs, scope, true);
}
}
}

function setClassAttribute(style: object, attrs: SVGVNodeAttrs, scope: BrushScope, withHover: boolean) {
const styleKey = JSON.stringify(style);
let className = scope.cssStyleCache[styleKey];
if (!className) {
className = scope.zrId + '-cls-' + getClassId();
scope.cssStyleCache[styleKey] = className;
scope.cssNodes['.' + className + (withHover ? ':hover' : '')] = style as any;
}
attrs.class = attrs.class ? (attrs.class + ' ' + className) : className;
}
25 changes: 24 additions & 1 deletion src/svg/graphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { getLineHeight } from '../contain/text';
import TSpan, { TSpanStyleProps } from '../graphic/TSpan';
import SVGPathRebuilder from './SVGPathRebuilder';
import mapStyleToAttrs from './mapStyleToAttrs';
import { SVGVNodeAttrs, createVNode, SVGVNode, vNodeToString, BrushScope } from './core';
import { SVGVNodeAttrs, createVNode, SVGVNode, vNodeToString, BrushScope, META_DATA_PREFIX } from './core';
import { MatrixArray } from '../core/matrix';
import Displayable from '../graphic/Displayable';
import { assert, clone, isFunction, isString, logError, map, retrieve2 } from '../core/util';
Expand All @@ -39,6 +39,8 @@ import { ImageLike } from '../core/types';
import { createCSSAnimation } from './cssAnimation';
import { hasSeparateFont, parseFontSize } from '../graphic/Text';
import { DEFAULT_FONT, DEFAULT_FONT_FAMILY } from '../core/platform';
import { createCSSEmphasis } from './cssEmphasis';
import { getElementSSRData } from '../zrender';

const round = Math.round;

Expand All @@ -61,6 +63,10 @@ function setStyleAttrs(attrs: SVGVNodeAttrs, style: AllStyleOption, el: Path | T
else if (isFillStroke && isPattern(val)) {
setPattern(el, attrs, key, scope);
}
else if (isFillStroke && val === 'none') {
// When is none, it cannot be interacted when ssr
attrs[key] = 'transparent';
}
else {
attrs[key] = val;
}
Expand All @@ -69,6 +75,19 @@ function setStyleAttrs(attrs: SVGVNodeAttrs, style: AllStyleOption, el: Path | T
setShadow(el, attrs, scope);
}

function setMetaData(attrs: SVGVNodeAttrs, el: Path | TSpan | ZRImage) {
const metaData = getElementSSRData(el);
if (metaData) {
metaData.each((val, key) => {
attrs[(META_DATA_PREFIX + key).toLowerCase()]
= val + '';
});
if (el.isSilent()) {
attrs[META_DATA_PREFIX + 'silent'] = 'true';
}
}
}

function noRotateScale(m: MatrixArray) {
return isAroundZero(m[0] - 1)
&& isAroundZero(m[1])
Expand Down Expand Up @@ -204,8 +223,10 @@ export function brushSVGPath(el: Path, scope: BrushScope) {

setTransform(attrs, el.transform);
setStyleAttrs(attrs, style, el, scope);
setMetaData(attrs, el);

scope.animation && createCSSAnimation(el, attrs, scope);
scope.emphasis && createCSSEmphasis(el, attrs, scope);

return createVNode(svgElType, el.id + '', attrs);
}
Expand Down Expand Up @@ -248,6 +269,7 @@ export function brushSVGImage(el: ZRImage, scope: BrushScope) {

setTransform(attrs, el.transform);
setStyleAttrs(attrs, style, el, scope);
setMetaData(attrs, el);

scope.animation && createCSSAnimation(el, attrs, scope);

Expand Down Expand Up @@ -319,6 +341,7 @@ export function brushSVGTSpan(el: TSpan, scope: BrushScope) {
}
setTransform(attrs, el.transform);
setStyleAttrs(attrs, style, el, scope);
setMetaData(attrs, el);

scope.animation && createCSSAnimation(el, attrs, scope);

Expand Down
28 changes: 27 additions & 1 deletion src/tool/color.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import LRU from '../core/LRU';
import { extend, isGradientObject, isString, map } from '../core/util';
import { GradientObject } from '../graphic/Gradient';

const kCSSColorTable = {
'transparent': [0, 0, 0, 0], 'aliceblue': [240, 248, 255, 1],
Expand Down Expand Up @@ -566,4 +568,28 @@ export function random(): string {
Math.round(Math.random() * 255),
Math.round(Math.random() * 255)
], 'rgb');
}
}

const liftedColorCache = new LRU<string>(100);
export function liftColor(color: GradientObject): GradientObject;
export function liftColor(color: string): string;
export function liftColor(color: string | GradientObject): string | GradientObject {
if (isString(color)) {
let liftedColor = liftedColorCache.get(color);
if (!liftedColor) {
liftedColor = lift(color, -0.1);
liftedColorCache.put(color, liftedColor);
}
return liftedColor;
}
else if (isGradientObject(color)) {
const ret = extend({}, color) as GradientObject;
ret.colorStops = map(color.colorStops, stop => ({
offset: stop.offset,
color: lift(stop.color, -0.1)
}));
return ret;
}
// Change nothing.
return color;
}
17 changes: 17 additions & 0 deletions src/zrender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,23 @@ export function registerPainter(name: string, Ctor: PainterBaseCtor) {
painterCtors[name] = Ctor;
}

export type ElementSSRData = zrUtil.HashMap<unknown>;
export type ElementSSRDataGetter<T> = (el: Element) => zrUtil.HashMap<T>;

let ssrDataGetter = function (el: Element): ElementSSRData {
return null;
}

export function getElementSSRData(el: Element): ElementSSRData {
if (typeof ssrDataGetter === 'function') {
return ssrDataGetter(el);
}
}

export function registerSSRDataGetter<T>(getter: ElementSSRDataGetter<T>) {
ssrDataGetter = getter;
}

/**
* @type {string}
*/
Expand Down
Loading

0 comments on commit 78f84fd

Please sign in to comment.