UNPKG

vis-graph3d

Version:

Create interactive, animated 3d graphs. Surfaces, lines, dots and block styling out of the box.

1 lines 215 kB
{"version":3,"file":"vis-graph3d.min.cjs","sources":["../../lib/graph3d/Point3d.js","../../lib/graph3d/Point2d.js","../../lib/graph3d/Slider.js","../../lib/graph3d/StepNumber.js","../../lib/graph3d/Camera.js","../../lib/graph3d/Settings.js","../../lib/graph3d/options.js","../../lib/graph3d/Range.js","../../lib/graph3d/Filter.js","../../lib/graph3d/DataGroup.js","../../lib/graph3d/Graph3d.js"],"sourcesContent":["/**\n * @param {number} [x]\n * @param {number} [y]\n * @param {number} [z]\n */\nfunction Point3d(x, y, z) {\n this.x = x !== undefined ? x : 0;\n this.y = y !== undefined ? y : 0;\n this.z = z !== undefined ? z : 0;\n}\n\n/**\n * Subtract the two provided points, returns a-b\n * @param {Point3d} a\n * @param {Point3d} b\n * @returns {Point3d} a-b\n */\nPoint3d.subtract = function (a, b) {\n const sub = new Point3d();\n sub.x = a.x - b.x;\n sub.y = a.y - b.y;\n sub.z = a.z - b.z;\n return sub;\n};\n\n/**\n * Add the two provided points, returns a+b\n * @param {Point3d} a\n * @param {Point3d} b\n * @returns {Point3d} a+b\n */\nPoint3d.add = function (a, b) {\n const sum = new Point3d();\n sum.x = a.x + b.x;\n sum.y = a.y + b.y;\n sum.z = a.z + b.z;\n return sum;\n};\n\n/**\n * Calculate the average of two 3d points\n * @param {Point3d} a\n * @param {Point3d} b\n * @returns {Point3d} The average, (a+b)/2\n */\nPoint3d.avg = function (a, b) {\n return new Point3d((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2);\n};\n\n/**\n * Scale the provided point by a scalar, returns p*c\n * @param {Point3d} p\n * @param {number} c\n * @returns {Point3d} p*c\n */\nPoint3d.scalarProduct = function (p, c) {\n return new Point3d(p.x * c, p.y * c, p.z * c);\n};\n\n/**\n * Calculate the dot product of the two provided points, returns a.b\n * Documentation: http://en.wikipedia.org/wiki/Dot_product\n * @param {Point3d} a\n * @param {Point3d} b\n * @returns {Point3d} dot product a.b\n */\nPoint3d.dotProduct = function (a, b) {\n return a.x * b.x + a.y * b.y + a.z * b.z;\n};\n\n/**\n * Calculate the cross product of the two provided points, returns axb\n * Documentation: http://en.wikipedia.org/wiki/Cross_product\n * @param {Point3d} a\n * @param {Point3d} b\n * @returns {Point3d} cross product axb\n */\nPoint3d.crossProduct = function (a, b) {\n const crossproduct = new Point3d();\n\n crossproduct.x = a.y * b.z - a.z * b.y;\n crossproduct.y = a.z * b.x - a.x * b.z;\n crossproduct.z = a.x * b.y - a.y * b.x;\n\n return crossproduct;\n};\n\n/**\n * Retrieve the length of the vector (or the distance from this point to the origin\n * @returns {number} length\n */\nPoint3d.prototype.length = function () {\n return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);\n};\n\n/**\n * Return a normalized vector pointing in the same direction.\n * @returns {Point3d} normalized\n */\nPoint3d.prototype.normalize = function () {\n return Point3d.scalarProduct(this, 1 / this.length());\n};\n\nexport default Point3d;\n","/**\n * @param {number} [x]\n * @param {number} [y]\n */\nfunction Point2d(x, y) {\n this.x = x !== undefined ? x : 0;\n this.y = y !== undefined ? y : 0;\n}\n\nexport default Point2d;\n","import * as util from \"vis-util/esnext\";\n\n/**\n * An html slider control with start/stop/prev/next buttons\n * @function Object() { [native code] } Slider\n * @param {Element} container The element where the slider will be created\n * @param {object} options Available options:\n * {boolean} visible If true (default) the\n * slider is visible.\n */\nfunction Slider(container, options) {\n if (container === undefined) {\n throw new Error(\"No container element defined\");\n }\n this.container = container;\n this.visible =\n options && options.visible != undefined ? options.visible : true;\n\n if (this.visible) {\n this.frame = document.createElement(\"DIV\");\n //this.frame.style.backgroundColor = '#E5E5E5';\n this.frame.style.width = \"100%\";\n this.frame.style.position = \"relative\";\n this.container.appendChild(this.frame);\n\n this.frame.prev = document.createElement(\"INPUT\");\n this.frame.prev.type = \"BUTTON\";\n this.frame.prev.value = \"Prev\";\n this.frame.appendChild(this.frame.prev);\n\n this.frame.play = document.createElement(\"INPUT\");\n this.frame.play.type = \"BUTTON\";\n this.frame.play.value = \"Play\";\n this.frame.appendChild(this.frame.play);\n\n this.frame.next = document.createElement(\"INPUT\");\n this.frame.next.type = \"BUTTON\";\n this.frame.next.value = \"Next\";\n this.frame.appendChild(this.frame.next);\n\n this.frame.bar = document.createElement(\"INPUT\");\n this.frame.bar.type = \"BUTTON\";\n this.frame.bar.style.position = \"absolute\";\n this.frame.bar.style.border = \"1px solid red\";\n this.frame.bar.style.width = \"100px\";\n this.frame.bar.style.height = \"6px\";\n this.frame.bar.style.borderRadius = \"2px\";\n this.frame.bar.style.MozBorderRadius = \"2px\";\n this.frame.bar.style.border = \"1px solid #7F7F7F\";\n this.frame.bar.style.backgroundColor = \"#E5E5E5\";\n this.frame.appendChild(this.frame.bar);\n\n this.frame.slide = document.createElement(\"INPUT\");\n this.frame.slide.type = \"BUTTON\";\n this.frame.slide.style.margin = \"0px\";\n this.frame.slide.value = \" \";\n this.frame.slide.style.position = \"relative\";\n this.frame.slide.style.left = \"-100px\";\n this.frame.appendChild(this.frame.slide);\n\n // create events\n const me = this;\n this.frame.slide.onmousedown = function (event) {\n me._onMouseDown(event);\n };\n this.frame.prev.onclick = function (event) {\n me.prev(event);\n };\n this.frame.play.onclick = function (event) {\n me.togglePlay(event);\n };\n this.frame.next.onclick = function (event) {\n me.next(event);\n };\n }\n\n this.onChangeCallback = undefined;\n\n this.values = [];\n this.index = undefined;\n\n this.playTimeout = undefined;\n this.playInterval = 1000; // milliseconds\n this.playLoop = true;\n}\n\n/**\n * Select the previous index\n */\nSlider.prototype.prev = function () {\n let index = this.getIndex();\n if (index > 0) {\n index--;\n this.setIndex(index);\n }\n};\n\n/**\n * Select the next index\n */\nSlider.prototype.next = function () {\n let index = this.getIndex();\n if (index < this.values.length - 1) {\n index++;\n this.setIndex(index);\n }\n};\n\n/**\n * Select the next index\n */\nSlider.prototype.playNext = function () {\n const start = new Date();\n\n let index = this.getIndex();\n if (index < this.values.length - 1) {\n index++;\n this.setIndex(index);\n } else if (this.playLoop) {\n // jump to the start\n index = 0;\n this.setIndex(index);\n }\n\n const end = new Date();\n const diff = end - start;\n\n // calculate how much time it to to set the index and to execute the callback\n // function.\n const interval = Math.max(this.playInterval - diff, 0);\n // document.title = diff // TODO: cleanup\n\n const me = this;\n this.playTimeout = setTimeout(function () {\n me.playNext();\n }, interval);\n};\n\n/**\n * Toggle start or stop playing\n */\nSlider.prototype.togglePlay = function () {\n if (this.playTimeout === undefined) {\n this.play();\n } else {\n this.stop();\n }\n};\n\n/**\n * Start playing\n */\nSlider.prototype.play = function () {\n // Test whether already playing\n if (this.playTimeout) return;\n\n this.playNext();\n\n if (this.frame) {\n this.frame.play.value = \"Stop\";\n }\n};\n\n/**\n * Stop playing\n */\nSlider.prototype.stop = function () {\n clearInterval(this.playTimeout);\n this.playTimeout = undefined;\n\n if (this.frame) {\n this.frame.play.value = \"Play\";\n }\n};\n\n/**\n * Set a callback function which will be triggered when the value of the\n * slider bar has changed.\n * @param {Function} callback\n */\nSlider.prototype.setOnChangeCallback = function (callback) {\n this.onChangeCallback = callback;\n};\n\n/**\n * Set the interval for playing the list\n * @param {number} interval The interval in milliseconds\n */\nSlider.prototype.setPlayInterval = function (interval) {\n this.playInterval = interval;\n};\n\n/**\n * Retrieve the current play interval\n * @returns {number} interval The interval in milliseconds\n */\nSlider.prototype.getPlayInterval = function () {\n return this.playInterval;\n};\n\n/**\n * Set looping on or off\n * @param {boolean} doLoop If true, the slider will jump to the start when\n * the end is passed, and will jump to the end\n * when the start is passed.\n */\nSlider.prototype.setPlayLoop = function (doLoop) {\n this.playLoop = doLoop;\n};\n\n/**\n * Execute the onchange callback function\n */\nSlider.prototype.onChange = function () {\n if (this.onChangeCallback !== undefined) {\n this.onChangeCallback();\n }\n};\n\n/**\n * redraw the slider on the correct place\n */\nSlider.prototype.redraw = function () {\n if (this.frame) {\n // resize the bar\n this.frame.bar.style.top =\n this.frame.clientHeight / 2 - this.frame.bar.offsetHeight / 2 + \"px\";\n this.frame.bar.style.width =\n this.frame.clientWidth -\n this.frame.prev.clientWidth -\n this.frame.play.clientWidth -\n this.frame.next.clientWidth -\n 30 +\n \"px\";\n\n // position the slider button\n const left = this.indexToLeft(this.index);\n this.frame.slide.style.left = left + \"px\";\n }\n};\n\n/**\n * Set the list with values for the slider\n * @param {Array} values A javascript array with values (any type)\n */\nSlider.prototype.setValues = function (values) {\n this.values = values;\n\n if (this.values.length > 0) this.setIndex(0);\n else this.index = undefined;\n};\n\n/**\n * Select a value by its index\n * @param {number} index\n */\nSlider.prototype.setIndex = function (index) {\n if (index < this.values.length) {\n this.index = index;\n\n this.redraw();\n this.onChange();\n } else {\n throw new Error(\"Index out of range\");\n }\n};\n\n/**\n * retrieve the index of the currently selected vaue\n * @returns {number} index\n */\nSlider.prototype.getIndex = function () {\n return this.index;\n};\n\n/**\n * retrieve the currently selected value\n * @returns {*} value\n */\nSlider.prototype.get = function () {\n return this.values[this.index];\n};\n\nSlider.prototype._onMouseDown = function (event) {\n // only react on left mouse button down\n const leftButtonDown = event.which ? event.which === 1 : event.button === 1;\n if (!leftButtonDown) return;\n\n this.startClientX = event.clientX;\n this.startSlideX = parseFloat(this.frame.slide.style.left);\n\n this.frame.style.cursor = \"move\";\n\n // add event listeners to handle moving the contents\n // we store the function onmousemove and onmouseup in the graph, so we can\n // remove the eventlisteners lateron in the function mouseUp()\n const me = this;\n this.onmousemove = function (event) {\n me._onMouseMove(event);\n };\n this.onmouseup = function (event) {\n me._onMouseUp(event);\n };\n document.addEventListener(\"mousemove\", this.onmousemove);\n document.addEventListener(\"mouseup\", this.onmouseup);\n util.preventDefault(event);\n};\n\nSlider.prototype.leftToIndex = function (left) {\n const width =\n parseFloat(this.frame.bar.style.width) - this.frame.slide.clientWidth - 10;\n const x = left - 3;\n\n let index = Math.round((x / width) * (this.values.length - 1));\n if (index < 0) index = 0;\n if (index > this.values.length - 1) index = this.values.length - 1;\n\n return index;\n};\n\nSlider.prototype.indexToLeft = function (index) {\n const width =\n parseFloat(this.frame.bar.style.width) - this.frame.slide.clientWidth - 10;\n\n const x = (index / (this.values.length - 1)) * width;\n const left = x + 3;\n\n return left;\n};\n\nSlider.prototype._onMouseMove = function (event) {\n const diff = event.clientX - this.startClientX;\n const x = this.startSlideX + diff;\n\n const index = this.leftToIndex(x);\n\n this.setIndex(index);\n\n util.preventDefault();\n};\n\nSlider.prototype._onMouseUp = function () {\n this.frame.style.cursor = \"auto\";\n\n // remove event listeners\n document.removeEventListener(\"mousemove\", this.onmousemove);\n document.removeEventListener(\"mouseup\", this.onmouseup);\n\n util.preventDefault();\n};\n\nexport default Slider;\n","/**\n * The class StepNumber is an iterator for Numbers. You provide a start and end\n * value, and a best step size. StepNumber itself rounds to fixed values and\n * a finds the step that best fits the provided step.\n *\n * If prettyStep is true, the step size is chosen as close as possible to the\n * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....\n *\n * Example usage:\n * var step = new StepNumber(0, 10, 2.5, true);\n * step.start();\n * while (!step.end()) {\n * alert(step.getCurrent());\n * step.next();\n * }\n *\n * Version: 1.0\n * @param {number} start The start value\n * @param {number} end The end value\n * @param {number} step Optional. Step size. Must be a positive value.\n * @param {boolean} prettyStep Optional. If true, the step size is rounded\n * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)\n */\nfunction StepNumber(start, end, step, prettyStep) {\n // set default values\n this._start = 0;\n this._end = 0;\n this._step = 1;\n this.prettyStep = true;\n this.precision = 5;\n\n this._current = 0;\n this.setRange(start, end, step, prettyStep);\n}\n\n/**\n * Check for input values, to prevent disasters from happening\n *\n * Source: http://stackoverflow.com/a/1830844\n * @param {string} n\n * @returns {boolean}\n */\nStepNumber.prototype.isNumeric = function (n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n};\n\n/**\n * Set a new range: start, end and step.\n * @param {number} start The start value\n * @param {number} end The end value\n * @param {number} step Optional. Step size. Must be a positive value.\n * @param {boolean} prettyStep Optional. If true, the step size is rounded\n * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)\n */\nStepNumber.prototype.setRange = function (start, end, step, prettyStep) {\n if (!this.isNumeric(start)) {\n throw new Error(\"Parameter 'start' is not numeric; value: \" + start);\n }\n if (!this.isNumeric(end)) {\n throw new Error(\"Parameter 'end' is not numeric; value: \" + start);\n }\n if (!this.isNumeric(step)) {\n throw new Error(\"Parameter 'step' is not numeric; value: \" + start);\n }\n\n this._start = start ? start : 0;\n this._end = end ? end : 0;\n\n this.setStep(step, prettyStep);\n};\n\n/**\n * Set a new step size\n * @param {number} step New step size. Must be a positive value\n * @param {boolean} prettyStep Optional. If true, the provided step is rounded\n * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)\n */\nStepNumber.prototype.setStep = function (step, prettyStep) {\n if (step === undefined || step <= 0) return;\n\n if (prettyStep !== undefined) this.prettyStep = prettyStep;\n\n if (this.prettyStep === true)\n this._step = StepNumber.calculatePrettyStep(step);\n else this._step = step;\n};\n\n/**\n * Calculate a nice step size, closest to the desired step size.\n * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an\n * integer Number. For example 1, 2, 5, 10, 20, 50, etc...\n * @param {number} step Desired step size\n * @returns {number} Nice step size\n */\nStepNumber.calculatePrettyStep = function (step) {\n const log10 = function (x) {\n return Math.log(x) / Math.LN10;\n };\n\n // try three steps (multiple of 1, 2, or 5\n const step1 = Math.pow(10, Math.round(log10(step))),\n step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),\n step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));\n\n // choose the best step (closest to minimum step)\n let prettyStep = step1;\n if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;\n if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;\n\n // for safety\n if (prettyStep <= 0) {\n prettyStep = 1;\n }\n\n return prettyStep;\n};\n\n/**\n * returns the current value of the step\n * @returns {number} current value\n */\nStepNumber.prototype.getCurrent = function () {\n return parseFloat(this._current.toPrecision(this.precision));\n};\n\n/**\n * returns the current step size\n * @returns {number} current step size\n */\nStepNumber.prototype.getStep = function () {\n return this._step;\n};\n\n/**\n * Set the current to its starting value.\n *\n * By default, this will be the largest value smaller than start, which\n * is a multiple of the step size.\n *\n * Parameters checkFirst is optional, default false.\n * If set to true, move the current value one step if smaller than start.\n * @param {boolean} [checkFirst]\n */\nStepNumber.prototype.start = function (checkFirst) {\n if (checkFirst === undefined) {\n checkFirst = false;\n }\n\n this._current = this._start - (this._start % this._step);\n\n if (checkFirst) {\n if (this.getCurrent() < this._start) {\n this.next();\n }\n }\n};\n\n/**\n * Do a step, add the step size to the current value\n */\nStepNumber.prototype.next = function () {\n this._current += this._step;\n};\n\n/**\n * Returns true whether the end is reached\n * @returns {boolean} True if the current value has passed the end value.\n */\nStepNumber.prototype.end = function () {\n return this._current > this._end;\n};\n\nexport default StepNumber;\n","import Point3d from \"./Point3d.js\";\n\n/**\n * The camera is mounted on a (virtual) camera arm. The camera arm can rotate\n * The camera is always looking in the direction of the origin of the arm.\n * This way, the camera always rotates around one fixed point, the location\n * of the camera arm.\n *\n * Documentation:\n * http://en.wikipedia.org/wiki/3D_projection\n * @class Camera\n */\nfunction Camera() {\n this.armLocation = new Point3d();\n this.armRotation = {};\n this.armRotation.horizontal = 0;\n this.armRotation.vertical = 0;\n this.armLength = 1.7;\n this.cameraOffset = new Point3d();\n this.offsetMultiplier = 0.6;\n\n this.cameraLocation = new Point3d();\n this.cameraRotation = new Point3d(0.5 * Math.PI, 0, 0);\n\n this.calculateCameraOrientation();\n}\n\n/**\n * Set offset camera in camera coordinates\n * @param {number} x offset by camera horisontal\n * @param {number} y offset by camera vertical\n */\nCamera.prototype.setOffset = function (x, y) {\n const abs = Math.abs,\n sign = Math.sign,\n mul = this.offsetMultiplier,\n border = this.armLength * mul;\n\n if (abs(x) > border) {\n x = sign(x) * border;\n }\n if (abs(y) > border) {\n y = sign(y) * border;\n }\n this.cameraOffset.x = x;\n this.cameraOffset.y = y;\n this.calculateCameraOrientation();\n};\n\n/**\n * Get camera offset by horizontal and vertical\n * @returns {number}\n */\nCamera.prototype.getOffset = function () {\n return this.cameraOffset;\n};\n\n/**\n * Set the location (origin) of the arm\n * @param {number} x Normalized value of x\n * @param {number} y Normalized value of y\n * @param {number} z Normalized value of z\n */\nCamera.prototype.setArmLocation = function (x, y, z) {\n this.armLocation.x = x;\n this.armLocation.y = y;\n this.armLocation.z = z;\n\n this.calculateCameraOrientation();\n};\n\n/**\n * Set the rotation of the camera arm\n * @param {number} horizontal The horizontal rotation, between 0 and 2*PI.\n * Optional, can be left undefined.\n * @param {number} vertical The vertical rotation, between 0 and 0.5*PI\n * if vertical=0.5*PI, the graph is shown from the\n * top. Optional, can be left undefined.\n */\nCamera.prototype.setArmRotation = function (horizontal, vertical) {\n if (horizontal !== undefined) {\n this.armRotation.horizontal = horizontal;\n }\n\n if (vertical !== undefined) {\n this.armRotation.vertical = vertical;\n if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;\n if (this.armRotation.vertical > 0.5 * Math.PI)\n this.armRotation.vertical = 0.5 * Math.PI;\n }\n\n if (horizontal !== undefined || vertical !== undefined) {\n this.calculateCameraOrientation();\n }\n};\n\n/**\n * Retrieve the current arm rotation\n * @returns {object} An object with parameters horizontal and vertical\n */\nCamera.prototype.getArmRotation = function () {\n const rot = {};\n rot.horizontal = this.armRotation.horizontal;\n rot.vertical = this.armRotation.vertical;\n\n return rot;\n};\n\n/**\n * Set the (normalized) length of the camera arm.\n * @param {number} length A length between 0.71 and 5.0\n */\nCamera.prototype.setArmLength = function (length) {\n if (length === undefined) return;\n\n this.armLength = length;\n\n // Radius must be larger than the corner of the graph,\n // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the\n // graph\n if (this.armLength < 0.71) this.armLength = 0.71;\n if (this.armLength > 5.0) this.armLength = 5.0;\n\n this.setOffset(this.cameraOffset.x, this.cameraOffset.y);\n this.calculateCameraOrientation();\n};\n\n/**\n * Retrieve the arm length\n * @returns {number} length\n */\nCamera.prototype.getArmLength = function () {\n return this.armLength;\n};\n\n/**\n * Retrieve the camera location\n * @returns {Point3d} cameraLocation\n */\nCamera.prototype.getCameraLocation = function () {\n return this.cameraLocation;\n};\n\n/**\n * Retrieve the camera rotation\n * @returns {Point3d} cameraRotation\n */\nCamera.prototype.getCameraRotation = function () {\n return this.cameraRotation;\n};\n\n/**\n * Calculate the location and rotation of the camera based on the\n * position and orientation of the camera arm\n */\nCamera.prototype.calculateCameraOrientation = function () {\n // calculate location of the camera\n this.cameraLocation.x =\n this.armLocation.x -\n this.armLength *\n Math.sin(this.armRotation.horizontal) *\n Math.cos(this.armRotation.vertical);\n this.cameraLocation.y =\n this.armLocation.y -\n this.armLength *\n Math.cos(this.armRotation.horizontal) *\n Math.cos(this.armRotation.vertical);\n this.cameraLocation.z =\n this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);\n\n // calculate rotation of the camera\n this.cameraRotation.x = Math.PI / 2 - this.armRotation.vertical;\n this.cameraRotation.y = 0;\n this.cameraRotation.z = -this.armRotation.horizontal;\n\n const xa = this.cameraRotation.x;\n const za = this.cameraRotation.z;\n const dx = this.cameraOffset.x;\n const dy = this.cameraOffset.y;\n const sin = Math.sin,\n cos = Math.cos;\n\n this.cameraLocation.x =\n this.cameraLocation.x + dx * cos(za) + dy * -sin(za) * cos(xa);\n this.cameraLocation.y =\n this.cameraLocation.y + dx * sin(za) + dy * cos(za) * cos(xa);\n this.cameraLocation.z = this.cameraLocation.z + dy * sin(xa);\n};\n\nexport default Camera;\n","////////////////////////////////////////////////////////////////////////////////\n// This modules handles the options for Graph3d.\n//\n////////////////////////////////////////////////////////////////////////////////\nimport * as util from \"vis-util/esnext\";\nimport Camera from \"./Camera.js\";\nimport Point3d from \"./Point3d.js\";\n\n// enumerate the available styles\nconst STYLE = {\n BAR: 0,\n BARCOLOR: 1,\n BARSIZE: 2,\n DOT: 3,\n DOTLINE: 4,\n DOTCOLOR: 5,\n DOTSIZE: 6,\n GRID: 7,\n LINE: 8,\n SURFACE: 9,\n};\n\n// The string representations of the styles\nconst STYLENAME = {\n dot: STYLE.DOT,\n \"dot-line\": STYLE.DOTLINE,\n \"dot-color\": STYLE.DOTCOLOR,\n \"dot-size\": STYLE.DOTSIZE,\n line: STYLE.LINE,\n grid: STYLE.GRID,\n surface: STYLE.SURFACE,\n bar: STYLE.BAR,\n \"bar-color\": STYLE.BARCOLOR,\n \"bar-size\": STYLE.BARSIZE,\n};\n\n/**\n * Field names in the options hash which are of relevance to the user.\n *\n * Specifically, these are the fields which require no special handling,\n * and can be directly copied over.\n */\nconst OPTIONKEYS = [\n \"width\",\n \"height\",\n \"filterLabel\",\n \"legendLabel\",\n \"xLabel\",\n \"yLabel\",\n \"zLabel\",\n \"xValueLabel\",\n \"yValueLabel\",\n \"zValueLabel\",\n \"showXAxis\",\n \"showYAxis\",\n \"showZAxis\",\n \"showGrayBottom\",\n \"showGrid\",\n \"showPerspective\",\n \"showShadow\",\n \"showSurfaceGrid\",\n \"keepAspectRatio\",\n \"rotateAxisLabels\",\n \"verticalRatio\",\n \"dotSizeRatio\",\n \"dotSizeMinFraction\",\n \"dotSizeMaxFraction\",\n \"showAnimationControls\",\n \"animationInterval\",\n \"animationPreload\",\n \"animationAutoStart\",\n \"axisColor\",\n \"axisFontSize\",\n \"axisFontType\",\n \"gridColor\",\n \"xCenter\",\n \"yCenter\",\n \"zoomable\",\n \"tooltipDelay\",\n \"ctrlToZoom\",\n];\n\n/**\n * Field names in the options hash which are of relevance to the user.\n *\n * Same as OPTIONKEYS, but internally these fields are stored with\n * prefix 'default' in the name.\n */\nconst PREFIXEDOPTIONKEYS = [\n \"xBarWidth\",\n \"yBarWidth\",\n \"valueMin\",\n \"valueMax\",\n \"xMin\",\n \"xMax\",\n \"xStep\",\n \"yMin\",\n \"yMax\",\n \"yStep\",\n \"zMin\",\n \"zMax\",\n \"zStep\",\n];\n\n// Placeholder for DEFAULTS reference\nlet DEFAULTS = undefined;\n\n/**\n * Check if given hash is empty.\n *\n * Source: http://stackoverflow.com/a/679937\n * @param {object} obj\n * @returns {boolean}\n */\nfunction isEmpty(obj) {\n for (const prop in obj) {\n if (Object.prototype.hasOwnProperty.call(obj, prop)) return false;\n }\n\n return true;\n}\n\n/**\n * Make first letter of parameter upper case.\n *\n * Source: http://stackoverflow.com/a/1026087\n * @param {string} str\n * @returns {string}\n */\nfunction capitalize(str) {\n if (str === undefined || str === \"\" || typeof str != \"string\") {\n return str;\n }\n\n return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\n/**\n * Add a prefix to a field name, taking style guide into account\n * @param {string} prefix\n * @param {string} fieldName\n * @returns {string}\n */\nfunction prefixFieldName(prefix, fieldName) {\n if (prefix === undefined || prefix === \"\") {\n return fieldName;\n }\n\n return prefix + capitalize(fieldName);\n}\n\n/**\n * Forcibly copy fields from src to dst in a controlled manner.\n *\n * A given field in dst will always be overwitten. If this field\n * is undefined or not present in src, the field in dst will\n * be explicitly set to undefined.\n *\n * The intention here is to be able to reset all option fields.\n *\n * Only the fields mentioned in array 'fields' will be handled.\n * @param {object} src\n * @param {object} dst\n * @param {Array<string>} fields array with names of fields to copy\n * @param {string} [prefix] prefix to use for the target fields.\n */\nfunction forceCopy(src, dst, fields, prefix) {\n let srcKey;\n let dstKey;\n\n for (let i = 0; i < fields.length; ++i) {\n srcKey = fields[i];\n dstKey = prefixFieldName(prefix, srcKey);\n\n dst[dstKey] = src[srcKey];\n }\n}\n\n/**\n * Copy fields from src to dst in a safe and controlled manner.\n *\n * Only the fields mentioned in array 'fields' will be copied over,\n * and only if these are actually defined.\n * @param {object} src\n * @param {object} dst\n * @param {Array<string>} fields array with names of fields to copy\n * @param {string} [prefix] prefix to use for the target fields.\n */\nfunction safeCopy(src, dst, fields, prefix) {\n let srcKey;\n let dstKey;\n\n for (let i = 0; i < fields.length; ++i) {\n srcKey = fields[i];\n if (src[srcKey] === undefined) continue;\n\n dstKey = prefixFieldName(prefix, srcKey);\n\n dst[dstKey] = src[srcKey];\n }\n}\n\n/**\n * Initialize dst with the values in src.\n *\n * src is the hash with the default values.\n * A reference DEFAULTS to this hash is stored locally for\n * further handling.\n *\n * For now, dst is assumed to be a Graph3d instance.\n * @param {object} src\n * @param {object} dst\n */\nfunction setDefaults(src, dst) {\n if (src === undefined || isEmpty(src)) {\n throw new Error(\"No DEFAULTS passed\");\n }\n if (dst === undefined) {\n throw new Error(\"No dst passed\");\n }\n\n // Remember defaults for future reference\n DEFAULTS = src;\n\n // Handle the defaults which can be simply copied over\n forceCopy(src, dst, OPTIONKEYS);\n forceCopy(src, dst, PREFIXEDOPTIONKEYS, \"default\");\n\n // Handle the more complex ('special') fields\n setSpecialSettings(src, dst);\n\n // Following are internal fields, not part of the user settings\n dst.margin = 10; // px\n dst.showTooltip = false;\n dst.onclick_callback = null;\n dst.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?\n}\n\n/**\n *\n * @param {object} options\n * @param {object} dst\n */\nfunction setOptions(options, dst) {\n if (options === undefined) {\n return;\n }\n if (dst === undefined) {\n throw new Error(\"No dst passed\");\n }\n\n if (DEFAULTS === undefined || isEmpty(DEFAULTS)) {\n throw new Error(\"DEFAULTS not set for module Settings\");\n }\n\n // Handle the parameters which can be simply copied over\n safeCopy(options, dst, OPTIONKEYS);\n safeCopy(options, dst, PREFIXEDOPTIONKEYS, \"default\");\n\n // Handle the more complex ('special') fields\n setSpecialSettings(options, dst);\n}\n\n/**\n * Special handling for certain parameters\n *\n * 'Special' here means: setting requires more than a simple copy\n * @param {object} src\n * @param {object} dst\n */\nfunction setSpecialSettings(src, dst) {\n if (src.backgroundColor !== undefined) {\n setBackgroundColor(src.backgroundColor, dst);\n }\n\n setDataColor(src.dataColor, dst);\n setStyle(src.style, dst);\n if (src.surfaceColors !== undefined) {\n console.warn(\n \"`options.surfaceColors` is deprecated and may be removed in a future \" +\n \"version. Please use `options.colormap` instead. Note that the `colormap` \" +\n \"option uses the inverse array ordering (running from vMin to vMax).\",\n );\n if (src.colormap !== undefined) {\n throw new Error(\n \"The `colormap` and `surfaceColors` options are mutually exclusive.\",\n );\n }\n if (dst.style !== \"surface\") {\n console.warn(\n \"Ignoring `surfaceColors` in graph style `\" +\n dst.style +\n \"` for \" +\n \"backward compatibility (only effective in `surface` plots).\",\n );\n } else {\n setSurfaceColor(src.surfaceColors, dst);\n }\n } else {\n setColormap(src.colormap, dst);\n }\n setShowLegend(src.showLegend, dst);\n setCameraPosition(src.cameraPosition, dst);\n\n // As special fields go, this is an easy one; just a translation of the name.\n // Can't use this.tooltip directly, because that field exists internally\n if (src.tooltip !== undefined) {\n dst.showTooltip = src.tooltip;\n }\n if (src.onclick != undefined) {\n dst.onclick_callback = src.onclick;\n console.warn(\n \"`options.onclick` is deprecated and may be removed in a future version.\" +\n \" Please use `Graph3d.on('click', handler)` instead.\",\n );\n }\n\n if (src.tooltipStyle !== undefined) {\n util.selectiveDeepExtend([\"tooltipStyle\"], dst, src);\n }\n}\n\n/**\n * Set the value of setting 'showLegend'\n *\n * This depends on the value of the style fields, so it must be called\n * after the style field has been initialized.\n * @param {boolean} showLegend\n * @param {object} dst\n */\nfunction setShowLegend(showLegend, dst) {\n if (showLegend === undefined) {\n // If the default was auto, make a choice for this field\n const isAutoByDefault = DEFAULTS.showLegend === undefined;\n\n if (isAutoByDefault) {\n // these styles default to having legends\n const isLegendGraphStyle =\n dst.style === STYLE.DOTCOLOR || dst.style === STYLE.DOTSIZE;\n\n dst.showLegend = isLegendGraphStyle;\n } else {\n // Leave current value as is\n }\n } else {\n dst.showLegend = showLegend;\n }\n}\n\n/**\n * Retrieve the style index from given styleName\n * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'\n * @returns {number} styleNumber Enumeration value representing the style, or -1\n * when not found\n */\nfunction getStyleNumberByName(styleName) {\n const number = STYLENAME[styleName];\n\n if (number === undefined) {\n return -1;\n }\n\n return number;\n}\n\n/**\n * Check if given number is a valid style number.\n * @param {string | number} style\n * @returns {boolean} true if valid, false otherwise\n */\nfunction checkStyleNumber(style) {\n let valid = false;\n\n for (const n in STYLE) {\n if (STYLE[n] === style) {\n valid = true;\n break;\n }\n }\n\n return valid;\n}\n\n/**\n *\n * @param {string | number} style\n * @param {object} dst\n */\nfunction setStyle(style, dst) {\n if (style === undefined) {\n return; // Nothing to do\n }\n\n let styleNumber;\n\n if (typeof style === \"string\") {\n styleNumber = getStyleNumberByName(style);\n\n if (styleNumber === -1) {\n throw new Error(\"Style '\" + style + \"' is invalid\");\n }\n } else {\n // Do a pedantic check on style number value\n if (!checkStyleNumber(style)) {\n throw new Error(\"Style '\" + style + \"' is invalid\");\n }\n\n styleNumber = style;\n }\n\n dst.style = styleNumber;\n}\n\n/**\n * Set the background styling for the graph\n * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor\n * @param {object} dst\n */\nfunction setBackgroundColor(backgroundColor, dst) {\n let fill = \"white\";\n let stroke = \"gray\";\n let strokeWidth = 1;\n\n if (typeof backgroundColor === \"string\") {\n fill = backgroundColor;\n stroke = \"none\";\n strokeWidth = 0;\n } else if (typeof backgroundColor === \"object\") {\n if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;\n if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;\n if (backgroundColor.strokeWidth !== undefined)\n strokeWidth = backgroundColor.strokeWidth;\n } else {\n throw new Error(\"Unsupported type of backgroundColor\");\n }\n\n dst.frame.style.backgroundColor = fill;\n dst.frame.style.borderColor = stroke;\n dst.frame.style.borderWidth = strokeWidth + \"px\";\n dst.frame.style.borderStyle = \"solid\";\n}\n\n/**\n *\n * @param {string | object} dataColor\n * @param {object} dst\n */\nfunction setDataColor(dataColor, dst) {\n if (dataColor === undefined) {\n return; // Nothing to do\n }\n\n if (dst.dataColor === undefined) {\n dst.dataColor = {};\n }\n\n if (typeof dataColor === \"string\") {\n dst.dataColor.fill = dataColor;\n dst.dataColor.stroke = dataColor;\n } else {\n if (dataColor.fill) {\n dst.dataColor.fill = dataColor.fill;\n }\n if (dataColor.stroke) {\n dst.dataColor.stroke = dataColor.stroke;\n }\n if (dataColor.strokeWidth !== undefined) {\n dst.dataColor.strokeWidth = dataColor.strokeWidth;\n }\n }\n}\n\n/**\n *\n * @param {object | Array<string>} surfaceColors Either an object that describes the HUE, or an array of HTML hex color codes\n * @param {object} dst\n */\nfunction setSurfaceColor(surfaceColors, dst) {\n if (surfaceColors === undefined || surfaceColors === true) {\n return; // Nothing to do\n }\n if (surfaceColors === false) {\n dst.surfaceColors = undefined;\n return;\n }\n\n if (dst.surfaceColors === undefined) {\n dst.surfaceColors = {};\n }\n\n let rgbColors;\n if (Array.isArray(surfaceColors)) {\n rgbColors = parseColorArray(surfaceColors);\n } else if (typeof surfaceColors === \"object\") {\n rgbColors = parseColorObject(surfaceColors.hue);\n } else {\n throw new Error(\"Unsupported type of surfaceColors\");\n }\n // for some reason surfaceColors goes from vMax to vMin:\n rgbColors.reverse();\n dst.colormap = rgbColors;\n}\n\n/**\n *\n * @param {object | Array<string>} colormap Either an object that describes the HUE, or an array of HTML hex color codes\n * @param {object} dst\n */\nfunction setColormap(colormap, dst) {\n if (colormap === undefined) {\n return;\n }\n\n let rgbColors;\n if (Array.isArray(colormap)) {\n rgbColors = parseColorArray(colormap);\n } else if (typeof colormap === \"object\") {\n rgbColors = parseColorObject(colormap.hue);\n } else if (typeof colormap === \"function\") {\n rgbColors = colormap;\n } else {\n throw new Error(\"Unsupported type of colormap\");\n }\n dst.colormap = rgbColors;\n}\n\n/**\n *\n * @param {Array} colormap\n */\nfunction parseColorArray(colormap) {\n if (colormap.length < 2) {\n throw new Error(\"Colormap array length must be 2 or above.\");\n }\n return colormap.map(function (colorCode) {\n if (!util.isValidHex(colorCode)) {\n throw new Error(`Invalid hex color code supplied to colormap.`);\n }\n return util.hexToRGB(colorCode);\n });\n}\n\n/**\n * Converts an object to a certain amount of hex color stops. At which point:\n * the HTML hex color codes is converted into an RGB color object.\n * @param {object} hues\n */\nfunction parseColorObject(hues) {\n if (hues === undefined) {\n throw new Error(\"Unsupported type of colormap\");\n }\n if (!(hues.saturation >= 0 && hues.saturation <= 100)) {\n throw new Error(\"Saturation is out of bounds. Expected range is 0-100.\");\n }\n if (!(hues.brightness >= 0 && hues.brightness <= 100)) {\n throw new Error(\"Brightness is out of bounds. Expected range is 0-100.\");\n }\n if (!(hues.colorStops >= 2)) {\n throw new Error(\"colorStops is out of bounds. Expected 2 or above.\");\n }\n\n const hueStep = (hues.end - hues.start) / (hues.colorStops - 1);\n\n const rgbColors = [];\n for (let i = 0; i < hues.colorStops; ++i) {\n const hue = ((hues.start + hueStep * i) % 360) / 360;\n rgbColors.push(\n util.HSVToRGB(\n hue < 0 ? hue + 1 : hue,\n hues.saturation / 100,\n hues.brightness / 100,\n ),\n );\n }\n return rgbColors;\n}\n\n/**\n *\n * @param {object} cameraPosition\n * @param {object} dst\n */\nfunction setCameraPosition(cameraPosition, dst) {\n const camPos = cameraPosition;\n if (camPos === undefined) {\n return;\n }\n\n if (dst.camera === undefined) {\n dst.camera = new Camera();\n }\n\n dst.camera.setArmRotation(camPos.horizontal, camPos.vertical);\n dst.camera.setArmLength(camPos.distance);\n}\n\nexport { STYLE, setCameraPosition, setDefaults, setOptions };\n","/**\n * This object contains all possible options. It will check if the types are correct, if required if the option is one\n * of the allowed values.\n *\n * __any__ means that the name of the property does not matter.\n * __type__ is a required field for all objects and contains the allowed types of all objects\n */\nconst string = \"string\";\nconst bool = \"boolean\";\nconst number = \"number\";\nconst object = \"object\"; // should only be in a __type__ property\nconst array = \"array\";\n// Following not used here, but useful for reference\n//let dom = 'dom';\n//let any = 'any';\n\nconst colorOptions = {\n fill: { string },\n stroke: { string },\n strokeWidth: { number },\n __type__: { string, object, undefined: \"undefined\" },\n};\n\nconst surfaceColorsOptions = {\n hue: {\n start: { number },\n end: { number },\n saturation: { number },\n brightness: { number },\n colorStops: { number },\n __type__: { object },\n },\n __type__: { boolean: bool, array, object, undefined: \"undefined\" },\n};\n\nconst colormapOptions = {\n hue: {\n start: { number },\n end: { number },\n saturation: { number },\n brightness: { number },\n colorStops: { number },\n __type__: { object },\n },\n __type__: { array, object, function: \"function\", undefined: \"undefined\" },\n};\n\n/**\n * Order attempted to be alphabetical.\n * - x/y/z-prefixes ignored in sorting\n * - __type__ always at end\n * - globals at end\n */\nconst allOptions = {\n animationAutoStart: { boolean: bool, undefined: \"undefined\" },\n animationInterval: { number },\n animationPreload: { boolean: bool },\n axisColor: { string },\n axisFontSize: { number: number },\n axisFontType: { string: string },\n backgroundColor: colorOptions,\n xBarWidth: { number, undefined: \"undefined\" },\n yBarWidth: { number, undefined: \"undefined\" },\n cameraPosition: {\n distance: { number },\n horizontal: { number },\n vertical: { number },\n __type__: { object },\n },\n zoomable: { boolean: bool },\n ctrlToZoom: { boolean: bool },\n xCenter: { string },\n yCenter: { string },\n colormap: colormapOptions,\n dataColor: colorOptions,\n dotSizeMinFraction: { number },\n dotSizeMaxFraction: { number },\n dotSizeRatio: { number },\n filterLabel: { string },\n gridColor: { string },\n onclick: { function: \"function\" },\n keepAspectRatio: { boolean: bool },\n xLabel: { string },\n yLabel: { string },\n zLabel: { string },\n legendLabel: { string },\n xMin: { number, undefined: \"undefined\" },\n yMin: { number, undefined: \"undefined\" },\n zMin: { number, undefined: \"undefined\" },\n xMax: { number, undefined: \"undefined\" },\n yMax: { number, undefined: \"undefined\" },\n zMax: { number, undefined: \"undefined\" },\n showAnimationControls: { boolean: bool, undefined: \"undefined\" },\n showGrayBottom: { boolean: bool },\n showGrid: { boolean: bool },\n showLegend: { boolean: bool, undefined: \"undefined\" },\n showPerspective: { boolean: bool },\n showShadow: { boolean: bool },\n showSurfaceGrid: { boolean: bool },\n showXAxis: { boolean: bool },\n showYAxis: { boolean: bool },\n showZAxis: { boolean: bool },\n rotateAxisLabels: { boolean: bool },\n surfaceColors: surfaceColorsOptions,\n xStep: { number, undefined: \"undefined\" },\n yStep: { number, undefined: \"undefined\" },\n zStep: { number, undefined: \"undefined\" },\n style: {\n number, // TODO: either Graph3d.DEFAULT has string, or number allowed in documentation\n string: [\n \"bar\",\n \"bar-color\",\n \"bar-size\",\n \"dot\",\n \"dot-line\",\n \"dot-color\",\n \"dot-size\",\n \"line\",\n \"grid\",\n \"surface\",\n ],\n },\n tooltip: { boolean: bool, function: \"function\" },\n tooltipDelay: { number: number },\n tooltipStyle: {\n content: {\n color: { string },\n background: { string },\n border: { string },\n borderRadius: { string },\n boxShadow: { string },\n padding: { string },\n __type__: { object },\n },\n line: {\n borderLeft: { string },\n height: { string },\n width: { string },\n pointerEvents: { string },\n __type__: { object },\n },\n dot: {\n border: { string },\n borderRadius: { string },\n height: { string },\n width: { string },\n pointerEvents: { string },\n __type__: { object },\n },\n __type__: { object },\n },\n xValueLabel: { function: \"function\" },\n yValueLabel: { function: \"function\" },\n zValueLabel: { function: \"function\" },\n valueMax: { number, undefined: \"undefined\" },\n valueMin: { number, undefined: \"undefined\" },\n verticalRatio: { number },\n\n //globals :\n height: { string },\n width: { string },\n __type__: { object },\n};\n\nexport { allOptions };\n","/**\n * Helper class to make working with related min and max values easier.\n *\n * The range is inclusive; a given value is considered part of the range if:\n *\n * this.min <= value <= this.max\n */\nfunction Range() {\n this.min = undefined;\n this.max = undefined;\n}\n\n/**\n * Adjust the range so that the passed value fits in it.\n *\n * If the value is outside of the current extremes, adjust\n * the min or max so that the value is within the range.\n * @param {number} value Numeric value to fit in range\n */\nRange.prototype.adjust = function (value) {\n if (value === undefined) return;\n\n if (this.min === undefined || this.min > value) {\n this.min = value;\n }\n\n if (this.max === undefined || this.max < value) {\n this.max = value;\n }\n};\n\n/**\n * Adjust the current range so that the passed range fits in it.\n * @param {Range} range Range instance to fit in current instance\n */\nRange.prototype.combine = function (range) {\n this.add(range.min);\n this.add(range.max);\n};\n\n/**\n * Expand the range by the given value\n *\n * min will be lowered by given value;\n * max will be raised by given value\n *\n * Shrinking by passing a negative value is allowed.\n * @param {number} val Amount by which to expand or shrink current range with\n */\nRange.prototype.expand = function (val) {\n if (val === undefined) {\n return;\n }\n\n const newMin = this.min - val;\n const newMax = this.max + val;\n\n // Note that following allows newMin === newMax.\n // This should be OK, since method expand() allows this also.\n if (newMin > newMax) {\n throw new Error(\"Passed expansion value makes range invalid\");\n }\n\n this.min = newMin;\n this.max = newMax;\n};\n\n/**\n * Determine the full range width of current instance.\n * @returns {num} The calculated width of this range\n */\nRange.prototype.range = function () {\n return this.max - this.min;\n};\n\n/**\n * Determine the central point of current instance.\n * @returns {number} the value in the middle of min and max\n */\nRange.prototype.center = function () {\n return (this.min + this.max) / 2;\n};\n\nexport default Range;\n","import { DataView } from \"vis-data/esnext\";\n\n/**\n * @class Filter\n * @param {DataGroup} dataGroup the data group\n * @param {number} column The index of the column to be filtered\n * @param {Graph3d} graph The graph\n */\nfunction Filter(dataGroup, column, graph) {\n this.dataGroup = dataGroup;\n this.column = column;\n this.graph = graph; // the parent graph\n\n this.index = undefined;\n this.value = undefined;\n\n // read all distinct values and select the first one\n this.values = dataGroup.getDistinctValues(this.column);\n\n if (this.values.length > 0) {\n this.selectValue(0);\n }\n\n // create an array with the filtered datapoints. this will be loaded afterwards\n this.dataPoints = [];\n\n this.loaded = false;\n this.onLoadCallback = undefined;\n\n if (graph.animationPreload) {\n this.loaded = false;\n this.loadInBackground();\n } else {\n this.loaded = true;\n }\n}\n\n/**\n * Return the label\n * @returns {string} label\n */\nFilter.prototype.isLoaded = function () {\n return this.loaded;\n};\n\n/**\n * Return the loaded progress\n * @returns {number} percentage between 0 and 100\n */\nFilter.prototype.getLoadedProgress = function () {\n const len = this.values.length;\n\n let i = 0;\n while (this.dataPoints[i]) {\n i++;\n }\n\n return Math.round((i / len) * 100);\n};\n\n/**\n * Return the label\n * @returns {string} label\n */\nFilter.prototype.getLabel = function () {\n return this.graph.filterLabel;\n};\n\n/**\n * Return the columnIndex of the filter\n * @returns {number} columnIndex\n */\nFilter.prototype.getColumn = function () {\n return this.column;\n};\n\n/**\n * Return the currently selected value. Returns undefined if there is no selection\n * @returns {*} value\n */\nFilter.prototype.getSelectedValue = function () {\n if (this.index === undefined) return undefined;\n\n return this.values[this.index];\n};\n\n/**\n * Retrieve all values of the filter\n * @returns {Array} values\n */\nFilter.prototype.getValues = function () {\n return this.values;\n};\n\n/**\n * Retrieve one value of the filter\n * @param {number} index\n * @returns {*} value\n */\nFilter.prototype.getValue = function (index) {\n if (index >= this.values.length) throw new Error(\"Index out of range\");\n\n return this.values[index];\n};\n\n/**\n * Retrieve the (filtered) dataPoints for the currently selected filter index\n * @param {number} [index] (optional)\n * @returns {Array} dataPoints\n */\nFilter.prototype._getDataPoints = function (index) {\n if (index === undefined) index = this.index;\n\n if (index === undefined) return [];\n\n let dataPoints;\n if (this.dataPoints[index]) {\n dataPoints = this.dataPoints[index];\n } else {\n const f = {};\n f.column = this.column;\n f.value = this.values[index];\n\n const dataView = new DataView(this.dataGroup.getDataSet(), {\n filter: function (item) {\n return item[f.column] == f.value;\n },\n }).get();\n dataPoints = this.dataGroup._getDataPoints(dataView);\n\n this.dataPoints[index] = dataPoints;\n }\n\n return dataPoints;\n};\n\n/**\n * Set a callback function when the filter is fully loaded.\n * @param {Function} callback\n */\nFilter.prototype.setOnLoadCallback = function (callback) {\n this.onLoadCallback = callback;\n};\n\n/**\n * Add a value to the list with available values for this filter\n * No double entries will be created.\n * @param {number} index\n */\nFilter.prototype.selectValue = function (index) {\n if (index >= this.values.length) throw new Error(\"Index out of range\");\n\n this.index = index;\n this.value = this.values[index];\n};\n\n/**\n * Load all filtered rows in the background one by one\n * Start this method without providing an index!\n * @param {number} [index]\n */\nFilter.prototype.loadInBackground = function (index) {\n if (index === undefined) index = 0;\n\n const frame = this.graph.frame;\n\n if (index < this.values.length) {\n // create a prog