devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
491 lines (484 loc) • 18.1 kB
JavaScript
/**
* DevExtreme (viz/sparklines/sparkline.js)
* Version: 19.2.6
* Build date: Thu Jan 30 2020
*
* Copyright (c) 2012 - 2020 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
var BaseSparkline = require("./base_sparkline");
var dataValidatorModule = require("../components/data_validator");
var seriesModule = require("../series/base_series");
var MIN_BAR_WIDTH = 1;
var MAX_BAR_WIDTH = 50;
var DEFAULT_BAR_INTERVAL = 4;
var DEFAULT_CANVAS_WIDTH = 250;
var DEFAULT_CANVAS_HEIGHT = 30;
var DEFAULT_POINT_BORDER = 2;
var ALLOWED_TYPES = {
line: true,
spline: true,
stepline: true,
area: true,
steparea: true,
splinearea: true,
bar: true,
winloss: true
};
var _math = Math;
var _abs = _math.abs;
var _round = _math.round;
var _max = _math.max;
var _min = _math.min;
var _isFinite = isFinite;
var vizUtils = require("../core/utils");
var _map = vizUtils.map;
var _normalizeEnum = vizUtils.normalizeEnum;
var _isDefined = require("../../core/utils/type").isDefined;
var _Number = Number;
var _String = String;
function findMinMax(data, valField) {
var firstItem = data[0] || {};
var firstValue = firstItem[valField] || 0;
var min = firstValue;
var max = firstValue;
var minIndexes = [0];
var maxIndexes = [0];
var dataLength = data.length;
var value;
var i;
for (i = 1; i < dataLength; i++) {
value = data[i][valField];
if (value < min) {
min = value;
minIndexes = [i]
} else {
if (value === min) {
minIndexes.push(i)
}
}
if (value > max) {
max = value;
maxIndexes = [i]
} else {
if (value === max) {
maxIndexes.push(i)
}
}
}
if (max === min) {
minIndexes = maxIndexes = []
}
return {
minIndexes: minIndexes,
maxIndexes: maxIndexes
}
}
function parseNumericDataSource(data, argField, valField, ignoreEmptyPoints) {
return _map(data, function(dataItem, index) {
var item = null;
var isDataNumber;
var value;
if (void 0 !== dataItem) {
item = {};
isDataNumber = _isFinite(dataItem);
item[argField] = isDataNumber ? _String(index) : dataItem[argField];
value = isDataNumber ? dataItem : dataItem[valField];
item[valField] = null === value ? ignoreEmptyPoints ? void 0 : value : _Number(value);
item = void 0 !== item[argField] && void 0 !== item[valField] ? item : null
}
return item
})
}
function parseWinlossDataSource(data, argField, valField, target) {
var lowBarValue = -1;
var zeroBarValue = 0;
var highBarValue = 1;
var delta = 1e-4;
return _map(data, function(dataItem) {
var item = {};
item[argField] = dataItem[argField];
if (_abs(dataItem[valField] - target) < delta) {
item[valField] = zeroBarValue
} else {
if (dataItem[valField] > target) {
item[valField] = highBarValue
} else {
item[valField] = lowBarValue
}
}
return item
})
}
function selectPointColor(color, options, index, pointIndexes) {
if (index === pointIndexes.first || index === pointIndexes.last) {
color = options.firstLastColor
}
if ((pointIndexes.min || []).indexOf(index) >= 0) {
color = options.minColor
}
if ((pointIndexes.max || []).indexOf(index) >= 0) {
color = options.maxColor
}
return color
}
function createLineCustomizeFunction(pointIndexes, options) {
return function() {
var color = selectPointColor(void 0, options, this.index, pointIndexes);
return color ? {
visible: true,
border: {
color: color
}
} : {}
}
}
function createBarCustomizeFunction(pointIndexes, options, winlossData) {
return function() {
var index = this.index;
var isWinloss = "winloss" === options.type;
var target = isWinloss ? options.winlossThreshold : 0;
var value = isWinloss ? winlossData[index][options.valueField] : this.value;
var positiveColor = isWinloss ? options.winColor : options.barPositiveColor;
var negativeColor = isWinloss ? options.lossColor : options.barNegativeColor;
return {
color: selectPointColor(value >= target ? positiveColor : negativeColor, options, index, pointIndexes)
}
}
}
var dxSparkline = BaseSparkline.inherit({
_rootClassPrefix: "dxsl",
_rootClass: "dxsl-sparkline",
_themeSection: "sparkline",
_defaultSize: {
width: DEFAULT_CANVAS_WIDTH,
height: DEFAULT_CANVAS_HEIGHT
},
_initCore: function() {
this.callBase();
this._createSeries()
},
_initialChanges: ["DATA_SOURCE"],
_dataSourceChangedHandler: function() {
this._requestChange(["UPDATE"])
},
_updateWidgetElements: function() {
this._updateSeries();
this.callBase()
},
_disposeWidgetElements: function() {
var that = this;
that._series && that._series.dispose();
that._series = that._seriesGroup = that._seriesLabelGroup = null
},
_cleanWidgetElements: function() {
this._seriesGroup.remove();
this._seriesLabelGroup.remove();
this._seriesGroup.clear();
this._seriesLabelGroup.clear()
},
_drawWidgetElements: function() {
if (this._dataIsLoaded()) {
this._drawSeries();
this._drawn()
}
},
_getCorrectCanvas: function() {
var options = this._allOptions;
var canvas = this._canvas;
var halfPointSize = options.pointSize && Math.ceil(options.pointSize / 2) + DEFAULT_POINT_BORDER;
var type = options.type;
if ("bar" !== type && "winloss" !== type && (options.showFirstLast || options.showMinMax)) {
return {
width: canvas.width,
height: canvas.height,
left: canvas.left + halfPointSize,
right: canvas.right + halfPointSize,
top: canvas.top + halfPointSize,
bottom: canvas.bottom + halfPointSize
}
}
return canvas
},
_prepareOptions: function() {
var that = this;
that._allOptions = that.callBase();
that._allOptions.type = _normalizeEnum(that._allOptions.type);
if (!ALLOWED_TYPES[that._allOptions.type]) {
that._allOptions.type = "line"
}
},
_createHtmlElements: function() {
this._seriesGroup = this._renderer.g().attr({
"class": "dxsl-series"
});
this._seriesLabelGroup = this._renderer.g().attr({
"class": "dxsl-series-labels"
})
},
_createSeries: function() {
this._series = new seriesModule.Series({
renderer: this._renderer,
seriesGroup: this._seriesGroup,
labelsGroup: this._seriesLabelGroup,
argumentAxis: this._argumentAxis,
valueAxis: this._valueAxis
}, {
widgetType: "chart",
type: "line"
})
},
_updateSeries: function() {
var that = this;
var groupsData;
var seriesOptions;
var singleSeries = that._series;
that._prepareDataSource();
seriesOptions = that._prepareSeriesOptions();
singleSeries.updateOptions(seriesOptions);
groupsData = {
groups: [{
series: [singleSeries]
}]
};
groupsData.argumentOptions = {
type: "bar" === seriesOptions.type ? "discrete" : void 0
};
that._simpleDataSource = dataValidatorModule.validateData(that._simpleDataSource, groupsData, that._incidentOccurred, {
checkTypeForAllData: false,
convertToAxisDataType: true,
sortingMethod: true
})[singleSeries.getArgumentField()];
seriesOptions.customizePoint = that._getCustomizeFunction();
singleSeries.updateData(that._simpleDataSource);
singleSeries.createPoints();
that._groupsDataCategories = groupsData.categories
},
_optionChangesMap: {
dataSource: "DATA_SOURCE"
},
_optionChangesOrder: ["DATA_SOURCE"],
_change_DATA_SOURCE: function() {
this._updateDataSource()
},
_prepareDataSource: function() {
var that = this;
var options = that._allOptions;
var argField = options.argumentField;
var valField = options.valueField;
var dataSource = that._dataSourceItems() || [];
var data = parseNumericDataSource(dataSource, argField, valField, that.option("ignoreEmptyPoints"));
if ("winloss" === options.type) {
that._winlossDataSource = data;
that._simpleDataSource = parseWinlossDataSource(data, argField, valField, options.winlossThreshold)
} else {
that._simpleDataSource = data
}
},
_prepareSeriesOptions: function() {
var that = this;
var options = that._allOptions;
var type = "winloss" === options.type ? "bar" : options.type;
return {
visible: true,
argumentField: options.argumentField,
valueField: options.valueField,
color: options.lineColor,
width: options.lineWidth,
widgetType: "chart",
type: type,
opacity: type.indexOf("area") !== -1 ? that._allOptions.areaOpacity : void 0,
point: {
size: options.pointSize,
symbol: options.pointSymbol,
border: {
visible: true,
width: DEFAULT_POINT_BORDER
},
color: options.pointColor,
visible: false,
hoverStyle: {
border: {}
},
selectionStyle: {
border: {}
}
},
border: {
color: options.lineColor,
width: options.lineWidth,
visible: "bar" !== type
}
}
},
_getCustomizeFunction: function() {
var that = this;
var options = that._allOptions;
var dataSource = that._winlossDataSource || that._simpleDataSource;
var drawnPointIndexes = that._getExtremumPointsIndexes(dataSource);
var customizeFunction;
if ("winloss" === options.type || "bar" === options.type) {
customizeFunction = createBarCustomizeFunction(drawnPointIndexes, options, that._winlossDataSource)
} else {
customizeFunction = createLineCustomizeFunction(drawnPointIndexes, options)
}
return customizeFunction
},
_getExtremumPointsIndexes: function(data) {
var that = this;
var options = that._allOptions;
var lastIndex = data.length - 1;
var indexes = {};
that._minMaxIndexes = findMinMax(data, options.valueField);
if (options.showFirstLast) {
indexes.first = 0;
indexes.last = lastIndex
}
if (options.showMinMax) {
indexes.min = that._minMaxIndexes.minIndexes;
indexes.max = that._minMaxIndexes.maxIndexes
}
return indexes
},
_getStick: function() {
return {
stick: "bar" !== this._series.type
}
},
_updateRange: function() {
var that = this;
var series = that._series;
var type = series.type;
var isBarType = "bar" === type;
var isWinlossType = "winloss" === type;
var DEFAULT_VALUE_RANGE_MARGIN = .15;
var DEFAULT_ARGUMENT_RANGE_MARGIN = .1;
var WINLOSS_MAX_RANGE = 1;
var WINLOSS_MIN_RANGE = -1;
var rangeData = series.getRangeData();
var minValue = that._allOptions.minValue;
var hasMinY = _isDefined(minValue) && _isFinite(minValue);
var maxValue = that._allOptions.maxValue;
var hasMaxY = _isDefined(maxValue) && _isFinite(maxValue);
var valCoef;
var argCoef;
valCoef = (rangeData.val.max - rangeData.val.min) * DEFAULT_VALUE_RANGE_MARGIN;
if (isBarType || isWinlossType || "area" === type) {
if (0 !== rangeData.val.min) {
rangeData.val.min -= valCoef
}
if (0 !== rangeData.val.max) {
rangeData.val.max += valCoef
}
} else {
rangeData.val.min -= valCoef;
rangeData.val.max += valCoef
}
if (hasMinY || hasMaxY) {
if (hasMinY && hasMaxY) {
rangeData.val.minVisible = _min(minValue, maxValue);
rangeData.val.maxVisible = _max(minValue, maxValue)
} else {
rangeData.val.minVisible = hasMinY ? _Number(minValue) : void 0;
rangeData.val.maxVisible = hasMaxY ? _Number(maxValue) : void 0
}
if (isWinlossType) {
rangeData.val.minVisible = hasMinY ? _max(rangeData.val.minVisible, WINLOSS_MIN_RANGE) : void 0;
rangeData.val.maxVisible = hasMaxY ? _min(rangeData.val.maxVisible, WINLOSS_MAX_RANGE) : void 0
}
}
if (series.getPoints().length > 1) {
if (isBarType) {
argCoef = (rangeData.arg.max - rangeData.arg.min) * DEFAULT_ARGUMENT_RANGE_MARGIN;
rangeData.arg.min = rangeData.arg.min - argCoef;
rangeData.arg.max = rangeData.arg.max + argCoef
}
}
rangeData.arg.categories = that._groupsDataCategories;
that._ranges = rangeData
},
_getBarWidth: function(pointsCount) {
var that = this;
var canvas = that._canvas;
var intervalWidth = pointsCount * DEFAULT_BAR_INTERVAL;
var rangeWidth = canvas.width - canvas.left - canvas.right - intervalWidth;
var width = _round(rangeWidth / pointsCount);
if (width < MIN_BAR_WIDTH) {
width = MIN_BAR_WIDTH
}
if (width > MAX_BAR_WIDTH) {
width = MAX_BAR_WIDTH
}
return width
},
_correctPoints: function() {
var that = this;
var seriesType = that._allOptions.type;
var seriesPoints = that._series.getPoints();
var pointsLength = seriesPoints.length;
var barWidth;
var i;
if ("bar" === seriesType || "winloss" === seriesType) {
barWidth = that._getBarWidth(pointsLength);
for (i = 0; i < pointsLength; i++) {
seriesPoints[i].correctCoordinates({
width: barWidth,
offset: 0
})
}
}
},
_drawSeries: function() {
var that = this;
if (that._simpleDataSource.length > 0) {
that._correctPoints();
that._series.draw();
that._seriesGroup.append(that._renderer.root)
}
},
_isTooltipEnabled: function() {
return !!this._simpleDataSource.length
},
_getTooltipData: function() {
var that = this;
var options = that._allOptions;
var dataSource = that._winlossDataSource || that._simpleDataSource;
var tooltip = that._tooltip;
if (0 === dataSource.length) {
return {}
}
var minMax = that._minMaxIndexes;
var valueField = options.valueField;
var first = dataSource[0][valueField];
var last = dataSource[dataSource.length - 1][valueField];
var min = _isDefined(minMax.minIndexes[0]) ? dataSource[minMax.minIndexes[0]][valueField] : first;
var max = _isDefined(minMax.maxIndexes[0]) ? dataSource[minMax.maxIndexes[0]][valueField] : first;
var formattedFirst = tooltip.formatValue(first);
var formattedLast = tooltip.formatValue(last);
var formattedMin = tooltip.formatValue(min);
var formattedMax = tooltip.formatValue(max);
var customizeObject = {
firstValue: formattedFirst,
lastValue: formattedLast,
minValue: formattedMin,
maxValue: formattedMax,
originalFirstValue: first,
originalLastValue: last,
originalMinValue: min,
originalMaxValue: max,
valueText: ["Start:", formattedFirst, "End:", formattedLast, "Min:", formattedMin, "Max:", formattedMax]
};
if ("winloss" === options.type) {
customizeObject.originalThresholdValue = options.winlossThreshold;
customizeObject.thresholdValue = tooltip.formatValue(options.winlossThreshold)
}
return customizeObject
}
});
_map(["lineColor", "lineWidth", "areaOpacity", "minColor", "maxColor", "barPositiveColor", "barNegativeColor", "winColor", "lessColor", "firstLastColor", "pointSymbol", "pointColor", "pointSize", "type", "argumentField", "valueField", "winlossThreshold", "showFirstLast", "showMinMax", "ignoreEmptyPoints", "minValue", "maxValue"], function(name) {
dxSparkline.prototype._optionChangesMap[name] = "OPTIONS"
});
require("../../core/component_registrator")("dxSparkline", dxSparkline);
module.exports = dxSparkline;
dxSparkline.addPlugin(require("../core/data_source").plugin);