123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612 |
- /*
- * 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.
- */
- var zrUtil = require("zrender/lib/core/util");
- var zrColor = require("zrender/lib/tool/color");
- var _number = require("../util/number");
- var linearMap = _number.linearMap;
- /*
- * 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.
- */
- var each = zrUtil.each;
- var isObject = zrUtil.isObject;
- var CATEGORY_DEFAULT_VISUAL_INDEX = -1;
- /**
- * @param {Object} option
- * @param {string} [option.type] See visualHandlers.
- * @param {string} [option.mappingMethod] 'linear' or 'piecewise' or 'category' or 'fixed'
- * @param {Array.<number>=} [option.dataExtent] [minExtent, maxExtent],
- * required when mappingMethod is 'linear'
- * @param {Array.<Object>=} [option.pieceList] [
- * {value: someValue},
- * {interval: [min1, max1], visual: {...}},
- * {interval: [min2, max2]}
- * ],
- * required when mappingMethod is 'piecewise'.
- * Visual for only each piece can be specified.
- * @param {Array.<string|Object>=} [option.categories] ['cate1', 'cate2']
- * required when mappingMethod is 'category'.
- * If no option.categories, categories is set
- * as [0, 1, 2, ...].
- * @param {boolean} [option.loop=false] Whether loop mapping when mappingMethod is 'category'.
- * @param {(Array|Object|*)} [option.visual] Visual data.
- * when mappingMethod is 'category',
- * visual data can be array or object
- * (like: {cate1: '#222', none: '#fff'})
- * or primary types (which represents
- * default category visual), otherwise visual
- * can be array or primary (which will be
- * normalized to array).
- *
- */
- var VisualMapping = function (option) {
- var mappingMethod = option.mappingMethod;
- var visualType = option.type;
- /**
- * @readOnly
- * @type {Object}
- */
- var thisOption = this.option = zrUtil.clone(option);
- /**
- * @readOnly
- * @type {string}
- */
- this.type = visualType;
- /**
- * @readOnly
- * @type {string}
- */
- this.mappingMethod = mappingMethod;
- /**
- * @private
- * @type {Function}
- */
- this._normalizeData = normalizers[mappingMethod];
- var visualHandler = visualHandlers[visualType];
- /**
- * @public
- * @type {Function}
- */
- this.applyVisual = visualHandler.applyVisual;
- /**
- * @public
- * @type {Function}
- */
- this.getColorMapper = visualHandler.getColorMapper;
- /**
- * @private
- * @type {Function}
- */
- this._doMap = visualHandler._doMap[mappingMethod];
- if (mappingMethod === 'piecewise') {
- normalizeVisualRange(thisOption);
- preprocessForPiecewise(thisOption);
- } else if (mappingMethod === 'category') {
- thisOption.categories ? preprocessForSpecifiedCategory(thisOption) // categories is ordinal when thisOption.categories not specified,
- // which need no more preprocess except normalize visual.
- : normalizeVisualRange(thisOption, true);
- } else {
- // mappingMethod === 'linear' or 'fixed'
- zrUtil.assert(mappingMethod !== 'linear' || thisOption.dataExtent);
- normalizeVisualRange(thisOption);
- }
- };
- VisualMapping.prototype = {
- constructor: VisualMapping,
- mapValueToVisual: function (value) {
- var normalized = this._normalizeData(value);
- return this._doMap(normalized, value);
- },
- getNormalizer: function () {
- return zrUtil.bind(this._normalizeData, this);
- }
- };
- var visualHandlers = VisualMapping.visualHandlers = {
- color: {
- applyVisual: makeApplyVisual('color'),
- /**
- * Create a mapper function
- * @return {Function}
- */
- getColorMapper: function () {
- var thisOption = this.option;
- return zrUtil.bind(thisOption.mappingMethod === 'category' ? function (value, isNormalized) {
- !isNormalized && (value = this._normalizeData(value));
- return doMapCategory.call(this, value);
- } : function (value, isNormalized, out) {
- // If output rgb array
- // which will be much faster and useful in pixel manipulation
- var returnRGBArray = !!out;
- !isNormalized && (value = this._normalizeData(value));
- out = zrColor.fastLerp(value, thisOption.parsedVisual, out);
- return returnRGBArray ? out : zrColor.stringify(out, 'rgba');
- }, this);
- },
- _doMap: {
- linear: function (normalized) {
- return zrColor.stringify(zrColor.fastLerp(normalized, this.option.parsedVisual), 'rgba');
- },
- category: doMapCategory,
- piecewise: function (normalized, value) {
- var result = getSpecifiedVisual.call(this, value);
- if (result == null) {
- result = zrColor.stringify(zrColor.fastLerp(normalized, this.option.parsedVisual), 'rgba');
- }
- return result;
- },
- fixed: doMapFixed
- }
- },
- colorHue: makePartialColorVisualHandler(function (color, value) {
- return zrColor.modifyHSL(color, value);
- }),
- colorSaturation: makePartialColorVisualHandler(function (color, value) {
- return zrColor.modifyHSL(color, null, value);
- }),
- colorLightness: makePartialColorVisualHandler(function (color, value) {
- return zrColor.modifyHSL(color, null, null, value);
- }),
- colorAlpha: makePartialColorVisualHandler(function (color, value) {
- return zrColor.modifyAlpha(color, value);
- }),
- opacity: {
- applyVisual: makeApplyVisual('opacity'),
- _doMap: makeDoMap([0, 1])
- },
- liftZ: {
- applyVisual: makeApplyVisual('liftZ'),
- _doMap: {
- linear: doMapFixed,
- category: doMapFixed,
- piecewise: doMapFixed,
- fixed: doMapFixed
- }
- },
- symbol: {
- applyVisual: function (value, getter, setter) {
- var symbolCfg = this.mapValueToVisual(value);
- if (zrUtil.isString(symbolCfg)) {
- setter('symbol', symbolCfg);
- } else if (isObject(symbolCfg)) {
- for (var name in symbolCfg) {
- if (symbolCfg.hasOwnProperty(name)) {
- setter(name, symbolCfg[name]);
- }
- }
- }
- },
- _doMap: {
- linear: doMapToArray,
- category: doMapCategory,
- piecewise: function (normalized, value) {
- var result = getSpecifiedVisual.call(this, value);
- if (result == null) {
- result = doMapToArray.call(this, normalized);
- }
- return result;
- },
- fixed: doMapFixed
- }
- },
- symbolSize: {
- applyVisual: makeApplyVisual('symbolSize'),
- _doMap: makeDoMap([0, 1])
- }
- };
- function preprocessForPiecewise(thisOption) {
- var pieceList = thisOption.pieceList;
- thisOption.hasSpecialVisual = false;
- zrUtil.each(pieceList, function (piece, index) {
- piece.originIndex = index; // piece.visual is "result visual value" but not
- // a visual range, so it does not need to be normalized.
- if (piece.visual != null) {
- thisOption.hasSpecialVisual = true;
- }
- });
- }
- function preprocessForSpecifiedCategory(thisOption) {
- // Hash categories.
- var categories = thisOption.categories;
- var visual = thisOption.visual;
- var categoryMap = thisOption.categoryMap = {};
- each(categories, function (cate, index) {
- categoryMap[cate] = index;
- }); // Process visual map input.
- if (!zrUtil.isArray(visual)) {
- var visualArr = [];
- if (zrUtil.isObject(visual)) {
- each(visual, function (v, cate) {
- var index = categoryMap[cate];
- visualArr[index != null ? index : CATEGORY_DEFAULT_VISUAL_INDEX] = v;
- });
- } else {
- // Is primary type, represents default visual.
- visualArr[CATEGORY_DEFAULT_VISUAL_INDEX] = visual;
- }
- visual = setVisualToOption(thisOption, visualArr);
- } // Remove categories that has no visual,
- // then we can mapping them to CATEGORY_DEFAULT_VISUAL_INDEX.
- for (var i = categories.length - 1; i >= 0; i--) {
- if (visual[i] == null) {
- delete categoryMap[categories[i]];
- categories.pop();
- }
- }
- }
- function normalizeVisualRange(thisOption, isCategory) {
- var visual = thisOption.visual;
- var visualArr = [];
- if (zrUtil.isObject(visual)) {
- each(visual, function (v) {
- visualArr.push(v);
- });
- } else if (visual != null) {
- visualArr.push(visual);
- }
- var doNotNeedPair = {
- color: 1,
- symbol: 1
- };
- if (!isCategory && visualArr.length === 1 && !doNotNeedPair.hasOwnProperty(thisOption.type)) {
- // Do not care visualArr.length === 0, which is illegal.
- visualArr[1] = visualArr[0];
- }
- setVisualToOption(thisOption, visualArr);
- }
- function makePartialColorVisualHandler(applyValue) {
- return {
- applyVisual: function (value, getter, setter) {
- value = this.mapValueToVisual(value); // Must not be array value
- setter('color', applyValue(getter('color'), value));
- },
- _doMap: makeDoMap([0, 1])
- };
- }
- function doMapToArray(normalized) {
- var visual = this.option.visual;
- return visual[Math.round(linearMap(normalized, [0, 1], [0, visual.length - 1], true))] || {};
- }
- function makeApplyVisual(visualType) {
- return function (value, getter, setter) {
- setter(visualType, this.mapValueToVisual(value));
- };
- }
- function doMapCategory(normalized) {
- var visual = this.option.visual;
- return visual[this.option.loop && normalized !== CATEGORY_DEFAULT_VISUAL_INDEX ? normalized % visual.length : normalized];
- }
- function doMapFixed() {
- return this.option.visual[0];
- }
- function makeDoMap(sourceExtent) {
- return {
- linear: function (normalized) {
- return linearMap(normalized, sourceExtent, this.option.visual, true);
- },
- category: doMapCategory,
- piecewise: function (normalized, value) {
- var result = getSpecifiedVisual.call(this, value);
- if (result == null) {
- result = linearMap(normalized, sourceExtent, this.option.visual, true);
- }
- return result;
- },
- fixed: doMapFixed
- };
- }
- function getSpecifiedVisual(value) {
- var thisOption = this.option;
- var pieceList = thisOption.pieceList;
- if (thisOption.hasSpecialVisual) {
- var pieceIndex = VisualMapping.findPieceIndex(value, pieceList);
- var piece = pieceList[pieceIndex];
- if (piece && piece.visual) {
- return piece.visual[this.type];
- }
- }
- }
- function setVisualToOption(thisOption, visualArr) {
- thisOption.visual = visualArr;
- if (thisOption.type === 'color') {
- thisOption.parsedVisual = zrUtil.map(visualArr, function (item) {
- return zrColor.parse(item);
- });
- }
- return visualArr;
- }
- /**
- * Normalizers by mapping methods.
- */
- var normalizers = {
- linear: function (value) {
- return linearMap(value, this.option.dataExtent, [0, 1], true);
- },
- piecewise: function (value) {
- var pieceList = this.option.pieceList;
- var pieceIndex = VisualMapping.findPieceIndex(value, pieceList, true);
- if (pieceIndex != null) {
- return linearMap(pieceIndex, [0, pieceList.length - 1], [0, 1], true);
- }
- },
- category: function (value) {
- var index = this.option.categories ? this.option.categoryMap[value] : value; // ordinal
- return index == null ? CATEGORY_DEFAULT_VISUAL_INDEX : index;
- },
- fixed: zrUtil.noop
- };
- /**
- * List available visual types.
- *
- * @public
- * @return {Array.<string>}
- */
- VisualMapping.listVisualTypes = function () {
- var visualTypes = [];
- zrUtil.each(visualHandlers, function (handler, key) {
- visualTypes.push(key);
- });
- return visualTypes;
- };
- /**
- * @public
- */
- VisualMapping.addVisualHandler = function (name, handler) {
- visualHandlers[name] = handler;
- };
- /**
- * @public
- */
- VisualMapping.isValidType = function (visualType) {
- return visualHandlers.hasOwnProperty(visualType);
- };
- /**
- * Convinent method.
- * Visual can be Object or Array or primary type.
- *
- * @public
- */
- VisualMapping.eachVisual = function (visual, callback, context) {
- if (zrUtil.isObject(visual)) {
- zrUtil.each(visual, callback, context);
- } else {
- callback.call(context, visual);
- }
- };
- VisualMapping.mapVisual = function (visual, callback, context) {
- var isPrimary;
- var newVisual = zrUtil.isArray(visual) ? [] : zrUtil.isObject(visual) ? {} : (isPrimary = true, null);
- VisualMapping.eachVisual(visual, function (v, key) {
- var newVal = callback.call(context, v, key);
- isPrimary ? newVisual = newVal : newVisual[key] = newVal;
- });
- return newVisual;
- };
- /**
- * @public
- * @param {Object} obj
- * @return {Object} new object containers visual values.
- * If no visuals, return null.
- */
- VisualMapping.retrieveVisuals = function (obj) {
- var ret = {};
- var hasVisual;
- obj && each(visualHandlers, function (h, visualType) {
- if (obj.hasOwnProperty(visualType)) {
- ret[visualType] = obj[visualType];
- hasVisual = true;
- }
- });
- return hasVisual ? ret : null;
- };
- /**
- * Give order to visual types, considering colorSaturation, colorAlpha depends on color.
- *
- * @public
- * @param {(Object|Array)} visualTypes If Object, like: {color: ..., colorSaturation: ...}
- * IF Array, like: ['color', 'symbol', 'colorSaturation']
- * @return {Array.<string>} Sorted visual types.
- */
- VisualMapping.prepareVisualTypes = function (visualTypes) {
- if (isObject(visualTypes)) {
- var types = [];
- each(visualTypes, function (item, type) {
- types.push(type);
- });
- visualTypes = types;
- } else if (zrUtil.isArray(visualTypes)) {
- visualTypes = visualTypes.slice();
- } else {
- return [];
- }
- visualTypes.sort(function (type1, type2) {
- // color should be front of colorSaturation, colorAlpha, ...
- // symbol and symbolSize do not matter.
- return type2 === 'color' && type1 !== 'color' && type1.indexOf('color') === 0 ? 1 : -1;
- });
- return visualTypes;
- };
- /**
- * 'color', 'colorSaturation', 'colorAlpha', ... are depends on 'color'.
- * Other visuals are only depends on themself.
- *
- * @public
- * @param {string} visualType1
- * @param {string} visualType2
- * @return {boolean}
- */
- VisualMapping.dependsOn = function (visualType1, visualType2) {
- return visualType2 === 'color' ? !!(visualType1 && visualType1.indexOf(visualType2) === 0) : visualType1 === visualType2;
- };
- /**
- * @param {number} value
- * @param {Array.<Object>} pieceList [{value: ..., interval: [min, max]}, ...]
- * Always from small to big.
- * @param {boolean} [findClosestWhenOutside=false]
- * @return {number} index
- */
- VisualMapping.findPieceIndex = function (value, pieceList, findClosestWhenOutside) {
- var possibleI;
- var abs = Infinity; // value has the higher priority.
- for (var i = 0, len = pieceList.length; i < len; i++) {
- var pieceValue = pieceList[i].value;
- if (pieceValue != null) {
- if (pieceValue === value // FIXME
- // It is supposed to compare value according to value type of dimension,
- // but currently value type can exactly be string or number.
- // Compromise for numeric-like string (like '12'), especially
- // in the case that visualMap.categories is ['22', '33'].
- || typeof pieceValue === 'string' && pieceValue === value + '') {
- return i;
- }
- findClosestWhenOutside && updatePossible(pieceValue, i);
- }
- }
- for (var i = 0, len = pieceList.length; i < len; i++) {
- var piece = pieceList[i];
- var interval = piece.interval;
- var close = piece.close;
- if (interval) {
- if (interval[0] === -Infinity) {
- if (littleThan(close[1], value, interval[1])) {
- return i;
- }
- } else if (interval[1] === Infinity) {
- if (littleThan(close[0], interval[0], value)) {
- return i;
- }
- } else if (littleThan(close[0], interval[0], value) && littleThan(close[1], value, interval[1])) {
- return i;
- }
- findClosestWhenOutside && updatePossible(interval[0], i);
- findClosestWhenOutside && updatePossible(interval[1], i);
- }
- }
- if (findClosestWhenOutside) {
- return value === Infinity ? pieceList.length - 1 : value === -Infinity ? 0 : possibleI;
- }
- function updatePossible(val, index) {
- var newAbs = Math.abs(val - value);
- if (newAbs < abs) {
- abs = newAbs;
- possibleI = index;
- }
- }
- };
- function littleThan(close, a, b) {
- return close ? a <= b : a < b;
- }
- var _default = VisualMapping;
- module.exports = _default;
|