@gitlab/ui
Version:
GitLab UI Components
594 lines (527 loc) • 17.3 kB
JavaScript
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 = ['>', '>'];
/**
* These comparison operators are currently in monitoring
* charts that have alerting related data.
*
* {Array} Possible values for less than
*/
var LESS_THAN = ['<', '<'];
/**
* 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 };