GaugeView.ts 16.0 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* 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.
*/

S
sushuang 已提交
20
import PointerPath from './PointerPath';
S
sushuang 已提交
21
import * as graphic from '../../util/graphic';
22
import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states';
23
import {createTextStyle} from '../../label/labelStyle';
S
sushuang 已提交
24 25
import ChartView from '../../view/Chart';
import {parsePercent, round, linearMap} from '../../util/number';
26
import GaugeSeriesModel, { GaugeDataItemOption } from './GaugeSeries';
P
pissang 已提交
27 28 29 30 31 32 33 34 35 36
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../ExtensionAPI';
import { ColorString } from '../../util/types';
import List from '../../data/List';

interface PosInfo {
    cx: number
    cy: number
    r: number
}
S
sushuang 已提交
37

P
pissang 已提交
38
function parsePosition(seriesModel: GaugeSeriesModel, api: ExtensionAPI): PosInfo {
39 40 41 42 43 44 45
    const center = seriesModel.get('center');
    const width = api.getWidth();
    const height = api.getHeight();
    const size = Math.min(width, height);
    const cx = parsePercent(center[0], api.getWidth());
    const cy = parsePercent(center[1], api.getHeight());
    const r = parsePercent(seriesModel.get('radius'), size / 2);
S
sushuang 已提交
46 47 48 49 50 51 52 53

    return {
        cx: cx,
        cy: cy,
        r: r
    };
}

P
pissang 已提交
54
function formatLabel(value: number, labelFormatter: string | ((value: number) => string)): string {
P
pissang 已提交
55
    let label = value == null ? '' : (value + '');
S
sushuang 已提交
56 57
    if (labelFormatter) {
        if (typeof labelFormatter === 'string') {
P
pissang 已提交
58
            label = labelFormatter.replace('{value}', label);
S
sushuang 已提交
59 60
        }
        else if (typeof labelFormatter === 'function') {
P
pissang 已提交
61
            label = labelFormatter(value);
S
sushuang 已提交
62
        }
L
Gauge  
lang 已提交
63 64
    }

S
sushuang 已提交
65 66
    return label;
}
L
lang 已提交
67

68
const PI2 = Math.PI * 2;
L
lang 已提交
69

P
pissang 已提交
70
class GaugeView extends ChartView {
1
100pah 已提交
71 72
    static type = 'gauge' as const;
    type = GaugeView.type;
L
Gauge  
lang 已提交
73

1
100pah 已提交
74
    private _data: List;
L
Gauge  
lang 已提交
75

P
pissang 已提交
76
    render(seriesModel: GaugeSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
L
Gauge  
lang 已提交
77

S
sushuang 已提交
78
        this.group.removeAll();
L
Gauge  
lang 已提交
79

80 81
        const colorList = seriesModel.get(['axisLine', 'lineStyle', 'color']);
        const posInfo = parsePosition(seriesModel, api);
L
Gauge  
lang 已提交
82

S
sushuang 已提交
83 84 85
        this._renderMain(
            seriesModel, ecModel, api, colorList, posInfo
        );
P
pissang 已提交
86
    }
L
Gauge  
lang 已提交
87

P
pissang 已提交
88
    dispose() {}
L
Gauge  
lang 已提交
89

P
pissang 已提交
90 91 92 93 94 95 96
    _renderMain(
        seriesModel: GaugeSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        colorList: [number, ColorString][],
        posInfo: PosInfo
    ) {
97
        const group = this.group;
1
100pah 已提交
98

99 100
        const axisLineModel = seriesModel.getModel('axisLine');
        const lineStyleModel = axisLineModel.getModel('lineStyle');
L
Gauge  
lang 已提交
101

102
        const clockwise = seriesModel.get('clockwise');
103 104
        let startAngle = -seriesModel.get('startAngle') / 180 * Math.PI;
        let endAngle = -seriesModel.get('endAngle') / 180 * Math.PI;
L
Gauge  
lang 已提交
105

106
        const angleRangeSpan = (endAngle - startAngle) % PI2;
L
Gauge  
lang 已提交
107

108
        let prevEndAngle = startAngle;
109 110
        const axisLineWidth = lineStyleModel.get('width');
        const showAxis = axisLineModel.get('show');
111

112
        for (let i = 0; showAxis && i < colorList.length; i++) {
S
sushuang 已提交
113
            // Clamp
114
            const percent = Math.min(Math.max(colorList[i][0], 0), 1);
1
100pah 已提交
115
            endAngle = startAngle + angleRangeSpan * percent;
116
            const sector = new graphic.Sector({
S
sushuang 已提交
117 118 119 120 121 122 123 124 125 126 127
                shape: {
                    startAngle: prevEndAngle,
                    endAngle: endAngle,
                    cx: posInfo.cx,
                    cy: posInfo.cy,
                    clockwise: clockwise,
                    r0: posInfo.r - axisLineWidth,
                    r: posInfo.r
                },
                silent: true
            });
L
Gauge  
lang 已提交
128

S
sushuang 已提交
129 130 131
            sector.setStyle({
                fill: colorList[i][1]
            });
L
Gauge  
lang 已提交
132

S
sushuang 已提交
133 134 135
            sector.setStyle(lineStyleModel.getLineStyle(
                // Because we use sector to simulate arc
                // so the properties for stroking are useless
P
pissang 已提交
136
                ['color', 'width']
S
sushuang 已提交
137
            ));
L
Gauge  
lang 已提交
138

S
sushuang 已提交
139
            group.add(sector);
L
Gauge  
lang 已提交
140

S
sushuang 已提交
141 142
            prevEndAngle = endAngle;
        }
L
Gauge  
lang 已提交
143

144
        const getColor = function (percent: number) {
S
sushuang 已提交
145 146 147
            // Less than 0
            if (percent <= 0) {
                return colorList[0][1];
L
Gauge  
lang 已提交
148
            }
149
            let i;
1
100pah 已提交
150
            for (i = 0; i < colorList.length; i++) {
S
sushuang 已提交
151 152 153 154
                if (colorList[i][0] >= percent
                    && (i === 0 ? 0 : colorList[i - 1][0]) < percent
                ) {
                    return colorList[i][1];
L
Gauge  
lang 已提交
155
                }
L
lang 已提交
156
            }
S
sushuang 已提交
157 158 159
            // More than 1
            return colorList[i - 1][1];
        };
L
lang 已提交
160

S
sushuang 已提交
161
        if (!clockwise) {
162
            const tmp = startAngle;
S
sushuang 已提交
163 164 165
            startAngle = endAngle;
            endAngle = tmp;
        }
L
Gauge  
lang 已提交
166

S
sushuang 已提交
167
        this._renderTicks(
L
Gauge  
lang 已提交
168 169
            seriesModel, ecModel, api, getColor, posInfo,
            startAngle, endAngle, clockwise
S
sushuang 已提交
170
        );
L
Gauge  
lang 已提交
171

S
sushuang 已提交
172 173 174 175
        this._renderPointer(
            seriesModel, ecModel, api, getColor, posInfo,
            startAngle, endAngle, clockwise
        );
L
Gauge  
lang 已提交
176

S
sushuang 已提交
177 178 179 180 181 182
        this._renderTitle(
            seriesModel, ecModel, api, getColor, posInfo
        );
        this._renderDetail(
            seriesModel, ecModel, api, getColor, posInfo
        );
P
pissang 已提交
183
    }
S
sushuang 已提交
184

P
pissang 已提交
185 186 187 188 189 190 191 192 193
    _renderTicks(
        seriesModel: GaugeSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        getColor: (percent: number) => ColorString,
        posInfo: PosInfo,
        startAngle: number,
        endAngle: number,
        clockwise: boolean
S
sushuang 已提交
194
    ) {
195 196 197 198
        const group = this.group;
        const cx = posInfo.cx;
        const cy = posInfo.cy;
        const r = posInfo.r;
S
sushuang 已提交
199

200 201
        const minVal = +seriesModel.get('min');
        const maxVal = +seriesModel.get('max');
S
sushuang 已提交
202

203 204 205
        const splitLineModel = seriesModel.getModel('splitLine');
        const tickModel = seriesModel.getModel('axisTick');
        const labelModel = seriesModel.getModel('axisLabel');
S
sushuang 已提交
206

207 208
        const splitNumber = seriesModel.get('splitNumber');
        const subSplitNumber = tickModel.get('splitNumber');
S
sushuang 已提交
209

210
        const splitLineLen = parsePercent(
S
sushuang 已提交
211 212
            splitLineModel.get('length'), r
        );
213
        const tickLen = parsePercent(
S
sushuang 已提交
214 215 216
            tickModel.get('length'), r
        );

217
        let angle = startAngle;
218 219
        const step = (endAngle - startAngle) / splitNumber;
        const subStep = step / subSplitNumber;
S
sushuang 已提交
220

221 222
        const splitLineStyle = splitLineModel.getModel('lineStyle').getLineStyle();
        const tickLineStyle = tickModel.getModel('lineStyle').getLineStyle();
S
sushuang 已提交
223

224 225
        let unitX;
        let unitY;
1
100pah 已提交
226

227
        for (let i = 0; i <= splitNumber; i++) {
1
100pah 已提交
228 229
            unitX = Math.cos(angle);
            unitY = Math.sin(angle);
S
sushuang 已提交
230 231
            // Split line
            if (splitLineModel.get('show')) {
232
                const splitLine = new graphic.Line({
S
sushuang 已提交
233 234 235 236 237 238 239 240 241 242 243 244 245 246
                    shape: {
                        x1: unitX * r + cx,
                        y1: unitY * r + cy,
                        x2: unitX * (r - splitLineLen) + cx,
                        y2: unitY * (r - splitLineLen) + cy
                    },
                    style: splitLineStyle,
                    silent: true
                });
                if (splitLineStyle.stroke === 'auto') {
                    splitLine.setStyle({
                        stroke: getColor(i / splitNumber)
                    });
                }
L
Gauge  
lang 已提交
247

S
sushuang 已提交
248 249
                group.add(splitLine);
            }
L
Gauge  
lang 已提交
250

S
sushuang 已提交
251 252
            // Label
            if (labelModel.get('show')) {
253
                const label = formatLabel(
S
sushuang 已提交
254 255 256
                    round(i / splitNumber * (maxVal - minVal) + minVal),
                    labelModel.get('formatter')
                );
257 258
                const distance = labelModel.get('distance');
                const autoColor = getColor(i / splitNumber);
S
sushuang 已提交
259 260

                group.add(new graphic.Text({
261
                    style: createTextStyle(labelModel, {
S
sushuang 已提交
262 263 264
                        text: label,
                        x: unitX * (r - splitLineLen - distance) + cx,
                        y: unitY * (r - splitLineLen - distance) + cy,
P
pissang 已提交
265 266
                        verticalAlign: unitY < -0.4 ? 'top' : (unitY > 0.4 ? 'bottom' : 'middle'),
                        align: unitX < -0.4 ? 'left' : (unitX > 0.4 ? 'right' : 'center')
267 268 269
                    }, {
                        inheritColor: autoColor
                    }),
S
sushuang 已提交
270 271 272
                    silent: true
                }));
            }
L
Gauge  
lang 已提交
273

S
sushuang 已提交
274 275
            // Axis tick
            if (tickModel.get('show') && i !== splitNumber) {
276
                for (let j = 0; j <= subSplitNumber; j++) {
1
100pah 已提交
277 278
                    unitX = Math.cos(angle);
                    unitY = Math.sin(angle);
279
                    const tickLine = new graphic.Line({
L
Gauge  
lang 已提交
280 281 282
                        shape: {
                            x1: unitX * r + cx,
                            y1: unitY * r + cy,
S
sushuang 已提交
283 284
                            x2: unitX * (r - tickLen) + cx,
                            y2: unitY * (r - tickLen) + cy
L
Gauge  
lang 已提交
285
                        },
S
sushuang 已提交
286 287
                        silent: true,
                        style: tickLineStyle
L
Gauge  
lang 已提交
288 289
                    });

S
sushuang 已提交
290 291 292
                    if (tickLineStyle.stroke === 'auto') {
                        tickLine.setStyle({
                            stroke: getColor((i + j / subSplitNumber) / splitNumber)
L
Gauge  
lang 已提交
293 294
                        });
                    }
S
sushuang 已提交
295 296 297

                    group.add(tickLine);
                    angle += subStep;
L
Gauge  
lang 已提交
298
                }
S
sushuang 已提交
299
                angle -= subStep;
L
Gauge  
lang 已提交
300
            }
S
sushuang 已提交
301 302
            else {
                angle += step;
L
lang 已提交
303
            }
S
sushuang 已提交
304
        }
P
pissang 已提交
305
    }
L
lang 已提交
306

P
pissang 已提交
307 308 309 310 311 312 313 314 315
    _renderPointer(
        seriesModel: GaugeSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        getColor: (percent: number) => ColorString,
        posInfo: PosInfo,
        startAngle: number,
        endAngle: number,
        clockwise: boolean
S
sushuang 已提交
316
    ) {
L
lang 已提交
317

318 319
        const group = this.group;
        const oldData = this._data;
L
lang 已提交
320

P
pissang 已提交
321
        if (!seriesModel.get(['pointer', 'show'])) {
S
sushuang 已提交
322 323 324 325 326 327
            // Remove old element
            oldData && oldData.eachItemGraphicEl(function (el) {
                group.remove(el);
            });
            return;
        }
L
lang 已提交
328

329 330
        const valueExtent = [+seriesModel.get('min'), +seriesModel.get('max')];
        const angleExtent = [startAngle, endAngle];
L
lang 已提交
331

332 333
        const data = seriesModel.getData();
        const valueDim = data.mapDimension('value');
L
lang 已提交
334

S
sushuang 已提交
335 336
        data.diff(oldData)
            .add(function (idx) {
337
                const pointer = new PointerPath({
S
sushuang 已提交
338 339 340
                    shape: {
                        angle: startAngle
                    }
L
lang 已提交
341 342
                });

S
sushuang 已提交
343 344
                graphic.initProps(pointer, {
                    shape: {
P
pissang 已提交
345
                        angle: linearMap(data.get(valueDim, idx) as number, valueExtent, angleExtent, true)
S
sushuang 已提交
346 347
                    }
                }, seriesModel);
348

S
sushuang 已提交
349 350 351 352
                group.add(pointer);
                data.setItemGraphicEl(idx, pointer);
            })
            .update(function (newIdx, oldIdx) {
353
                const pointer = oldData.getItemGraphicEl(oldIdx) as PointerPath;
L
Tweak  
lang 已提交
354

S
sushuang 已提交
355 356
                graphic.updateProps(pointer, {
                    shape: {
P
pissang 已提交
357
                        angle: linearMap(data.get(valueDim, newIdx) as number, valueExtent, angleExtent, true)
S
sushuang 已提交
358 359 360 361 362 363 364
                    }
                }, seriesModel);

                group.add(pointer);
                data.setItemGraphicEl(newIdx, pointer);
            })
            .remove(function (idx) {
365
                const pointer = oldData.getItemGraphicEl(idx);
S
sushuang 已提交
366 367 368 369
                group.remove(pointer);
            })
            .execute();

P
pissang 已提交
370
        data.eachItemGraphicEl(function (pointer: PointerPath, idx) {
371 372
            const itemModel = data.getItemModel<GaugeDataItemOption>(idx);
            const pointerModel = itemModel.getModel('pointer');
P
pissang 已提交
373
            const emphasisModel = itemModel.getModel('emphasis');
S
sushuang 已提交
374 375 376 377 378 379 380 381

            pointer.setShape({
                x: posInfo.cx,
                y: posInfo.cy,
                width: parsePercent(
                    pointerModel.get('width'), posInfo.r
                ),
                r: parsePercent(pointerModel.get('length'), posInfo.r)
L
lang 已提交
382
            });
L
Gauge  
lang 已提交
383

384
            pointer.useStyle(itemModel.getModel('itemStyle').getItemStyle());
L
Gauge  
lang 已提交
385

S
sushuang 已提交
386 387
            if (pointer.style.fill === 'auto') {
                pointer.setStyle('fill', getColor(
P
pissang 已提交
388
                    linearMap(data.get(valueDim, idx) as number, valueExtent, [0, 1], true)
S
sushuang 已提交
389
                ));
L
lang 已提交
390
            }
L
Gauge  
lang 已提交
391

P
pissang 已提交
392

393
            setStatesStylesFromModel(pointer, itemModel);
P
pissang 已提交
394
            enableHoverEmphasis(pointer, emphasisModel.get('focus'), emphasisModel.get('blurScope'));
S
sushuang 已提交
395 396 397
        });

        this._data = data;
P
pissang 已提交
398
    }
S
sushuang 已提交
399

P
pissang 已提交
400 401 402 403 404 405
    _renderTitle(
        seriesModel: GaugeSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        getColor: (percent: number) => ColorString,
        posInfo: PosInfo
S
sushuang 已提交
406
    ) {
407 408 409
        const data = seriesModel.getData();
        const valueDim = data.mapDimension('value');
        const titleModel = seriesModel.getModel('title');
S
sushuang 已提交
410
        if (titleModel.get('show')) {
411 412 413 414 415 416 417 418
            const offsetCenter = titleModel.get('offsetCenter');
            const x = posInfo.cx + parsePercent(offsetCenter[0], posInfo.r);
            const y = posInfo.cy + parsePercent(offsetCenter[1], posInfo.r);

            const minVal = +seriesModel.get('min');
            const maxVal = +seriesModel.get('max');
            const value = seriesModel.getData().get(valueDim, 0) as number;
            const autoColor = getColor(
S
sushuang 已提交
419 420
                linearMap(value, [minVal, maxVal], [0, 1], true)
            );
P
pah100 已提交
421

S
sushuang 已提交
422 423
            this.group.add(new graphic.Text({
                silent: true,
424
                style: createTextStyle(titleModel, {
S
sushuang 已提交
425 426 427
                    x: x,
                    y: y,
                    // FIXME First data name ?
428
                    text: data.getName(0),
P
pissang 已提交
429 430
                    align: 'center',
                    verticalAlign: 'middle'
431
                }, {inheritColor: autoColor})
S
sushuang 已提交
432
            }));
L
Gauge  
lang 已提交
433
        }
P
pissang 已提交
434
    }
S
sushuang 已提交
435

P
pissang 已提交
436 437 438 439 440 441
    _renderDetail(
        seriesModel: GaugeSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        getColor: (percent: number) => ColorString,
        posInfo: PosInfo
S
sushuang 已提交
442
    ) {
443 444 445
        const detailModel = seriesModel.getModel('detail');
        const minVal = +seriesModel.get('min');
        const maxVal = +seriesModel.get('max');
S
sushuang 已提交
446
        if (detailModel.get('show')) {
447 448 449 450 451 452 453 454
            const offsetCenter = detailModel.get('offsetCenter');
            const x = posInfo.cx + parsePercent(offsetCenter[0], posInfo.r);
            const y = posInfo.cy + parsePercent(offsetCenter[1], posInfo.r);
            const width = parsePercent(detailModel.get('width'), posInfo.r);
            const height = parsePercent(detailModel.get('height'), posInfo.r);
            const data = seriesModel.getData();
            const value = data.get(data.mapDimension('value'), 0) as number;
            const autoColor = getColor(
S
sushuang 已提交
455 456 457 458 459
                linearMap(value, [minVal, maxVal], [0, 1], true)
            );

            this.group.add(new graphic.Text({
                silent: true,
460
                style: createTextStyle(detailModel, {
S
sushuang 已提交
461 462 463 464 465 466
                    x: x,
                    y: y,
                    text: formatLabel(
                        // FIXME First data name ?
                        value, detailModel.get('formatter')
                    ),
P
pissang 已提交
467 468 469 470
                    width: isNaN(width) ? null : width,
                    height: isNaN(height) ? null : height,
                    align: 'center',
                    verticalAlign: 'middle'
471
                }, {inheritColor: autoColor})
S
sushuang 已提交
472 473 474
            }));
        }
    }
P
pissang 已提交
475
}
L
Gauge  
lang 已提交
476

P
pissang 已提交
477 478
ChartView.registerClass(GaugeView);

479
export default GaugeView;