{"version":3,"file":"vis-timeline-graph2d.min.js","sources":["../lib/timeline/component/ClusterGenerator.js","../node_modules/hammerjs/hammer.js","../../vis-util/dist/esm.js","../lib/DOMutil.js","../node_modules/vis-data/dist/esm.js","../lib/timeline/DateUtil.js","../lib/timeline/Range.js","../node_modules/emitter-component/index.js","../lib/module/hammer.js","../lib/hammerUtil.js","../lib/shared/Activator.js","../lib/timeline/Stack.js","../lib/timeline/Timeline.js","../lib/timeline/component/graph2d_types/points.js","../lib/timeline/component/graph2d_types/bar.js","../lib/timeline/component/graph2d_types/line.js","../lib/timeline/component/GraphGroup.js","../lib/timeline/component/Legend.js","../lib/timeline/component/LineGraph.js","../lib/timeline/Graph2d.js","../node_modules/moment/moment.js","../lib/module/moment.js","../lib/timeline/component/Component.js","../node_modules/propagating-hammerjs/propagating.js","../lib/timeline/TimeStep.js","../lib/timeline/component/TimeAxis.js","../node_modules/keycharm/keycharm.js","../node_modules/moment/locale/it.js","../node_modules/moment/locale/nl.js","../node_modules/moment/locale/de.js","../node_modules/moment/locale/fr.js","../node_modules/moment/locale/es.js","../node_modules/moment/locale/uk.js","../node_modules/moment/locale/ru.js","../lib/timeline/locales.js","../lib/timeline/component/CustomTime.js","../lib/timeline/Core.js","../lib/timeline/component/CurrentTime.js","../lib/timeline/component/Group.js","../lib/timeline/component/BackgroundGroup.js","../lib/timeline/component/item/Item.js","../lib/timeline/component/item/BoxItem.js","../lib/timeline/component/item/PointItem.js","../lib/timeline/component/item/RangeItem.js","../lib/timeline/component/item/BackgroundItem.js","../lib/shared/Popup.js","../lib/timeline/component/item/ClusterItem.js","../lib/timeline/component/ItemSet.js","../lib/shared/Validator.js","../lib/timeline/optionsTimeline.js","../lib/shared/ColorPicker.js","../lib/shared/Configurator.js","../lib/timeline/component/DataScale.js","../lib/timeline/component/DataAxis.js","../lib/timeline/optionsGraph2d.js","../index.js"],"sourcesContent":["import ClusterItem from './item/ClusterItem';\n\nconst UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items\nconst BACKGROUND = '__background__'; // reserved group id for background items without group\n\nexport const ReservedGroupIds = {\n UNGROUPED,\n BACKGROUND\n}\n\n/**\n * An Cluster generator generates cluster items\n */\nexport default class ClusterGenerator {\n /**\n * @param {ItemSet} itemSet itemsSet instance\n * @constructor ClusterGenerator\n */\n constructor(itemSet) {\n this.itemSet = itemSet;\n this.groups = {};\n this.cache = {};\n this.cache[-1] = [];\n }\n\n /**\n * @param {Object} itemData Object containing parameters start content, className.\n * @param {{toScreen: function, toTime: function}} conversion\n * Conversion functions from time to screen and vice versa\n * @param {Object} [options] Configuration options\n * @return {Object} newItem\n */\n createClusterItem(itemData, conversion, options) {\n const newItem = new ClusterItem(itemData, conversion, options);\n return newItem;\n }\n\n /**\n * Set the items to be clustered.\n * This will clear cached clusters.\n * @param {Item[]} items\n * @param {Object} [options] Available options:\n * {boolean} applyOnChangedLevel\n * If true (default), the changed data is applied\n * as soon the cluster level changes. If false,\n * The changed data is applied immediately\n */\n setItems(items, options) {\n this.items = items || [];\n this.dataChanged = true;\n this.applyOnChangedLevel = false;\n\n if (options && options.applyOnChangedLevel) {\n this.applyOnChangedLevel = options.applyOnChangedLevel;\n }\n }\n\n /**\n * Update the current data set: clear cache, and recalculate the clustering for\n * the current level\n */\n updateData() {\n this.dataChanged = true;\n this.applyOnChangedLevel = false;\n }\n\n /**\n * Cluster the items which are too close together\n * @param {oldClusters: {maxItems: number, clusterCriteria: function, titleTemplate: string}} options \n * @param {scale: number} scale The scale of the current window : (windowWidth / (endDate - startDate)) \n * @return {clusters: array} clusters\n */\n getClusters(oldClusters, scale, options) {\n let { maxItems, clusterCriteria } = typeof options === \"boolean\" ? {} : options;\n \n if (!clusterCriteria) {\n clusterCriteria = () => true;\n }\n\n maxItems = maxItems || 1;\n\n let level = -1;\n let granularity = 2;\n let timeWindow = 0;\n\n if (scale > 0) {\n if (scale >= 1) {\n return [];\n }\n\n level = Math.abs(Math.round(Math.log(100 / scale) / Math.log(granularity)));\n timeWindow = Math.abs(Math.pow(granularity, level));\n }\n\n // clear the cache when and re-generate groups the data when needed.\n if (this.dataChanged) {\n const levelChanged = (level != this.cacheLevel);\n const applyDataNow = this.applyOnChangedLevel ? levelChanged : true;\n if (applyDataNow) {\n this._dropLevelsCache();\n this._filterData();\n }\n }\n\n this.cacheLevel = level;\n let clusters = this.cache[level];\n if (!clusters) {\n clusters = [];\n for (let groupName in this.groups) {\n if (this.groups.hasOwnProperty(groupName)) {\n const items = this.groups[groupName];\n const iMax = items.length;\n let i = 0;\n while (i < iMax) {\n // find all items around current item, within the timeWindow\n let item = items[i];\n let neighbors = 1; // start at 1, to include itself)\n\n // loop through items left from the current item\n let j = i - 1;\n while (j >= 0 && (item.center - items[j].center) < timeWindow / 2) {\n if (!items[j].cluster && clusterCriteria(item.data, items[j].data)) {\n neighbors++;\n }\n j--;\n }\n\n // loop through items right from the current item\n let k = i + 1;\n while (k < items.length && (items[k].center - item.center) < timeWindow / 2) {\n if (clusterCriteria(item.data, items[k].data)) {\n neighbors++;\n }\n k++;\n }\n\n // loop through the created clusters\n let l = clusters.length - 1;\n while (l >= 0 && (item.center - clusters[l].center) < timeWindow) {\n if (item.group == clusters[l].group && clusterCriteria(item.data, clusters[l].data)) {\n neighbors++;\n }\n l--;\n }\n\n // aggregate until the number of items is within maxItems\n if (neighbors > maxItems) {\n // too busy in this window.\n const num = neighbors - maxItems + 1;\n const clusterItems = [];\n\n // append the items to the cluster,\n // and calculate the average start for the cluster\n let m = i;\n while (clusterItems.length < num && m < items.length) {\n if (clusterCriteria(items[m].data, items[m].data)) {\n clusterItems.push(items[m]);\n }\n m++;\n }\n\n const groupId = this.itemSet.getGroupId(item.data);\n const group = this.itemSet.groups[groupId] || this.itemSet.groups[ReservedGroupIds.UNGROUPED];\n let cluster = this._getClusterForItems(clusterItems, group, oldClusters, options);\n clusters.push(cluster);\n\n i += num;\n } else {\n delete item.cluster;\n i += 1;\n }\n }\n }\n }\n\n this.cache[level] = clusters;\n }\n\n return clusters;\n }\n\n /**\n * Filter the items per group.\n * @private\n */\n _filterData() {\n // filter per group\n const groups = {};\n this.groups = groups;\n\n // split the items per group\n for (const item of Object.values(this.items)) {\n // put the item in the correct group\n const groupName = item.parent ? item.parent.groupId : '';\n let group = groups[groupName];\n if (!group) {\n group = [];\n groups[groupName] = group;\n }\n group.push(item);\n\n // calculate the center of the item\n if (item.data.start) {\n if (item.data.end) {\n // range\n item.center = (item.data.start.valueOf() + item.data.end.valueOf()) / 2;\n } else {\n // box, dot\n item.center = item.data.start.valueOf();\n }\n }\n }\n\n // sort the items per group\n for (let currentGroupName in groups) {\n if (groups.hasOwnProperty(currentGroupName)) {\n groups[currentGroupName].sort((a, b) => a.center - b.center);\n }\n }\n\n this.dataChanged = false;\n }\n\n /**\n * Create new cluster or return existing\n * @private\n * @param {clusterItems: array} clusterItems \n * @param {group: object} group \n * @param {oldClusters: array} oldClusters \n * @param {options: object} options \n * @returns {cluster: object} cluster\n */\n _getClusterForItems(clusterItems, group, oldClusters, options) {\n const oldClustersLookup = (oldClusters || []).map(cluster => ({\n cluster,\n itemsIds: new Set(cluster.data.uiItems.map(item => item.id))\n }));\n let cluster;\n if (oldClustersLookup.length) {\n for (let oldClusterData of oldClustersLookup) {\n if (oldClusterData.itemsIds.size === clusterItems.length \n && clusterItems.every(clusterItem => oldClusterData.itemsIds.has(clusterItem.id))) {\n cluster = oldClusterData.cluster;\n break;\n }\n }\n }\n\n if (cluster) {\n cluster.setUiItems(clusterItems);\n if (cluster.group !== group) {\n if (cluster.group) {\n cluster.group.remove(cluster); \n }\n\n if (group) {\n group.add(cluster, false); \n cluster.group = group;\n }\n }\n return cluster;\n }\n\n let titleTemplate = options.titleTemplate || '';\n const conversion = {\n toScreen: this.itemSet.body.util.toScreen,\n toTime: this.itemSet.body.util.toTime\n };\n\n const clusterContent = '