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 3 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
5 changes: 5 additions & 0 deletions src/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ class Element<Props extends ElementProps = ElementProps> {
*/
__inHover: boolean

/**
* Any information to be binded on the element when rendering.
*/
__metaData: Record<string, string | number | boolean>

/**
* path to clip the elements and its children, if it is a group.
* @see http://www.w3.org/TR/2dcontext/#clipping-region
Expand Down
16 changes: 12 additions & 4 deletions src/svg/Painter.ts
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
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
10 changes: 10 additions & 0 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,6 +129,10 @@ 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
Expand All @@ -141,6 +146,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,6 +173,7 @@ export function createBrushScope(zrId: string): BrushScope {

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

cssClassIdx: 0,
cssAnimIdx: 0,
Expand Down
82 changes: 82 additions & 0 deletions src/svg/cssEmphasis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import LRU from '../core/LRU';
import { extend, isGradientObject, isString, map } from '../core/util';
import * as colorTool from '../tool/color';
import Displayable from '../graphic/Displayable';
import { GradientObject } from '../graphic/Gradient';
import { BrushScope, SVGVNodeAttrs } from './core';

// TODO: Consider deleting the same logic in ECharts and call this method?
const liftedColorCache = new LRU<string>(100);
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
function liftColor(color: GradientObject): GradientObject;
function liftColor(color: string): string;
function liftColor(color: string | GradientObject): string | GradientObject {
if (isString(color)) {
let liftedColor = liftedColorCache.get(color);
if (!liftedColor) {
liftedColor = colorTool.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: colorTool.lift(stop.color, -0.1)
}));
return ret;
}
// Change nothing.
return color;
}

export function createCSSEmphasis(
el: Displayable,
attrs: SVGVNodeAttrs,
scope: BrushScope
) {
if (!el.ignore && el.__metaData) {
const empahsisStyle = el.states.emphasis
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
? el.states.emphasis.style
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
: {};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the emphasis.disabled option is not being respected. The chart still highlights the item that the emphasis state is disabled.

let fill = empahsisStyle.fill;
if (!fill) {
// No empahsis fill, lift color
const normalFill = el.style.fill;
const selectFill = el.states.select && el.states.select.style.fill;
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
const fromFill = el.currentStates.indexOf('select') >= 0
? (selectFill || normalFill)
: normalFill;
if (fromFill) {
fill = liftColor(fromFill);
}
}
let lineWidth = empahsisStyle.lineWidth;
if (lineWidth) {
// Symbols use transform to set size, so lineWidth
// should be divided by scaleX
const scaleX = el.transform ? el.transform[0] : 1;
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
lineWidth = lineWidth / scaleX;
}
const style = {
cursor: 'pointer', // TODO: Should be included in el
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
} as any;
if (fill) {
style.fill = fill;
}
if (empahsisStyle.stroke) {
style.stroke = empahsisStyle.stroke;
}
if (lineWidth) {
style['stroke-width'] = lineWidth;
}
const styleKey = JSON.stringify(style);
let className = scope.cssStyleCache[styleKey];
if (!className) {
className = scope.zrId + '-cls-' + scope.cssClassIdx++;
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
scope.cssStyleCache[styleKey] = className;
scope.cssNodes['.' + className + ':hover'] = style;
}
attrs.class = attrs.class ? (attrs.class + ' ' + className) : className;
}
}
15 changes: 14 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,7 @@ 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';

const round = Math.round;

Expand Down Expand Up @@ -69,6 +70,14 @@ function setStyleAttrs(attrs: SVGVNodeAttrs, style: AllStyleOption, el: Path | T
setShadow(el, attrs, scope);
}

function setMetaData(attrs: SVGVNodeAttrs, el: Path | TSpan | ZRImage) {
if (el.__metaData) {
for (const key in el.__metaData) {
attrs[META_DATA_PREFIX + key] = el.__metaData[key] + '';
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
}
Ovilia marked this conversation as resolved.
Show resolved Hide resolved
}
}

function noRotateScale(m: MatrixArray) {
return isAroundZero(m[0] - 1)
&& isAroundZero(m[1])
Expand Down Expand Up @@ -204,8 +213,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 +259,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 +331,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
92 changes: 92 additions & 0 deletions test/svg-meta-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Image</title>
<script src="./lib/config.js"></script>
<script src="../dist/zrender.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div id="main" style="width:1000px;height:800px;"></div>
<script type="text/javascript">
// 初始化zrender
var zr = zrender.init(document.getElementById('main'), {
renderer: window.__ZRENDER__DEFAULT__RENDERER__ || 'svg'
});

var rect = new zrender.Rect({
shape: {
x: 10,
y: 10,
width: 80,
height: 80
},
style: {
fill: 'red'
}
});
rect.__metaData = {
type: 'seriesItem',
seriesIndex: 0,
dataIndex: 0,
styleType: 'itemStyle'
}
zr.add(rect);

setTimeout(() => {
const metaData = {
series: [{
normal: {
itemStyle: {
fill: 'red'
}
},
emphasis: {
itemStyle: {
fill: 'blue'
}
}
}]
}

const svg = zr.painter.getViewportRoot().getElementsByTagName('svg')[0];
const root = svg.children[0];
for (let i = 0; i < root.children.length; i++) {
const child = root.children[i];
const type = child.getAttribute('ecmeta_type');
if (type === 'seriesItem') {
child.addEventListener('mouseover', event => {
const attrs = event.target.attributes;
const seriesIndex = attrs.ecmeta_seriesIndex.value;
const dataIndex = attrs.ecmeta_dataIndex.value;
const styleType = attrs.ecmeta_styleType.value;
const emphasis = metaData.series[seriesIndex].emphasis;
if (emphasis && emphasis[styleType]) {
const style = emphasis[styleType];
for (let key in style) {
attrs[key].value = style[key];
}
}
});
child.addEventListener('mouseout', event => {
const attrs = event.target.attributes;
const seriesIndex = attrs.ecmeta_seriesIndex.value;
const dataIndex = attrs.ecmeta_dataIndex.value;
const styleType = attrs.ecmeta_styleType.value;
const normal = metaData.series[seriesIndex].normal;
if (normal && normal[styleType]) {
const style = normal[styleType];
for (let key in style) {
attrs[key].value = style[key];
}
}
});
}
}
console.log(root)
});
</script>

</body>
</html>