| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 | 
/** 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 _number = require("../util/number");var parsePercent = _number.parsePercent;var _dataStackHelper = require("../data/helper/dataStackHelper");var isDimensionStacked = _dataStackHelper.isDimensionStacked;var createRenderPlanner = require("../chart/helper/createRenderPlanner");/** 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.*//* global Float32Array */var STACK_PREFIX = '__ec_stack_';var LARGE_BAR_MIN_WIDTH = 0.5;var LargeArr = typeof Float32Array !== 'undefined' ? Float32Array : Array;function getSeriesStackId(seriesModel) {  return seriesModel.get('stack') || STACK_PREFIX + seriesModel.seriesIndex;}function getAxisKey(axis) {  return axis.dim + axis.index;}/** * @param {Object} opt * @param {module:echarts/coord/Axis} opt.axis Only support category axis currently. * @param {number} opt.count Positive interger. * @param {number} [opt.barWidth] * @param {number} [opt.barMaxWidth] * @param {number} [opt.barMinWidth] * @param {number} [opt.barGap] * @param {number} [opt.barCategoryGap] * @return {Object} {width, offset, offsetCenter} If axis.type is not 'category', return undefined. */function getLayoutOnAxis(opt) {  var params = [];  var baseAxis = opt.axis;  var axisKey = 'axis0';  if (baseAxis.type !== 'category') {    return;  }  var bandWidth = baseAxis.getBandWidth();  for (var i = 0; i < opt.count || 0; i++) {    params.push(zrUtil.defaults({      bandWidth: bandWidth,      axisKey: axisKey,      stackId: STACK_PREFIX + i    }, opt));  }  var widthAndOffsets = doCalBarWidthAndOffset(params);  var result = [];  for (var i = 0; i < opt.count; i++) {    var item = widthAndOffsets[axisKey][STACK_PREFIX + i];    item.offsetCenter = item.offset + item.width / 2;    result.push(item);  }  return result;}function prepareLayoutBarSeries(seriesType, ecModel) {  var seriesModels = [];  ecModel.eachSeriesByType(seriesType, function (seriesModel) {    // Check series coordinate, do layout for cartesian2d only    if (isOnCartesian(seriesModel) && !isInLargeMode(seriesModel)) {      seriesModels.push(seriesModel);    }  });  return seriesModels;}/** * Map from (baseAxis.dim + '_' + baseAxis.index) to min gap of two adjacent * values. * This works for time axes, value axes, and log axes. * For a single time axis, return value is in the form like * {'x_0': [1000000]}. * The value of 1000000 is in milliseconds. */function getValueAxesMinGaps(barSeries) {  /**   * Map from axis.index to values.   * For a single time axis, axisValues is in the form like   * {'x_0': [1495555200000, 1495641600000, 1495728000000]}.   * Items in axisValues[x], e.g. 1495555200000, are time values of all   * series.   */  var axisValues = {};  zrUtil.each(barSeries, function (seriesModel) {    var cartesian = seriesModel.coordinateSystem;    var baseAxis = cartesian.getBaseAxis();    if (baseAxis.type !== 'time' && baseAxis.type !== 'value') {      return;    }    var data = seriesModel.getData();    var key = baseAxis.dim + '_' + baseAxis.index;    var dim = data.mapDimension(baseAxis.dim);    for (var i = 0, cnt = data.count(); i < cnt; ++i) {      var value = data.get(dim, i);      if (!axisValues[key]) {        // No previous data for the axis        axisValues[key] = [value];      } else {        // No value in previous series        axisValues[key].push(value);      } // Ignore duplicated time values in the same axis    }  });  var axisMinGaps = [];  for (var key in axisValues) {    if (axisValues.hasOwnProperty(key)) {      var valuesInAxis = axisValues[key];      if (valuesInAxis) {        // Sort axis values into ascending order to calculate gaps        valuesInAxis.sort(function (a, b) {          return a - b;        });        var min = null;        for (var j = 1; j < valuesInAxis.length; ++j) {          var delta = valuesInAxis[j] - valuesInAxis[j - 1];          if (delta > 0) {            // Ignore 0 delta because they are of the same axis value            min = min === null ? delta : Math.min(min, delta);          }        } // Set to null if only have one data        axisMinGaps[key] = min;      }    }  }  return axisMinGaps;}function makeColumnLayout(barSeries) {  var axisMinGaps = getValueAxesMinGaps(barSeries);  var seriesInfoList = [];  zrUtil.each(barSeries, function (seriesModel) {    var cartesian = seriesModel.coordinateSystem;    var baseAxis = cartesian.getBaseAxis();    var axisExtent = baseAxis.getExtent();    var bandWidth;    if (baseAxis.type === 'category') {      bandWidth = baseAxis.getBandWidth();    } else if (baseAxis.type === 'value' || baseAxis.type === 'time') {      var key = baseAxis.dim + '_' + baseAxis.index;      var minGap = axisMinGaps[key];      var extentSpan = Math.abs(axisExtent[1] - axisExtent[0]);      var scale = baseAxis.scale.getExtent();      var scaleSpan = Math.abs(scale[1] - scale[0]);      bandWidth = minGap ? extentSpan / scaleSpan * minGap : extentSpan; // When there is only one data value    } else {      var data = seriesModel.getData();      bandWidth = Math.abs(axisExtent[1] - axisExtent[0]) / data.count();    }    var barWidth = parsePercent(seriesModel.get('barWidth'), bandWidth);    var barMaxWidth = parsePercent(seriesModel.get('barMaxWidth'), bandWidth);    var barMinWidth = parsePercent( // barMinWidth by default is 1 in cartesian. Because in value axis,    // the auto-calculated bar width might be less than 1.    seriesModel.get('barMinWidth') || 1, bandWidth);    var barGap = seriesModel.get('barGap');    var barCategoryGap = seriesModel.get('barCategoryGap');    seriesInfoList.push({      bandWidth: bandWidth,      barWidth: barWidth,      barMaxWidth: barMaxWidth,      barMinWidth: barMinWidth,      barGap: barGap,      barCategoryGap: barCategoryGap,      axisKey: getAxisKey(baseAxis),      stackId: getSeriesStackId(seriesModel)    });  });  return doCalBarWidthAndOffset(seriesInfoList);}function doCalBarWidthAndOffset(seriesInfoList) {  // Columns info on each category axis. Key is cartesian name  var columnsMap = {};  zrUtil.each(seriesInfoList, function (seriesInfo, idx) {    var axisKey = seriesInfo.axisKey;    var bandWidth = seriesInfo.bandWidth;    var columnsOnAxis = columnsMap[axisKey] || {      bandWidth: bandWidth,      remainedWidth: bandWidth,      autoWidthCount: 0,      categoryGap: '20%',      gap: '30%',      stacks: {}    };    var stacks = columnsOnAxis.stacks;    columnsMap[axisKey] = columnsOnAxis;    var stackId = seriesInfo.stackId;    if (!stacks[stackId]) {      columnsOnAxis.autoWidthCount++;    }    stacks[stackId] = stacks[stackId] || {      width: 0,      maxWidth: 0    }; // Caution: In a single coordinate system, these barGrid attributes    // will be shared by series. Consider that they have default values,    // only the attributes set on the last series will work.    // Do not change this fact unless there will be a break change.    var barWidth = seriesInfo.barWidth;    if (barWidth && !stacks[stackId].width) {      // See #6312, do not restrict width.      stacks[stackId].width = barWidth;      barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);      columnsOnAxis.remainedWidth -= barWidth;    }    var barMaxWidth = seriesInfo.barMaxWidth;    barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth);    var barMinWidth = seriesInfo.barMinWidth;    barMinWidth && (stacks[stackId].minWidth = barMinWidth);    var barGap = seriesInfo.barGap;    barGap != null && (columnsOnAxis.gap = barGap);    var barCategoryGap = seriesInfo.barCategoryGap;    barCategoryGap != null && (columnsOnAxis.categoryGap = barCategoryGap);  });  var result = {};  zrUtil.each(columnsMap, function (columnsOnAxis, coordSysName) {    result[coordSysName] = {};    var stacks = columnsOnAxis.stacks;    var bandWidth = columnsOnAxis.bandWidth;    var categoryGap = parsePercent(columnsOnAxis.categoryGap, bandWidth);    var barGapPercent = parsePercent(columnsOnAxis.gap, 1);    var remainedWidth = columnsOnAxis.remainedWidth;    var autoWidthCount = columnsOnAxis.autoWidthCount;    var autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);    autoWidth = Math.max(autoWidth, 0); // Find if any auto calculated bar exceeded maxBarWidth    zrUtil.each(stacks, function (column) {      var maxWidth = column.maxWidth;      var minWidth = column.minWidth;      if (!column.width) {        var finalWidth = autoWidth;        if (maxWidth && maxWidth < finalWidth) {          finalWidth = Math.min(maxWidth, remainedWidth);        } // `minWidth` has higher priority. `minWidth` decide that wheter the        // bar is able to be visible. So `minWidth` should not be restricted        // by `maxWidth` or `remainedWidth` (which is from `bandWidth`). In        // the extreme cases for `value` axis, bars are allowed to overlap        // with each other if `minWidth` specified.        if (minWidth && minWidth > finalWidth) {          finalWidth = minWidth;        }        if (finalWidth !== autoWidth) {          column.width = finalWidth;          remainedWidth -= finalWidth + barGapPercent * finalWidth;          autoWidthCount--;        }      } else {        // `barMinWidth/barMaxWidth` has higher priority than `barWidth`, as        // CSS does. Becuase barWidth can be a percent value, where        // `barMaxWidth` can be used to restrict the final width.        var finalWidth = column.width;        if (maxWidth) {          finalWidth = Math.min(finalWidth, maxWidth);        } // `minWidth` has higher priority, as described above        if (minWidth) {          finalWidth = Math.max(finalWidth, minWidth);        }        column.width = finalWidth;        remainedWidth -= finalWidth + barGapPercent * finalWidth;        autoWidthCount--;      }    }); // Recalculate width again    autoWidth = (remainedWidth - categoryGap) / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);    autoWidth = Math.max(autoWidth, 0);    var widthSum = 0;    var lastColumn;    zrUtil.each(stacks, function (column, idx) {      if (!column.width) {        column.width = autoWidth;      }      lastColumn = column;      widthSum += column.width * (1 + barGapPercent);    });    if (lastColumn) {      widthSum -= lastColumn.width * barGapPercent;    }    var offset = -widthSum / 2;    zrUtil.each(stacks, function (column, stackId) {      result[coordSysName][stackId] = result[coordSysName][stackId] || {        bandWidth: bandWidth,        offset: offset,        width: column.width      };      offset += column.width * (1 + barGapPercent);    });  });  return result;}/** * @param {Object} barWidthAndOffset The result of makeColumnLayout * @param {module:echarts/coord/Axis} axis * @param {module:echarts/model/Series} [seriesModel] If not provided, return all. * @return {Object} {stackId: {offset, width}} or {offset, width} if seriesModel provided. */function retrieveColumnLayout(barWidthAndOffset, axis, seriesModel) {  if (barWidthAndOffset && axis) {    var result = barWidthAndOffset[getAxisKey(axis)];    if (result != null && seriesModel != null) {      result = result[getSeriesStackId(seriesModel)];    }    return result;  }}/** * @param {string} seriesType * @param {module:echarts/model/Global} ecModel */function layout(seriesType, ecModel) {  var seriesModels = prepareLayoutBarSeries(seriesType, ecModel);  var barWidthAndOffset = makeColumnLayout(seriesModels);  var lastStackCoords = {};  var lastStackCoordsOrigin = {};  zrUtil.each(seriesModels, function (seriesModel) {    var data = seriesModel.getData();    var cartesian = seriesModel.coordinateSystem;    var baseAxis = cartesian.getBaseAxis();    var stackId = getSeriesStackId(seriesModel);    var columnLayoutInfo = barWidthAndOffset[getAxisKey(baseAxis)][stackId];    var columnOffset = columnLayoutInfo.offset;    var columnWidth = columnLayoutInfo.width;    var valueAxis = cartesian.getOtherAxis(baseAxis);    var barMinHeight = seriesModel.get('barMinHeight') || 0;    lastStackCoords[stackId] = lastStackCoords[stackId] || [];    lastStackCoordsOrigin[stackId] = lastStackCoordsOrigin[stackId] || []; // Fix #4243    data.setLayout({      bandWidth: columnLayoutInfo.bandWidth,      offset: columnOffset,      size: columnWidth    });    var valueDim = data.mapDimension(valueAxis.dim);    var baseDim = data.mapDimension(baseAxis.dim);    var stacked = isDimensionStacked(data, valueDim    /*, baseDim*/    );    var isValueAxisH = valueAxis.isHorizontal();    var valueAxisStart = getValueAxisStart(baseAxis, valueAxis, stacked);    for (var idx = 0, len = data.count(); idx < len; idx++) {      var value = data.get(valueDim, idx);      var baseValue = data.get(baseDim, idx);      var sign = value >= 0 ? 'p' : 'n';      var baseCoord = valueAxisStart; // Because of the barMinHeight, we can not use the value in      // stackResultDimension directly.      if (stacked) {        // Only ordinal axis can be stacked.        if (!lastStackCoords[stackId][baseValue]) {          lastStackCoords[stackId][baseValue] = {            p: valueAxisStart,            // Positive stack            n: valueAxisStart // Negative stack          };        } // Should also consider #4243        baseCoord = lastStackCoords[stackId][baseValue][sign];      }      var x;      var y;      var width;      var height;      if (isValueAxisH) {        var coord = cartesian.dataToPoint([value, baseValue]);        x = baseCoord;        y = coord[1] + columnOffset;        width = coord[0] - valueAxisStart;        height = columnWidth;        if (Math.abs(width) < barMinHeight) {          width = (width < 0 ? -1 : 1) * barMinHeight;        } // Ignore stack from NaN value        if (!isNaN(width)) {          stacked && (lastStackCoords[stackId][baseValue][sign] += width);        }      } else {        var coord = cartesian.dataToPoint([baseValue, value]);        x = coord[0] + columnOffset;        y = baseCoord;        width = columnWidth;        height = coord[1] - valueAxisStart;        if (Math.abs(height) < barMinHeight) {          // Include zero to has a positive bar          height = (height <= 0 ? -1 : 1) * barMinHeight;        } // Ignore stack from NaN value        if (!isNaN(height)) {          stacked && (lastStackCoords[stackId][baseValue][sign] += height);        }      }      data.setItemLayout(idx, {        x: x,        y: y,        width: width,        height: height      });    }  }, this);} // TODO: Do not support stack in large mode yet.var largeLayout = {  seriesType: 'bar',  plan: createRenderPlanner(),  reset: function (seriesModel) {    if (!isOnCartesian(seriesModel) || !isInLargeMode(seriesModel)) {      return;    }    var data = seriesModel.getData();    var cartesian = seriesModel.coordinateSystem;    var coordLayout = cartesian.grid.getRect();    var baseAxis = cartesian.getBaseAxis();    var valueAxis = cartesian.getOtherAxis(baseAxis);    var valueDim = data.mapDimension(valueAxis.dim);    var baseDim = data.mapDimension(baseAxis.dim);    var valueAxisHorizontal = valueAxis.isHorizontal();    var valueDimIdx = valueAxisHorizontal ? 0 : 1;    var barWidth = retrieveColumnLayout(makeColumnLayout([seriesModel]), baseAxis, seriesModel).width;    if (!(barWidth > LARGE_BAR_MIN_WIDTH)) {      // jshint ignore:line      barWidth = LARGE_BAR_MIN_WIDTH;    }    return {      progress: progress    };    function progress(params, data) {      var count = params.count;      var largePoints = new LargeArr(count * 2);      var largeBackgroundPoints = new LargeArr(count * 2);      var largeDataIndices = new LargeArr(count);      var dataIndex;      var coord = [];      var valuePair = [];      var pointsOffset = 0;      var idxOffset = 0;      while ((dataIndex = params.next()) != null) {        valuePair[valueDimIdx] = data.get(valueDim, dataIndex);        valuePair[1 - valueDimIdx] = data.get(baseDim, dataIndex);        coord = cartesian.dataToPoint(valuePair, null, coord); // Data index might not be in order, depends on `progressiveChunkMode`.        largeBackgroundPoints[pointsOffset] = valueAxisHorizontal ? coordLayout.x + coordLayout.width : coord[0];        largePoints[pointsOffset++] = coord[0];        largeBackgroundPoints[pointsOffset] = valueAxisHorizontal ? coord[1] : coordLayout.y + coordLayout.height;        largePoints[pointsOffset++] = coord[1];        largeDataIndices[idxOffset++] = dataIndex;      }      data.setLayout({        largePoints: largePoints,        largeDataIndices: largeDataIndices,        largeBackgroundPoints: largeBackgroundPoints,        barWidth: barWidth,        valueAxisStart: getValueAxisStart(baseAxis, valueAxis, false),        backgroundStart: valueAxisHorizontal ? coordLayout.x : coordLayout.y,        valueAxisHorizontal: valueAxisHorizontal      });    }  }};function isOnCartesian(seriesModel) {  return seriesModel.coordinateSystem && seriesModel.coordinateSystem.type === 'cartesian2d';}function isInLargeMode(seriesModel) {  return seriesModel.pipelineContext && seriesModel.pipelineContext.large;} // See cases in `test/bar-start.html` and `#7412`, `#8747`.function getValueAxisStart(baseAxis, valueAxis, stacked) {  return valueAxis.toGlobalCoord(valueAxis.dataToCoord(valueAxis.type === 'log' ? 1 : 0));}exports.getLayoutOnAxis = getLayoutOnAxis;exports.prepareLayoutBarSeries = prepareLayoutBarSeries;exports.makeColumnLayout = makeColumnLayout;exports.retrieveColumnLayout = retrieveColumnLayout;exports.layout = layout;exports.largeLayout = largeLayout;
 |