UNPKG

highcharts

Version:
1,516 lines (1,365 loc) 208 kB
/** * @license Highcharts JS v6.2.0 (2018-10-17) * Gantt series * * (c) 2016 Lars A. V. Cabrera * * --- WORK IN PROGRESS --- * * License: www.highcharts.com/license */ 'use strict'; (function (factory) { if (typeof module === 'object' && module.exports) { module.exports = factory; } else if (typeof define === 'function' && define.amd) { define(function () { return factory; }); } else { factory(Highcharts); } }(function (Highcharts) { (function (H) { /** * (c) 2016 Highsoft AS * Author: Lars A. V. Cabrera * * License: www.highcharts.com/license */ /** * Show an indicator on the axis for the current date and time. Can be a boolean * or a configuration object similar to [xAxis.plotLines](#xAxis.plotLines). * * @type {Object} * @extends {xAxis.plotLines} * @excluding value * @sample gantt/current-date-indicator/demo * Current date indicator enabled * @sample gantt/current-date-indicator/object-config * Current date indicator with custom options * @product gantt * @apioption xAxis.currentDateIndicator */ var addEvent = H.addEvent, Axis = H.Axis, PlotLineOrBand = H.PlotLineOrBand, merge = H.merge, defaultConfig = { currentDateIndicator: true, color: '#ccd6eb', width: 2, label: { format: '%a, %b %d %Y, %H:%M', formatter: undefined, rotation: 0, style: { fontSize: '10px' } } }; addEvent(Axis, 'afterSetOptions', function () { var options = this.options, cdiOptions = options.currentDateIndicator; if (cdiOptions) { if (typeof cdiOptions === 'object') { // Ignore formatter if custom format is defined if (cdiOptions.label && cdiOptions.label.format) { cdiOptions.label.formatter = undefined; } cdiOptions = merge(defaultConfig, cdiOptions); } else { cdiOptions = merge(defaultConfig); } cdiOptions.value = new Date(); if (!options.plotLines) { options.plotLines = []; } options.plotLines.push(cdiOptions); } }); addEvent(PlotLineOrBand, 'render', function () { var options = this.options, format, formatter; if (options.currentDateIndicator && options.label) { format = options.label.format; formatter = options.label.formatter; options.value = new Date(); if (typeof formatter === 'function') { options.label.text = formatter(this); } else { options.label.text = H.dateFormat(format, new Date()); } // If the label already exists, update its text if (this.label) { this.label.attr({ text: options.label.text }); } } }); }(Highcharts)); (function (H) { /** * (c) 2016 Highsoft AS * Authors: Lars A. V. Cabrera * * License: www.highcharts.com/license */ var argsToArray = function (args) { return Array.prototype.slice.call(args, 1); }, dateFormat = H.dateFormat, defined = H.defined, each = H.each, isArray = H.isArray, isNumber = H.isNumber, isObject = function (x) { // Always use strict mode return H.isObject(x, true); }, merge = H.merge, pick = H.pick, wrap = H.wrap, Axis = H.Axis, Tick = H.Tick; /** * Set grid options for the axis labels. Requires Highcharts Gantt. * * @type {object} * @since 6.2.0 * @product gantt * @apioption xAxis.grid */ /** * Enable grid on the axis labels. Defaults to true for Gantt charts. * * @type {boolean} * @since 6.2.0 * @default true * @product gantt * @apioption xAxis.grid.enabled */ /** * Set specific options for each column (or row for horizontal axes) in the * grid. Each extra column/row is its own axis, and the axis options can be set * here. * * @type {Array<object>} * @sample gantt/demo/left-axis-table * Left axis as a table * @apioption xAxis.grid.columns */ /** * Set border color for the label grid lines. * * @type {Highcharts.ColorString} * @apioption xAxis.grid.borderColor */ /** * Set border width of the label grid lines. * * @type {number} * @default 1 * @apioption xAxis.grid.borderWidth */ /** * Set cell height for grid axis labels. By default this is calculated from font * size. * * @type {number} * @default null * @apioption xAxis.grid.cellHeight */ // Enum for which side the axis is on. // Maps to axis.side var axisSide = { top: 0, right: 1, bottom: 2, left: 3, 0: 'top', 1: 'right', 2: 'bottom', 3: 'left' }; /** * Checks if an axis is a navigator axis. * @return {Boolean} true if axis is found in axis.chart.navigator */ Axis.prototype.isNavigatorAxis = function () { return /highcharts-navigator-[xy]axis/.test(this.options.className); }; /** * Checks if an axis is the outer axis in its dimension. Since * axes are placed outwards in order, the axis with the highest * index is the outermost axis. * * Example: If there are multiple x-axes at the top of the chart, * this function returns true if the axis supplied is the last * of the x-axes. * * @return true if the axis is the outermost axis in its dimension; * false if not */ Axis.prototype.isOuterAxis = function () { var axis = this, chart = axis.chart, thisIndex = -1, isOuter = true; each(chart.axes, function (otherAxis, index) { if (otherAxis.side === axis.side && !otherAxis.isNavigatorAxis()) { if (otherAxis === axis) { // Get the index of the axis in question thisIndex = index; // Check thisIndex >= 0 in case thisIndex has // not been found yet } else if (thisIndex >= 0 && index > thisIndex) { // There was an axis on the same side with a // higher index. isOuter = false; } } }); // There were either no other axes on the same side, // or the other axes were not farther from the chart return isOuter; }; /** * Get the largest label width and height. * @param {object} ticks All the ticks on one axis. * @param {array} tickPositions All the tick positions on one axis. * @return {object} object containing the properties height and width. */ Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) { var dimensions = { width: 0, height: 0 }; each(tickPositions, function (pos) { var tick = ticks[pos], tickHeight = 0, tickWidth = 0, label; if (isObject(tick)) { label = isObject(tick.label) ? tick.label : {}; // Find width and height of tick tickHeight = label.getBBox ? label.getBBox().height : 0; tickWidth = isNumber(label.textPxLength) ? label.textPxLength : 0; // Update the result if width and/or height are larger dimensions.height = Math.max(tickHeight, dimensions.height); dimensions.width = Math.max(tickWidth, dimensions.width); } }); return dimensions; }; /** * Add custom date formats */ H.dateFormats = { // Week number W: function (timestamp) { var d = new Date(timestamp), yearStart, weekNo; d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - (d.getDay() || 7)); yearStart = new Date(d.getFullYear(), 0, 1); weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); return weekNo; }, // First letter of the day of the week, e.g. 'M' for 'Monday'. E: function (timestamp) { return dateFormat('%a', timestamp, true).charAt(0); } }; /** * If chart is stockChart, always return 'left' to avoid labels being placed * inside chart. Stock charts place yAxis labels inside by default. * @param {function} proceed - the original function * @return {string} 'left' if stockChart, or auto calculated alignment */ wrap(Axis.prototype, 'autoLabelAlign', function (proceed) { var axis = this, retVal; if (axis.chart.isStock) { retVal = 'left'; } else { retVal = proceed.apply(axis, argsToArray(arguments)); } return retVal; }); /** * Center tick labels in cells. * * @param {function} proceed - the original function * * @return {object} object - an object containing x and y positions * for the tick */ wrap(Tick.prototype, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOpts, tickmarkOffset, index) { var tick = this, axis = tick.axis, reversed = axis.reversed, chart = axis.chart, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}, align = labelOpts.align, // verticalAlign is currently not supported for axis.labels. verticalAlign = 'middle', // labelOpts.verticalAlign, side = axisSide[axis.side], tickPositions = axis.tickPositions, tickPos = tick.pos - tickmarkOffset, nextTickPos = ( isNumber(tickPositions[index + 1]) ? tickPositions[index + 1] - tickmarkOffset : axis.max + tickmarkOffset ), tickSize = axis.tickSize('tick', true), tickWidth = isArray(tickSize) ? tickSize[0] : 0, crispCorr = tickSize && tickSize[1] / 2, labelHeight, lblMetrics, lines, result, bottom, top, left, right; // Only center tick labels in grid axes if (gridOptions.enabled === true) { /** * Calculate top and bottom positions of the cell. */ if (side === 'top') { bottom = axis.top + axis.offset; top = bottom - tickWidth; } else if (side === 'bottom') { top = chart.chartHeight - axis.bottom + axis.offset; bottom = top + tickWidth; } else { bottom = axis.top + axis.len - axis.translate( reversed ? nextTickPos : tickPos ); top = axis.top + axis.len - axis.translate( reversed ? tickPos : nextTickPos ); } /** * Calculate left and right positions of the cell. */ if (side === 'right') { left = chart.chartWidth - axis.right + axis.offset; right = left + tickWidth; } else if (side === 'left') { right = axis.left + axis.offset; left = right - tickWidth; } else { left = Math.round(axis.left + axis.translate( reversed ? nextTickPos : tickPos )) - crispCorr; right = Math.round(axis.left + axis.translate( reversed ? tickPos : nextTickPos )) - crispCorr; } tick.slotWidth = right - left; /** * Calculate the positioning of the label based on alignment. */ result = { x: ( align === 'left' ? left : align === 'right' ? right : left + ((right - left) / 2) // default to center ), y: ( verticalAlign === 'top' ? top : verticalAlign === 'bottom' ? bottom : top + ((bottom - top) / 2) // default to middle ) }; lblMetrics = chart.renderer.fontMetrics( labelOpts.style.fontSize, label.element ); labelHeight = label.getBBox().height; // Adjustment to y position to align the label correctly. // Would be better to have a setter or similar for this. if (!labelOpts.useHTML) { lines = Math.round(labelHeight / lblMetrics.h); result.y += ( // Center the label // TODO: why does this actually center the label? ((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) + // Adjust for height of additional lines. -(((lines - 1) * lblMetrics.h) / 2) ); } else { result.y += ( // Readjust yCorr in htmlUpdateTransform lblMetrics.b + // Adjust for height of html label -(labelHeight / 2) ); } result.x += (axis.horiz && labelOpts.x || 0); } else { result = proceed.apply(tick, argsToArray(arguments)); } return result; }); /** * Draw vertical axis ticks extra long to create cell floors and roofs. * Overrides the tickLength for vertical axes. * * @param {function} proceed - the original function * @returns {array} retVal - */ wrap(Axis.prototype, 'tickSize', function (proceed) { var axis = this, dimensions = axis.maxLabelDimensions, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}, retVal = proceed.apply(axis, argsToArray(arguments)), labelPadding, distance; if (gridOptions.enabled === true) { labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2); distance = labelPadding + (axis.horiz ? dimensions.height : dimensions.width); if (isArray(retVal)) { retVal[0] = distance; } else { retVal = [distance]; } } return retVal; }); wrap(Axis.prototype, 'getTitlePosition', function (proceed) { var axis = this, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}; if (gridOptions.enabled === true) { // compute anchor points for each of the title align options var title = axis.axisTitle, titleWidth = title && title.getBBox().width, horiz = axis.horiz, axisLeft = axis.left, axisTop = axis.top, axisWidth = axis.width, axisHeight = axis.height, axisTitleOptions = options.title, opposite = axis.opposite, offset = axis.offset, tickSize = axis.tickSize() || [0], xOption = axisTitleOptions.x || 0, yOption = axisTitleOptions.y || 0, titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10), titleFontSize = axis.chart.renderer.fontMetrics( axisTitleOptions.style && axisTitleOptions.style.fontSize, title ).f, // TODO account for alignment // the position in the perpendicular direction of the axis offAxis = (horiz ? axisTop + axisHeight : axisLeft) + (horiz ? 1 : -1) * // horizontal axis reverses the margin (opposite ? -1 : 1) * // so does opposite axes (tickSize[0] / 2) + (axis.side === axisSide.bottom ? titleFontSize : 0); return { x: horiz ? axisLeft - titleWidth / 2 - titleMargin + xOption : offAxis + (opposite ? axisWidth : 0) + offset + xOption, y: horiz ? ( offAxis - (opposite ? axisHeight : 0) + (opposite ? titleFontSize : -titleFontSize) / 2 + offset + yOption ) : axisTop - titleMargin + yOption }; } return proceed.apply(this, argsToArray(arguments)); }); /** * Avoid altering tickInterval when reserving space. */ wrap(Axis.prototype, 'unsquish', function (proceed) { var axis = this, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}; if (gridOptions.enabled === true && this.categories) { return this.tickInterval; } return proceed.apply(this, argsToArray(arguments)); }); /** * Creates a left and right wall on horizontal axes: * - Places leftmost tick at the start of the axis, to create a left wall * - Ensures that the rightmost tick is at the end of the axis, to create a * right wall. * * @param {function} proceed - the original function * @param {object} options - the pure axis options as input by the user */ H.addEvent(Axis, 'afterSetOptions', function (e) { var options = this.options, userOptions = e.userOptions, gridAxisOptions, gridOptions = (options && isObject(options.grid)) ? options.grid : {}; if (gridOptions.enabled === true) { // Merge the user options into default grid axis options so that when a // user option is set, it takes presedence. gridAxisOptions = merge(true, { className: 'highcharts-grid-axis ' + (userOptions.className || ''), dateTimeLabelFormats: { hour: { list: ['%H:%M', '%H'] }, day: { list: ['%A, %e. %B', '%a, %e. %b', '%E'] }, week: { list: ['Week %W', 'W%W'] }, month: { list: ['%B', '%b', '%o'] } }, grid: { borderWidth: 1 }, labels: { padding: 2, style: { fontSize: '13px' } }, title: { text: null, reserveSpace: false, rotation: 0 }, // In a grid axis, only allow one unit of certain types, for example // we shouln't have one grid cell spanning two days. units: [[ 'millisecond', // unit name [1, 10, 100] ], [ 'second', [1, 10] ], [ 'minute', [1, 5, 15] ], [ 'hour', [1, 6] ], [ 'day', [1] ], [ 'week', [1] ], [ 'month', [1] ], [ 'year', null ]] }, userOptions); // X-axis specific options if (this.coll === 'xAxis') { // For linked axes, tickPixelInterval is used only if the // tickPositioner below doesn't run or returns undefined (like // multiple years) if ( defined(userOptions.linkedTo) && !defined(userOptions.tickPixelInterval) ) { gridAxisOptions.tickPixelInterval = 350; } // For the secondary grid axis, use the primary axis' tick intervals // and return ticks one level higher. if ( // Check for tick pixel interval in options !defined(userOptions.tickPixelInterval) && // Only for linked axes defined(userOptions.linkedTo) && !defined(userOptions.tickPositioner) && !defined(userOptions.tickInterval) ) { gridAxisOptions.tickPositioner = function (min, max) { var parentInfo = ( this.linkedParent && this.linkedParent.tickPositions && this.linkedParent.tickPositions.info ); if (parentInfo) { var unitIdx, count, unitName, i, units = gridAxisOptions.units, unitRange; for (i = 0; i < units.length; i++) { if (units[i][0] === parentInfo.unitName) { unitIdx = i; break; } } // Spanning multiple years, go default if (!units[unitIdx][1]) { return; } // Get the first allowed count on the next unit. if (units[unitIdx + 1]) { unitName = units[unitIdx + 1][0]; count = (units[unitIdx + 1][1] || [1])[0]; } unitRange = H.timeUnits[unitName]; this.tickInterval = unitRange * count; return this.getTimeTicks( { unitRange: unitRange, count: count, unitName: unitName }, min, max, this.options.startOfWeek ); } }; } } // Now merge the combined options into the axis options merge(true, this.options, gridAxisOptions); if (this.horiz) { /** _________________________ * Make this: ___|_____|_____|_____|__| * ^ ^ * _________________________ * Into this: |_____|_____|_____|_____| * ^ ^ */ options.minPadding = pick(userOptions.minPadding, 0); options.maxPadding = pick(userOptions.maxPadding, 0); } // If borderWidth is set, then use its value for tick and line width. if (isNumber(options.grid.borderWidth)) { options.tickWidth = options.lineWidth = gridOptions.borderWidth; } } }); /** * Ensures a left wall on horizontal axes with series inheriting from column. * ColumnSeries normally sets pointRange to null, resulting in Axis to select * other values for point ranges. This enforces the above Axis.setOptions() * override. * _________________________ * Enforce this: ___|_____|_____|_____|__| * ^ * _________________________ * To be this: |_____|_____|_____|_____| * ^ * * @param {function} proceed - the original function * @param {object} options - the pure axis options as input by the user */ wrap(Axis.prototype, 'setAxisTranslation', function (proceed) { var axis = this, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}, tickInfo = this.tickPositions && this.tickPositions.info, userLabels = this.userOptions.labels || {}; if (this.horiz) { if (gridOptions.enabled === true) { each(axis.series, function (series) { series.options.pointRange = 0; }); } // Lower level time ticks, like hours or minutes, represent points in // time and not ranges. These should be aligned left in the grid cell // by default. The same applies to years of higher order. if ( tickInfo && ( options.dateTimeLabelFormats[tickInfo.unitName] .range === false || tickInfo.count > 1 // years ) && !defined(userLabels.align) ) { options.labels.align = 'left'; if (!defined(userLabels.x)) { options.labels.x = 3; } } } proceed.apply(axis, argsToArray(arguments)); }); // TODO: Does this function do what the drawing says? Seems to affect ticks and // not the labels directly? /** * Makes tick labels which are usually ignored in a linked axis displayed if * they are within range of linkedParent.min. * _____________________________ * | | | | | * Make this: | | 2 | 3 | 4 | * |___|_______|_______|_______| * ^ * _____________________________ * | | | | | * Into this: | 1 | 2 | 3 | 4 | * |___|_______|_______|_______| * ^ * @param {function} proceed - the original function */ wrap(Axis.prototype, 'trimTicks', function (proceed) { var axis = this, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}, categoryAxis = axis.categories, tickPositions = axis.tickPositions, firstPos = tickPositions[0], lastPos = tickPositions[tickPositions.length - 1], linkedMin = axis.linkedParent && axis.linkedParent.min, linkedMax = axis.linkedParent && axis.linkedParent.max, min = linkedMin || axis.min, max = linkedMax || axis.max, tickInterval = axis.tickInterval, moreThanMin = firstPos > min, lessThanMax = lastPos < max, endMoreThanMin = firstPos < min && firstPos + tickInterval > min, startLessThanMax = lastPos > max && lastPos - tickInterval < max; if ( gridOptions.enabled === true && !categoryAxis && (axis.horiz || axis.isLinked) ) { if ((moreThanMin || endMoreThanMin) && !options.startOnTick) { tickPositions[0] = min; } if ((lessThanMax || startLessThanMax) && !options.endOnTick) { tickPositions[tickPositions.length - 1] = max; } } proceed.apply(axis, argsToArray(arguments)); }); /** * Draw an extra line on the far side of the outermost axis, * creating floor/roof/wall of a grid. And some padding. * * Make this: * (axis.min) __________________________ (axis.max) * | | | | | * Into this: * (axis.min) __________________________ (axis.max) * ___|____|____|____|____|__ * * @param {function} proceed - the original function */ wrap(Axis.prototype, 'render', function (proceed) { var axis = this, options = axis.options, gridOptions = (options && isObject(options.grid)) ? options.grid : {}, labelPadding, distance, lineWidth, linePath, yStartIndex, yEndIndex, xStartIndex, xEndIndex, renderer = axis.chart.renderer, horiz = axis.horiz, axisGroupBox; if (gridOptions.enabled === true) { // TODO acutual label padding (top, bottom, left, right) // Label padding is needed to figure out where to draw the outer line. labelPadding = (Math.abs(axis.defaultLeftAxisOptions.labels.x) * 2); axis.maxLabelDimensions = axis.getMaxLabelDimensions( axis.ticks, axis.tickPositions ); distance = axis.maxLabelDimensions.width + labelPadding; lineWidth = options.lineWidth; // Remove right wall before rendering if updating if (axis.rightWall) { axis.rightWall.destroy(); } // Call original Axis.render() to obtain axis.axisLine and // axis.axisGroup proceed.apply(axis); axisGroupBox = axis.axisGroup.getBBox(); /* * Draw an extra axis line on outer axes * > * Make this: |______|______|______|___ * * > _________________________ * Into this: |______|______|______|__| * */ if (axis.isOuterAxis() && axis.axisLine) { if (horiz) { // -1 to avoid adding distance each time the chart updates distance = axisGroupBox.height - 1; } if (lineWidth) { linePath = axis.getLinePath(lineWidth); xStartIndex = linePath.indexOf('M') + 1; xEndIndex = linePath.indexOf('L') + 1; yStartIndex = linePath.indexOf('M') + 2; yEndIndex = linePath.indexOf('L') + 2; // Negate distance if top or left axis if (axis.side === axisSide.top || axis.side === axisSide.left) { distance = -distance; } // If axis is horizontal, reposition line path vertically if (horiz) { linePath[yStartIndex] = linePath[yStartIndex] + distance; linePath[yEndIndex] = linePath[yEndIndex] + distance; } else { // If axis is vertical, reposition line path horizontally linePath[xStartIndex] = linePath[xStartIndex] + distance; linePath[xEndIndex] = linePath[xEndIndex] + distance; } if (!axis.axisLineExtra) { axis.axisLineExtra = renderer.path(linePath) .attr({ zIndex: 7 }) .addClass('highcharts-axis-line') .add(axis.axisGroup); } else { axis.axisLineExtra.animate({ d: linePath }); } // show or hide the line depending on options.showEmpty axis.axisLine[axis.showAxis ? 'show' : 'hide'](true); } } } else { proceed.apply(axis); } }); /** * Wraps axis init to draw cell walls on vertical axes. * * @param {function} proceed - the original function */ wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) { var axis = this, gridOptions = ( (userOptions && isObject(userOptions.grid)) ? userOptions.grid : {} ), columnOptions, column, columnIndex, i; function applyGridOptions(axis) { var options = axis.options, // TODO: Consider using cell margins defined in % of font size? // 25 is optimal height for default fontSize (11px) // 25 / 11 ≈ 2.28 fontSizeToCellHeightRatio = 25 / 11, fontSize = options.labels.style.fontSize, fontMetrics = axis.chart.renderer.fontMetrics(fontSize); // Center-align by default if (!options.labels) { options.labels = {}; } options.labels.align = pick(options.labels.align, 'center'); // TODO: Check against tickLabelPlacement between/on etc /** * Prevents adding the last tick label if the axis is not a category * axis. * * Since numeric labels are normally placed at starts and ends of a * range of value, and this module makes the label point at the value, * an "extra" label would appear. */ if (!axis.categories) { options.showLastLabel = false; } /** * Make tick marks taller, creating cell walls of a grid. * Use cellHeight axis option if set */ if (axis.horiz) { options.tickLength = gridOptions.cellHeight || fontMetrics.h * fontSizeToCellHeightRatio; } /** * Prevents rotation of labels when squished, as rotating them would not * help. */ axis.labelRotation = 0; options.labels.rotation = 0; } if (gridOptions.enabled) { if (defined(gridOptions.borderColor)) { userOptions.tickColor = userOptions.lineColor = gridOptions.borderColor; } // Handle columns, each column is a grid axis if (isArray(gridOptions.columns)) { columnIndex = 0; i = gridOptions.columns.length; while (i--) { columnOptions = merge( userOptions, gridOptions.columns[i], { // Force to behave like category axis type: 'category' } ); delete columnOptions.grid.columns; // Prevent recursion column = new Axis(chart, columnOptions); column.isColumn = true; column.columnIndex = columnIndex; wrap(column, 'labelFormatter', function (proceed) { var axis = this.axis, tickPos = axis.tickPositions, value = this.value, series = axis.series[0], isFirst = value === tickPos[0], isLast = value === tickPos[tickPos.length - 1], point = H.find(series.options.data, function (p) { return p[axis.isXAxis ? 'x' : 'y'] === value; }); // Make additional properties available for the formatter this.isFirst = isFirst; this.isLast = isLast; this.point = point; // Call original labelFormatter return proceed.call(this); }); columnIndex++; } } else { // Call original Axis.init() proceed.apply(axis, argsToArray(arguments)); applyGridOptions(axis); } } else { // Call original Axis.init() proceed.apply(axis, argsToArray(arguments)); } }); }(Highcharts)); (function (H) { /** * (c) 2018 Torstein Honsi, Lars Cabrera * * License: www.highcharts.com/license */ var Chart = H.Chart, each = H.each, pick = H.pick; /** * For vertical axes only. Setting the static scale ensures that each tick unit * is translated into a fixed pixel height. For example, setting the static * scale to 24 results in each Y axis category taking up 24 pixels, and the * height of the chart adjusts. Adding or removing items will make the chart * resize. * * @type {number} * @sample gantt/xrange-series/demo/ X-range series with static scale * @since 6.2.0 * @product gantt * @default 50 * @apioption yAxis.staticScale */ H.addEvent(H.Axis, 'afterSetOptions', function () { if ( !this.horiz && H.isNumber(this.options.staticScale) && !this.chart.options.chart.height ) { this.staticScale = this.options.staticScale; } }); Chart.prototype.adjustHeight = function () { if (this.redrawTrigger !== 'adjustHeight') { each(this.axes || [], function (axis) { var chart = axis.chart, animate = !!chart.initiatedScale && chart.options.animation, staticScale = axis.options.staticScale, height, diff; if (axis.staticScale && H.defined(axis.min)) { height = pick( axis.unitLength, axis.max + axis.tickInterval - axis.min ) * staticScale; // Minimum height is 1 x staticScale. height = Math.max(height, staticScale); diff = height - chart.plotHeight; if (Math.abs(diff) >= 1) { chart.plotHeight = height; chart.redrawTrigger = 'adjustHeight'; chart.setSize(undefined, chart.chartHeight + diff, animate); } // Make sure clip rects have the right height before initial // animation. each(axis.series, function (series) { var clipRect = series.sharedClipKey && chart[series.sharedClipKey]; if (clipRect) { clipRect.attr({ height: chart.plotHeight }); } }); } }); this.initiatedScale = true; } this.redrawTrigger = null; }; H.addEvent(Chart, 'render', Chart.prototype.adjustHeight); }(Highcharts)); var Tree = (function (H) { /** * (c) 2016 Highsoft AS * Authors: Jon Arild Nygard * * License: www.highcharts.com/license */ /* eslint no-console: 0 */ var each = H.each, extend = H.extend, isNumber = H.isNumber, keys = H.keys, map = H.map, pick = H.pick, reduce = H.reduce, isFunction = function (x) { return typeof x === 'function'; }; /** * Creates an object map from parent id to childrens index. * @param {Array} data List of points set in options. * @param {string} data[].parent Parent id of point. * @param {Array} ids List of all point ids. * @returns {Object} Map from parent id to children index in data */ var getListOfParents = function (data, ids) { var listOfParents = reduce(data, function (prev, curr) { var parent = pick(curr.parent, ''); if (prev[parent] === undefined) { prev[parent] = []; } prev[parent].push(curr); return prev; }, {}), parents = keys(listOfParents); // If parent does not exist, hoist parent to root of tree. each(parents, function (parent, list) { var children = listOfParents[parent]; if ((parent !== '') && (H.inArray(parent, ids) === -1)) { each(children, function (child) { list[''].push(child); }); delete list[parent]; } }); return listOfParents; }; var getNode = function (id, parent, level, data, mapOfIdToChildren, options) { var descendants = 0, height = 0, after = options && options.after, before = options && options.before, node = { data: data, depth: level - 1, id: id, level: level, parent: parent }, start, end, children; // Allow custom logic before the children has been created. if (isFunction(before)) { before(node, options); } /** * Call getNode recursively on the children. Calulate the height of the * node, and the number of descendants. */ children = map((mapOfIdToChildren[id] || []), function (child) { var node = getNode( child.id, id, (level + 1), child, mapOfIdToChildren, options ), childStart = child.start, childEnd = ( child.milestone === true ? childStart : child.end ); // Start should be the lowest child.start. start = ( (!isNumber(start) || childStart < start) ? childStart : start ); // End should be the largest child.end. // If child is milestone, then use start as end. end = ( (!isNumber(end) || childEnd > end) ? childEnd : end ); descendants = descendants + 1 + node.descendants; height = Math.max(node.height + 1, height); return node; }); // Calculate start and end for point if it is not already explicitly set. if (data) { data.start = pick(data.start, start); data.end = pick(data.end, end); } extend(node, { children: children, descendants: descendants, height: height }); // Allow custom logic after the children has been created. if (isFunction(after)) { after(node, options); } return node; }; var getTree = function (data, options) { var ids = map(data, function (d) { return d.id; }), mapOfIdToChildren = getListOfParents(data, ids); return getNode('', null, 1, null, mapOfIdToChildren, options); }; var Tree = { getListOfParents: getListOfParents, getNode: getNode, getTree: getTree }; return Tree; }(Highcharts)); var result = (function (H) { var each = H.each, extend = H.extend, isArray = H.isArray, isBoolean = function (x) { return typeof x === 'boolean'; }, isFn = function (x) { return typeof x === 'function'; }, isObject = H.isObject, isNumber = H.isNumber, merge = H.merge, pick = H.pick, reduce = H.reduce; // TODO Combine buildTree and buildNode with setTreeValues // TODO Remove logic from Treemap and make it utilize this mixin. var setTreeValues = function setTreeValues(tree, options) { var before = options.before, idRoot = options.idRoot, mapIdToNode = options.mapIdToNode, nodeRoot = mapIdToNode[idRoot], levelIsConstant = ( isBoolean(options.levelIsConstant) ? options.levelIsConstant : true ), points = options.points, point = points[tree.i], optionsPoint = point && point.options || {}, childrenTotal = 0, children = [], value; extend(tree, { levelDynamic: tree.level - (levelIsConstant ? 0 : nodeRoot.level), name: pick(point && point.name, ''), visible: ( idRoot === tree.id || (isBoolean(options.visible) ? options.visible : false) ) }); if (isFn(before)) { tree = before(tree, options); } // First give the children some values each(tree.children, function (child, i) { var newOptions = extend({}, options); extend(newOptions, { index: i, siblings: tree.children.length, visible: tree.visible }); child = setTreeValues(child, newOptions); children.push(child); if (child.visible) { childrenTotal += child.val; } }); tree.visible = childrenTotal > 0 || tree.visible; // Set the values value = pick(optionsPoint.value, childrenTotal); extend(tree, { children: children, childrenTotal: childrenTotal, isLeaf: tree.visible && !childrenTotal, val: value }); return tree; }; var getColor = function getColor(node, options) { var index = options.index, mapOptionsToLevel = options.mapOptionsToLevel, parentColor = options.parentColor, parentColorIndex = options.parentColorIndex, series = options.series, colors = options.colors, siblings = options.siblings, points = series.points, getColorByPoint, point, level, colorByPoint, colorIndexByPoint, color, colorIndex; function variation(color) { var colorVariation = level && level.colorVariation; if (colorVariation) { if (colorVariation.key === 'brightness') { return H.color(color).brighten( colorVariation.to * (index / siblings) ).get(); } } return color; } if (node) { point = points[node.i]; level = mapOptionsToLevel[node.level] || {}; getColorByPoint = point && level.colorByPoint; if (getColorByPoint) { colorIndexByPoint = point.index % (colors ? colors.length : series.chart.options.chart.colorCount ); colorByPoint = colors && colors[colorIndexByPoint]; } colorIndex = pick( point && point.options.colorIndex, level && level.colorIndex, colorIndexByPoint, parentColorIndex, options.colorIndex ); } return { color: color, colorIndex: colorIndex }; }; /** * getLevelOptions - Creates a map from level number to its given options. * @param {Object} params Object containing parameters. * @param {Object} params.defaults Object containing default options. The * default options are merged with the userOptions to get the final options for * a specific level. * @param {Number} params.from The lowest level number. * @param {Array} params.levels User options from series.levels. * @param {Number} params.to The highest level number. * @return {null|Object} Returns a map from level number to its given options. * Returns null if invalid input parameters. */ var getLevelOptions = function getLevelOptions(params) { var result = null, defaults, converted, i, from, to, levels; if (isObject(params)) { result = {}; from = isNumber(params.from) ? params.from : 1; levels = params.levels; converted = {}; defaults = isObject(params.defaults) ? params.defaults : {}; if (isArray(levels)) { converted = reduce(levels, function (obj, item) { var level, levelIsConstant, options; if (isObject(item) && isNumber(item.level)) { options = merge({}, item); levelIsConstant = ( isBoolean(options.levelIsConstant) ? options.levelIsConstant : defaults.levelIsConstant ); // Delete redundant properties. delete options.levelIsConstant; delete options.level; // Calculate which level these options apply to. level = item.level + (levelIsConstant ? 0 : from - 1); if (isObject(obj[level])) { extend(obj[level], options); } else { obj[level] = options; } } return obj; }, {}); } to = isNumber(params.to) ? params.to : 1; for (i = 0; i <= to; i++) { result[i] = merge( {}, defaults, isObject(converted[i]) ? converted[i] : {} ); } } return result; }; /** * Update the rootId property on the series. Also makes sure that it is * accessible to exporting. * @param {object} series The series to operate on. * @returns Returns the resulting rootId after update. */ var updateRootId = function (series) { var rootId, options; if (isObject(series)) { // Get the series options. options = isObject(series.options) ? series.options : {}; // Calculate the rootId. rootId = pick(series.rootNode, options.rootId, '');