UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

483 lines (482 loc) • 18.5 kB
/** * DevExtreme (viz/sparklines/sparkline.js) * Version: 18.1.3 * Build date: Tue May 15 2018 * * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; var BaseSparkline = require("./base_sparkline"), dataValidatorModule = require("../components/data_validator"), seriesModule = require("../series/base_series"), MIN_BAR_WIDTH = 1, MAX_BAR_WIDTH = 50, DEFAULT_BAR_INTERVAL = 4, DEFAULT_CANVAS_WIDTH = 250, DEFAULT_CANVAS_HEIGHT = 30, DEFAULT_POINT_BORDER = 2, ALLOWED_TYPES = { line: true, spline: true, stepline: true, area: true, steparea: true, splinearea: true, bar: true, winloss: true }, _math = Math, _abs = _math.abs, _round = _math.round, _max = _math.max, _min = _math.min, _isFinite = isFinite, vizUtils = require("../core/utils"), _map = vizUtils.map, _normalizeEnum = vizUtils.normalizeEnum, _isDefined = require("../../core/utils/type").isDefined, _Number = Number, _String = String; var dxSparkline = BaseSparkline.inherit({ _rootClassPrefix: "dxsl", _rootClass: "dxsl-sparkline", _widgetType: "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, canvas = this._canvas, halfPointSize = options.pointSize && Math.ceil(options.pointSize / 2) + DEFAULT_POINT_BORDER, 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 groupsData, seriesOptions, that = this, 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()]; singleSeries.updateData(that._simpleDataSource); singleSeries.createPoints(); that._groupsDataCategories = groupsData.categories }, _optionChangesMap: { dataSource: "DATA_SOURCE" }, _optionChangesOrder: ["DATA_SOURCE"], _change_DATA_SOURCE: function() { this._updateDataSource() }, _parseNumericDataSource: function(data, argField, valField) { var ignoreEmptyPoints = this.option("ignoreEmptyPoints"); return _map(data, function(dataItem, index) { var isDataNumber, value, item = null; 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 }) }, _parseWinlossDataSource: function(data, argField, valField) { var lowBarValue = -1, zeroBarValue = 0, highBarValue = 1, delta = 1e-4, target = this._allOptions.winlossThreshold; 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 }) }, _prepareDataSource: function() { var that = this, options = that._allOptions, argField = options.argumentField, valField = options.valueField, dataSource = that._dataSourceItems() || [], data = that._parseNumericDataSource(dataSource, argField, valField); if ("winloss" === options.type) { that._winlossDataSource = data; that._simpleDataSource = that._parseWinlossDataSource(data, argField, valField) } else { that._simpleDataSource = data } }, _prepareSeriesOptions: function() { var that = this, options = that._allOptions, 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, customizePoint: that._getCustomizeFunction(), 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 } } }, _createBarCustomizeFunction: function(pointIndexes) { var that = this, options = that._allOptions, winlossData = that._winlossDataSource; return function() { var color, index = this.index, isWinloss = "winloss" === options.type, target = isWinloss ? options.winlossThreshold : 0, value = isWinloss ? winlossData[index][options.valueField] : this.value, positiveColor = isWinloss ? options.winColor : options.barPositiveColor, negativeColor = isWinloss ? options.lossColor : options.barNegativeColor; if (value >= target) { color = positiveColor } else { color = negativeColor } if (index === pointIndexes.first || index === pointIndexes.last) { color = options.firstLastColor } if (index === pointIndexes.min) { color = options.minColor } if (index === pointIndexes.max) { color = options.maxColor } return { color: color } } }, _createLineCustomizeFunction: function(pointIndexes) { var that = this, options = that._allOptions; return function() { var color, index = this.index; if (index === pointIndexes.first || index === pointIndexes.last) { color = options.firstLastColor } if (index === pointIndexes.min) { color = options.minColor } if (index === pointIndexes.max) { color = options.maxColor } return color ? { visible: true, border: { color: color } } : {} } }, _getCustomizeFunction: function() { var customizeFunction, that = this, options = that._allOptions, dataSource = that._winlossDataSource || that._simpleDataSource, drawnPointIndexes = that._getExtremumPointsIndexes(dataSource); if ("winloss" === options.type || "bar" === options.type) { customizeFunction = that._createBarCustomizeFunction(drawnPointIndexes) } else { customizeFunction = that._createLineCustomizeFunction(drawnPointIndexes) } return customizeFunction }, _getExtremumPointsIndexes: function(data) { var that = this, options = that._allOptions, lastIndex = data.length - 1, indexes = {}; that._minMaxIndexes = that._findMinMax(data); if (options.showFirstLast) { indexes.first = 0; indexes.last = lastIndex } if (options.showMinMax) { indexes.min = that._minMaxIndexes.minIndex; indexes.max = that._minMaxIndexes.maxIndex } return indexes }, _findMinMax: function(data) { var value, i, that = this, valField = that._allOptions.valueField, firstItem = data[0] || {}, firstValue = firstItem[valField] || 0, min = firstValue, max = firstValue, minIndex = 0, maxIndex = 0, dataLength = data.length; for (i = 1; i < dataLength; i++) { value = data[i][valField]; if (value < min) { min = value; minIndex = i } if (value > max) { max = value; maxIndex = i } } return { minIndex: minIndex, maxIndex: maxIndex } }, _getStick: function() { return { stick: "bar" !== this._series.type } }, _updateRange: function() { var valCoef, argCoef, that = this, series = that._series, type = series.type, isBarType = "bar" === type, isWinlossType = "winloss" === type, DEFAULT_VALUE_RANGE_MARGIN = .15, DEFAULT_ARGUMENT_RANGE_MARGIN = .1, WINLOSS_MAX_RANGE = 1, WINLOSS_MIN_RANGE = -1, rangeData = series.getRangeData(), minValue = that._allOptions.minValue, hasMinY = _isDefined(minValue) && _isFinite(minValue), maxValue = that._allOptions.maxValue, hasMaxY = _isDefined(maxValue) && _isFinite(maxValue); 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, canvas = that._canvas, intervalWidth = pointsCount * DEFAULT_BAR_INTERVAL, rangeWidth = canvas.width - canvas.left - canvas.right - intervalWidth, 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 barWidth, i, that = this, seriesType = that._allOptions.type, seriesPoints = that._series.getPoints(), pointsLength = seriesPoints.length; 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, options = that._allOptions, dataSource = that._winlossDataSource || that._simpleDataSource, tooltip = that._tooltip; if (0 === dataSource.length) { return {} } var minMax = that._minMaxIndexes, valueField = options.valueField, first = dataSource[0][valueField], last = dataSource[dataSource.length - 1][valueField], min = dataSource[minMax.minIndex][valueField], max = dataSource[minMax.maxIndex][valueField], formattedFirst = tooltip.formatValue(first), formattedLast = tooltip.formatValue(last), formattedMin = tooltip.formatValue(min), formattedMax = tooltip.formatValue(max), 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);