UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

472 lines (465 loc) • 17.3 kB
/** * DevExtreme (esm/viz/sparklines/sparkline.js) * Version: 21.1.4 * Build date: Mon Jun 21 2021 * * Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ import BaseSparkline from "./base_sparkline"; import { validateData } from "../components/data_validator"; import { Series } from "../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; import { map as _map, normalizeEnum as _normalizeEnum } from "../core/utils"; import { isDefined as _isDefined } from "../../core/utils/type"; 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) { return _map(data, (function(dataItem) { var item = {}; item[argField] = dataItem[argField]; if (_abs(dataItem[valField] - target) < 1e-4) { item[valField] = 0 } else if (dataItem[valField] > target) { item[valField] = 1 } else { item[valField] = -1 } 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() { this._series && this._series.dispose(); this._series = this._seriesGroup = this._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() { this._allOptions = this.callBase(); this._allOptions.type = _normalizeEnum(this._allOptions.type); if (!ALLOWED_TYPES[this._allOptions.type]) { this._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 Series({ renderer: this._renderer, seriesGroup: this._seriesGroup, labelsGroup: this._seriesLabelGroup, argumentAxis: this._argumentAxis, valueAxis: this._valueAxis }, { widgetType: "chart", type: "line" }) }, _updateSeries: function() { var singleSeries = this._series; this._prepareDataSource(); var seriesOptions = this._prepareSeriesOptions(); singleSeries.updateOptions(seriesOptions); var groupsData = { groups: [{ series: [singleSeries] }] }; groupsData.argumentOptions = { type: "bar" === seriesOptions.type ? "discrete" : void 0 }; this._simpleDataSource = validateData(this._simpleDataSource, groupsData, this._incidentOccurred, { checkTypeForAllData: false, convertToAxisDataType: true, sortingMethod: true })[singleSeries.getArgumentField()]; seriesOptions.customizePoint = this._getCustomizeFunction(); singleSeries.updateData(this._simpleDataSource); singleSeries.createPoints(); this._groupsDataCategories = groupsData.categories }, _optionChangesMap: { dataSource: "DATA_SOURCE" }, _optionChangesOrder: ["DATA_SOURCE"], _change_DATA_SOURCE: function() { this._updateDataSource() }, _prepareDataSource: function() { var options = this._allOptions; var argField = options.argumentField; var valField = options.valueField; var dataSource = this._dataSourceItems() || []; var data = parseNumericDataSource(dataSource, argField, valField, this.option("ignoreEmptyPoints")); if ("winloss" === options.type) { this._winlossDataSource = data; this._simpleDataSource = parseWinlossDataSource(data, argField, valField, options.winlossThreshold) } else { this._simpleDataSource = data } }, _prepareSeriesOptions: function() { var options = this._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: -1 !== type.indexOf("area") ? this._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 options = this._allOptions; var dataSource = this._winlossDataSource || this._simpleDataSource; var drawnPointIndexes = this._getExtremumPointsIndexes(dataSource); var customizeFunction; if ("winloss" === options.type || "bar" === options.type) { customizeFunction = createBarCustomizeFunction(drawnPointIndexes, options, this._winlossDataSource) } else { customizeFunction = createLineCustomizeFunction(drawnPointIndexes, options) } return customizeFunction }, _getExtremumPointsIndexes: function(data) { var options = this._allOptions; var lastIndex = data.length - 1; var indexes = {}; this._minMaxIndexes = findMinMax(data, options.valueField); if (options.showFirstLast) { indexes.first = 0; indexes.last = lastIndex } if (options.showMinMax) { indexes.min = this._minMaxIndexes.minIndexes; indexes.max = this._minMaxIndexes.maxIndexes } return indexes }, _getStick: function() { return { stick: "bar" !== this._series.type } }, _updateRange: function() { var series = this._series; var type = series.type; var isBarType = "bar" === type; var isWinlossType = "winloss" === type; var rangeData = series.getRangeData(); var minValue = this._allOptions.minValue; var hasMinY = _isDefined(minValue) && _isFinite(minValue); var maxValue = this._allOptions.maxValue; var hasMaxY = _isDefined(maxValue) && _isFinite(maxValue); var argCoef; var valCoef = .15 * (rangeData.val.max - rangeData.val.min); 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, -1) : void 0; rangeData.val.maxVisible = hasMaxY ? _min(rangeData.val.maxVisible, 1) : void 0 } } if (series.getPoints().length > 1) { if (isBarType) { argCoef = .1 * (rangeData.arg.max - rangeData.arg.min); rangeData.arg.min = rangeData.arg.min - argCoef; rangeData.arg.max = rangeData.arg.max + argCoef } } rangeData.arg.categories = this._groupsDataCategories; this._ranges = rangeData }, _getBarWidth: function(pointsCount) { var canvas = this._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 seriesType = this._allOptions.type; var seriesPoints = this._series.getPoints(); var pointsLength = seriesPoints.length; var barWidth; var i; if ("bar" === seriesType || "winloss" === seriesType) { barWidth = this._getBarWidth(pointsLength); for (i = 0; i < pointsLength; i++) { seriesPoints[i].correctCoordinates({ width: barWidth, offset: 0 }) } } }, _drawSeries: function() { if (this._simpleDataSource.length > 0) { this._correctPoints(); this._series.draw(); this._seriesGroup.append(this._renderer.root) } }, _isTooltipEnabled: function() { return !!this._simpleDataSource.length }, _getTooltipData: function() { var options = this._allOptions; var dataSource = this._winlossDataSource || this._simpleDataSource; var tooltip = this._tooltip; if (0 === dataSource.length) { return {} } var minMax = this._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" })); import componentRegistrator from "../../core/component_registrator"; componentRegistrator("dxSparkline", dxSparkline); export default dxSparkline; import { plugin } from "../core/data_source"; dxSparkline.addPlugin(plugin);