Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssr): add emphasis style in ssr css apache/echarts#18334 #999

Merged
merged 6 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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