提交 77575f91 编写于 作者: O Ovilia

feat(decal): decal palette

上级 2e45e349
......@@ -77,7 +77,8 @@ const getStateItemStyle = makeStyleMapper([
['shadowBlur'],
['shadowOffsetX'],
['shadowOffsetY'],
['shadowColor']
['shadowColor'],
['decal']
]);
const getItemStyleNormal = function (model: Model<TreemapSeriesNodeItemOption['itemStyle']>): PathStyleProps {
// Normal style props should include emphasis style props.
......
......@@ -79,7 +79,8 @@ class ParallelAxisModel extends ComponentModel<ParallelAxisOption> {
['lineWidth', 'borderWidth'],
['stroke', 'borderColor'],
['width', 'width'],
['opacity', 'opacity']
['opacity', 'opacity'],
['decal']
]
)(this.getModel('areaSelectStyle')) as ParallelAreaSelectStyleProps;
}
......
......@@ -44,6 +44,7 @@ import type Tree from './Tree';
import type { VisualMeta } from '../component/visualMap/VisualMapModel';
import { parseDataValue } from './helper/dataValueHelper';
import { isSourceInstance } from './Source';
import {DecalObject} from 'zrender/src/graphic/Decal';
const mathFloor = Math.floor;
const isObject = zrUtil.isObject;
......@@ -144,6 +145,8 @@ export interface DefaultDataVisual {
// If color is encoded from palette
colorFromPalette?: boolean
decal?: DecalObject
}
export interface DataCalculationInfo<SERIES_MODEL> {
......
......@@ -105,6 +105,7 @@ import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale';
import type {EChartsFullOption} from './option';
import { findEventDispatcher } from './util/event';
import aria from './visual/aria';
import decal from './visual/decal';
declare let global: any;
type ModelFinder = modelUtil.ModelFinder;
......@@ -143,6 +144,7 @@ const PRIORITY_VISUAL_CHART_DATA_CUSTOM = 4500; // visual property in data
// necessary?
const PRIORITY_VISUAL_BRUSH = 5000;
const PRIORITY_VISUAL_ARIA = 6000;
const PRIORITY_VISUAL_DECAL = 7000;
export const PRIORITY = {
PROCESSOR: {
......@@ -159,7 +161,8 @@ export const PRIORITY = {
COMPONENT: PRIORITY_VISUAL_COMPONENT,
BRUSH: PRIORITY_VISUAL_BRUSH,
CHART_ITEM: PRIORITY_VISUAL_CHART_DATA_CUSTOM,
ARIA: PRIORITY_VISUAL_ARIA
ARIA: PRIORITY_VISUAL_ARIA,
DECAL: PRIORITY_VISUAL_DECAL
}
};
......@@ -2758,6 +2761,8 @@ registerVisual(PRIORITY_VISUAL_CHART_DATA_CUSTOM, dataSymbolTask);
registerVisual(PRIORITY_VISUAL_ARIA, aria);
registerVisual(PRIORITY_VISUAL_DECAL, decal);
registerPreprocessor(backwardCompat);
registerProcessor(PRIORITY_PROCESSOR_DATASTACK, dataStack);
registerLoading('default', loadingDefault);
......
......@@ -24,6 +24,8 @@ if (typeof navigator !== 'undefined') {
platform = navigator.platform || '';
}
const decalColor = 'rgba(0, 0, 0, 0.2)';
export default {
darkMode: 'auto',
......@@ -54,6 +56,46 @@ export default {
gradientColor: ['#f6efa6', '#d88273', '#bf444c'],
decal: [{
color: decalColor,
dashArrayX: [1, 0],
dashArrayY: [2, 5],
dashLineOffset: 0,
rotation: Math.PI / 6
}, {
color: decalColor,
symbol: 'circle',
dashArrayX: [4, 6],
dashArrayY: [4, 4],
dashLineOffset: 5,
rotation: 0
}, {
color: decalColor,
dashArrayX: [1, 0],
dashArrayY: [4, 3],
dashLineOffset: 0,
rotation: -Math.PI / 4
}, {
color: decalColor,
dashArrayX: 6,
dashArrayY: [6, 0],
dashLineOffset: 6,
rotation: 0
}, {
color: decalColor,
dashArrayX: [[1, 0], [1, 6]],
dashArrayY: [1, 0, 6, 0],
dashLineOffset: 0,
rotation: Math.PI / 4
}, {
color: decalColor,
symbol: 'triangle',
dashArrayX: [6, 6],
dashArrayY: [6, 6],
dashLineOffset: 6,
rotation: 0
}],
// If xAxis and yAxis declared, grid is created by default.
// grid: {},
......
......@@ -28,7 +28,8 @@ export const AREA_STYLE_KEY_MAP = [
['shadowOffsetX'],
['shadowOffsetY'],
['opacity'],
['shadowColor']
['shadowColor'],
['decal']
];
const getAreaStyle = makeStyleMapper(AREA_STYLE_KEY_MAP);
......
......@@ -35,7 +35,8 @@ export const ITEM_STYLE_KEY_MAP = [
['lineDashOffset', 'borderDashOffset'],
['lineCap', 'borderCap'],
['lineJoin', 'borderJoin'],
['miterLimit', 'borderMiterLimit']
['miterLimit', 'borderMiterLimit'],
['decal']
];
const getItemStyle = makeStyleMapper(ITEM_STYLE_KEY_MAP);
......
......@@ -34,7 +34,8 @@ export const LINE_STYLE_KEY_MAP = [
['lineDashOffset', 'dashOffset'],
['lineCap', 'cap'],
['lineJoin', 'join'],
['miterLimit']
['miterLimit'],
['decal']
];
const getLineStyle = makeStyleMapper(LINE_STYLE_KEY_MAP);
......
......@@ -3,22 +3,11 @@ import {DecalObject, DecalDashArrayX, DecalDashArrayY} from 'zrender/src/graphic
import Pattern from 'zrender/src/graphic/Pattern';
import {brushSingle} from 'zrender/src/canvas/graphic';
import {defaults, createCanvas, map} from 'zrender/src/core/util';
import {PathStyleProps} from 'zrender/src/graphic/Path';
import Model from '../model/Model';
import {getLeastCommonMultiple} from './number';
import {createSymbol} from './symbol';
import * as graphic from './graphic';
const decalMap = new WeakMap<DecalObject, Pattern>();
export default function setDecalWithPattern(styleModel: Model, style: PathStyleProps) {
const decalOption = styleModel.getShallow('decal') as DecalObject;
if (decalOption) {
style.decal = createOrUpdatePatternFromDecal(decalOption);
}
return style;
}
/**
* Create or update pattern image from decal options
*
......@@ -37,12 +26,12 @@ export function createOrUpdatePatternFromDecal(
symbol: 'rect',
symbolSize: 1,
symbolKeepAspect: true,
color: 'rgba(255, 255, 255, 0.4)',
color: 'rgba(0, 0, 0, 0.2)',
backgroundColor: null,
dashArrayX: 5,
dashArrayY: 5,
dashLineOffset: 0,
rotation: Math.PI / 4,
rotation: 0,
maxTileWidth: 512,
maxTileHeight: 512
} as DecalObject);
......
......@@ -32,101 +32,148 @@ export default function (ecModel: GlobalModel, api: ExtensionAPI) {
return;
}
const labelModel = ariaModel.getModel('label') || ariaModel;
setDecal();
setLabel();
const dom = api.getZr().dom;
if (labelModel.get('description')) {
dom.setAttribute('aria-label', labelModel.get('description'));
return;
}
function setDecal() {
const decalModel = ariaModel.getModel('decal');
const seriesCnt = ecModel.getSeriesCount();
const maxDataCnt = labelModel.get('data.maxCount') || 10;
const maxSeriesCnt = labelModel.get('series.maxCount') || 10;
const displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt);
const useDecal = !decalModel || (decalModel.get('show') !== false);
if (useDecal) {
// default decal show value is true
ecModel.eachRawSeries(seriesModel => {
const data = seriesModel.getData();
let ariaLabel;
if (seriesCnt < 1) {
// No series, no aria label
return;
}
else {
const title = getTitle();
if (title) {
ariaLabel = replace(getConfig(labelModel, 'general.withTitle'), {
title: title
if (seriesModel.useColorPaletteOnData) {
const dataCount = data.count();
data.each(idx => {
const itemStyle = data.ensureUniqueItemVisual(idx, 'style');
const name = data.getName(idx) || (idx + '');
const paletteDecal = seriesModel.getDecalFromPalette(
name,
null,
dataCount
);
const decal = zrUtil.defaults(
itemStyle.decal || {},
paletteDecal
);
data.setItemVisual(idx, 'decal', decal);
});
}
else {
const style = data.getVisual('style');
const paletteDecal = seriesModel.getDecalFromPalette(
seriesModel.name,
null,
ecModel.getSeriesCount()
);
const decal = zrUtil.defaults(
style.decal || {},
paletteDecal
);
data.setVisual('decal', decal);
}
});
}
else {
ariaLabel = getConfig(labelModel, 'general.withoutTitle');
}
function setLabel() {
const labelModel = ariaModel.getModel('label') || ariaModel;
const dom = api.getZr().dom;
if (labelModel.get('description')) {
dom.setAttribute('aria-label', labelModel.get('description'));
return;
}
const seriesLabels = [];
const prefix = seriesCnt > 1
? 'series.multiple.prefix'
: 'series.single.prefix';
ariaLabel += replace(getConfig(labelModel, prefix), { seriesCount: seriesCnt });
ecModel.eachSeries(function (seriesModel, idx) {
if (idx < displaySeriesCnt) {
let seriesLabel;
const seriesName = seriesModel.get('name');
const seriesTpl = 'series.'
+ (seriesCnt > 1 ? 'multiple' : 'single') + '.';
seriesLabel = getConfig(labelModel, seriesName
? seriesTpl + 'withName'
: seriesTpl + 'withoutName');
seriesLabel = replace(seriesLabel, {
seriesId: seriesModel.seriesIndex,
seriesName: seriesModel.get('name'),
seriesType: getSeriesTypeName(seriesModel.subType)
const seriesCnt = ecModel.getSeriesCount();
const maxDataCnt = labelModel.get('data.maxCount') || 10;
const maxSeriesCnt = labelModel.get('series.maxCount') || 10;
const displaySeriesCnt = Math.min(seriesCnt, maxSeriesCnt);
let ariaLabel;
if (seriesCnt < 1) {
// No series, no aria label
return;
}
else {
const title = getTitle();
if (title) {
ariaLabel = replace(getConfig(labelModel, 'general.withTitle'), {
title: title
});
}
else {
ariaLabel = getConfig(labelModel, 'general.withoutTitle');
}
const data = seriesModel.getData();
window.data = data;
if (data.count() > maxDataCnt) {
// Show part of data
seriesLabel += replace(getConfig(labelModel, 'data.partialData'), {
displayCnt: maxDataCnt
const seriesLabels = [];
const prefix = seriesCnt > 1
? 'series.multiple.prefix'
: 'series.single.prefix';
ariaLabel += replace(getConfig(labelModel, prefix), { seriesCount: seriesCnt });
ecModel.eachSeries(function (seriesModel, idx) {
if (idx < displaySeriesCnt) {
let seriesLabel;
const seriesName = seriesModel.get('name');
const seriesTpl = 'series.'
+ (seriesCnt > 1 ? 'multiple' : 'single') + '.';
seriesLabel = getConfig(labelModel, seriesName
? seriesTpl + 'withName'
: seriesTpl + 'withoutName');
seriesLabel = replace(seriesLabel, {
seriesId: seriesModel.seriesIndex,
seriesName: seriesModel.get('name'),
seriesType: getSeriesTypeName(seriesModel.subType)
});
}
else {
seriesLabel += getConfig(labelModel, 'data.allData');
}
const dataLabels = [];
for (let i = 0; i < data.count(); i++) {
if (i < maxDataCnt) {
const name = data.getName(i);
const value = retrieveRawValue(data, i);
dataLabels.push(
replace(
name
? getConfig(labelModel, 'data.withName')
: getConfig(labelModel, 'data.withoutName'),
{
name: name,
value: value
}
)
);
const data = seriesModel.getData();
window.data = data;
if (data.count() > maxDataCnt) {
// Show part of data
seriesLabel += replace(getConfig(labelModel, 'data.partialData'), {
displayCnt: maxDataCnt
});
}
else {
seriesLabel += getConfig(labelModel, 'data.allData');
}
}
seriesLabel += dataLabels
.join(getConfig(labelModel, 'data.separator.middle'))
+ getConfig(labelModel, 'data.separator.end');
seriesLabels.push(seriesLabel);
}
});
const dataLabels = [];
for (let i = 0; i < data.count(); i++) {
if (i < maxDataCnt) {
const name = data.getName(i);
const value = retrieveRawValue(data, i);
dataLabels.push(
replace(
name
? getConfig(labelModel, 'data.withName')
: getConfig(labelModel, 'data.withoutName'),
{
name: name,
value: value
}
)
);
}
}
seriesLabel += dataLabels
.join(getConfig(labelModel, 'data.separator.middle'))
+ getConfig(labelModel, 'data.separator.end');
ariaLabel += seriesLabels
.join(getConfig(labelModel, 'series.multiple.separator.middle'))
+ getConfig(labelModel, 'series.multiple.separator.end');
seriesLabels.push(seriesLabel);
}
});
dom.setAttribute('aria-label', ariaLabel);
ariaLabel += seriesLabels
.join(getConfig(labelModel, 'series.multiple.separator.middle'))
+ getConfig(labelModel, 'series.multiple.separator.end');
dom.setAttribute('aria-label', ariaLabel);
}
}
function replace(str, keyValues) {
......
/*
* 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.
*/
// @ts-nocheck
import ExtensionAPI from '../ExtensionAPI';
import GlobalModel from '../model/Global';
import {createOrUpdatePatternFromDecal} from '../util/decal';
export default function (ecModel: GlobalModel, api: ExtensionAPI) {
ecModel.eachRawSeries(seriesModel => {
const data = seriesModel.getData();
if (seriesModel.useColorPaletteOnData) {
data.each(idx => {
const decal = data.getItemVisual(idx, 'decal');
if (decal) {
const itemStyle = data.ensureUniqueItemVisual(idx, 'style');
itemStyle.decal = createOrUpdatePatternFromDecal(decal);
}
});
}
else {
const decal = data.getVisual('decal');
if (decal) {
const style = data.getVisual('style');
style.decal = createOrUpdatePatternFromDecal(decal);
}
}
});
}
......@@ -25,7 +25,7 @@ import { LINE_STYLE_KEY_MAP } from '../model/mixin/lineStyle';
import SeriesModel from '../model/Series';
import Model from '../model/Model';
import { makeInner } from '../util/model';
import setDecalWithPattern, {createOrUpdatePatternFromDecal} from '../util/decal';
import {DecalObject} from 'zrender/src/graphic/Decal';
const inner = makeInner<{scope: object}, SeriesModel>();
......@@ -76,7 +76,11 @@ const seriesStyleTask: StageHandler = {
const getStyle = getStyleMapper(seriesModel, stylePath);
const globalStyle = getStyle(styleModel);
setDecalWithPattern(styleModel, globalStyle);
const decalOption = styleModel.getShallow('decal') as DecalObject;
if (decalOption) {
data.setVisual('decal', decalOption);
}
// TODO
const colorKey = getDefaultColorKey(seriesModel, stylePath);
......@@ -141,7 +145,7 @@ const dataStyleTask: StageHandler = {
extend(existsStyle, style);
if (sharedModel.option.decal) {
existsStyle.decal = createOrUpdatePatternFromDecal(sharedModel.option.decal);
data.setItemVisual(idx, 'decal', sharedModel.option.decal);
}
if (colorKey in style) {
......
......@@ -38,82 +38,140 @@ under the License.
<div id="main0"></div>
<div id="main1"></div>
<script>
require(['echarts'/*, 'map/js/china' */], function (echarts) {
var option;
var series = [];
var pieData = [];
var itemStyle = {
decal: {
color: 'blue'
}
};
for (var i = 0; i < 6; ++i) {
var s = {
name: 'bar' + i,
data: [i * 10 + 100],
type: 'bar'
};
if (i === 0) {
s.itemStyle = itemStyle;
}
series.push(s);
var p = {
name: 'pie' + i,
value: i * 5 + 10
};
if (i === 0) {
p.itemStyle = itemStyle;
}
pieData.push(p);
}
series.push({
type: 'pie',
center: ['75%', '50%'],
data: pieData,
radius: '50%'
});
option = {
xAxis: {
type: 'category',
data: ['x']
},
yAxis: {
type: 'value'
},
series: series,
grid: {
left: 50,
right: '40%'
},
aria: {
show: true
}
};
var chart = testHelper.create(echarts, 'main0', {
title: [
'It should use decal when aria.show is true',
'(1) Each bar and pie piece should have different decal',
'(2) The first bar and pie piece decal should be blue'
],
option: option
// height: 300,
// buttons: [{text: 'btn-txt', onclick: function () {}}],
// recordCanvas: true,
});
});
</script>
<script>
require(['echarts'/*, 'map/js/china' */], function (echarts) {
var option;
var decal1 = {};
var decal2 = {
rotation: Math.PI / 6,
dashArrayX: [6, 2],
dashArrayY: [2, 10]
var series = [];
var pieData = [];
var itemStyle = {
decal: {
color: 'blue'
}
};
for (var i = 0; i < 6; ++i) {
var s = {
name: 'bar' + i,
data: [i * 10 + 100],
type: 'bar'
};
if (i === 0) {
s.itemStyle = itemStyle;
}
series.push(s);
var p = {
name: 'pie' + i,
value: i * 5 + 10
};
if (i === 0) {
p.itemStyle = itemStyle;
}
pieData.push(p);
}
series.push({
type: 'pie',
center: ['75%', '50%'],
data: pieData,
radius: '50%'
});
option = {
legend: {
data: ['a', 'b']
},
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
data: ['x']
},
yAxis: {
type: 'value'
},
series: [{
name: 'a',
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
itemStyle: {
decal: decal1
}
}, {
name: 'b',
data: [80, 70, 110, 130, 120, 200, 150],
type: 'bar',
itemStyle: {
decal: decal2
}
}, {
type: 'pie',
center: ['80%', '50%'],
data: [{
name: 'c',
value: 10,
itemStyle: {
decal: decal1
}
}, {
name: 'd',
value: 5,
itemStyle: {
decal: decal2
}
}]
}],
series: series,
grid: {
left: 50,
right: '40%'
}
};
var chart = testHelper.create(echarts, 'main0', {
var chart = testHelper.create(echarts, 'main1', {
title: [
'Default decals for different series'
'If aria is not enabled, decal can also be enabled',
'(1) Only the first bar and pie piece should use decal'
],
option: option
// height: 300,
// buttons: [{text: 'btn-txt', onclick: function () {}}],
// recordCanvas: true,
});
});
</script>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册