UNPKG

@gitlab/ui

Version:
594 lines (527 loc) • 17.3 kB
import merge from 'lodash/merge'; import castArray from 'lodash/castArray'; import isArray from 'lodash/isArray'; import Breakpoints from '../breakpoints'; import { engineeringNotation } from '../number_utils'; import { areDatesEqual } from '../datetime_utility'; import { arrowSymbol, ANNOTATIONS_SERIES_NAME } from './constants'; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread2(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); } function _iterableToArrayLimit(arr, i) { if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return; var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var blue500 = '#1f75cb'; var defaultAreaOpacity = 0.2; var defaultFontSize = 12; var defaultHeight = 400; var validRenderers = ['canvas', 'svg']; var axes = { name: 'Value', type: 'value', nameLocation: 'center' }; var xAxis = merge({}, axes, { boundaryGap: false, splitLine: { show: false } }); var yAxis = merge({}, axes, { nameGap: 50, axisLabel: { formatter: function formatter(num) { return engineeringNotation(num, 2); } } }); var grid = { top: 16, bottom: 44, left: 64, right: 32 }; var lineStyle = { symbol: 'circle', type: 'line', width: 2 }; /** * Annotations series consists of annotations lines * along with markers. Annotations co-exist with data * series but have their own virtual coords so that they stay put * irrespective of data series extents. */ var annotationsYAxisCoords = { min: 0, pos: 3, // 3% height of chart's grid max: 100, show: false }; var symbolSize = 6; /** * These comparison operators are currently in monitoring * charts that have alerting related data. * * {Array} Possible values for greater than */ var GREATER_THAN = ['>', '&gt;']; /** * These comparison operators are currently in monitoring * charts that have alerting related data. * * {Array} Possible values for less than */ var LESS_THAN = ['<', '&lt;']; /** * All default dataZoom configs will have slider & inside * (for reference, see https://gitlab.com/gitlab-org/gitlab-ui/issues/240) * Inside is disabled for larger viewports (lg and xl) * and is specifically to enable touch zoom for mobile devices * @param {Object} options */ var getDataZoomConfig = function getDataZoomConfig() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$filterMode = _ref.filterMode, filterMode = _ref$filterMode === void 0 ? 'none' : _ref$filterMode; var disabledBreakpoints = ['lg', 'xl']; var disabled = disabledBreakpoints.includes(Breakpoints.getBreakpointSize()); var minSpan = filterMode === 'none' ? 0.01 : null; return { grid: { bottom: 81 }, xAxis: { nameGap: 67 }, dataZoom: [{ type: 'slider', bottom: 22, filterMode: filterMode, minSpan: minSpan }, { type: 'inside', filterMode: filterMode, minSpan: minSpan, disabled: disabled }] }; }; // All chart options can be merged but series // needs to be concatenated. // Series can be an object for single series or // an array of objects. var mergeSeriesToOptions = function mergeSeriesToOptions(options) { var series = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var _options$series = options.series, optSeries = _options$series === void 0 ? [] : _options$series; return _objectSpread2(_objectSpread2({}, options), {}, { series: [].concat(_toConsumableArray(castArray(series)), _toConsumableArray(castArray(optSeries))) }); }; /** * If an annotation series exists, the chart options should have an * array of yAxis settings so that the series can exist in its own * coordinate system without interfering with the data series * * @param {Object} options options to merge annotation series yAxis with * @param {Boolean} hasAnnotations if annotation series yAxis should be merged * @returns {Object} options */ var mergeAnnotationAxisToOptions = function mergeAnnotationAxisToOptions(options) { var hasAnnotations = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; return _objectSpread2(_objectSpread2({}, options), hasAnnotations && { yAxis: [options.yAxis, annotationsYAxisCoords] }); }; var dataZoomAdjustments = function dataZoomAdjustments(dataZoom) { // handle cases where dataZoom is array and object. var useSlider = dataZoom && isArray(dataZoom) ? dataZoom.length : Boolean(dataZoom); return useSlider ? getDataZoomConfig({ filterMode: 'weakFilter' }) : []; }; var getToolboxConfig = function getToolboxConfig() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$restoreIconPath = _ref2.restoreIconPath, restoreIconPath = _ref2$restoreIconPath === void 0 ? '' : _ref2$restoreIconPath, _ref2$saveImageIconPa = _ref2.saveImageIconPath, saveImageIconPath = _ref2$saveImageIconPa === void 0 ? '' : _ref2$saveImageIconPa, _ref2$zoomIconPath = _ref2.zoomIconPath, zoomIconPath = _ref2$zoomIconPath === void 0 ? '' : _ref2$zoomIconPath, _ref2$backIconPath = _ref2.backIconPath, backIconPath = _ref2$backIconPath === void 0 ? '' : _ref2$backIconPath; var toolboxConfig = { toolbox: { feature: {} } }; if (restoreIconPath.length) { toolboxConfig.toolbox.feature.restore = { icon: restoreIconPath }; } if (saveImageIconPath.length) { toolboxConfig.toolbox.feature.saveAsImage = { icon: restoreIconPath }; } if (zoomIconPath.length && backIconPath.length) { toolboxConfig.toolbox.feature.dataZoom = { icon: { zoom: zoomIconPath, back: backIconPath } }; } return toolboxConfig; }; /** * Generate eCharts markArea arrays for thresholds and annotations. * * This method purposefully has no knowledge of comparison * operators used in thresholds as it is not necessary and instead * expects explict value bounds * * Examples: * { min: 7, max: 10 } => markArea from 7 to 10 * { min: 1, max: 7 } => markArea from 1 to 7 * * If min and max are equal it would be markLine and would be * generated by `generateMarkLines` * * @param {Object} threshold Threshold/Annotation object with min and max values * @param {String} axis markArea is generated against this axis * @returns {Array} */ var generateMarkArea = function generateMarkArea(_ref3) { var min = _ref3.min, max = _ref3.max; var axis = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yAxis'; return [_defineProperty({}, axis, min), _defineProperty({}, axis, max)]; }; /** * Generate eCharts markLine objects for thresholds and annotations. * * This method purposefully has no knowledge of comparison * operators used in thresholds as it is not necessary and instead * expects explict value bounds * * In order to continue supporting existing thresholds format, min * is passed as undefined so the correct markLine object is generated. * * For annotations, min and max will be the same value. * * Threshold Examples: * { max: 7 } => markLine at 7 * * Annotation Examples: * { min: 7, max: 7 } => markLine at 7 * * @param {Object} threshold Threshold/Annotation object with min and max values * @param {String} axis markLine is generated against this axis * @returns {Object} */ var generateMarkLines = function generateMarkLines(_ref6) { var min = _ref6.min, max = _ref6.max; var axis = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'yAxis'; if (min) { return _defineProperty({}, axis, min); } return _defineProperty({}, axis, max); }; /** * Generates markPoints that are placed under the markLines. * * These are used only in annotation lines. For annotation lines, * both min and max are same values so only one is enough to generate * the markPoint. * * @param {Object} annotation object * @return {Object} */ var generateMarkPoints = function generateMarkPoints(_ref9) { var min = _ref9.min, tooltipData = _ref9.tooltipData; return { name: 'annotations', xAxis: min, yAxis: 0, tooltipData: tooltipData }; }; /** * Generate set of markAreas and markLines to draw on charts * as alert thresholds. * * Alert thresholds always have a markLine associated with a markArea * * @param {Array} thresholds Array of alert thresholds * @returns {Object} markAreas and markLines */ function getThresholdConfig(thresholds) { if (!thresholds.length) { return {}; } var data = thresholds.reduce(function (acc, alert) { var threshold = alert.threshold, operator = alert.operator; if (GREATER_THAN.includes(operator)) { acc.areas.push(generateMarkArea({ min: threshold, max: Infinity })); } else if (LESS_THAN.includes(operator)) { acc.areas.push(generateMarkArea({ min: Number.NEGATIVE_INFINITY, max: threshold })); } acc.lines.push(generateMarkLines({ max: threshold })); return acc; }, { lines: [], areas: [] }); return { markLine: { data: data.lines }, markArea: { data: data.areas, zlevel: -1 } }; } /** * This method is only for testing both markLines and markAreas * that are used for annotations. * * `getAnnotationsConfig` as of %12.10 supports only markLines. * But this method can generate lines, points and areas. * * @param {Array} annotations Array of annotation objects * @returns {Object} { areas, lines, points } */ var parseAnnotations = function parseAnnotations(annotations) { return annotations.reduce(function (acc, annotation) { // because only markLines are supported all cases will // satisfy this condition. This is more of a sanity check // until markAreas are supported. // https://gitlab.com/gitlab-org/gitlab/-/issues/212910 if (areDatesEqual(annotation.min, annotation.max)) { acc.lines.push(generateMarkLines(annotation, 'xAxis')); acc.points.push(generateMarkPoints(annotation)); return acc; } acc.areas.push(generateMarkArea(annotation, 'xAxis')); return acc; }, { areas: [], lines: [], points: [] }); }; /** * Generate set of markAreas and markLines to draw on charts * as annotations. * * Annotations as of %12.10 will only be markLines. * markAreas are not supported yet. They are generated by * `parseAnnotations` but not rendered. * * @param {Array} annotations Array of annotations * @returns {Object} { markLines } */ var getAnnotationsConfig = function getAnnotationsConfig(annotations) { if (!annotations.length) { return {}; } // annotations parsing is moved out so that it can be tested // for markLines and markAreas. var _parseAnnotations = parseAnnotations(annotations), lines = _parseAnnotations.lines, points = _parseAnnotations.points; return { markLine: { lineStyle: { color: blue500 }, silent: true, data: lines }, markPoint: { itemStyle: { color: blue500 }, symbol: arrowSymbol, symbolSize: '8', symbolOffset: [0, ' 60%'], data: points } }; }; /** * Given thresholds and annotations options, this method generates * an annotation series that co-exists along with the data series. * * yAxis option is useful in cases where multiple yAxis settings * are used in a chart. Currently, all of our charts have single * yAxis settings. * * @param {Object} params Thresholds, annotations and yAxis options * @returns {Object} Annotation series */ var generateAnnotationSeries = function generateAnnotationSeries(annotations) { var yAxisIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; if (!annotations.length) { return null; } return merge({ name: ANNOTATIONS_SERIES_NAME, yAxisIndex: yAxisIndex, type: 'scatter', data: [] }, getAnnotationsConfig(annotations)); }; /** * The method works well if tooltip content should be against y-axis values. * However, for bar charts, the tooltip should be against x-axis values. * This method should be updated to work with all types of visualizations. * https://gitlab.com/gitlab-org/gitlab-ui/-/issues/674 * * @param {Object} params series data * @param {String} yAxisTitle y-axis title * @returns {Object} tooltip title and content */ var getDefaultTooltipContent = function getDefaultTooltipContent(params) { var yAxisTitle = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var seriesDataLength = params.seriesData.length; var _params$seriesData$re = params.seriesData.reduce(function (acc, chartItem) { var _ref10 = chartItem.value || [], _ref11 = _slicedToArray(_ref10, 2), title = _ref11[0], value = _ref11[1]; // Let's use the y axis title as series name when only one series exists // This way, TooltipDefaultFormat will display the y axis title as label var seriesName = seriesDataLength === 1 && yAxisTitle ? yAxisTitle : chartItem.seriesName; var color = seriesDataLength === 1 ? '' : chartItem.color; acc.tooltipContent[seriesName] = { value: value, color: color }; if (!acc.xLabels.includes(title)) { acc.xLabels.push(title); } return acc; }, { xLabels: [], tooltipContent: {} }), xLabels = _params$seriesData$re.xLabels, tooltipContent = _params$seriesData$re.tooltipContent; return { xLabels: xLabels, tooltipContent: tooltipContent }; }; var config = { grid: grid, xAxis: xAxis, yAxis: yAxis }; export default config; export { annotationsYAxisCoords, axes, dataZoomAdjustments, defaultAreaOpacity, defaultFontSize, defaultHeight, generateAnnotationSeries, getAnnotationsConfig, getDataZoomConfig, getDefaultTooltipContent, getThresholdConfig, getToolboxConfig, grid, lineStyle, mergeAnnotationAxisToOptions, mergeSeriesToOptions, parseAnnotations, symbolSize, validRenderers, xAxis, yAxis };