} path\n * @param {{}} optionsObj\n * @returns {{}}\n * @private\n */\n _constructOptions(value, path, optionsObj = {}) {\n let pointer = optionsObj;\n\n // when dropdown boxes can be string or boolean, we typecast it into correct types\n value = value === 'true' ? true : value;\n value = value === 'false' ? false : value;\n\n for (let i = 0; i < path.length; i++) {\n if (path[i] !== 'global') {\n if (pointer[path[i]] === undefined) {\n pointer[path[i]] = {};\n }\n if (i !== path.length - 1) {\n pointer = pointer[path[i]];\n }\n else {\n pointer[path[i]] = value;\n }\n }\n }\n return optionsObj;\n }\n\n /**\n * @private\n */\n _printOptions() {\n let options = this.getOptions();\n this.optionsContainer.innerHTML = 'var options = ' + JSON.stringify(options, null, 2) + '
';\n }\n\n /**\n *\n * @returns {{}} options\n */\n getOptions() {\n let options = {};\n for (var i = 0; i < this.changedOptions.length; i++) {\n this._constructOptions(this.changedOptions[i].value, this.changedOptions[i].path, options)\n }\n return options;\n }\n}\n\n\nexport default Configurator;\n","import moment from '../module/moment';\nimport util from '../util';\nimport { DataSet } from 'vis-data';\nimport { DataView } from 'vis-data';\nimport Range from './Range';\nimport Core from './Core';\nimport TimeAxis from './component/TimeAxis';\nimport CurrentTime from './component/CurrentTime';\nimport CustomTime from './component/CustomTime';\nimport ItemSet from './component/ItemSet';\n\nimport { Validator } from '../shared/Validator';\nimport { printStyle } from '../shared/Validator';\nimport { allOptions } from './optionsTimeline';\nimport { configureOptions } from './optionsTimeline';\n\nimport Configurator from '../shared/Configurator';\n\n/**\n * Create a timeline visualization\n * @extends Core\n */\nexport default class Timeline extends Core {\n /**\n * @param {HTMLElement} container\n * @param {vis.DataSet | vis.DataView | Array} [items]\n * @param {vis.DataSet | vis.DataView | Array} [groups]\n * @param {Object} [options] See Timeline.setOptions for the available options.\n * @constructor Timeline\n */\n constructor(container, items, groups, options) {\n super()\n this.initTime = new Date();\n this.itemsDone = false;\n\n if (!(this instanceof Timeline)) {\n throw new SyntaxError('Constructor must be called with the new operator');\n }\n\n // if the third element is options, the forth is groups (optionally);\n if (!(Array.isArray(groups) || groups instanceof DataSet || groups instanceof DataView) && groups instanceof Object) {\n const forthArgument = options;\n options = groups;\n groups = forthArgument;\n }\n\n // TODO: REMOVE THIS in the next MAJOR release\n // see https://github.com/almende/vis/issues/2511\n if (options && options.throttleRedraw) {\n console.warn(\"Timeline option \\\"throttleRedraw\\\" is DEPRICATED and no longer supported. It will be removed in the next MAJOR release.\");\n }\n\n const me = this;\n this.defaultOptions = {\n autoResize: true,\n orientation: {\n axis: 'bottom', // axis orientation: 'bottom', 'top', or 'both'\n item: 'bottom' // not relevant\n },\n moment,\n };\n this.options = util.deepExtend({}, this.defaultOptions);\n\n // Create the DOM, props, and emitter\n this._create(container);\n if (!options || (options && typeof options.rtl == \"undefined\")) {\n this.dom.root.style.visibility = 'hidden';\n let directionFromDom;\n let domNode = this.dom.root;\n while (!directionFromDom && domNode) {\n directionFromDom = window.getComputedStyle(domNode, null).direction;\n domNode = domNode.parentElement;\n }\n this.options.rtl = (directionFromDom && (directionFromDom.toLowerCase() == \"rtl\"));\n } else {\n this.options.rtl = options.rtl;\n }\n\n if (options) {\n if (options.rollingMode) { this.options.rollingMode = options.rollingMode; }\n if (options.onInitialDrawComplete) { this.options.onInitialDrawComplete = options.onInitialDrawComplete; }\n if (options.onTimeout) { this.options.onTimeout = options.onTimeout; }\n if (options.loadingScreenTemplate) { this.options.loadingScreenTemplate = options.loadingScreenTemplate; }\n }\n\n // Prepare loading screen\n const loadingScreenFragment = document.createElement('div');\n if (this.options.loadingScreenTemplate) {\n const templateFunction = this.options.loadingScreenTemplate.bind(this);\n const loadingScreen = templateFunction(this.dom.loadingScreen);\n if ((loadingScreen instanceof Object) && !(loadingScreen instanceof Element)) {\n templateFunction(loadingScreenFragment)\n } else {\n if (loadingScreen instanceof Element) {\n loadingScreenFragment.innerHTML = '';\n loadingScreenFragment.appendChild(loadingScreen);\n }\n else if (loadingScreen != undefined) {\n loadingScreenFragment.innerHTML = loadingScreen;\n }\n }\n }\n this.dom.loadingScreen.appendChild(loadingScreenFragment);\n\n // all components listed here will be repainted automatically\n this.components = [];\n\n this.body = {\n dom: this.dom,\n domProps: this.props,\n emitter: {\n on: this.on.bind(this),\n off: this.off.bind(this),\n emit: this.emit.bind(this)\n },\n hiddenDates: [],\n util: {\n getScale() {\n return me.timeAxis.step.scale;\n },\n getStep() {\n return me.timeAxis.step.step;\n },\n\n toScreen: me._toScreen.bind(me),\n toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width\n toTime: me._toTime.bind(me),\n toGlobalTime : me._toGlobalTime.bind(me)\n }\n };\n\n // range\n this.range = new Range(this.body, this.options);\n this.components.push(this.range);\n this.body.range = this.range;\n\n // time axis\n this.timeAxis = new TimeAxis(this.body, this.options);\n this.timeAxis2 = null; // used in case of orientation option 'both'\n this.components.push(this.timeAxis);\n\n // current time bar\n this.currentTime = new CurrentTime(this.body, this.options);\n this.components.push(this.currentTime);\n\n // item set\n this.itemSet = new ItemSet(this.body, this.options);\n this.components.push(this.itemSet);\n\n this.itemsData = null; // DataSet\n this.groupsData = null; // DataSet\n\n this.dom.root.onclick = event => {\n me.emit('click', me.getEventProperties(event))\n };\n this.dom.root.ondblclick = event => {\n me.emit('doubleClick', me.getEventProperties(event))\n };\n this.dom.root.oncontextmenu = event => {\n me.emit('contextmenu', me.getEventProperties(event))\n };\n this.dom.root.onmouseover = event => {\n me.emit('mouseOver', me.getEventProperties(event))\n };\n if(window.PointerEvent) {\n this.dom.root.onpointerdown = event => {\n me.emit('mouseDown', me.getEventProperties(event))\n };\n this.dom.root.onpointermove = event => {\n me.emit('mouseMove', me.getEventProperties(event))\n };\n this.dom.root.onpointerup = event => {\n me.emit('mouseUp', me.getEventProperties(event))\n };\n } else {\n this.dom.root.onmousemove = event => {\n me.emit('mouseMove', me.getEventProperties(event))\n };\n this.dom.root.onmousedown = event => {\n me.emit('mouseDown', me.getEventProperties(event))\n };\n this.dom.root.onmouseup = event => {\n me.emit('mouseUp', me.getEventProperties(event))\n };\n }\n\n //Single time autoscale/fit\n this.initialFitDone = false;\n this.on('changed', () => {\n if (me.itemsData == null) return;\n if (!me.initialFitDone && !me.options.rollingMode) {\n me.initialFitDone = true;\n if (me.options.start != undefined || me.options.end != undefined) {\n if (me.options.start == undefined || me.options.end == undefined) {\n var range = me.getItemRange();\n }\n\n const start = me.options.start != undefined ? me.options.start : range.min;\n const end = me.options.end != undefined ? me.options.end : range.max;\n me.setWindow(start, end, {animation: false});\n } else {\n me.fit({animation: false});\n }\n }\n\n if (!me.initialDrawDone && (me.initialRangeChangeDone || (!me.options.start && !me.options.end) \n || me.options.rollingMode)) {\n me.initialDrawDone = true;\n me.itemSet.initialDrawDone = true;\n me.dom.root.style.visibility = 'visible';\n me.dom.loadingScreen.parentNode.removeChild(me.dom.loadingScreen);\n if (me.options.onInitialDrawComplete) {\n setTimeout(() => {\n return me.options.onInitialDrawComplete();\n }, 0)\n }\n }\n });\n\n this.on('destroyTimeline', () => {\n me.destroy()\n });\n\n // apply options\n if (options) {\n this.setOptions(options);\n }\n\n this.body.emitter.on('fit', (args) => {\n this._onFit(args);\n this.redraw();\n });\n\n // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!\n if (groups) {\n this.setGroups(groups);\n }\n\n // create itemset\n if (items) {\n this.setItems(items);\n }\n\n // draw for the first time\n this._redraw();\n }\n\n /**\n * Load a configurator\n * @return {Object}\n * @private\n */\n _createConfigurator() {\n return new Configurator(this, this.dom.container, configureOptions);\n }\n\n /**\n * Force a redraw. The size of all items will be recalculated.\n * Can be useful to manually redraw when option autoResize=false and the window\n * has been resized, or when the items CSS has been changed.\n *\n * Note: this function will be overridden on construction with a trottled version\n */\n redraw() {\n this.itemSet && this.itemSet.markDirty({refreshItems: true});\n this._redraw();\n }\n\n /**\n * Remove an item from the group\n * @param {object} options\n */\n setOptions(options) {\n // validate options\n let errorFound = Validator.validate(options, allOptions);\n\n if (errorFound === true) {\n console.log('%cErrors have been found in the supplied options object.', printStyle);\n }\n\n Core.prototype.setOptions.call(this, options);\n\n if ('type' in options) {\n if (options.type !== this.options.type) {\n this.options.type = options.type;\n\n // force recreation of all items\n const itemsData = this.itemsData;\n if (itemsData) {\n const selection = this.getSelection();\n this.setItems(null); // remove all\n this.setItems(itemsData); // add all\n this.setSelection(selection); // restore selection\n }\n }\n }\n }\n\n /**\n * Set items\n * @param {vis.DataSet | Array | null} items\n */\n setItems(items) {\n this.itemsDone = false;\n \n // convert to type DataSet when needed\n let newDataSet;\n if (!items) {\n newDataSet = null;\n }\n else if (items instanceof DataSet || items instanceof DataView) {\n newDataSet = items;\n }\n else {\n // turn an array into a dataset\n newDataSet = new DataSet(items, {\n type: {\n start: 'Date',\n end: 'Date'\n }\n });\n }\n\n // set items\n this.itemsData = newDataSet;\n this.itemSet && this.itemSet.setItems(newDataSet);\n }\n\n /**\n * Set groups\n * @param {vis.DataSet | Array} groups\n */\n setGroups(groups) {\n // convert to type DataSet when needed\n let newDataSet;\n if (!groups) {\n newDataSet = null;\n }\n else {\n const filter = group => group.visible !== false;\n if (groups instanceof DataSet || groups instanceof DataView) {\n newDataSet = new DataView(groups,{filter});\n }\n else {\n // turn an array into a dataset\n newDataSet = new DataSet(groups.filter(filter));\n }\n }\n\n this.groupsData = newDataSet;\n this.itemSet.setGroups(newDataSet);\n }\n\n /**\n * Set both items and groups in one go\n * @param {{items: (Array | vis.DataSet), groups: (Array | vis.DataSet)}} data\n */\n setData(data) {\n if (data && data.groups) {\n this.setGroups(data.groups);\n }\n\n if (data && data.items) {\n this.setItems(data.items);\n }\n }\n\n /**\n * Set selected items by their id. Replaces the current selection\n * Unknown id's are silently ignored.\n * @param {string[] | string} [ids] An array with zero or more id's of the items to be\n * selected. If ids is an empty array, all items will be\n * unselected.\n * @param {Object} [options] Available options:\n * `focus: boolean`\n * If true, focus will be set to the selected item(s)\n * `animation: boolean | {duration: number, easingFunction: string}`\n * If true (default), the range is animated\n * smoothly to the new window. An object can be\n * provided to specify duration and easing function.\n * Default duration is 500 ms, and default easing\n * function is 'easeInOutQuad'.\n * Only applicable when option focus is true.\n */\n setSelection(ids, options) {\n this.itemSet && this.itemSet.setSelection(ids);\n\n if (options && options.focus) {\n this.focus(ids, options);\n }\n }\n\n /**\n * Get the selected items by their id\n * @return {Array} ids The ids of the selected items\n */\n getSelection() {\n return this.itemSet && this.itemSet.getSelection() || [];\n }\n\n /**\n * Adjust the visible window such that the selected item (or multiple items)\n * are centered on screen.\n * @param {string | String[]} id An item id or array with item ids\n * @param {Object} [options] Available options:\n * `animation: boolean | {duration: number, easingFunction: string}`\n * If true (default), the range is animated\n * smoothly to the new window. An object can be\n * provided to specify duration and easing function.\n * Default duration is 500 ms, and default easing\n * function is 'easeInOutQuad'.\n * `zoom: boolean`\n * If true (default), the timeline will\n * zoom on the element after focus it.\n */\n focus(id, options) {\n if (!this.itemsData || id == undefined) return;\n\n const ids = Array.isArray(id) ? id : [id];\n\n // get the specified item(s)\n const itemsData = this.itemsData.getDataSet().get(ids, {\n type: {\n start: 'Date',\n end: 'Date'\n }\n });\n\n // calculate minimum start and maximum end of specified items\n let start = null;\n let end = null;\n itemsData.forEach(itemData => {\n const s = itemData.start.valueOf();\n const e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();\n\n if (start === null || s < start) {\n start = s;\n }\n\n if (end === null || e > end) {\n end = e;\n }\n });\n\n\n if (start !== null && end !== null) {\n const me = this;\n // Use the first item for the vertical focus\n const item = this.itemSet.items[ids[0]];\n let startPos = this._getScrollTop() * -1;\n let initialVerticalScroll = null;\n\n // Setup a handler for each frame of the vertical scroll\n const verticalAnimationFrame = (ease, willDraw, done) => {\n const verticalScroll = getItemVerticalScroll(me, item);\n\n if (verticalScroll === false) {\n return; // We don't need to scroll, so do nothing\n }\n\n if(!initialVerticalScroll) {\n initialVerticalScroll = verticalScroll;\n }\n\n if(initialVerticalScroll.itemTop == verticalScroll.itemTop && !initialVerticalScroll.shouldScroll) {\n return; // We don't need to scroll, so do nothing\n }\n else if(initialVerticalScroll.itemTop != verticalScroll.itemTop && verticalScroll.shouldScroll) {\n // The redraw shifted elements, so reset the animation to correct\n initialVerticalScroll = verticalScroll;\n startPos = me._getScrollTop() * -1;\n } \n\n const from = startPos;\n const to = initialVerticalScroll.scrollOffset;\n const scrollTop = done ? to : (from + (to - from) * ease);\n\n me._setScrollTop(-scrollTop);\n\n if(!willDraw) {\n me._redraw();\n }\n };\n\n // Enforces the final vertical scroll position\n const setFinalVerticalPosition = () => {\n const finalVerticalScroll = getItemVerticalScroll(me, item);\n\n if (finalVerticalScroll.shouldScroll && finalVerticalScroll.itemTop != initialVerticalScroll.itemTop) {\n me._setScrollTop(-finalVerticalScroll.scrollOffset);\n me._redraw();\n }\n };\n\n // Perform one last check at the end to make sure the final vertical\n // position is correct\n const finalVerticalCallback = () => {\n // Double check we ended at the proper scroll position\n setFinalVerticalPosition();\n\n // Let the redraw settle and finalize the position. \n setTimeout(setFinalVerticalPosition, 100);\n };\n\n // calculate the new middle and interval for the window\n const zoom = options && options.zoom !== undefined ? options.zoom : true;\n const middle = (start + end) / 2;\n const interval = zoom ? (end - start) * 1.1 : Math.max(this.range.end - this.range.start, (end - start) * 1.1);\n\n const animation = options && options.animation !== undefined ? options.animation : true;\n\n if (!animation) {\n // We aren't animating so set a default so that the final callback forces the vertical location\n initialVerticalScroll = { shouldScroll: false, scrollOffset: -1, itemTop: -1 };\n }\n\n this.range.setRange(middle - interval / 2, middle + interval / 2, { animation }, finalVerticalCallback, verticalAnimationFrame); \n }\n }\n\n /**\n * Set Timeline window such that it fits all items\n * @param {Object} [options] Available options:\n * `animation: boolean | {duration: number, easingFunction: string}`\n * If true (default), the range is animated\n * smoothly to the new window. An object can be\n * provided to specify duration and easing function.\n * Default duration is 500 ms, and default easing\n * function is 'easeInOutQuad'.\n * @param {function} [callback]\n */\n fit(options, callback) {\n const animation = (options && options.animation !== undefined) ? options.animation : true;\n let range;\n\n const dataset = this.itemsData && this.itemsData.getDataSet();\n if (dataset.length === 1 && dataset.get()[0].end === undefined) {\n // a single item -> don't fit, just show a range around the item from -4 to +3 days\n range = this.getDataRange();\n this.moveTo(range.min.valueOf(), {animation}, callback);\n }\n else {\n // exactly fit the items (plus a small margin)\n range = this.getItemRange();\n this.range.setRange(range.min, range.max, { animation }, callback);\n }\n }\n\n /**\n * Determine the range of the items, taking into account their actual width\n * and a margin of 10 pixels on both sides.\n *\n * @returns {{min: Date, max: Date}}\n */\n getItemRange() {\n // get a rough approximation for the range based on the items start and end dates\n const range = this.getDataRange();\n let min = range.min !== null ? range.min.valueOf() : null;\n let max = range.max !== null ? range.max.valueOf() : null;\n let minItem = null;\n let maxItem = null;\n\n if (min != null && max != null) {\n let interval = (max - min); // ms\n if (interval <= 0) {\n interval = 10;\n }\n const factor = interval / this.props.center.width;\n\n const redrawQueue = {};\n let redrawQueueLength = 0;\n\n // collect redraw functions\n util.forEach(this.itemSet.items, (item, key) => {\n if (item.groupShowing) {\n const returnQueue = true;\n redrawQueue[key] = item.redraw(returnQueue);\n redrawQueueLength = redrawQueue[key].length;\n }\n })\n\n const needRedraw = redrawQueueLength > 0;\n if (needRedraw) {\n // redraw all regular items\n for (let i = 0; i < redrawQueueLength; i++) {\n util.forEach(redrawQueue, fns => {\n fns[i]();\n });\n }\n }\n\n // calculate the date of the left side and right side of the items given\n util.forEach(this.itemSet.items, item => {\n const start = getStart(item);\n const end = getEnd(item);\n let startSide;\n let endSide;\n\n if (this.options.rtl) {\n startSide = start - (item.getWidthRight() + 10) * factor;\n endSide = end + (item.getWidthLeft() + 10) * factor;\n } else {\n startSide = start - (item.getWidthLeft() + 10) * factor;\n endSide = end + (item.getWidthRight() + 10) * factor;\n }\n\n if (startSide < min) {\n min = startSide;\n minItem = item;\n }\n if (endSide > max) {\n max = endSide;\n maxItem = item;\n }\n });\n\n if (minItem && maxItem) {\n const lhs = minItem.getWidthLeft() + 10;\n const rhs = maxItem.getWidthRight() + 10;\n const delta = this.props.center.width - lhs - rhs; // px\n\n if (delta > 0) {\n if (this.options.rtl) {\n min = getStart(minItem) - rhs * interval / delta; // ms\n max = getEnd(maxItem) + lhs * interval / delta; // ms\n } else {\n min = getStart(minItem) - lhs * interval / delta; // ms\n max = getEnd(maxItem) + rhs * interval / delta; // ms\n }\n }\n }\n }\n\n return {\n min: min != null ? new Date(min) : null,\n max: max != null ? new Date(max) : null\n }\n }\n\n /**\n * Calculate the data range of the items start and end dates\n * @returns {{min: Date, max: Date}}\n */\n getDataRange() {\n let min = null;\n let max = null;\n\n const dataset = this.itemsData && this.itemsData.getDataSet();\n if (dataset) {\n dataset.forEach(item => {\n const start = util.convert(item.start, 'Date').valueOf();\n const end = util.convert(item.end != undefined ? item.end : item.start, 'Date').valueOf();\n if (min === null || start < min) {\n min = start;\n }\n if (max === null || end > max) {\n max = end;\n }\n });\n }\n\n return {\n min: min != null ? new Date(min) : null,\n max: max != null ? new Date(max) : null\n }\n }\n\n /**\n * Generate Timeline related information from an event\n * @param {Event} event\n * @return {Object} An object with related information, like on which area\n * The event happened, whether clicked on an item, etc.\n */\n getEventProperties(event) {\n const clientX = event.center ? event.center.x : event.clientX;\n const clientY = event.center ? event.center.y : event.clientY;\n const centerContainerRect = this.dom.centerContainer.getBoundingClientRect();\n const x = this.options.rtl ? centerContainerRect.right - clientX : clientX - centerContainerRect.left;\n const y = clientY - centerContainerRect.top;\n \n const item = this.itemSet.itemFromTarget(event);\n const group = this.itemSet.groupFromTarget(event);\n const customTime = CustomTime.customTimeFromTarget(event);\n\n const snap = this.itemSet.options.snap || null;\n const scale = this.body.util.getScale();\n const step = this.body.util.getStep();\n const time = this._toTime(x);\n const snappedTime = snap ? snap(time, scale, step) : time;\n\n const element = util.getTarget(event);\n let what = null;\n if (item != null) {what = 'item';}\n else if (customTime != null) {what = 'custom-time';}\n else if (util.hasParent(element, this.timeAxis.dom.foreground)) {what = 'axis';}\n else if (this.timeAxis2 && util.hasParent(element, this.timeAxis2.dom.foreground)) {what = 'axis';}\n else if (util.hasParent(element, this.itemSet.dom.labelSet)) {what = 'group-label';}\n else if (util.hasParent(element, this.currentTime.bar)) {what = 'current-time';}\n else if (util.hasParent(element, this.dom.center)) {what = 'background';}\n\n return {\n event,\n item: item ? item.id : null,\n isCluster: item ? !!item.isCluster: false,\n items: item ? item.items || []: null,\n group: group ? group.groupId : null,\n customTime: customTime ? customTime.options.id : null,\n what,\n pageX: event.srcEvent ? event.srcEvent.pageX : event.pageX,\n pageY: event.srcEvent ? event.srcEvent.pageY : event.pageY,\n x,\n y,\n time,\n snappedTime\n }\n }\n\n /**\n * Toggle Timeline rolling mode\n */\n toggleRollingMode() {\n if (this.range.rolling) {\n this.range.stopRolling();\n } else {\n if (this.options.rollingMode == undefined) {\n this.setOptions(this.options)\n }\n this.range.startRolling();\n }\n }\n\n /**\n * redraw\n * @private\n */\n _redraw() {\n Core.prototype._redraw.call(this);\n }\n\n /**\n * on fit callback\n * @param {object} args\n * @private\n */\n _onFit(args) {\n const { start, end, animation } = args;\n if (!end) {\n this.moveTo(start.valueOf(), {\n animation\n });\n } else {\n this.range.setRange(start, end, {\n animation: animation\n });\n }\n }\n}\n\n/**\n *\n * @param {timeline.Item} item\n * @returns {number}\n */\nfunction getStart(item) {\n return util.convert(item.data.start, 'Date').valueOf()\n}\n\n/**\n *\n * @param {timeline.Item} item\n * @returns {number}\n */\nfunction getEnd(item) {\n const end = item.data.end != undefined ? item.data.end : item.data.start;\n return util.convert(end, 'Date').valueOf();\n}\n\n/**\n * @param {vis.Timeline} timeline\n * @param {timeline.Item} item\n * @return {{shouldScroll: bool, scrollOffset: number, itemTop: number}}\n */\nfunction getItemVerticalScroll(timeline, item) {\n if (!item.parent) {\n // The item no longer exists, so ignore this focus.\n return false;\n }\n\n const itemsetHeight = timeline.options.rtl ? timeline.props.rightContainer.height : timeline.props.leftContainer.height;\n const contentHeight = timeline.props.center.height;\n \n const group = item.parent;\n let offset = group.top;\n let shouldScroll = true;\n const orientation = timeline.timeAxis.options.orientation.axis;\n \n const itemTop = () => {\n if (orientation == \"bottom\") {\n return group.height - item.top - item.height;\n }\n else {\n return item.top;\n }\n };\n\n const currentScrollHeight = timeline._getScrollTop() * -1;\n const targetOffset = offset + itemTop();\n const height = item.height;\n\n if (targetOffset < currentScrollHeight) {\n if (offset + itemsetHeight <= offset + itemTop() + height) {\n offset += itemTop() - timeline.itemSet.options.margin.item.vertical;\n }\n }\n else if (targetOffset + height > currentScrollHeight + itemsetHeight) {\n offset += itemTop() + height - itemsetHeight + timeline.itemSet.options.margin.item.vertical;\n }\n else {\n shouldScroll = false;\n }\n\n offset = Math.min(offset, contentHeight - itemsetHeight);\n\n return { shouldScroll, scrollOffset: offset, itemTop: targetOffset };\n}\n","/** DataScale */\nclass DataScale {\n /**\n *\n * @param {number} start\n * @param {number} end\n * @param {boolean} autoScaleStart\n * @param {boolean} autoScaleEnd\n * @param {number} containerHeight\n * @param {number} majorCharHeight\n * @param {boolean} zeroAlign\n * @param {function} formattingFunction\n * @constructor DataScale\n */\n constructor(\n start,\n end,\n autoScaleStart,\n autoScaleEnd,\n containerHeight,\n majorCharHeight,\n zeroAlign = false,\n formattingFunction=false) {\n this.majorSteps = [1, 2, 5, 10];\n this.minorSteps = [0.25, 0.5, 1, 2];\n this.customLines = null;\n\n this.containerHeight = containerHeight;\n this.majorCharHeight = majorCharHeight;\n this._start = start;\n this._end = end;\n\n this.scale = 1;\n this.minorStepIdx = -1;\n this.magnitudefactor = 1;\n this.determineScale();\n\n this.zeroAlign = zeroAlign;\n this.autoScaleStart = autoScaleStart;\n this.autoScaleEnd = autoScaleEnd;\n\n this.formattingFunction = formattingFunction;\n\n if (autoScaleStart || autoScaleEnd) {\n const me = this;\n const roundToMinor = value => {\n const rounded = value - (value % (me.magnitudefactor * me.minorSteps[me.minorStepIdx]));\n if (value % (me.magnitudefactor * me.minorSteps[me.minorStepIdx]) > 0.5 * (me.magnitudefactor * me.minorSteps[me.minorStepIdx])) {\n return rounded + (me.magnitudefactor * me.minorSteps[me.minorStepIdx]);\n }\n else {\n return rounded;\n }\n };\n if (autoScaleStart) {\n this._start -= this.magnitudefactor * 2 * this.minorSteps[this.minorStepIdx];\n this._start = roundToMinor(this._start);\n }\n\n if (autoScaleEnd) {\n this._end += this.magnitudefactor * this.minorSteps[this.minorStepIdx];\n this._end = roundToMinor(this._end);\n }\n this.determineScale();\n }\n }\n\n /**\n * set chart height\n * @param {number} majorCharHeight \n */\n setCharHeight(majorCharHeight) {\n this.majorCharHeight = majorCharHeight;\n }\n\n /**\n * set height\n * @param {number} containerHeight \n */\n setHeight(containerHeight) {\n this.containerHeight = containerHeight;\n }\n\n /**\n * determine scale\n */\n determineScale() {\n const range = this._end - this._start;\n this.scale = this.containerHeight / range;\n const minimumStepValue = this.majorCharHeight / this.scale;\n const orderOfMagnitude = (range > 0)\n ? Math.round(Math.log(range) / Math.LN10)\n : 0;\n\n this.minorStepIdx = -1;\n this.magnitudefactor = Math.pow(10, orderOfMagnitude);\n\n let start = 0;\n if (orderOfMagnitude < 0) {\n start = orderOfMagnitude;\n }\n\n let solutionFound = false;\n for (let l = start; Math.abs(l) <= Math.abs(orderOfMagnitude); l++) {\n this.magnitudefactor = Math.pow(10, l);\n for (let j = 0; j < this.minorSteps.length; j++) {\n const stepSize = this.magnitudefactor * this.minorSteps[j];\n if (stepSize >= minimumStepValue) {\n solutionFound = true;\n this.minorStepIdx = j;\n break;\n }\n }\n if (solutionFound === true) {\n break;\n }\n }\n }\n\n /**\n * returns if value is major\n * @param {number} value\n * @returns {boolean} \n */\n is_major(value) {\n return (value % (this.magnitudefactor * this.majorSteps[this.minorStepIdx]) === 0);\n }\n\n /**\n * returns step size\n * @returns {number} \n */\n getStep() {\n return this.magnitudefactor * this.minorSteps[this.minorStepIdx];\n }\n\n /**\n * returns first major\n * @returns {number} \n */\n getFirstMajor() {\n const majorStep = this.magnitudefactor * this.majorSteps[this.minorStepIdx];\n return this.convertValue(this._start + ((majorStep - (this._start % majorStep)) % majorStep));\n }\n\n /**\n * returns first major\n * @param {date} current\n * @returns {date} formatted date\n */\n formatValue(current) {\n let returnValue = current.toPrecision(5);\n if (typeof this.formattingFunction === 'function') {\n returnValue = this.formattingFunction(current);\n }\n\n if (typeof returnValue === 'number') {\n return `${returnValue}`;\n }\n else if (typeof returnValue === 'string') {\n return returnValue;\n }\n else {\n return current.toPrecision(5);\n }\n\n }\n\n /**\n * returns lines\n * @returns {object} lines\n */\n getLines() {\n const lines = [];\n const step = this.getStep();\n const bottomOffset = (step - (this._start % step)) % step;\n for (let i = (this._start + bottomOffset); this._end-i > 0.00001; i += step) {\n if (i != this._start) { //Skip the bottom line\n lines.push({major: this.is_major(i), y: this.convertValue(i), val: this.formatValue(i)});\n }\n }\n return lines;\n }\n\n /**\n * follow scale\n * @param {object} other\n */\n followScale(other) {\n const oldStepIdx = this.minorStepIdx;\n const oldStart = this._start;\n const oldEnd = this._end;\n\n const me = this;\n const increaseMagnitude = () => {\n me.magnitudefactor *= 2;\n };\n const decreaseMagnitude = () => {\n me.magnitudefactor /= 2;\n };\n\n if ((other.minorStepIdx <= 1 && this.minorStepIdx <= 1) || (other.minorStepIdx > 1 && this.minorStepIdx > 1)) {\n //easy, no need to change stepIdx nor multiplication factor\n } else if (other.minorStepIdx < this.minorStepIdx) {\n //I'm 5, they are 4 per major.\n this.minorStepIdx = 1;\n if (oldStepIdx == 2) {\n increaseMagnitude();\n } else {\n increaseMagnitude();\n increaseMagnitude();\n }\n } else {\n //I'm 4, they are 5 per major\n this.minorStepIdx = 2;\n if (oldStepIdx == 1) {\n decreaseMagnitude();\n } else {\n decreaseMagnitude();\n decreaseMagnitude();\n }\n }\n\n //Get masters stats:\n const otherZero = other.convertValue(0);\n const otherStep = other.getStep() * other.scale;\n\n let done = false;\n let count = 0;\n //Loop until magnitude is correct for given constrains.\n while (!done && count++ <5) {\n\n //Get my stats:\n this.scale = otherStep / (this.minorSteps[this.minorStepIdx] * this.magnitudefactor);\n const newRange = this.containerHeight / this.scale;\n\n //For the case the magnitudefactor has changed:\n this._start = oldStart;\n this._end = this._start + newRange;\n\n const myOriginalZero = this._end * this.scale;\n const majorStep = this.magnitudefactor * this.majorSteps[this.minorStepIdx];\n const majorOffset = this.getFirstMajor() - other.getFirstMajor();\n\n if (this.zeroAlign) {\n const zeroOffset = otherZero - myOriginalZero;\n this._end += (zeroOffset / this.scale);\n this._start = this._end - newRange;\n } else {\n if (!this.autoScaleStart) {\n this._start += majorStep - (majorOffset / this.scale);\n this._end = this._start + newRange;\n } else {\n this._start -= majorOffset / this.scale;\n this._end = this._start + newRange;\n }\n }\n if (!this.autoScaleEnd && this._end > oldEnd+0.00001) {\n //Need to decrease magnitude to prevent scale overshoot! (end)\n decreaseMagnitude();\n done = false;\n continue;\n }\n if (!this.autoScaleStart && this._start < oldStart-0.00001) {\n if (this.zeroAlign && oldStart >= 0) {\n console.warn(\"Can't adhere to given 'min' range, due to zeroalign\");\n } else {\n //Need to decrease magnitude to prevent scale overshoot! (start)\n decreaseMagnitude();\n done = false;\n continue;\n }\n }\n if (this.autoScaleStart && this.autoScaleEnd && newRange < (oldEnd-oldStart)){\n increaseMagnitude();\n done = false;\n continue;\n }\n done = true;\n }\n }\n\n /**\n * convert value\n * @param {number} value\n * @returns {number} \n */\n convertValue(value) {\n return this.containerHeight - ((value - this._start) * this.scale);\n }\n\n /**\n * returns screen to value\n * @param {number} pixels\n * @returns {number} \n */\n screenToValue(pixels) {\n return ((this.containerHeight - pixels) / this.scale) + this._start;\n }\n}\n\nexport default DataScale;","import util from '../../util';\nimport * as DOMutil from '../../DOMutil';\nimport Component from './Component';\nimport DataScale from './DataScale';\n\nimport './css/dataaxis.css';\n\n/** A horizontal time axis */\nclass DataAxis extends Component {\n /**\n * @param {Object} body\n * @param {Object} [options] See DataAxis.setOptions for the available\n * options.\n * @param {SVGElement} svg\n * @param {timeline.LineGraph.options} linegraphOptions\n * @constructor DataAxis\n * @extends Component\n */\n constructor(body, options, svg, linegraphOptions) {\n super()\n this.id = util.randomUUID();\n this.body = body;\n\n this.defaultOptions = {\n orientation: 'left', // supported: 'left', 'right'\n showMinorLabels: true,\n showMajorLabels: true,\n icons: false,\n majorLinesOffset: 7,\n minorLinesOffset: 4,\n labelOffsetX: 10,\n labelOffsetY: 2,\n iconWidth: 20,\n width: '40px',\n visible: true,\n alignZeros: true,\n left: {\n range: {min: undefined, max: undefined},\n format(value) {\n return `${parseFloat(value.toPrecision(3))}`;\n },\n title: {text: undefined, style: undefined}\n },\n right: {\n range: {min: undefined, max: undefined},\n format(value) {\n return `${parseFloat(value.toPrecision(3))}`;\n },\n title: {text: undefined, style: undefined}\n }\n };\n\n this.linegraphOptions = linegraphOptions;\n this.linegraphSVG = svg;\n this.props = {};\n this.DOMelements = { // dynamic elements\n lines: {},\n labels: {},\n title: {}\n };\n\n this.dom = {};\n this.scale = undefined;\n this.range = {start: 0, end: 0};\n\n this.options = util.extend({}, this.defaultOptions);\n this.conversionFactor = 1;\n\n this.setOptions(options);\n this.width = Number((`${this.options.width}`).replace(\"px\", \"\"));\n this.minWidth = this.width;\n this.height = this.linegraphSVG.getBoundingClientRect().height;\n this.hidden = false;\n\n this.stepPixels = 25;\n this.zeroCrossing = -1;\n this.amountOfSteps = -1;\n\n this.lineOffset = 0;\n this.master = true;\n this.masterAxis = null;\n this.svgElements = {};\n this.iconsRemoved = false;\n\n this.groups = {};\n this.amountOfGroups = 0;\n\n // create the HTML DOM\n this._create();\n if (this.scale == undefined) {\n this._redrawLabels();\n }\n this.framework = {svg: this.svg, svgElements: this.svgElements, options: this.options, groups: this.groups};\n\n const me = this;\n this.body.emitter.on(\"verticalDrag\", () => {\n me.dom.lineContainer.style.top = `${me.body.domProps.scrollTop}px`;\n });\n }\n\n /**\n * Adds group to data axis\n * @param {string} label \n * @param {object} graphOptions\n */\n addGroup(label, graphOptions) {\n if (!this.groups.hasOwnProperty(label)) {\n this.groups[label] = graphOptions;\n }\n this.amountOfGroups += 1;\n }\n\n /**\n * updates group of data axis\n * @param {string} label \n * @param {object} graphOptions\n */\n updateGroup(label, graphOptions) {\n if (!this.groups.hasOwnProperty(label)) {\n this.amountOfGroups += 1;\n }\n this.groups[label] = graphOptions;\n }\n\n /**\n * removes group of data axis\n * @param {string} label \n */\n removeGroup(label) {\n if (this.groups.hasOwnProperty(label)) {\n delete this.groups[label];\n this.amountOfGroups -= 1;\n }\n }\n\n /**\n * sets options\n * @param {object} options\n */\n setOptions(options) {\n if (options) {\n let redraw = false;\n if (this.options.orientation != options.orientation && options.orientation !== undefined) {\n redraw = true;\n }\n const fields = [\n 'orientation',\n 'showMinorLabels',\n 'showMajorLabels',\n 'icons',\n 'majorLinesOffset',\n 'minorLinesOffset',\n 'labelOffsetX',\n 'labelOffsetY',\n 'iconWidth',\n 'width',\n 'visible',\n 'left',\n 'right',\n 'alignZeros'\n ];\n util.selectiveDeepExtend(fields, this.options, options);\n\n this.minWidth = Number((`${this.options.width}`).replace(\"px\", \"\"));\n if (redraw === true && this.dom.frame) {\n this.hide();\n this.show();\n }\n }\n }\n\n /**\n * Create the HTML DOM for the DataAxis\n */\n _create() {\n this.dom.frame = document.createElement('div');\n this.dom.frame.style.width = this.options.width;\n this.dom.frame.style.height = this.height;\n\n this.dom.lineContainer = document.createElement('div');\n this.dom.lineContainer.style.width = '100%';\n this.dom.lineContainer.style.height = this.height;\n this.dom.lineContainer.style.position = 'relative';\n this.dom.lineContainer.style.visibility = 'visible';\n this.dom.lineContainer.style.display = 'block';\n\n // create svg element for graph drawing.\n this.svg = document.createElementNS('http://www.w3.org/2000/svg', \"svg\");\n this.svg.style.position = \"absolute\";\n this.svg.style.top = '0px';\n this.svg.style.height = '100%';\n this.svg.style.width = '100%';\n this.svg.style.display = \"block\";\n this.dom.frame.appendChild(this.svg);\n }\n\n /**\n * redraws groups icons\n */\n _redrawGroupIcons() {\n DOMutil.prepareElements(this.svgElements);\n\n let x;\n const iconWidth = this.options.iconWidth;\n const iconHeight = 15;\n const iconOffset = 4;\n let y = iconOffset + 0.5 * iconHeight;\n\n if (this.options.orientation === 'left') {\n x = iconOffset;\n }\n else {\n x = this.width - iconWidth - iconOffset;\n }\n\n const groupArray = Object.keys(this.groups);\n groupArray.sort((a, b) => a < b ? -1 : 1)\n\n for (const groupId of groupArray) {\n if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) {\n this.groups[groupId].getLegend(iconWidth, iconHeight, this.framework, x, y);\n y += iconHeight + iconOffset;\n }\n }\n\n DOMutil.cleanupElements(this.svgElements);\n this.iconsRemoved = false;\n }\n\n /**\n * Cleans up icons\n */\n _cleanupIcons() {\n if (this.iconsRemoved === false) {\n DOMutil.prepareElements(this.svgElements);\n DOMutil.cleanupElements(this.svgElements);\n this.iconsRemoved = true;\n }\n }\n\n /**\n * Create the HTML DOM for the DataAxis\n */\n show() {\n this.hidden = false;\n if (!this.dom.frame.parentNode) {\n if (this.options.orientation === 'left') {\n this.body.dom.left.appendChild(this.dom.frame);\n }\n else {\n this.body.dom.right.appendChild(this.dom.frame);\n }\n }\n\n if (!this.dom.lineContainer.parentNode) {\n this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);\n }\n this.dom.lineContainer.style.display = 'block';\n }\n\n /**\n * Create the HTML DOM for the DataAxis\n */\n hide() {\n this.hidden = true;\n if (this.dom.frame.parentNode) {\n this.dom.frame.parentNode.removeChild(this.dom.frame);\n }\n\n this.dom.lineContainer.style.display = 'none';\n }\n\n /**\n * Set a range (start and end)\n * @param {number} start\n * @param {number} end\n */\n setRange(start, end) {\n this.range.start = start;\n this.range.end = end;\n }\n\n /**\n * Repaint the component\n * @return {boolean} Returns true if the component is resized\n */\n redraw() {\n let resized = false;\n let activeGroups = 0;\n\n // Make sure the line container adheres to the vertical scrolling.\n this.dom.lineContainer.style.top = `${this.body.domProps.scrollTop}px`;\n\n for (const groupId in this.groups) {\n if (this.groups.hasOwnProperty(groupId)) {\n if (this.groups[groupId].visible === true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] === true)) {\n activeGroups++;\n }\n }\n }\n if (this.amountOfGroups === 0 || activeGroups === 0) {\n this.hide();\n }\n else {\n this.show();\n this.height = Number(this.linegraphSVG.style.height.replace(\"px\", \"\"));\n\n // svg offsetheight did not work in firefox and explorer...\n this.dom.lineContainer.style.height = `${this.height}px`;\n this.width = this.options.visible === true ? Number((`${this.options.width}`).replace(\"px\", \"\")) : 0;\n\n const props = this.props;\n const frame = this.dom.frame;\n\n // update classname\n frame.className = 'vis-data-axis';\n\n // calculate character width and height\n this._calculateCharSize();\n\n const orientation = this.options.orientation;\n const showMinorLabels = this.options.showMinorLabels;\n const showMajorLabels = this.options.showMajorLabels;\n\n const backgroundHorizontalOffsetWidth = this.body.dom.backgroundHorizontal.offsetWidth;\n\n // determine the width and height of the elements for the axis\n props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;\n props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;\n\n props.minorLineWidth = backgroundHorizontalOffsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;\n props.minorLineHeight = 1;\n props.majorLineWidth = backgroundHorizontalOffsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;\n props.majorLineHeight = 1;\n\n // take frame offline while updating (is almost twice as fast)\n if (orientation === 'left') {\n frame.style.top = '0';\n frame.style.left = '0';\n frame.style.bottom = '';\n frame.style.width = `${this.width}px`;\n frame.style.height = `${this.height}px`;\n this.props.width = this.body.domProps.left.width;\n this.props.height = this.body.domProps.left.height;\n }\n else { // right\n frame.style.top = '';\n frame.style.bottom = '0';\n frame.style.left = '0';\n frame.style.width = `${this.width}px`;\n frame.style.height = `${this.height}px`;\n this.props.width = this.body.domProps.right.width;\n this.props.height = this.body.domProps.right.height;\n }\n\n resized = this._redrawLabels();\n resized = this._isResized() || resized;\n\n if (this.options.icons === true) {\n this._redrawGroupIcons();\n }\n else {\n this._cleanupIcons();\n }\n\n this._redrawTitle(orientation);\n }\n return resized;\n }\n\n /**\n * Repaint major and minor text labels and vertical grid lines\n *\n * @returns {boolean}\n * @private\n */\n _redrawLabels() {\n let resized = false;\n DOMutil.prepareElements(this.DOMelements.lines);\n DOMutil.prepareElements(this.DOMelements.labels);\n const orientation = this.options['orientation'];\n const customRange = this.options[orientation].range != undefined ? this.options[orientation].range : {};\n\n //Override range with manual options:\n let autoScaleEnd = true;\n if (customRange.max != undefined) {\n this.range.end = customRange.max;\n autoScaleEnd = false;\n }\n let autoScaleStart = true;\n if (customRange.min != undefined) {\n this.range.start = customRange.min;\n autoScaleStart = false;\n }\n\n this.scale = new DataScale(\n this.range.start,\n this.range.end,\n autoScaleStart,\n autoScaleEnd,\n this.dom.frame.offsetHeight,\n this.props.majorCharHeight,\n this.options.alignZeros,\n this.options[orientation].format\n );\n\n if (this.master === false && this.masterAxis != undefined) {\n this.scale.followScale(this.masterAxis.scale);\n this.dom.lineContainer.style.display = 'none';\n } else {\n this.dom.lineContainer.style.display = 'block';\n }\n\n //Is updated in side-effect of _redrawLabel():\n this.maxLabelSize = 0;\n\n const lines = this.scale.getLines();\n lines.forEach(\n line=> {\n const y = line.y;\n const isMajor = line.major;\n if (this.options['showMinorLabels'] && isMajor === false) {\n this._redrawLabel(y - 2, line.val, orientation, 'vis-y-axis vis-minor', this.props.minorCharHeight);\n }\n if (isMajor) {\n if (y >= 0) {\n this._redrawLabel(y - 2, line.val, orientation, 'vis-y-axis vis-major', this.props.majorCharHeight);\n }\n }\n if (this.master === true) {\n if (isMajor) {\n this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-major', this.options.majorLinesOffset, this.props.majorLineWidth);\n }\n else {\n this._redrawLine(y, orientation, 'vis-grid vis-horizontal vis-minor', this.options.minorLinesOffset, this.props.minorLineWidth);\n }\n }\n });\n\n // Note that title is rotated, so we're using the height, not width!\n let titleWidth = 0;\n if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) {\n titleWidth = this.props.titleCharHeight;\n }\n const offset = this.options.icons === true ? Math.max(this.options.iconWidth, titleWidth) + this.options.labelOffsetX + 15 : titleWidth + this.options.labelOffsetX + 15;\n\n // this will resize the yAxis to accommodate the labels.\n if (this.maxLabelSize > (this.width - offset) && this.options.visible === true) {\n this.width = this.maxLabelSize + offset;\n this.options.width = `${this.width}px`;\n DOMutil.cleanupElements(this.DOMelements.lines);\n DOMutil.cleanupElements(this.DOMelements.labels);\n this.redraw();\n resized = true;\n }\n // this will resize the yAxis if it is too big for the labels.\n else if (this.maxLabelSize < (this.width - offset) && this.options.visible === true && this.width > this.minWidth) {\n this.width = Math.max(this.minWidth, this.maxLabelSize + offset);\n this.options.width = `${this.width}px`;\n DOMutil.cleanupElements(this.DOMelements.lines);\n DOMutil.cleanupElements(this.DOMelements.labels);\n this.redraw();\n resized = true;\n }\n else {\n DOMutil.cleanupElements(this.DOMelements.lines);\n DOMutil.cleanupElements(this.DOMelements.labels);\n resized = false;\n }\n\n return resized;\n }\n\n /**\n * converts value\n * @param {number} value\n * @returns {number} converted number\n */\n convertValue(value) {\n return this.scale.convertValue(value);\n }\n\n /**\n * converts value\n * @param {number} x\n * @returns {number} screen value\n */\n screenToValue(x) {\n return this.scale.screenToValue(x);\n }\n\n /**\n * Create a label for the axis at position x\n *\n * @param {number} y\n * @param {string} text\n * @param {'top'|'right'|'bottom'|'left'} orientation\n * @param {string} className\n * @param {number} characterHeight\n * @private\n */\n _redrawLabel(y, text, orientation, className, characterHeight) {\n // reuse redundant label\n const label = DOMutil.getDOMElement('div', this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();\n label.className = className;\n label.innerHTML = text;\n if (orientation === 'left') {\n label.style.left = `-${this.options.labelOffsetX}px`;\n label.style.textAlign = \"right\";\n }\n else {\n label.style.right = `-${this.options.labelOffsetX}px`;\n label.style.textAlign = \"left\";\n }\n\n label.style.top = `${y - 0.5 * characterHeight + this.options.labelOffsetY}px`;\n\n text += '';\n\n const largestWidth = Math.max(this.props.majorCharWidth, this.props.minorCharWidth);\n if (this.maxLabelSize < text.length * largestWidth) {\n this.maxLabelSize = text.length * largestWidth;\n }\n }\n\n /**\n * Create a minor line for the axis at position y\n * @param {number} y\n * @param {'top'|'right'|'bottom'|'left'} orientation\n * @param {string} className\n * @param {number} offset\n * @param {number} width\n */\n _redrawLine(y, orientation, className, offset, width) {\n if (this.master === true) {\n const line = DOMutil.getDOMElement('div', this.DOMelements.lines, this.dom.lineContainer); //this.dom.redundant.lines.shift();\n line.className = className;\n line.innerHTML = '';\n\n if (orientation === 'left') {\n line.style.left = `${this.width - offset}px`;\n }\n else {\n line.style.right = `${this.width - offset}px`;\n }\n\n line.style.width = `${width}px`;\n line.style.top = `${y}px`;\n }\n }\n\n /**\n * Create a title for the axis\n * @private\n * @param {'top'|'right'|'bottom'|'left'} orientation\n */\n _redrawTitle(orientation) {\n DOMutil.prepareElements(this.DOMelements.title);\n\n // Check if the title is defined for this axes\n if (this.options[orientation].title !== undefined && this.options[orientation].title.text !== undefined) {\n const title = DOMutil.getDOMElement('div', this.DOMelements.title, this.dom.frame);\n title.className = `vis-y-axis vis-title vis-${orientation}`;\n title.innerHTML = this.options[orientation].title.text;\n\n // Add style - if provided\n if (this.options[orientation].title.style !== undefined) {\n util.addCssText(title, this.options[orientation].title.style);\n }\n\n if (orientation === 'left') {\n title.style.left = `${this.props.titleCharHeight}px`;\n }\n else {\n title.style.right = `${this.props.titleCharHeight}px`;\n }\n\n title.style.width = `${this.height}px`;\n }\n\n // we need to clean up in case we did not use all elements.\n DOMutil.cleanupElements(this.DOMelements.title);\n }\n\n /**\n * Determine the size of text on the axis (both major and minor axis).\n * The size is calculated only once and then cached in this.props.\n * @private\n */\n _calculateCharSize() {\n // determine the char width and height on the minor axis\n if (!('minorCharHeight' in this.props)) {\n const textMinor = document.createTextNode('0');\n const measureCharMinor = document.createElement('div');\n measureCharMinor.className = 'vis-y-axis vis-minor vis-measure';\n measureCharMinor.appendChild(textMinor);\n this.dom.frame.appendChild(measureCharMinor);\n\n this.props.minorCharHeight = measureCharMinor.clientHeight;\n this.props.minorCharWidth = measureCharMinor.clientWidth;\n\n this.dom.frame.removeChild(measureCharMinor);\n }\n\n if (!('majorCharHeight' in this.props)) {\n const textMajor = document.createTextNode('0');\n const measureCharMajor = document.createElement('div');\n measureCharMajor.className = 'vis-y-axis vis-major vis-measure';\n measureCharMajor.appendChild(textMajor);\n this.dom.frame.appendChild(measureCharMajor);\n\n this.props.majorCharHeight = measureCharMajor.clientHeight;\n this.props.majorCharWidth = measureCharMajor.clientWidth;\n\n this.dom.frame.removeChild(measureCharMajor);\n }\n\n if (!('titleCharHeight' in this.props)) {\n const textTitle = document.createTextNode('0');\n const measureCharTitle = document.createElement('div');\n measureCharTitle.className = 'vis-y-axis vis-title vis-measure';\n measureCharTitle.appendChild(textTitle);\n this.dom.frame.appendChild(measureCharTitle);\n\n this.props.titleCharHeight = measureCharTitle.clientHeight;\n this.props.titleCharWidth = measureCharTitle.clientWidth;\n\n this.dom.frame.removeChild(measureCharTitle);\n }\n }\n}\n\nexport default DataAxis;\n","import * as DOMutil from '../../../DOMutil';\n\n/**\n *\n * @param {number | string} groupId\n * @param {Object} options // TODO: Describe options\n *\n * @constructor Points\n */\nfunction Points(groupId, options) { // eslint-disable-line no-unused-vars\n}\n\n/**\n * draw the data points\n *\n * @param {Array} dataset\n * @param {GraphGroup} group\n * @param {Object} framework | SVG DOM element\n * @param {number} [offset]\n */\nPoints.draw = function (dataset, group, framework, offset) {\n offset = offset || 0;\n var callback = getCallback(framework, group);\n\n for (var i = 0; i < dataset.length; i++) {\n if (!callback) {\n // draw the point the simple way.\n DOMutil.drawPoint(dataset[i].screen_x + offset, dataset[i].screen_y, getGroupTemplate(group), framework.svgElements, framework.svg, dataset[i].label);\n }\n else {\n var callbackResult = callback(dataset[i], group); // result might be true, false or an object\n if (callbackResult === true || typeof callbackResult === 'object') {\n DOMutil.drawPoint(dataset[i].screen_x + offset, dataset[i].screen_y, getGroupTemplate(group, callbackResult), framework.svgElements, framework.svg, dataset[i].label);\n }\n }\n }\n};\n\nPoints.drawIcon = function (group, x, y, iconWidth, iconHeight, framework) {\n var fillHeight = iconHeight * 0.5;\n\n var outline = DOMutil.getSVGElement(\"rect\", framework.svgElements, framework.svg);\n outline.setAttributeNS(null, \"x\", x);\n outline.setAttributeNS(null, \"y\", y - fillHeight);\n outline.setAttributeNS(null, \"width\", iconWidth);\n outline.setAttributeNS(null, \"height\", 2 * fillHeight);\n outline.setAttributeNS(null, \"class\", \"vis-outline\");\n\n //Don't call callback on icon\n DOMutil.drawPoint(x + 0.5 * iconWidth, y, getGroupTemplate(group), framework.svgElements, framework.svg);\n};\n\n/**\n *\n * @param {vis.Group} group\n * @param {any} callbackResult\n * @returns {{style: *, styles: (*|string), size: *, className: *}}\n */\nfunction getGroupTemplate(group, callbackResult) {\n callbackResult = (typeof callbackResult === 'undefined') ? {} : callbackResult;\n return {\n style: callbackResult.style || group.options.drawPoints.style,\n styles: callbackResult.styles || group.options.drawPoints.styles,\n size: callbackResult.size || group.options.drawPoints.size,\n className: callbackResult.className || group.className\n };\n}\n\n/**\n *\n * @param {Object} framework | SVG DOM element\n * @param {vis.Group} group\n * @returns {function}\n */\nfunction getCallback(framework, group) {\n var callback = undefined;\n // check for the graph2d onRender\n if (framework.options && framework.options.drawPoints && framework.options.drawPoints.onRender && typeof framework.options.drawPoints.onRender == 'function') {\n callback = framework.options.drawPoints.onRender;\n }\n\n // override it with the group onRender if defined\n if (group.group.options && group.group.options.drawPoints && group.group.options.drawPoints.onRender && typeof group.group.options.drawPoints.onRender == 'function') {\n callback = group.group.options.drawPoints.onRender;\n }\n return callback;\n}\n\nexport default Points;\n","import * as DOMutil from '../../../DOMutil';\nimport Points from './points';\n\n/**\n *\n * @param {vis.GraphGroup.id} groupId\n * @param {Object} options // TODO: Describe options\n * @constructor Bargraph\n */\nfunction Bargraph(groupId, options) { // eslint-disable-line no-unused-vars\n}\n\nBargraph.drawIcon = function (group, x, y, iconWidth, iconHeight, framework) {\n var fillHeight = iconHeight * 0.5;\n var outline = DOMutil.getSVGElement(\"rect\", framework.svgElements, framework.svg);\n outline.setAttributeNS(null, \"x\", x);\n outline.setAttributeNS(null, \"y\", y - fillHeight);\n outline.setAttributeNS(null, \"width\", iconWidth);\n outline.setAttributeNS(null, \"height\", 2 * fillHeight);\n outline.setAttributeNS(null, \"class\", \"vis-outline\");\n\n var barWidth = Math.round(0.3 * iconWidth);\n var originalWidth = group.options.barChart.width;\n var scale = originalWidth / barWidth;\n var bar1Height = Math.round(0.4 * iconHeight);\n var bar2Height = Math.round(0.75 * iconHeight);\n\n var offset = Math.round((iconWidth - (2 * barWidth)) / 3);\n\n DOMutil.drawBar(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, barWidth, bar1Height, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style);\n DOMutil.drawBar(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, group.className + ' vis-bar', framework.svgElements, framework.svg, group.style);\n\n if (group.options.drawPoints.enabled == true) {\n var groupTemplate = {\n style: group.options.drawPoints.style,\n styles: group.options.drawPoints.styles,\n size: (group.options.drawPoints.size / scale),\n className: group.className\n };\n DOMutil.drawPoint(x + 0.5 * barWidth + offset, y + fillHeight - bar1Height - 1, groupTemplate, framework.svgElements, framework.svg);\n DOMutil.drawPoint(x + 1.5 * barWidth + offset + 2, y + fillHeight - bar2Height - 1, groupTemplate, framework.svgElements, framework.svg);\n }\n};\n\n/**\n * draw a bar graph\n *\n * @param {Array.} groupIds\n * @param {Object} processedGroupData\n * @param {{svg: Object, svgElements: Array.