diff --git a/src/chart/bar/install.ts b/src/chart/bar/install.ts index e12314ba6f1caf857a84323b5eb6833bcf194c82..bd01afe008155d675a3d710d230a080d9230b4bc 100644 --- a/src/chart/bar/install.ts +++ b/src/chart/bar/install.ts @@ -36,14 +36,6 @@ export function install(registers: EChartsExtensionInstallRegisters) { // only exist in this module, but probably also exist in other modules, like `barPolar`. registers.registerLayout(registers.PRIORITY.VISUAL.PROGRESSIVE_LAYOUT, largeLayout); - registers.registerVisual({ - seriesType: 'bar', - reset: function (seriesModel) { - // Visual coding for legend - seriesModel.getData().setVisual('legendSymbol', 'roundRect'); - } - }); - // Down sample after filter registers.registerProcessor( registers.PRIORITY.PROCESSOR.STATISTIC, diff --git a/src/chart/boxplot/boxplotVisual.ts b/src/chart/boxplot/boxplotVisual.ts index d8ff69d279d8cea45d7c60bf3053544df88e710f..067738a7b0dfeb0d09b7d779f2303b6e0e204750 100644 --- a/src/chart/boxplot/boxplotVisual.ts +++ b/src/chart/boxplot/boxplotVisual.ts @@ -22,8 +22,5 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import BoxplotSeriesModel from './BoxplotSeries'; export default function boxplotVisual(ecModel: GlobalModel, api: ExtensionAPI) { - ecModel.eachRawSeriesByType('boxplot', function (seriesModel: BoxplotSeriesModel) { - seriesModel.getData().setVisual('legendSymbol', 'roundRect'); - }); } \ No newline at end of file diff --git a/src/chart/candlestick/candlestickVisual.ts b/src/chart/candlestick/candlestickVisual.ts index 103f528aeda22ea01c36b6230acddf8c614e9fbe..306123b1c577b83bcbdd582ebf08f7f37c6c2af1 100644 --- a/src/chart/candlestick/candlestickVisual.ts +++ b/src/chart/candlestick/candlestickVisual.ts @@ -53,8 +53,6 @@ const candlestickVisual: StageHandler = { const data = seriesModel.getData(); - data.setVisual('legendSymbol', 'roundRect'); - // Only visible series has each data be visual encoded if (ecModel.isSeriesFiltered(seriesModel)) { return; diff --git a/src/chart/line/LineSeries.ts b/src/chart/line/LineSeries.ts index f940873046e6c001933d5945ea975c08592fbc3f..bd8ae19196011a0883a578b9e6f738348a9ae5da 100644 --- a/src/chart/line/LineSeries.ts +++ b/src/chart/line/LineSeries.ts @@ -39,6 +39,9 @@ import { import List from '../../data/List'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Polar from '../../coord/polar/Polar'; +import {createSymbol, ECSymbol} from '../../util/symbol'; +import {Group} from '../../util/graphic'; +import {LegendSymbolParams} from '../../component/legend/LegendModel'; type LineDataValue = OptionDataValue | OptionDataValue[]; @@ -125,7 +128,6 @@ class LineSeriesModel extends SeriesModel { coordinateSystem: Cartesian2D | Polar; hasSymbolVisual = true; - legendSymbol = 'line'; getInitialData(option: LineSeriesOption): List { if (__DEV__) { @@ -151,6 +153,11 @@ class LineSeriesModel extends SeriesModel { position: 'top' }, + itemStyle: { + color: 'auto', + borderWidth: 2 + }, + endLabel: { show: false, valueAnimation: true, @@ -204,6 +211,48 @@ class LineSeriesModel extends SeriesModel { progressive: 0, hoverLayerThreshold: Infinity }; + + getLegendIcon(opt: LegendSymbolParams): ECSymbol | Group { + const group = new Group(); + + const line = createSymbol( + 'line', + 0, + opt.itemHeight / 2, + opt.itemWidth, + 0, + opt.lineStyle.stroke, + false + ); + group.add(line); + line.setStyle(opt.lineStyle); + + const visualType = this.getData().getVisual('symbol'); + const symbolType = visualType === 'none' ? 'circle' : visualType; + + // Symbol size is 80% when there is a line + const size = opt.itemHeight * 0.8; + const symbol = createSymbol( + symbolType, + (opt.itemWidth - size) / 2, + (opt.itemHeight - size) / 2, + size, + size, + opt.itemStyle.fill, + opt.symbolKeepAspect + ); + group.add(symbol); + + symbol.setStyle(opt.itemStyle); + + if (symbolType.indexOf('empty') > -1) { + symbol.style.stroke = symbol.style.fill; + symbol.style.fill = '#fff'; + symbol.style.lineWidth = 2; + } + + return group; + } } export default LineSeriesModel; diff --git a/src/chart/line/install.ts b/src/chart/line/install.ts index af11575715ab44c39ca35fcf1c4fde5c26edb9a7..896079d323d5e19c53616b08a8679ccc578020fa 100644 --- a/src/chart/line/install.ts +++ b/src/chart/line/install.ts @@ -19,6 +19,7 @@ import LineSeries from './LineSeries'; import LineView from './LineView'; +import LineSeriesModel from './LineSeries'; // In case developer forget to include grid component @@ -34,6 +35,21 @@ export function install(registers: EChartsExtensionInstallRegisters) { registers.registerLayout(layoutPoints('line', true)); + registers.registerVisual({ + seriesType: 'line', + reset: function (seriesModel: LineSeriesModel) { + const data = seriesModel.getData(); + // Visual coding for legend + const lineStyle = seriesModel.getModel('lineStyle').getLineStyle(); + if (lineStyle && !lineStyle.stroke) { + // Fill in visual should be palette color if + // has color callback + lineStyle.stroke = data.getVisual('style').fill; + } + data.setVisual('legendLineStyle', lineStyle); + } + }); + // Down sample after filter registers.registerProcessor( registers.PRIORITY.PROCESSOR.STATISTIC, diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts index 1532dca3198ba5ae2c6486c2642ea6054d709e37..bd7a3a22095668a9216d0f805897082723035f6d 100644 --- a/src/chart/map/MapSeries.ts +++ b/src/chart/map/MapSeries.ts @@ -40,6 +40,9 @@ import List from '../../data/List'; import Model from '../../model/Model'; import Geo from '../../coord/geo/Geo'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; +import {createSymbol, ECSymbol} from '../../util/symbol'; +import {LegendSymbolParams} from '../../component/legend/LegendModel'; +import {Group} from '../../util/graphic'; export interface MapStateOption { itemStyle?: GeoItemStyleOption @@ -224,6 +227,30 @@ class MapSeries extends SeriesModel { this.option.center = center; } + getLegendIcon(opt: LegendSymbolParams): ECSymbol | Group { + const symbolType = opt.symbolType || 'roundRect'; + const symbol = createSymbol( + symbolType, + 0, + 0, + opt.itemWidth, + opt.itemHeight, + opt.itemStyle.fill, + opt.symbolKeepAspect + ); + + symbol.setStyle(opt.itemStyle); + // Map do not use itemStyle.borderWidth as border width + symbol.style.stroke = 'none'; + + if (symbolType.indexOf('empty') > -1) { + symbol.style.stroke = symbol.style.fill; + symbol.style.fill = '#fff'; + symbol.style.lineWidth = 2; + } + return symbol; + } + static defaultOption: MapSeriesOption = { // 一级层叠 zlevel: 0, diff --git a/src/chart/radar/RadarSeries.ts b/src/chart/radar/RadarSeries.ts index 31c5e4cced1903b9e74394866a9930209f4c4202..f7681959869806ffa73aa4b5ff577b62f9facf91 100644 --- a/src/chart/radar/RadarSeries.ts +++ b/src/chart/radar/RadarSeries.ts @@ -160,8 +160,7 @@ class RadarSeriesModel extends SeriesModel { // areaStyle: { // }, // itemStyle: {} - symbol: 'emptyCircle', - symbolSize: 4 + symbolSize: 8 // symbolRotate: null }; } diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts index 7bca340a3dc81ccf1e1ca944b030e8c8e5fb0d58..c105ffe778a0acb120fa60bdaf45fac97506b33e 100644 --- a/src/chart/tree/TreeSeries.ts +++ b/src/chart/tree/TreeSeries.ts @@ -272,7 +272,7 @@ class TreeSeriesModel extends SeriesModel { itemStyle: { color: 'lightsteelblue', - borderColor: '#c23531', + // borderColor: '#c23531', borderWidth: 1.5 }, diff --git a/src/component/legend/LegendModel.ts b/src/component/legend/LegendModel.ts index 674e4f24e5ba23d70d07c8f46a86a10b435cca5a..ca5e0946686a0b8051b7c7c213c3043d3c4be51f 100644 --- a/src/component/legend/LegendModel.ts +++ b/src/component/legend/LegendModel.ts @@ -26,13 +26,21 @@ import { BoxLayoutOptionMixin, BorderOptionMixin, ColorString, - ItemStyleOption, LabelOption, LayoutOrient, - CommonTooltipOption + CommonTooltipOption, + ZRColor, + DecalObject, + ZRLineType, + ItemStyleOption, + LineStyleOption } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; import GlobalModel from '../../model/Global'; +import { ItemStyleProps } from '../../model/mixin/itemStyle'; +import { LineStyleProps } from './../../model/mixin/lineStyle'; +import {SeriesModel} from '../../echarts'; +import {PathStyleProps} from 'zrender/src/graphic/Path'; type LegendDefaultSelectorOptionsProps = { type: string; @@ -59,7 +67,63 @@ export interface LegendSelectorButtonOption { title?: string } -interface DataItem { +/** + * T: the type to be extended + * ET: extended type for keys of T + * ST: special type for T to be extended + */ +type ExtendPropertyType = { + [key in keyof T]: key extends keyof ST ? T[key] | ET | ST[key] : T[key] | ET +}; + +export interface LegendItemStyleOption extends ExtendPropertyType {} + +export interface LegendLineStyleOption extends ExtendPropertyType { + inactiveColor?: ColorString + inactiveWidth?: number +} + +export interface LegendStyleOption { + /** + * Icon of the legend items. + * @default 'roundRect' + */ + icon?: string + + /** + * Color when legend item is not selected + */ + inactiveColor?: ColorString + /** + * Border color when legend item is not selected + */ + inactiveBorderColor?: ColorString + /** + * Border color when legend item is not selected + */ + inactiveBorderWidth?: number | 'auto' + + /** + * Legend label formatter + */ + formatter?: string | ((name: string) => string) + + itemStyle?: LegendItemStyleOption + + lineStyle?: LegendLineStyleOption + + textStyle?: LabelOption + + symbolKeepAspect?: boolean + + symbolSize?: number | 'auto' | 'inherit' +} + +interface DataItem extends LegendStyleOption { name?: string icon?: string textStyle?: LabelOption @@ -74,7 +138,27 @@ export interface LegendTooltipFormatterParams { name: string $vars: ['name'] } -export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, BorderOptionMixin { + +export interface LegendSymbolParams { + itemWidth: number, + itemHeight: number, + /** + * symbolType is from legend.icon, legend.data.icon, or series visual + */ + symbolType: string, + symbolKeepAspect: boolean, + itemStyle: PathStyleProps, + lineStyle: LineStyleProps +} + +export interface LegendSymbolStyleOption { + itemStyle?: ItemStyleProps, + lineStyle?: LineStyleProps +} + +export interface LegendOption extends ComponentOption, LegendStyleOption, + BoxLayoutOptionMixin, BorderOptionMixin +{ mainType?: 'legend' @@ -97,11 +181,6 @@ export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, Bor * @default 5 */ padding?: number | number[] - /** - * Icon of the legend items. - * @default 'roundRect' - */ - icon?: string /** * Gap between each legend item. * @default 10 @@ -115,23 +194,6 @@ export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, Bor * Height of legend symbol */ itemHeight?: number - /** - * Color when legend item is not selected - */ - inactiveColor?: ColorString - /** - * Border color when legend item is not selected - */ - inactiveBorderColor?: ColorString - - itemStyle?: ItemStyleOption - - /** - * Legend label formatter - */ - formatter?: string | ((name: string) => string) - - textStyle?: LabelOption selectedMode?: boolean | 'single' | 'multiple' /** @@ -169,8 +231,6 @@ export interface LegendOption extends ComponentOption, BoxLayoutOptionMixin, Bor data?: (string | DataItem)[] - symbolKeepAspect?: boolean - /** * Tooltip option */ @@ -399,13 +459,43 @@ class LegendModel extends ComponentMode itemGap: 10, itemWidth: 25, itemHeight: 14, + symbolSize: 'auto', inactiveColor: '#ccc', - inactiveBorderColor: '#ccc', + inactiveBorderWidth: 'auto', itemStyle: { - borderWidth: 0 + color: 'inherit', + opacity: 'inherit', + decal: 'inherit', + shadowBlur: 0, + shadowColor: null, + shadowOffsetX: 0, + shadowOffsetY: 0, + borderColor: 'inherit', + borderWidth: 'auto', + borderCap: 'inherit', + borderJoin: 'inherit', + borderDashOffset: 'inherit', + borderMiterLimit: 'inherit' + }, + + lineStyle: { + width: 'auto', + color: 'inherit', + inactiveColor: '#ccc', + inactiveWidth: 2, + opacity: 'inherit', + type: 'inherit', + cap: 'inherit', + join: 'inherit', + dashOffset: 'inherit', + miterLimit: 'inherit', + shadowBlur: 0, + shadowColor: null, + shadowOffsetX: 0, + shadowOffsetY: 0 }, textStyle: { diff --git a/src/component/legend/LegendView.ts b/src/component/legend/LegendView.ts index 694e132074ee60f33bb268cd83af9e61b1cb503a..8b0a77522d91a652f751a499cc59366997c8b9e7 100644 --- a/src/component/legend/LegendView.ts +++ b/src/component/legend/LegendView.ts @@ -18,29 +18,31 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import {createSymbol} from '../../util/symbol'; +import { DisplayableState } from 'zrender/src/graphic/Displayable'; +import { PathStyleProps } from 'zrender/src/graphic/Path'; +import { parse, stringify } from 'zrender/src/tool/color'; import * as graphic from '../../util/graphic'; import { enableHoverEmphasis } from '../../util/states'; import {setLabelStyle, createTextStyle} from '../../label/labelStyle'; import {makeBackground} from '../helper/listComponent'; import * as layoutUtil from '../../util/layout'; import ComponentView from '../../view/Component'; -import LegendModel, { LegendOption, LegendSelectorButtonOption, LegendTooltipFormatterParams } from './LegendModel'; +import LegendModel, { LegendItemStyleOption, LegendLineStyleOption, LegendOption, LegendSelectorButtonOption, LegendSymbolParams, LegendTooltipFormatterParams } from './LegendModel'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { ZRTextAlign, - ZRColor, - ItemStyleOption, ZRRectLike, CommonTooltipOption, - ColorString + ColorString, + SeriesOption, + SymbolOptionMixin } from '../../util/types'; import Model from '../../model/Model'; -import Displayable, { DisplayableState } from 'zrender/src/graphic/Displayable'; -import { PathStyleProps } from 'zrender/src/graphic/Path'; -import { parse, stringify } from 'zrender/src/tool/color'; -import {PatternObject} from 'zrender/src/graphic/Pattern'; +import {SeriesModel} from '../../echarts'; +import {LineStyleProps, LINE_STYLE_KEY_MAP} from '../../model/mixin/lineStyle'; +import {ITEM_STYLE_KEY_MAP} from '../../model/mixin/itemStyle'; +import {createSymbol, ECSymbol} from '../../util/symbol'; const curry = zrUtil.curry; const each = zrUtil.each; @@ -173,8 +175,8 @@ class LegendView extends ComponentView { !seriesModel.get('legendHoverLink') && excludeSeriesId.push(seriesModel.id); }); - each(legendModel.getData(), function (itemModel, dataIndex) { - const name = itemModel.get('name'); + each(legendModel.getData(), function (legendItemModel, dataIndex) { + const name = legendItemModel.get('name'); // Use empty string or \n as a newline string if (!this.newlineDisabled && (name === '' || name === '\n')) { @@ -186,7 +188,8 @@ class LegendView extends ComponentView { } // Representitive series. - const seriesModel = ecModel.getSeriesByName(name)[0]; + const seriesModel = ecModel.getSeriesByName(name)[0] as + SeriesModel; if (legendDrawnMap.get(name)) { // Have been drawed @@ -196,20 +199,21 @@ class LegendView extends ComponentView { // Legend to control series. if (seriesModel) { const data = seriesModel.getData(); + const lineVisualStyle = data.getVisual('legendLineStyle') || {}; + const symbolType = data.getVisual('legendSymbol'); + + /** + * `data.getVisual('style')` may be the color from the register + * in series. For example, for line series, + */ const style = data.getVisual('style'); - const color = style[data.getVisual('drawType')] || style.fill; - const borderColor = style.stroke; - const decal = style.decal; - // Using rect symbol defaultly - const legendSymbolType = data.getVisual('legendSymbol') || 'roundRect'; - const symbolType = data.getVisual('symbol'); + data.getVisual('symbolSize'); const itemGroup = this._createItem( - name, dataIndex, itemModel, legendModel, - legendSymbolType, symbolType, - itemAlign, color, borderColor, decal, - selectMode + seriesModel, name, dataIndex, + legendItemModel, legendModel, itemAlign, + lineVisualStyle, style, symbolType, selectMode ); itemGroup.on('click', curry(dispatchSelectAction, name, null, api, excludeSeriesId)) @@ -236,25 +240,21 @@ class LegendView extends ComponentView { const idx = provider.indexOfName(name); const style = provider.getItemVisual(idx, 'style') as PathStyleProps; - const borderColor = style.stroke; - const decal = style.decal; - let color = style.fill; + const symbolType = provider.getItemVisual(idx, 'legendSymbol'); + const colorArr = parse(style.fill as ColorString); // Color may be set to transparent in visualMap when data is out of range. // Do not show nothing. if (colorArr && colorArr[3] === 0) { colorArr[3] = 0.2; // TODO color is set to 0, 0, 0, 0. Should show correct RGBA - color = stringify(colorArr, 'rgba'); + style.fill = stringify(colorArr, 'rgba'); } - const legendSymbolType = 'roundRect'; - const itemGroup = this._createItem( - name, dataIndex, itemModel, legendModel, - legendSymbolType, null, - itemAlign, color, borderColor, decal, - selectMode + seriesModel, name, dataIndex, + legendItemModel, legendModel, itemAlign, + {}, style, symbolType, selectMode ); // FIXME: consider different series has items with the same name. @@ -326,79 +326,56 @@ class LegendView extends ComponentView { } private _createItem( + seriesModel: SeriesModel, name: string, dataIndex: number, itemModel: LegendModel['_data'][number], legendModel: LegendModel, - legendSymbolType: string, - symbolType: string, itemAlign: LegendOption['align'], - color: ZRColor, - borderColor: ZRColor, - decal: PatternObject, + lineVisualStyle: LineStyleProps, + itemVisualStyle: PathStyleProps, + symbolType: string, selectMode: LegendOption['selectedMode'] ) { + const drawType = seriesModel.visualDrawType; const itemWidth = legendModel.get('itemWidth'); const itemHeight = legendModel.get('itemHeight'); - const inactiveColor = legendModel.get('inactiveColor'); - const inactiveBorderColor = legendModel.get('inactiveBorderColor'); - const symbolKeepAspect = legendModel.get('symbolKeepAspect'); - const legendModelItemStyle = legendModel.getModel('itemStyle'); - const isSelected = legendModel.isSelected(name); - const itemGroup = new Group(); - const textStyleModel = itemModel.getModel('textStyle'); + const symbolKeepAspect = itemModel.get('symbolKeepAspect'); + const legendIconType = itemModel.get('icon'); + symbolType = legendIconType || symbolType || 'roundRect'; - const itemIcon = itemModel.get('icon'); + const legendLineStyle = legendModel.getModel('lineStyle'); + const style = getLegendStyle(symbolType, itemModel, legendLineStyle, lineVisualStyle, itemVisualStyle, drawType, isSelected); + const itemGroup = new Group(); - // Use user given icon first - legendSymbolType = itemIcon || legendSymbolType; - const legendSymbol = createSymbol( - legendSymbolType, - 0, - 0, - itemWidth, - itemHeight, - isSelected ? color : inactiveColor, - // symbolKeepAspect default true for legend - symbolKeepAspect == null ? true : symbolKeepAspect - ); - itemGroup.add( - setSymbolStyle( - legendSymbol, legendSymbolType, legendModelItemStyle, - borderColor, inactiveBorderColor, decal, isSelected - ) - ); + const textStyleModel = itemModel.getModel('textStyle'); - // Compose symbols - // PENDING - if (!itemIcon && symbolType - // At least show one symbol, can't be all none - && ((symbolType !== legendSymbolType) || symbolType === 'none') + if (typeof seriesModel.getLegendIcon === 'function' + && !legendIconType ) { - const size = itemHeight * 0.8; - if (symbolType === 'none') { - symbolType = 'circle'; - } - const legendSymbolCenter = createSymbol( + // Series has specific way to define legend icon + itemGroup.add(seriesModel.getLegendIcon({ + itemWidth, + itemHeight, symbolType, - (itemWidth - size) / 2, - (itemHeight - size) / 2, - size, - size, - isSelected ? color : inactiveColor, - // symbolKeepAspect default true for legend - symbolKeepAspect == null ? true : symbolKeepAspect - ); - // Put symbol in the center - itemGroup.add( - setSymbolStyle( - legendSymbolCenter, symbolType, legendModelItemStyle, - borderColor, inactiveBorderColor, decal, isSelected - ) - ); + symbolKeepAspect, + itemStyle: style.itemStyle, + lineStyle: style.lineStyle + })); + } + else { + // Use default legend icon policy for most series + itemGroup.add(getDefaultLegendIcon({ + itemWidth, + itemHeight, + symbolType, + symbolKeepAspect, + itemStyle: style.itemStyle, + lineStyle: style.lineStyle + })); } const textX = itemAlign === 'left' ? itemWidth + 5 : -5; @@ -413,6 +390,7 @@ class LegendView extends ComponentView { content = formatter(name); } + const inactiveColor = itemModel.get('inactiveColor'); itemGroup.add(new graphic.Text({ style: createTextStyle(textStyleModel, { text: content, @@ -538,28 +516,139 @@ class LegendView extends ComponentView { } -function setSymbolStyle( - symbol: graphic.Path | graphic.Image, +function getLegendStyle( symbolType: string, - legendModelItemStyle: Model, - borderColor: ZRColor, - inactiveBorderColor: ZRColor, - decal: PatternObject, + legendModel: LegendModel['_data'][number], + legendLineStyle: Model, + lineVisualStyle: LineStyleProps, + itemVisualStyle: PathStyleProps, + drawType: 'fill' | 'stroke', isSelected: boolean ) { - let itemStyle; - if (symbolType !== 'line' && symbolType.indexOf('empty') < 0) { - itemStyle = legendModelItemStyle.getItemStyle(); - (symbol as graphic.Path).style.stroke = borderColor; - (symbol as graphic.Path).style.decal = decal; - if (!isSelected) { - itemStyle.stroke = inactiveBorderColor; + /** + * Use series style if is inherit; + * elsewise, use legend style + */ + + // itemStyle + const legendItemModel = legendModel.getModel('itemStyle') as Model; + const itemProperties = ITEM_STYLE_KEY_MAP.concat([ + ['decal'] + ]); + const itemStyle: PathStyleProps = {}; + for (let i = 0; i < itemProperties.length; ++i) { + const propName = itemProperties[i][ + itemProperties[i].length - 1 + ] as keyof LegendItemStyleOption; + const visualName = itemProperties[i][0] as keyof PathStyleProps; + const value = legendItemModel.getShallow(propName) as LegendItemStyleOption[keyof LegendItemStyleOption]; + if (value === 'inherit') { + switch (visualName) { + case 'fill': + /** + * Series with visualDrawType as 'stroke' should have + * series stroke as legend fill + */ + itemStyle.fill = itemVisualStyle[drawType]; + break; + + case 'stroke': + /** + * symbol type with "emptyXXX" should use fill color + * in visual style + */ + itemStyle.stroke = itemVisualStyle[ + symbolType.startsWith('empty') ? 'fill' : 'stroke' + ]; + break; + + case 'opacity': + /** + * Use lineStyle.opacity if drawType is stroke + */ + itemStyle.opacity = (drawType === 'fill' ? itemVisualStyle : lineVisualStyle).opacity; + break; + + default: + (itemStyle as any)[visualName] = itemVisualStyle[visualName]; + } + } + else if (value === 'auto' && visualName === 'lineWidth') { + // If lineStyle.width is 'auto', it is set to be 2 if series has border + itemStyle.lineWidth = (itemVisualStyle.lineWidth > 0) ? 2 : 0; + } + else { + (itemStyle as any)[visualName] = value; + } + } + + // lineStyle + const legendLineModel = legendModel.getModel('lineStyle') as Model; + const lineProperties = LINE_STYLE_KEY_MAP.concat([ + ['inactiveColor'], + ['inactiveWidth'] + ]); + const lineStyle: LineStyleProps = {}; + for (let i = 0; i < lineProperties.length; ++i) { + const propName = lineProperties[i][1] as keyof LegendLineStyleOption; + const visualName = lineProperties[i][0] as keyof LineStyleProps; + const value = legendLineModel.getShallow(propName) as LegendLineStyleOption[keyof LegendLineStyleOption]; + if (value === 'inherit') { + (lineStyle as any)[visualName] = lineVisualStyle[visualName]; + } + else if (value === 'auto' && visualName === 'lineWidth') { + // If lineStyle.width is 'auto', it is set to be 2 if series has border + lineStyle.lineWidth = lineVisualStyle.lineWidth > 0 ? 2 : 0; + } + else { + (lineStyle as any)[visualName] = value; } } - else { - itemStyle = legendModelItemStyle.getItemStyle(['borderWidth', 'borderColor']); + + // Fix auto color to real color + (itemStyle.fill === 'auto') && (itemStyle.fill = itemVisualStyle.fill); + (itemStyle.stroke === 'auto') && (itemStyle.stroke = itemVisualStyle.fill); + (lineStyle.stroke === 'auto') && (lineStyle.stroke = itemVisualStyle.fill); + + if (!isSelected) { + const borderWidth = legendModel.get('inactiveBorderWidth'); + /** + * Since stroke is set to be inactiveBorderColor, it may occur that + * there is no border in series but border in legend, so we need to + * use border only when series has border if is set to be auto + */ + const visualHasBorder = itemStyle[symbolType.indexOf('empty') > -1 ? 'fill' : 'stroke']; + itemStyle.lineWidth = borderWidth === 'auto' + ? (itemVisualStyle.lineWidth > 0 && visualHasBorder ? 2 : 0) + : itemStyle.lineWidth; + itemStyle.fill = legendModel.get('inactiveColor'); + itemStyle.stroke = legendModel.get('inactiveBorderColor'); + lineStyle.stroke = legendLineStyle.get('inactiveColor'); + lineStyle.lineWidth = legendLineStyle.get('inactiveWidth'); } - (symbol as Displayable).setStyle(itemStyle); + return { itemStyle, lineStyle }; +} + +function getDefaultLegendIcon(opt: LegendSymbolParams): ECSymbol { + const symboType = opt.symbolType || 'roundRect'; + const symbol = createSymbol( + symboType, + 0, + 0, + opt.itemWidth, + opt.itemHeight, + opt.itemStyle.fill, + opt.symbolKeepAspect + ); + + symbol.setStyle(opt.itemStyle); + + if (symboType.indexOf('empty') > -1) { + symbol.style.stroke = symbol.style.fill; + symbol.style.fill = '#fff'; + symbol.style.lineWidth = 2; + } + return symbol; } diff --git a/src/component/radar/install.ts b/src/component/radar/install.ts index eb6babf8a67cf2de8b386aec90baf5f7370753ec..42445b871b84454d1b4dba3431f82052741dc25d 100644 --- a/src/component/radar/install.ts +++ b/src/component/radar/install.ts @@ -21,9 +21,23 @@ import { EChartsExtensionInstallRegisters } from '../../extension'; import RadarModel from '../../coord/radar/RadarModel'; import RadarView from './RadarView'; import Radar from '../../coord/radar/Radar'; +import RadarSeriesModel from '../../chart/radar/RadarSeries'; export function install(registers: EChartsExtensionInstallRegisters) { registers.registerCoordinateSystem('radar', Radar); registers.registerComponentModel(RadarModel); registers.registerComponentView(RadarView); + + registers.registerVisual({ + seriesType: 'radar', + reset: function (seriesModel: RadarSeriesModel) { + const data = seriesModel.getData(); + // itemVisual symbol is for selected data + data.each(function (idx) { + data.setItemVisual(idx, 'legendSymbol', 'roundRect'); + }); + // visual is for unselected data + data.setVisual('legendSymbol', 'roundRect'); + } + }) } \ No newline at end of file diff --git a/src/data/List.ts b/src/data/List.ts index 116dec96f4d43dff2a4ca0adae280c800389d7d8..2e0f13141e16aabfb2b90cc98a529ff3159b398b 100644 --- a/src/data/List.ts +++ b/src/data/List.ts @@ -35,7 +35,8 @@ import Element from 'zrender/src/Element'; import { DimensionIndex, DimensionName, DimensionLoose, OptionDataItem, ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, - ModelOption, SeriesDataType, OptionSourceData, SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL, DecalObject + ModelOption, SeriesDataType, OptionSourceData, SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL, + DecalObject } from '../util/types'; import {isDataItemOption, convertOptionIdName} from '../util/model'; import { getECData } from '../util/innerStore'; @@ -45,6 +46,7 @@ import type { VisualMeta } from '../component/visualMap/VisualMapModel'; import { parseDataValue } from './helper/dataValueHelper'; import {isSourceInstance, Source} from './Source'; import OrdinalMeta from './OrdinalMeta'; +import { LineStyleProps } from '../model/mixin/lineStyle'; const mathFloor = Math.floor; const isObject = zrUtil.isObject; @@ -140,6 +142,7 @@ export interface DefaultDataVisual { liftZ?: number // For legend. legendSymbol?: string + legendLineStyle?: LineStyleProps // visualMap will inject visualMeta data visualMeta?: VisualMeta[] diff --git a/src/model/Series.ts b/src/model/Series.ts index 934aa4dec7e9a97bf62cf28dfd5b5bfd298546a9..2056c39aacca720fb09f305118343105264eb32a 100644 --- a/src/model/Series.ts +++ b/src/model/Series.ts @@ -20,6 +20,7 @@ import * as zrUtil from 'zrender/src/core/util'; import env from 'zrender/src/core/env'; import type {MorphDividingMethod} from 'zrender/src/tool/morphPath'; +import {PathStyleProps} from 'zrender/src/graphic/Path'; import * as modelUtil from '../util/model'; import { DataHost, DimensionName, StageHandlerProgressParams, @@ -48,6 +49,9 @@ import makeStyleMapper from './mixin/makeStyleMapper'; import { SourceManager } from '../data/helper/sourceManager'; import { Source } from '../data/Source'; import { defaultSeriesFormatTooltip } from '../component/tooltip/seriesFormatTooltip'; +import {ECSymbol} from '../util/symbol'; +import {Group} from '../util/graphic'; +import {LegendSymbolParams} from '../component/legend/LegendModel'; const inner = modelUtil.makeInner<{ data: List @@ -90,6 +94,11 @@ interface SeriesModel { */ getMarkerPosition(value: ScaleDataValue[]): number[]; + /** + * Get legend icon symbol according to each series type + */ + getLegendIcon(opt: LegendSymbolParams): ECSymbol | Group; + /** * See `component/brush/selector.js` * Defined the brush selector for this series. diff --git a/src/util/symbol.ts b/src/util/symbol.ts index 8b5dc2848383704ca474d16f6bceba11522b39e8..bc8de1810aa86a36306aaced0be609da364d340a 100644 --- a/src/util/symbol.ts +++ b/src/util/symbol.ts @@ -26,7 +26,7 @@ import {calculateTextPosition} from 'zrender/src/contain/text'; import { Dictionary } from 'zrender/src/core/types'; import { ZRColor } from './types'; -type ECSymbol = graphic.Path & { +export type ECSymbol = graphic.Path & { __isEmptyBrush?: boolean setColor: (color: ZRColor, innerColor?: string) => void getColor: () => ZRColor @@ -174,9 +174,7 @@ const Arrow = graphic.Path.extend({ */ // TODO Use function to build symbol path. const symbolCtors: Dictionary = { - // Use small height rect to simulate line. - // Avoid using stroke. - line: graphic.Rect as unknown as SymbolCtor, + line: graphic.Line as unknown as SymbolCtor, rect: graphic.Rect as unknown as SymbolCtor, @@ -196,16 +194,13 @@ const symbolCtors: Dictionary = { }; -// NOTICE Only use fill. No line! const symbolShapeMakers: Dictionary = { - line: function (x, y, w, h, shape: graphic.Rect['shape']) { - const thickness = 2; - // A thin line - shape.x = x; - shape.y = y + h / 2 - thickness / 2; - shape.width = w; - shape.height = thickness; + line: function (x, y, w, h, shape: graphic.Line['shape']) { + shape.x1 = x; + shape.y1 = y + h / 2; + shape.x2 = x + w; + shape.y2 = y + h / 2; }, rect: function (x, y, w, h, shape: graphic.Rect['shape']) { @@ -317,9 +312,12 @@ function symbolPathSetColor(this: ECSymbol, color: ZRColor, innerColor?: string) if (this.__isEmptyBrush) { symbolStyle.stroke = color; symbolStyle.fill = innerColor || '#fff'; - // TODO Same width with lineStyle in LineView. + // TODO Same width with lineStyle in LineView symbolStyle.lineWidth = 2; } + else if (this.shape.symbolType === 'line') { + symbolStyle.stroke = color; + } else { symbolStyle.fill = color; } diff --git a/src/util/types.ts b/src/util/types.ts index 8477d03d472f5c01fa66860a43a1b944c26e3cf5..50d255f9f4192d13b43ea03baae2ed75e1713180 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -832,7 +832,7 @@ export interface ShadowOptionMixin { } export interface BorderOptionMixin { - borderColor?: string + borderColor?: ZRColor borderWidth?: number borderType?: ZRLineType borderCap?: CanvasLineCap diff --git a/src/visual/style.ts b/src/visual/style.ts index e8c68328bf505986bd380041e7760e506f8caecf..7d4a0c117ff120e96af34225f5cd3973b6033596 100644 --- a/src/visual/style.ts +++ b/src/visual/style.ts @@ -88,16 +88,26 @@ const seriesStyleTask: StageHandler = { // TODO style callback const colorCallback = isFunction(color) ? color as unknown as ColorCallback : null; + const hasAutoColor = globalStyle.fill === 'auto' || globalStyle.stroke === 'auto'; // Get from color palette by default. - if (!globalStyle[colorKey] || colorCallback) { + if (!globalStyle[colorKey] || colorCallback || hasAutoColor) { // Note: if some series has color specified (e.g., by itemStyle.color), we DO NOT // make it effect palette. Bacause some scenarios users need to make some series // transparent or as background, which should better not effect the palette. - globalStyle[colorKey] = seriesModel.getColorFromPalette( + const colorPalette = seriesModel.getColorFromPalette( // TODO series count changed. seriesModel.name, null, ecModel.getSeriesCount() ); - data.setVisual('colorFromPalette', true); + if (!globalStyle[colorKey]) { + globalStyle[colorKey] = colorPalette; + data.setVisual('colorFromPalette', true); + } + globalStyle.fill = (globalStyle.fill === 'auto' || typeof globalStyle.fill === 'function') + ? colorPalette + : globalStyle.fill; + globalStyle.stroke = (globalStyle.stroke === 'auto' || typeof globalStyle.stroke === 'function') + ? colorPalette + : globalStyle.stroke; } data.setVisual('style', globalStyle); diff --git a/test/legend-style.html b/test/legend-style.html new file mode 100644 index 0000000000000000000000000000000000000000..a285ccb82186ce7322c9e3983ce9eefb2fab8cb5 --- /dev/null +++ b/test/legend-style.html @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + diff --git a/test/line-style.html b/test/line-style.html index 875dacb68399c783f4bafdd7f18db27768a6254b..df4d6de0ff2643d5159017cbffdda842c09f3d17 100644 --- a/test/line-style.html +++ b/test/line-style.html @@ -166,6 +166,7 @@ under the License. left: '10%', bottom: '2%', right: '10%', + itemWidth: 50, borderWidth: 1, borderColor: 'blue', borderType: 'dotted', diff --git a/test/radar.html b/test/radar.html index 671767b544f60b38302a338525feff21305fb07f..f0feb3c882b486f60c48ef7f8f63b0e2b2c463e9 100644 --- a/test/radar.html +++ b/test/radar.html @@ -49,7 +49,10 @@ under the License. }, tooltip: {}, legend: { - data: ['预算分配(Allocated Budget)', '实际开销(Actual Spending)含有 "-" 数据', '第一个元素是 null'] + data: [{ + icon: 'circle', + name: 'Legend 应为 Circle' + }, '实际开销(Actual Spending)含有 "-" 数据', '第一个元素是 null', 'symbol 为 none 的需要用 roundRect'] }, radar: { radius: [50, '70%'], @@ -80,7 +83,7 @@ under the License. data : [ { value : [0, 10000, 28000, 35000, 50000, 19000], - name : '预算分配(Allocated Budget)' + name : 'Legend 应为 Circle' }, { value : [50, 14000, 28000, 31000, '-', 21000], @@ -96,6 +99,21 @@ under the License. symbolRotate: function(value, params) { return ~~(360 * Math.random()); } + }, { + name: '2', + type: 'radar', + label: { + normal: { + show: true + } + }, + data : [ + { + value : [100, 2800, 3500, 5000, 1900, 1000], + name : 'symbol 为 none 的需要用 roundRect' + } + ], + symbol: 'none' }] }); var theIndex = 2; diff --git a/test/tree-legend.html b/test/tree-legend.html index cfbdcd561207fa43cedceea90370dff024d106ae..2e9abd984a0f180a90ef6db642021d6e86ba6d03 100644 --- a/test/tree-legend.html +++ b/test/tree-legend.html @@ -217,14 +217,11 @@ under the License. left: '3%', orient: 'vertical', data: [{ - name: 'tree1', - icon: 'rectangle' + name: 'tree1' } , { name: 'tree2', - icon: 'rectangle' - }], - borderColor: '#c23531' + }] }, series:[ @@ -278,7 +275,7 @@ under the License. label: { position: 'right', verticalAlign: 'middle', - align: 'left' + align: 'left' }, leaves: {