未验证 提交 304685d0 编写于 作者: Y Yi Shen 提交者: GitHub

Merge pull request #14497 from apache/fix-legend

feat(legend): make default legend more intuitive
......@@ -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,
......
......@@ -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
......@@ -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;
......
......@@ -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<LineSeriesOption> {
coordinateSystem: Cartesian2D | Polar;
hasSymbolVisual = true;
legendSymbol = 'line';
getInitialData(option: LineSeriesOption): List {
if (__DEV__) {
......@@ -151,6 +153,11 @@ class LineSeriesModel extends SeriesModel<LineSeriesOption> {
position: 'top'
},
itemStyle: {
color: 'auto',
borderWidth: 2
},
endLabel: {
show: false,
valueAnimation: true,
......@@ -204,6 +211,48 @@ class LineSeriesModel extends SeriesModel<LineSeriesOption> {
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;
......@@ -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,
......
......@@ -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<MapSeriesOption> {
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,
......
......@@ -160,8 +160,7 @@ class RadarSeriesModel extends SeriesModel<RadarSeriesOption> {
// areaStyle: {
// },
// itemStyle: {}
symbol: 'emptyCircle',
symbolSize: 4
symbolSize: 8
// symbolRotate: null
};
}
......
......@@ -272,7 +272,7 @@ class TreeSeriesModel extends SeriesModel<TreeSeriesOption> {
itemStyle: {
color: 'lightsteelblue',
borderColor: '#c23531',
// borderColor: '#c23531',
borderWidth: 1.5
},
......
......@@ -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<T, ET, ST extends { [key in keyof T]: any }> = {
[key in keyof T]: key extends keyof ST ? T[key] | ET | ST[key] : T[key] | ET
};
export interface LegendItemStyleOption extends ExtendPropertyType<ItemStyleOption, 'inherit', {
borderWidth: 'auto'
}> {}
export interface LegendLineStyleOption extends ExtendPropertyType<LineStyleOption, 'inherit', {
width: 'auto'
}> {
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<Ops extends LegendOption = LegendOption> 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: {
......
......@@ -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<SeriesOption & SymbolOptionMixin>;
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<SeriesOption & SymbolOptionMixin>,
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<ItemStyleOption>,
borderColor: ZRColor,
inactiveBorderColor: ZRColor,
decal: PatternObject,
legendModel: LegendModel['_data'][number],
legendLineStyle: Model<LegendLineStyleOption>,
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<LegendItemStyleOption>;
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<LegendLineStyleOption>;
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;
}
......
......@@ -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
......@@ -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[]
......
......@@ -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.
......
......@@ -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<SymbolCtor> = {
// 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<SymbolCtor> = {
};
// NOTICE Only use fill. No line!
const symbolShapeMakers: Dictionary<SymbolShapeMaker> = {
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;
}
......
......@@ -832,7 +832,7 @@ export interface ShadowOptionMixin {
}
export interface BorderOptionMixin {
borderColor?: string
borderColor?: ZRColor
borderWidth?: number
borderType?: ZRLineType
borderCap?: CanvasLineCap
......
......@@ -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);
......
<!DOCTYPE html>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="lib/esl.js"></script>
<script src="lib/config.js"></script>
<script src="lib/jquery.min.js"></script>
<script src="lib/facePrint.js"></script>
<script src="lib/testHelper.js"></script>
<!-- <script src="ut/lib/canteen.js"></script> -->
<link rel="stylesheet" href="lib/reset.css" />
</head>
<body>
<style>
</style>
<div id="main1"></div>
<div id="main2"></div>
<script>
function getData(seriesId) {
var data = [];
for (var i = 0; i < 7; ++i) {
data.push(Math.random() * 100 + (seriesId + 1) * 200);
}
return data;
}
</script>
<script>
require(['echarts'], function (echarts) {
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
legend: [{
data: [
'Line A',
'Line B',
{
name: 'Line C',
symbolSize: 'inherit'
},
'Line F',
'Pie A',
'Pie B',
'Pie C',
'Pie D'
]
}, {
bottom: 10,
data: [{
name: 'Line D',
icon: 'rect'
}, {
name: 'Line E',
itemStyle: {
color: '#f0f'
}
}],
itemStyle: {
color: 'orange'
},
icon: 'emptyRect'
}],
series: [{
data: getData(0),
type: 'line',
name: 'Line A'
}, {
data: getData(1),
type: 'line',
name: 'Line B',
itemStyle: {
color: 'red',
borderWidth: 5
},
symbolSize: 15,
symbol: 'emptyTriangle'
}, {
data: getData(2),
type: 'line',
name: 'Line C',
lineStyle: {
color: 'green',
width: 5,
shadowBlur: 10,
shadowColor: 'orange'
},
itemStyle: {
color: 'red'
},
symbolSize: 30
}, {
data: getData(3),
type: 'line',
name: 'Line D',
itemStyle: {
color: 'blue'
},
symbol: 'emptyTriangle',
symbolSize: 15
}, {
data: getData(4),
type: 'line',
name: 'Line E',
itemStyle: {
color: 'green'
},
symbolSize: 10
}, {
data: getData(5),
type: 'line',
name: 'Line F',
symbolSize: 10,
itemStyle: {
color: 'blue'
}
}, {
type: 'pie',
data: [{
name: 'Pie A',
value: 10
}, {
name: 'Pie B',
value: 8,
itemStyle: {
borderWidth: 0
}
}, {
name: 'Pie C',
value: 14,
itemStyle: {
color: 'blue',
borderColor: 'green'
}
}, {
name: 'Pie D',
value: 2
}],
itemStyle: {
borderWidth: 10,
borderColor: '#fff'
},
center: ['80%', '50%'],
radius: '40%'
}],
grid: {
left: 60,
width: '55%'
},
visualMap: [{
type: 'continuous',
min: 1200,
max: 1300,
inRange: {
color: ['green', 'yellow', 'red']
},
show: false,
seriesIndex: 5
}, {
type: 'continuous',
min: 5,
max: 15,
inRange: {
color: ['green', 'yellow', 'red']
},
show: false,
seriesIndex: 6
}],
animation: 0
};
var chart = testHelper.create(echarts, 'main1', {
title: [
'**Legend icon being more intuitive**',
'Line A: the style of the line and items should be the same in those in legend; all colored in the first theme color',
'Line B: the style of the line and items should be the same in those in legend; all colored in red',
'Line C: the style of the line and items should be the same in those in legend; items colored in red and line colored in green',
'Line D: the style of the line should be the same in that in the legend; items colored orange in legend and blue in series',
'Line E: the style of the line should be the same in that in the legend; items colored pink in legend and green in series',
'Line F: the colors in the legend should be blue'
],
option: option
});
});
</script>
</body>
</html>
......@@ -166,6 +166,7 @@ under the License.
left: '10%',
bottom: '2%',
right: '10%',
itemWidth: 50,
borderWidth: 1,
borderColor: 'blue',
borderType: 'dotted',
......
......@@ -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;
......
......@@ -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: {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册