PiecewiseModel.js 16.4 KB
Newer Older
P
pah100 已提交
1 2
define(function(require) {

P
pah100 已提交
3
    var VisualMapModel = require('./VisualMapModel');
P
pah100 已提交
4
    var zrUtil = require('zrender/core/util');
P
pah100 已提交
5
    var VisualMapping = require('../../visual/VisualMapping');
P
pah100 已提交
6

P
pah100 已提交
7
    var PiecewiseModel = VisualMapModel.extend({
P
pah100 已提交
8

9
        type: 'visualMap.piecewise',
P
pah100 已提交
10

P
pah100 已提交
11 12 13
        /**
         * Order Rule:
         *
P
pah100 已提交
14
         * option.categories / option.pieces / option.text / option.selected:
P
pah100 已提交
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
         *     If !option.inverse,
         *     Order when vertical: ['top', ..., 'bottom'].
         *     Order when horizontal: ['left', ..., 'right'].
         *     If option.inverse, the meaning of
         *     the order should be reversed.
         *
         * this._pieceList:
         *     The order is always [low, ..., high].
         *
         * Mapping from location to low-high:
         *     If !option.inverse
         *     When vertical, top is high.
         *     When horizontal, right is high.
         *     If option.inverse, reverse.
         */

P
pah100 已提交
31 32 33 34
        /**
         * @protected
         */
        defaultOption: {
P
pah100 已提交
35 36 37
            selected: null,             // Object. If not specified, means selected.
                                        // When pieces and splitNumber: {'0': true, '5': true}
                                        // When categories: {'cate1': false, 'cate3': true}
P
pah100 已提交
38
                                        // When selected === false, means all unselected.
P
pah100 已提交
39
            align: 'auto',              // 'auto', 'left', 'right'
P
pah100 已提交
40 41 42 43
            itemWidth: 20,              // When put the controller vertically, it is the length of
                                        // horizontal side of each item. Otherwise, vertical side.
            itemHeight: 14,             // When put the controller vertically, it is the length of
                                        // vertical side of each item. Otherwise, horizontal side.
P
pah100 已提交
44
            itemSymbol: 'roundRect',
P
pah100 已提交
45
            pieceList: null,            // Each item is Object, with some of those attrs:
46 47
                                        // {min, max, lt, gt, lte, gte, value,
                                        // color, colorSaturation, colorAlpha, opacity,
P
pah100 已提交
48 49 50 51 52 53 54 55 56 57 58
                                        // symbol, symbolSize}, which customize the range or visual
                                        // coding of the certain piece. Besides, see "Order Rule".
            categories: null,           // category names, like: ['some1', 'some2', 'some3'].
                                        // Attr min/max are ignored when categories set. See "Order Rule"
            splitNumber: 5,             // If set to 5, auto split five pieces equally.
                                        // If set to 0 and component type not set, component type will be
                                        // determined as "continuous". (It is less reasonable but for ec2
                                        // compatibility, see echarts/component/visualMap/typeDefaulter)
            selectedMode: 'multiple',   // Can be 'multiple' or 'single'.
            itemGap: 10,                // The gap between two items, in px.
            hoverLink: true             // Enable hover highlight.
P
pah100 已提交
59 60 61 62 63
        },

        /**
         * @override
         */
P
pah100 已提交
64 65
        optionUpdated: function (newOption, isInit) {
            PiecewiseModel.superApply(this, 'optionUpdated', arguments);
P
pah100 已提交
66 67

            /**
P
pah100 已提交
68
             * The order is always [low, ..., high].
P
pah100 已提交
69 70 71 72 73 74
             * [{text: string, interval: Array.<number>}, ...]
             * @private
             * @type {Array.<Object>}
             */
            this._pieceList = [];

P
pah100 已提交
75
            this.resetTargetSeries();
P
pah100 已提交
76 77
            this.resetExtent();

P
pah100 已提交
78 79 80 81
            /**
             * 'pieces', 'categories', 'splitNumber'
             * @type {string}
             */
P
pah100 已提交
82
            var mode = this._mode = this._determineMode();
P
pah100 已提交
83

P
pah100 已提交
84
            resetMethods[this._mode].call(this);
P
pah100 已提交
85

P
pah100 已提交
86
            this._resetSelected(newOption, isInit);
P
pah100 已提交
87

P
pah100 已提交
88
            var categories = this.option.categories;
P
pah100 已提交
89

P
pah100 已提交
90
            this.resetVisual(function (mappingOption, state) {
P
pah100 已提交
91 92 93 94 95
                if (mode === 'categories') {
                    mappingOption.mappingMethod = 'category';
                    mappingOption.categories = zrUtil.clone(categories);
                }
                else {
P
pah100 已提交
96
                    mappingOption.dataExtent = this.getExtent();
P
pah100 已提交
97 98 99 100 101 102 103 104 105
                    mappingOption.mappingMethod = 'piecewise';
                    mappingOption.pieceList = zrUtil.map(this._pieceList, function (piece) {
                        var piece = zrUtil.clone(piece);
                        if (state !== 'inRange') {
                            piece.visual = null;
                        }
                        return piece;
                    });
                }
P
pah100 已提交
106
            });
P
pah100 已提交
107 108
        },

P
pah100 已提交
109
        _resetSelected: function (newOption, isInit) {
P
pah100 已提交
110 111 112
            var thisOption = this.option;
            var pieceList = this._pieceList;

P
pah100 已提交
113 114 115 116 117 118 119 120 121
            // Selected do not merge but all override.
            var selected = (isInit ? thisOption : newOption).selected || {};
            thisOption.selected = selected;

            // Consider 'not specified' means true.
            zrUtil.each(pieceList, function (piece, index) {
                var key = this.getSelectedMapKey(piece);
                if (!(key in selected)) {
                    selected[key] = true;
P
pah100 已提交
122
                }
P
pah100 已提交
123
            }, this);
P
pah100 已提交
124

P
pah100 已提交
125
            if (thisOption.selectedMode === 'single') {
P
pah100 已提交
126 127
                // Ensure there is only one selected.
                var hasSel = false;
P
pah100 已提交
128

P
pah100 已提交
129
                zrUtil.each(pieceList, function (piece, index) {
P
pah100 已提交
130 131
                    var key = this.getSelectedMapKey(piece);
                    if (selected[key]) {
P
pah100 已提交
132
                        hasSel
P
pah100 已提交
133
                            ? (selected[key] = false)
P
pah100 已提交
134 135
                            : (hasSel = true);
                    }
P
pah100 已提交
136
                }, this);
P
pah100 已提交
137
            }
P
pah100 已提交
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
            // thisOption.selectedMode === 'multiple', default: all selected.
        },

        /**
         * @public
         */
        getSelectedMapKey: function (piece) {
            return this._mode === 'categories'
                ? piece.value + '' : piece.index + '';
        },

        /**
         * @public
         */
        getPieceList: function () {
            return this._pieceList;
        },

        /**
         * @private
         * @return {string}
         */
P
pah100 已提交
160
        _determineMode: function () {
P
pah100 已提交
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
            var option = this.option;

            return option.pieces && option.pieces.length > 0
                ? 'pieces'
                : this.option.categories
                ? 'categories'
                : 'splitNumber';
        },

        /**
         * @public
         * @override
         */
        setSelected: function (selected) {
            this.option.selected = zrUtil.clone(selected);
P
pah100 已提交
176 177
        },

P
pah100 已提交
178 179 180 181 182
        /**
         * @public
         * @override
         */
        getValueState: function (value) {
P
pah100 已提交
183
            var index = VisualMapping.findPieceIndex(value, this._pieceList);
P
pah100 已提交
184 185

            return index != null
P
pah100 已提交
186
                ? (this.option.selected[this.getSelectedMapKey(this._pieceList[index])]
P
pah100 已提交
187 188 189
                    ? 'inRange' : 'outOfRange'
                )
                : 'outOfRange';
P
pah100 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
        },

        /**
         * @public
         * @params {number} pieceIndex piece index in visualMapModel.getPieceList()
         * @return {Array.<Object>} [{seriesId, dataIndices: <Array.<number>>}, ...]
         */
        findTargetDataIndices: function (pieceIndex) {
            var result = [];

            this.eachTargetSeries(function (seriesModel) {
                var dataIndices = [];
                var data = seriesModel.getData();

                data.each(this.getDataDimension(data), function (value, dataIndex) {
                    // Should always base on model pieceList, because it is order sensitive.
                    var pIdx = VisualMapping.findPieceIndex(value, this._pieceList);
                    pIdx === pieceIndex && dataIndices.push(dataIndex);
                }, true, this);

P
pah100 已提交
210
                result.push({seriesId: seriesModel.id, dataIndex: dataIndices});
P
pah100 已提交
211 212
            }, this);

P
pah100 已提交
213 214 215
            return result;
        },

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
        /**
         * @private
         */
        getRepresentValue: function (piece) {
            var representValue;
            if (this.isCategory()) {
                representValue = piece.value;
            }
            else {
                if (piece.value != null) {
                    representValue = piece.value;
                }
                else {
                    var pieceInterval = piece.interval || [];
                    representValue = (pieceInterval[0] + pieceInterval[1]) / 2;
                }
            }
            return representValue;
        },

        getStops: function (seriesModel, getColorVisual) {
P
pah100 已提交
237
            var result = [];
238 239
            var model = this;

P
pah100 已提交
240
            if (this.isTargetSeries(seriesModel)) {
241
                var curr = -Infinity;
P
pah100 已提交
242 243 244 245
                zrUtil.each(this._pieceList, function (piece) {
                    // Do not support category yet.
                    var interval = piece.interval;
                    if (interval) {
246 247 248 249 250
                        interval[0] > curr && setPiece({
                            interval: [curr, interval[0]],
                            valueState: 'outOfRange'
                        });
                        setPiece({
P
pah100 已提交
251 252 253
                            interval: interval.slice(),
                            valueState: this.getValueState((interval[0] + interval[1]) / 2)
                        });
P
pah100 已提交
254
                        curr = interval[1];
P
pah100 已提交
255 256 257
                    }
                }, this);
            }
P
pah100 已提交
258
            return result;
259 260 261 262 263 264 265

            function setPiece(piece) {
                result.push(piece);
                piece.color = getColorVisual(
                    model, model.getRepresentValue(piece), piece.valueState
                );
            }
P
pah100 已提交
266 267 268 269 270 271 272 273 274 275 276 277
        }

    });

    /**
     * Key is this._mode
     * @type {Object}
     * @this {module:echarts/component/viusalMap/PiecewiseMode}
     */
    var resetMethods = {

        splitNumber: function () {
P
pah100 已提交
278
            var thisOption = this.option;
279
            var pieceList = this._pieceList;
P
pah100 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
            var precision = thisOption.precision;
            var dataExtent = this.getExtent();
            var splitNumber = thisOption.splitNumber;
            splitNumber = Math.max(parseInt(splitNumber, 10), 1);
            thisOption.splitNumber = splitNumber;

            var splitStep = (dataExtent[1] - dataExtent[0]) / splitNumber;
            // Precision auto-adaption
            while (+splitStep.toFixed(precision) !== splitStep && precision < 5) {
                precision++;
            }
            thisOption.precision = precision;
            splitStep = +splitStep.toFixed(precision);

            for (var i = 0, curr = dataExtent[0]; i < splitNumber; i++, curr += splitStep) {
                var max = i === splitNumber - 1 ? dataExtent[1] : (curr + splitStep);

297
                pieceList.push({
P
pah100 已提交
298
                    index: i,
299 300
                    interval: [curr, max],
                    close: [1, 1]
P
pah100 已提交
301 302
                });
            }
303 304 305 306 307 308

            normalizePieces(pieceList);

            zrUtil.each(pieceList, function (piece) {
                piece.text = this.formatValueText(piece.interval);
            }, this);
P
pah100 已提交
309 310
        },

P
pah100 已提交
311
        categories: function () {
P
pah100 已提交
312 313 314 315
            var thisOption = this.option;
            zrUtil.each(thisOption.categories, function (cate) {
                // FIXME category模式也使用pieceList,但在visualMapping中不是使用pieceList。
                // 是否改一致。
P
pah100 已提交
316
                this._pieceList.push({
P
pah100 已提交
317
                    text: this.formatValueText(cate, true),
P
pah100 已提交
318 319 320
                    value: cate
                });
            }, this);
P
pah100 已提交
321 322

            // See "Order Rule".
P
pah100 已提交
323
            normalizeReverse(thisOption, this._pieceList);
P
pah100 已提交
324 325
        },

P
pah100 已提交
326
        pieces: function () {
P
pah100 已提交
327
            var thisOption = this.option;
328 329
            var pieceList = this._pieceList;

P
pah100 已提交
330
            zrUtil.each(thisOption.pieces, function (pieceListItem, index) {
P
pah100 已提交
331

P
pah100 已提交
332 333
                if (!zrUtil.isObject(pieceListItem)) {
                    pieceListItem = {value: pieceListItem};
P
pah100 已提交
334 335
                }

P
pah100 已提交
336
                var item = {text: '', index: index};
P
pah100 已提交
337

P
pah100 已提交
338 339
                if (pieceListItem.label != null) {
                    item.text = pieceListItem.label;
P
pah100 已提交
340
                }
P
pah100 已提交
341

P
pah100 已提交
342
                if (pieceListItem.hasOwnProperty('value')) {
343 344 345
                    var value = item.value = pieceListItem.value;
                    item.interval = [value, value];
                    item.close = [1, 1];
P
pah100 已提交
346 347
                }
                else {
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
                    // `min` `max` is legacy option.
                    // `lt` `gt` `lte` `gte` is recommanded.
                    var interval = item.interval = [];
                    var close = item.close = [0, 0];

                    var closeList = [1, 0, 1];
                    var infinityList = [-Infinity, Infinity];

                    var useMinMax = [];
                    for (var lg = 0; lg < 2; lg++) {
                        var names = [['gte', 'gt', 'min'], ['lte', 'lt', 'max']][lg];
                        for (var i = 0; i < 3 && interval[lg] == null; i++) {
                            interval[lg] = pieceListItem[names[i]];
                            close[lg] = closeList[i];
                            useMinMax[lg] = i === 2;
                        }
                        interval[lg] == null && (interval[lg] = infinityList[lg]);
                    }
                    useMinMax[0] && interval[1] === Infinity && (close[0] = 0);
                    useMinMax[1] && interval[0] === -Infinity && (close[1] = 0);

                    if (__DEV__) {
                        if (interval[0] > interval[1]) {
                            console.warn(
                                'Piece ' + index + 'is illegal: ' + interval
                                + ' lower bound should not greater then uppper bound.'
                            );
                        }
P
pah100 已提交
376
                    }
P
pah100 已提交
377

378 379 380 381
                    if (interval[0] === interval[1] && close[0] && close[1]) {
                        // Consider: [{min: 5, max: 5, visual: {...}}, {min: 0, max: 5}],
                        // we use value to lift the priority when min === max
                        item.value = interval[0];
P
pah100 已提交
382
                    }
P
pah100 已提交
383 384
                }

P
pah100 已提交
385
                item.visual = VisualMapping.retrieveVisuals(pieceListItem);
P
pah100 已提交
386

387
                pieceList.push(item);
P
pah100 已提交
388

P
pah100 已提交
389 390 391
            }, this);

            // See "Order Rule".
392 393 394 395 396 397 398 399 400 401 402 403 404
            normalizeReverse(thisOption, pieceList);
            // Only pieces
            normalizePieces(pieceList);

            zrUtil.each(pieceList, function (piece) {
                var close = piece.close;
                var edgeSymbols = [['<', ''][close[1]], ['>', ''][close[0]]];
                piece.text = piece.text || this.formatValueText(
                    piece.value != null ? piece.value : piece.interval,
                    false,
                    edgeSymbols
                );
            }, this);
P
pah100 已提交
405
        }
P
pah100 已提交
406
    };
P
pah100 已提交
407

408
    function normalizeReverse(thisOption, pieceList) {
P
pah100 已提交
409 410
        var inverse = thisOption.inverse;
        if (thisOption.orient === 'vertical' ? !inverse : inverse) {
411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
             pieceList.reverse();
        }
    }

    // Reorder, remove duplicate, which are needed when using gradient.
    // Not applicable for categories.
    function normalizePieces(pieceList) {
        pieceList.sort(function (a, b) {
            return littleThan(a, b) ? -1 : 1;
        });

        var curr = -Infinity;
        for (var i = 0; i < pieceList.length; i++) {
            var interval = pieceList[i].interval;
            var close = pieceList[i].close;
            for (var lg = 0; lg < 2; lg++) {
                if (interval[lg] < curr) {
                    interval[lg] = curr;
                    close[lg] = 1 - lg;
                }
                curr = interval[lg];
            }
        }
        // console.log(JSON.stringify(pieceList.map(a => a.interval)));

        function littleThan(piece, standard, lg) {
            lg = lg || 0;
            return piece.interval[lg] < standard.interval[lg]
                || (
                    piece.interval[lg] === standard.interval[lg]
                    && (
                        +piece.close[lg] > standard.close[lg]
                        || littleThan(piece, standard, 1)
                    )
                );
P
pah100 已提交
446 447
        }
    }
P
pah100 已提交
448

P
pah100 已提交
449
    return PiecewiseModel;
P
pah100 已提交
450
});