You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by GitBox <gi...@apache.org> on 2020/10/15 11:44:27 UTC

[GitHub] [incubator-echarts] pissang commented on a change in pull request #13304: feat: decal

pissang commented on a change in pull request #13304:
URL: https://github.com/apache/incubator-echarts/pull/13304#discussion_r505466593



##########
File path: src/util/decal.ts
##########
@@ -0,0 +1,280 @@
+import WeakMap from 'zrender/src/core/WeakMap';
+import {DecalObject, DecalDashArrayX, DecalDashArrayY} from 'zrender/src/graphic/Decal';
+import Pattern from 'zrender/src/graphic/Pattern';
+import {brushSingle} from 'zrender/src/canvas/graphic';
+import {defaults, createCanvas, map} from 'zrender/src/core/util';
+import {getLeastCommonMultiple} from './number';
+import {createSymbol} from './symbol';
+
+const decalMap = new WeakMap<DecalObject, Pattern>();
+
+/**
+ * Create or update pattern image from decal options
+ *
+ * @param {DecalObject} decalObject decal options
+ * @return {Pattern} pattern with generated image
+ */
+export function createOrUpdatePatternFromDecal(
+    decalObject: DecalObject
+): Pattern {
+    const oldPattern = decalMap.get(decalObject);
+    if (oldPattern) {
+        return oldPattern;
+    }
+
+    const decalOpt = defaults(decalObject, {
+        symbol: 'rect',
+        symbolSize: 1,
+        symbolKeepAspect: true,
+        color: 'rgba(0, 0, 0, 0.2)',
+        backgroundColor: null,
+        dashArrayX: 5,
+        dashArrayY: 5,
+        dashLineOffset: 0,
+        rotation: 0,
+        maxTileWidth: 512,
+        maxTileHeight: 512
+    } as DecalObject);
+    if (decalOpt.backgroundColor === 'none') {
+        decalOpt.backgroundColor = null;
+    }
+
+    const dashArrayX = normalizeDashArrayX(decalOpt.dashArrayX);
+    const dashArrayY = normalizeDashArrayY(decalOpt.dashArrayY);
+
+    const lineBlockLengthsX = getLineBlockLengthX(dashArrayX);
+    const lineBlockLengthY = getLineBlockLengthY(dashArrayY);
+
+    const canvas = createCanvas();
+    const pSize = getPatternSize();
+
+    canvas.width = pSize.width;
+    canvas.height = pSize.height;
+    canvas.style.width = canvas.width + 'px';
+    canvas.style.height = canvas.height + 'px';
+

Review comment:
       No need to set canvas.style

##########
File path: src/visual/aria.ts
##########
@@ -20,111 +20,160 @@
 // @ts-nocheck

Review comment:
       `@ts-nocheck` needs to be removed since types have been added.

##########
File path: src/component/aria.ts
##########
@@ -0,0 +1,64 @@
+/*
+* 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.
+*/
+
+export interface AriaLabelOption {
+    show?: boolean;
+    description?: string;
+    general?: {
+        withTitle?: string;
+        withoutTitle?: string;
+    };
+    series?: {
+        maxCount?: number;
+        single?: {
+            prefix?: string;
+            withName?: string;
+            withoutName?: string;
+        };
+        multiple?: {
+            prefix?: string;
+            withName?: string;
+            withoutName?: string;
+            separator?: {
+                middle?: string;
+                end?: string;
+            }
+        }
+    },
+    data?: {
+        maxCount?: number;
+        allData?: string;
+        partialData?: string;
+        withName?: string;
+        withoutName?: string;
+        separator?: {
+            middle?: string;
+            end?: string;
+        }
+    }
+}
+
+// Extending is for compating ECharts 4
+export interface AriaOption extends AriaLabelOption {
+    show?: boolean;
+    label?: AriaLabelOption;
+    decal?: {
+        show?: boolean;
+    }
+}

Review comment:
       `visual/aria.ts` should be imported here instead of `echarts.ts`
   
   Then import this aria component in the `echarts.all.ts`, `echarts.common.ts`, `echarts.simple.ts`

##########
File path: src/visual/decal.ts
##########
@@ -0,0 +1,47 @@
+/*
+* 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) {

Review comment:
       It should be
   ```ts
   if (data.hasItemVisual()) { ... }
   ```
   Since in the `itemStyle` of each data decal can be set respectively.
   
   `hasItemVisual` is not implemented yet. It should be in the `List` and determine if `_itemVisuals.length > 0`
   
   ```ts
   hasItemVisual() {
     return this._itemVisuals.length > 0
   }
   ```

##########
File path: src/util/decal.ts
##########
@@ -0,0 +1,280 @@
+import WeakMap from 'zrender/src/core/WeakMap';
+import {DecalObject, DecalDashArrayX, DecalDashArrayY} from 'zrender/src/graphic/Decal';
+import Pattern from 'zrender/src/graphic/Pattern';
+import {brushSingle} from 'zrender/src/canvas/graphic';
+import {defaults, createCanvas, map} from 'zrender/src/core/util';
+import {getLeastCommonMultiple} from './number';
+import {createSymbol} from './symbol';
+
+const decalMap = new WeakMap<DecalObject, Pattern>();
+
+/**
+ * Create or update pattern image from decal options
+ *
+ * @param {DecalObject} decalObject decal options
+ * @return {Pattern} pattern with generated image
+ */
+export function createOrUpdatePatternFromDecal(
+    decalObject: DecalObject
+): Pattern {
+    const oldPattern = decalMap.get(decalObject);
+    if (oldPattern) {
+        return oldPattern;
+    }
+
+    const decalOpt = defaults(decalObject, {
+        symbol: 'rect',
+        symbolSize: 1,
+        symbolKeepAspect: true,
+        color: 'rgba(0, 0, 0, 0.2)',
+        backgroundColor: null,
+        dashArrayX: 5,
+        dashArrayY: 5,
+        dashLineOffset: 0,
+        rotation: 0,
+        maxTileWidth: 512,
+        maxTileHeight: 512
+    } as DecalObject);
+    if (decalOpt.backgroundColor === 'none') {
+        decalOpt.backgroundColor = null;
+    }
+
+    const dashArrayX = normalizeDashArrayX(decalOpt.dashArrayX);
+    const dashArrayY = normalizeDashArrayY(decalOpt.dashArrayY);
+
+    const lineBlockLengthsX = getLineBlockLengthX(dashArrayX);
+    const lineBlockLengthY = getLineBlockLengthY(dashArrayY);
+
+    const canvas = createCanvas();
+    const pSize = getPatternSize();
+
+    canvas.width = pSize.width;
+    canvas.height = pSize.height;
+    canvas.style.width = canvas.width + 'px';
+    canvas.style.height = canvas.height + 'px';
+
+    const ctx = canvas.getContext('2d');
+
+    brushDecal();
+
+    const pattern = new Pattern(canvas, 'repeat', decalOpt.rotation);
+    decalMap.set(decalObject, pattern);
+
+    return pattern;
+
+    /**
+     * Get minumum length that can make a repeatable pattern.
+     *
+     * @return {Object} pattern width and height
+     */
+    function getPatternSize()
+        : {
+            width: number,
+            height: number,
+            lines: number
+        }
+    {
+        /**
+         * For example, if dash is [[3, 2], [2, 1]] for X, it looks like
+         * |---  ---  ---  ---  --- ...
+         * |-- -- -- -- -- -- -- -- ...
+         * |---  ---  ---  ---  --- ...
+         * |-- -- -- -- -- -- -- -- ...
+         * So the minumum length of X is 15,
+         * which is the least common multiple of `3 + 2` and `2 + 1`
+         * |---  ---  ---  |---  --- ...
+         * |-- -- -- -- -- |-- -- -- ...
+         *
+         * When consider with dashLineOffset, it means the `n`th line has the offset
+         * of `n * dashLineOffset`.
+         * For example, if dash is [[3, 1], [1, 1]] and dashLineOffset is 3,
+         * and use `=` for the start to make it clear, it looks like
+         * |=-- --- --- --- --- -...
+         * | - = - - - - - - - - ...
+         * |- --- =-- --- --- -- ...
+         * | - - - - = - - - - - ...
+         * |--- --- --- =-- --- -...
+         * | - - - - - - - = - - ...
+         * In this case, the minumum length is 12, which is the least common
+         * multiple of `3 + 1`, `1 + 1` and `3 * 2` where `2` is xlen
+         * |=-- --- --- |--- --- -...
+         * | - = - - - -| - - - - ...
+         * |- --- =-- --|- --- -- ...
+         * | - - - - = -| - - - - ...
+         */
+        const offsetMultipleX = decalOpt.dashLineOffset || 1;
+        let width = 1;
+        for (let i = 0, xlen = lineBlockLengthsX.length; i < xlen; ++i) {
+            const x = getLeastCommonMultiple(offsetMultipleX * xlen, lineBlockLengthsX[i]);
+            width = getLeastCommonMultiple(width, x);
+        }
+        const columns = decalOpt.dashLineOffset
+            ? width / offsetMultipleX
+            : 2;
+        let height = lineBlockLengthY * columns;
+
+        return {
+            width: Math.max(1, Math.min(width, decalOpt.maxTileWidth)),
+            height: Math.max(1, Math.min(height, decalOpt.maxTileHeight)),
+            lines: columns
+        };
+    }
+
+    function fixStartPosition(lineOffset: number, blockLength: number) {
+        let start = lineOffset || 0;
+        while (start > 0) {
+            start -= blockLength;
+        }
+        return start;
+    }
+
+    function brushDecal() {
+        ctx.clearRect(0, 0, pSize.width, pSize.height);
+        if (decalOpt.backgroundColor) {
+            ctx.fillStyle = decalOpt.backgroundColor;
+            ctx.fillRect(0, 0, pSize.width, pSize.height);
+        }
+
+        ctx.fillStyle = decalOpt.color;
+
+        let yCnt = 0;
+        let y = -pSize.lines * lineBlockLengthY;
+        let yId = 0;
+        let xId0 = 0;
+        while (y < pSize.height) {
+            if (yId % 2 === 0) {
+                let x = fixStartPosition(
+                    decalOpt.dashLineOffset * (yCnt - pSize.lines) / 2,
+                    lineBlockLengthsX[0]
+                );
+                let xId1 = 0;
+                while (x < pSize.width * 2) {
+                    // E.g., [15, 5, 20, 5] draws only for 15 and 20
+                    if (xId1 % 2 === 0) {
+                        const size = (1 - decalOpt.symbolSize) * 0.5;
+                        const left = x + dashArrayX[xId0][xId1] * size;
+                        const top = y + dashArrayY[yId] * size;
+                        const width = dashArrayX[xId0][xId1] * decalOpt.symbolSize;
+                        const height = dashArrayY[yId] * decalOpt.symbolSize;
+                        brushSymbol(left, top, width, height);
+                    }
+
+                    x += dashArrayX[xId0][xId1];
+                    ++xId1;
+                    if (xId1 === dashArrayX[xId0].length) {
+                        xId1 = 0;
+                    }
+                }
+
+                ++xId0;
+                if (xId0 === dashArrayX.length) {
+                    xId0 = 0;
+                }
+            }
+
+            ++yCnt;
+            y += dashArrayY[yId];
+
+            ++yId;
+            if (yId === dashArrayY.length) {
+                yId = 0;
+            }
+        }
+    }
+
+    function brushSymbol(x: number, y: number, width: number, height: number) {
+        const symbol = createSymbol(decalOpt.symbol, x, y, width, height);
+        symbol.style.fill = decalOpt.color;
+        brushSingle(ctx, symbol);
+    }
+
+}
+
+/**
+ * Convert dash input into dashArray
+ *
+ * @param {DecalDashArrayX} dash dash input
+ * @return {number[][]} normolized dash array
+ */
+function normalizeDashArrayX(dash: DecalDashArrayX): number[][] {
+    if (!dash || typeof dash === 'object' && dash.length === 0) {
+        return [[0, 0]];

Review comment:
       No need to check `typeof dash === 'object'` here?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org