chartist
Version:
Simple, responsive charts
1 lines • 300 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/core/constants.ts","../src/core/lang.ts","../src/core/math.ts","../src/core/data/bounds.ts","../src/utils/extend.ts","../src/utils/functional.ts","../src/utils/utils.ts","../src/core/data/data.ts","../src/core/data/highLow.ts","../src/core/data/normalize.ts","../src/core/data/segments.ts","../src/core/data/serialize.ts","../src/svg/SvgList.ts","../src/svg/animation.ts","../src/svg/Svg.ts","../src/core/creation.ts","../src/core/optionsProvider.ts","../src/svg/SvgPath.ts","../src/interpolation/none.ts","../src/interpolation/simple.ts","../src/interpolation/step.ts","../src/interpolation/cardinal.ts","../src/interpolation/monotoneCubic.ts","../src/event/EventEmitter.ts","../src/charts/BaseChart.ts","../src/axes/Axis.ts","../src/axes/AutoScaleAxis.ts","../src/axes/FixedScaleAxis.ts","../src/axes/StepAxis.ts","../src/charts/LineChart/LineChart.ts","../src/charts/BarChart/BarChart.ts","../src/charts/PieChart/PieChart.ts"],"sourcesContent":["/**\n * This object contains all namespaces used within Chartist.\n */\nexport const namespaces: Record<string, string> = {\n svg: 'http://www.w3.org/2000/svg',\n xmlns: 'http://www.w3.org/2000/xmlns/',\n xhtml: 'http://www.w3.org/1999/xhtml',\n xlink: 'http://www.w3.org/1999/xlink',\n ct: 'http://gionkunz.github.com/chartist-js/ct'\n};\n\n/**\n * Precision level used internally in Chartist for rounding. If you require more decimal places you can increase this number.\n */\nexport const precision = 8;\n\n/**\n * A map with characters to escape for strings to be safely used as attribute values.\n */\nexport const escapingMap: Record<string, string> = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": '''\n};\n","/**\n * Converts a number to a string with a unit. If a string is passed then this will be returned unmodified.\n * @return Returns the passed number value with unit.\n */\nexport function ensureUnit<T>(value: T, unit: string) {\n if (typeof value === 'number') {\n return value + unit;\n }\n\n return value;\n}\n\n/**\n * Converts a number or string to a quantity object.\n * @return Returns an object containing the value as number and the unit as string.\n */\nexport function quantity<T>(input: T) {\n if (typeof input === 'string') {\n const match = /^(\\d+)\\s*(.*)$/g.exec(input);\n return {\n value: match ? +match[1] : 0,\n unit: match?.[2] || undefined\n };\n }\n\n return {\n value: Number(input)\n };\n}\n\n/**\n * Generates a-z from a number 0 to 26\n * @param n A number from 0 to 26 that will result in a letter a-z\n * @return A character from a-z based on the input number n\n */\nexport function alphaNumerate(n: number) {\n // Limit to a-z\n return String.fromCharCode(97 + (n % 26));\n}\n","import type { Bounds } from './types';\nimport { precision as globalPrecision } from './constants';\n\nexport const EPSILON = 2.221e-16;\n\n/**\n * Calculate the order of magnitude for the chart scale\n * @param value The value Range of the chart\n * @return The order of magnitude\n */\nexport function orderOfMagnitude(value: number) {\n return Math.floor(Math.log(Math.abs(value)) / Math.LN10);\n}\n\n/**\n * Project a data length into screen coordinates (pixels)\n * @param axisLength The svg element for the chart\n * @param length Single data value from a series array\n * @param bounds All the values to set the bounds of the chart\n * @return The projected data length in pixels\n */\nexport function projectLength(\n axisLength: number,\n length: number,\n bounds: Bounds\n) {\n return (length / bounds.range) * axisLength;\n}\n\n/**\n * This helper function can be used to round values with certain precision level after decimal. This is used to prevent rounding errors near float point precision limit.\n * @param value The value that should be rounded with precision\n * @param [digits] The number of digits after decimal used to do the rounding\n * @returns Rounded value\n */\nexport function roundWithPrecision(value: number, digits?: number) {\n const precision = Math.pow(10, digits || globalPrecision);\n return Math.round(value * precision) / precision;\n}\n\n/**\n * Pollard Rho Algorithm to find smallest factor of an integer value. There are more efficient algorithms for factorization, but this one is quite efficient and not so complex.\n * @param num An integer number where the smallest factor should be searched for\n * @returns The smallest integer factor of the parameter num.\n */\nexport function rho(num: number) {\n if (num === 1) {\n return num;\n }\n\n function gcd(p: number, q: number): number {\n if (p % q === 0) {\n return q;\n } else {\n return gcd(q, p % q);\n }\n }\n\n function f(x: number) {\n return x * x + 1;\n }\n\n let x1 = 2;\n let x2 = 2;\n let divisor: number;\n\n if (num % 2 === 0) {\n return 2;\n }\n\n do {\n x1 = f(x1) % num;\n x2 = f(f(x2)) % num;\n divisor = gcd(Math.abs(x1 - x2), num);\n } while (divisor === 1);\n\n return divisor;\n}\n\n/**\n * Calculate cartesian coordinates of polar coordinates\n * @param centerX X-axis coordinates of center point of circle segment\n * @param centerY X-axis coordinates of center point of circle segment\n * @param radius Radius of circle segment\n * @param angleInDegrees Angle of circle segment in degrees\n * @return Coordinates of point on circumference\n */\nexport function polarToCartesian(\n centerX: number,\n centerY: number,\n radius: number,\n angleInDegrees: number\n) {\n const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;\n\n return {\n x: centerX + radius * Math.cos(angleInRadians),\n y: centerY + radius * Math.sin(angleInRadians)\n };\n}\n","import type { Bounds } from '../types';\nimport {\n orderOfMagnitude,\n projectLength,\n roundWithPrecision,\n rho,\n EPSILON\n} from '../math';\n\n/**\n * Calculate and retrieve all the bounds for the chart and return them in one array\n * @param axisLength The length of the Axis used for\n * @param highLow An object containing a high and low property indicating the value range of the chart.\n * @param scaleMinSpace The minimum projected length a step should result in\n * @param onlyInteger\n * @return All the values to set the bounds of the chart\n */\nexport function getBounds(\n axisLength: number,\n highLow: { high: number; low: number },\n scaleMinSpace: number,\n onlyInteger = false\n) {\n const bounds: Bounds = {\n high: highLow.high,\n low: highLow.low,\n valueRange: 0,\n oom: 0,\n step: 0,\n min: 0,\n max: 0,\n range: 0,\n numberOfSteps: 0,\n values: []\n };\n\n bounds.valueRange = bounds.high - bounds.low;\n bounds.oom = orderOfMagnitude(bounds.valueRange);\n bounds.step = Math.pow(10, bounds.oom);\n bounds.min = Math.floor(bounds.low / bounds.step) * bounds.step;\n bounds.max = Math.ceil(bounds.high / bounds.step) * bounds.step;\n bounds.range = bounds.max - bounds.min;\n bounds.numberOfSteps = Math.round(bounds.range / bounds.step);\n\n // Optimize scale step by checking if subdivision is possible based on horizontalGridMinSpace\n // If we are already below the scaleMinSpace value we will scale up\n const length = projectLength(axisLength, bounds.step, bounds);\n const scaleUp = length < scaleMinSpace;\n const smallestFactor = onlyInteger ? rho(bounds.range) : 0;\n\n // First check if we should only use integer steps and if step 1 is still larger than scaleMinSpace so we can use 1\n if (onlyInteger && projectLength(axisLength, 1, bounds) >= scaleMinSpace) {\n bounds.step = 1;\n } else if (\n onlyInteger &&\n smallestFactor < bounds.step &&\n projectLength(axisLength, smallestFactor, bounds) >= scaleMinSpace\n ) {\n // If step 1 was too small, we can try the smallest factor of range\n // If the smallest factor is smaller than the current bounds.step and the projected length of smallest factor\n // is larger than the scaleMinSpace we should go for it.\n bounds.step = smallestFactor;\n } else {\n // Trying to divide or multiply by 2 and find the best step value\n let optimizationCounter = 0;\n for (;;) {\n if (\n scaleUp &&\n projectLength(axisLength, bounds.step, bounds) <= scaleMinSpace\n ) {\n bounds.step *= 2;\n } else if (\n !scaleUp &&\n projectLength(axisLength, bounds.step / 2, bounds) >= scaleMinSpace\n ) {\n bounds.step /= 2;\n if (onlyInteger && bounds.step % 1 !== 0) {\n bounds.step *= 2;\n break;\n }\n } else {\n break;\n }\n\n if (optimizationCounter++ > 1000) {\n throw new Error(\n 'Exceeded maximum number of iterations while optimizing scale step!'\n );\n }\n }\n }\n\n bounds.step = Math.max(bounds.step, EPSILON);\n function safeIncrement(value: number, increment: number) {\n // If increment is too small use *= (1+EPSILON) as a simple nextafter\n if (value === (value += increment)) {\n value *= 1 + (increment > 0 ? EPSILON : -EPSILON);\n }\n return value;\n }\n\n // Narrow min and max based on new step\n let newMin = bounds.min;\n let newMax = bounds.max;\n while (newMin + bounds.step <= bounds.low) {\n newMin = safeIncrement(newMin, bounds.step);\n }\n while (newMax - bounds.step >= bounds.high) {\n newMax = safeIncrement(newMax, -bounds.step);\n }\n bounds.min = newMin;\n bounds.max = newMax;\n bounds.range = bounds.max - bounds.min;\n\n const values: number[] = [];\n for (let i = bounds.min; i <= bounds.max; i = safeIncrement(i, bounds.step)) {\n const value = roundWithPrecision(i);\n if (value !== values[values.length - 1]) {\n values.push(value);\n }\n }\n bounds.values = values;\n\n return bounds;\n}\n","/**\n * Simple recursive object extend\n * @param target Target object where the source will be merged into\n * @param sources This object (objects) will be merged into target and then target is returned\n * @return An object that has the same reference as target but is extended and merged with the properties of source\n */\nexport function extend<T>(target: T): T;\nexport function extend<T, A>(target: T, a: A): T & A;\nexport function extend<T, A, B>(target: T, a: A, b: B): T & A & B;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function extend(target: any = {}, ...sources: any[]) {\n for (let i = 0; i < sources.length; i++) {\n const source = sources[i];\n const targetProto = Object.getPrototypeOf(target);\n for (const prop in source) {\n if (targetProto !== null && prop in targetProto) {\n continue; // prevent prototype pollution\n }\n const sourceProp = source[prop];\n if (\n typeof sourceProp === 'object' &&\n sourceProp !== null &&\n !(sourceProp instanceof Array)\n ) {\n target[prop] = extend(target[prop], sourceProp);\n } else {\n target[prop] = sourceProp;\n }\n }\n }\n\n return target;\n}\n","/**\n * Helps to simplify functional style code\n * @param n This exact value will be returned by the noop function\n * @return The same value that was provided to the n parameter\n */\nexport const noop = <T>(n: T) => n;\n\n/**\n * Functional style helper to produce array with given length initialized with undefined values\n */\nexport function times(length: number): undefined[];\nexport function times<T = unknown>(\n length: number,\n filler: (index: number) => T\n): T[];\nexport function times<T = unknown>(\n length: number,\n filler?: (index: number) => T\n) {\n return Array.from({ length }, filler ? (_, i) => filler(i) : () => void 0);\n}\n\n/**\n * Sum helper to be used in reduce functions\n */\nexport const sum = (previous: number, current: number) =>\n previous + (current ? current : 0);\n\n/**\n * Map for multi dimensional arrays where their nested arrays will be mapped in serial. The output array will have the length of the largest nested array. The callback function is called with variable arguments where each argument is the nested array value (or undefined if there are no more values).\n *\n * For example:\n * @example\n * ```ts\n * const data = [[1, 2], [3], []];\n * serialMap(data, cb);\n *\n * // where cb will be called 2 times\n * // 1. call arguments: (1, 3, undefined)\n * // 2. call arguments: (2, undefined, undefined)\n * ```\n */\nexport const serialMap = <T, K>(array: T[][], callback: (...args: T[]) => K) =>\n times(Math.max(...array.map(element => element.length)), index =>\n callback(...array.map(element => element[index]))\n );\n","import type { FilterByKey } from './types';\n\n/**\n * This function safely checks if an objects has an owned property.\n * @param target The object where to check for a property\n * @param property The property name\n * @returns Returns true if the object owns the specified property\n */\nexport function safeHasProperty<T, K extends string>(\n target: T,\n property: K\n): target is FilterByKey<T, K>;\nexport function safeHasProperty(target: unknown, property: string) {\n return (\n target !== null &&\n typeof target === 'object' &&\n Reflect.has(target, property)\n );\n}\n\n/**\n * Checks if a value can be safely coerced to a number. This includes all values except null which result in finite numbers when coerced. This excludes NaN, since it's not finite.\n */\nexport function isNumeric(value: number): true;\nexport function isNumeric(value: unknown): boolean;\nexport function isNumeric(value: unknown) {\n return value !== null && isFinite(value as number);\n}\n\n/**\n * Returns true on all falsey values except the numeric value 0.\n */\nexport function isFalseyButZero(\n value: unknown\n): value is undefined | null | false | '' {\n return !value && value !== 0;\n}\n\n/**\n * Returns a number if the passed parameter is a valid number or the function will return undefined. On all other values than a valid number, this function will return undefined.\n */\nexport function getNumberOrUndefined(value: number): number;\nexport function getNumberOrUndefined(value: unknown): number | undefined;\nexport function getNumberOrUndefined(value: unknown) {\n return isNumeric(value) ? Number(value) : undefined;\n}\n\n/**\n * Checks if value is array of arrays or not.\n */\nexport function isArrayOfArrays(data: unknown): data is unknown[][] {\n if (!Array.isArray(data)) {\n return false;\n }\n\n return data.every(Array.isArray);\n}\n\n/**\n * Loop over array.\n */\nexport function each<T>(\n list: T[],\n callback: (item: T, index: number, itemIndex: number) => void,\n reverse = false\n) {\n let index = 0;\n\n list[reverse ? 'reduceRight' : 'reduce']<void>(\n (_, item, itemIndex) => callback(item, index++, itemIndex),\n void 0\n );\n}\n","import type {\n Multi,\n AxisName,\n FlatSeriesValue,\n Series,\n SeriesObject\n} from '../types';\nimport { safeHasProperty, getNumberOrUndefined } from '../../utils';\n\n/**\n * Get meta data of a specific value in a series.\n */\nexport function getMetaData(\n seriesData: FlatSeriesValue | Series | SeriesObject,\n index: number\n) {\n const value = Array.isArray(seriesData)\n ? seriesData[index]\n : safeHasProperty(seriesData, 'data')\n ? seriesData.data[index]\n : null;\n return safeHasProperty(value, 'meta') ? value.meta : undefined;\n}\n\n/**\n * Checks if a value is considered a hole in the data series.\n * @returns True if the value is considered a data hole\n */\nexport function isDataHoleValue(value: unknown): value is null | undefined;\nexport function isDataHoleValue(value: unknown) {\n return (\n value === null ||\n value === undefined ||\n (typeof value === 'number' && isNaN(value))\n );\n}\n\n/**\n * Checks if value is array of series objects.\n */\nexport function isArrayOfSeries(\n value: unknown\n): value is (Series | SeriesObject)[] {\n return (\n Array.isArray(value) &&\n value.every(_ => Array.isArray(_) || safeHasProperty(_, 'data'))\n );\n}\n\n/**\n * Checks if provided value object is multi value (contains x or y properties)\n */\nexport function isMultiValue(value: unknown): value is Multi {\n return (\n typeof value === 'object' &&\n value !== null &&\n (Reflect.has(value, 'x') || Reflect.has(value, 'y'))\n );\n}\n\n/**\n * Gets a value from a dimension `value.x` or `value.y` while returning value directly if it's a valid numeric value. If the value is not numeric and it's falsey this function will return `defaultValue`.\n */\nexport function getMultiValue(\n value: Multi | number | unknown,\n dimension: AxisName = 'y'\n) {\n if (isMultiValue(value) && safeHasProperty(value, dimension)) {\n return getNumberOrUndefined(value[dimension]);\n } else {\n return getNumberOrUndefined(value);\n }\n}\n","import type {\n Options,\n AxisName,\n NormalizedSeries,\n NormalizedSeriesValue\n} from '../types';\nimport { safeHasProperty } from '../../utils';\nimport { isDataHoleValue } from './data';\n\n/**\n * Get highest and lowest value of data array. This Array contains the data that will be visualized in the chart.\n * @param data The array that contains the data to be visualized in the chart\n * @param options The Object that contains the chart options\n * @param dimension Axis dimension 'x' or 'y' used to access the correct value and high / low configuration\n * @return An object that contains the highest and lowest value that will be visualized on the chart.\n */\nexport function getHighLow(\n data: NormalizedSeries[],\n options: Options,\n dimension?: AxisName\n) {\n // TODO: Remove workaround for deprecated global high / low config. Axis high / low configuration is preferred\n options = {\n ...options,\n ...(dimension ? (dimension === 'x' ? options.axisX : options.axisY) : {})\n };\n\n const highLow = {\n high: options.high === undefined ? -Number.MAX_VALUE : +options.high,\n low: options.low === undefined ? Number.MAX_VALUE : +options.low\n };\n const findHigh = options.high === undefined;\n const findLow = options.low === undefined;\n\n // Function to recursively walk through arrays and find highest and lowest number\n function recursiveHighLow(\n sourceData: NormalizedSeriesValue | NormalizedSeries | NormalizedSeries[]\n ) {\n if (isDataHoleValue(sourceData)) {\n return;\n } else if (Array.isArray(sourceData)) {\n for (let i = 0; i < sourceData.length; i++) {\n recursiveHighLow(sourceData[i]);\n }\n } else {\n const value = Number(\n dimension && safeHasProperty(sourceData, dimension)\n ? sourceData[dimension]\n : sourceData\n );\n\n if (findHigh && value > highLow.high) {\n highLow.high = value;\n }\n\n if (findLow && value < highLow.low) {\n highLow.low = value;\n }\n }\n }\n\n // Start to find highest and lowest number recursively\n if (findHigh || findLow) {\n recursiveHighLow(data);\n }\n\n // Overrides of high / low based on reference value, it will make sure that the invisible reference value is\n // used to generate the chart. This is useful when the chart always needs to contain the position of the\n // invisible reference value in the view i.e. for bipolar scales.\n if (options.referenceValue || options.referenceValue === 0) {\n highLow.high = Math.max(options.referenceValue, highLow.high);\n highLow.low = Math.min(options.referenceValue, highLow.low);\n }\n\n // If high and low are the same because of misconfiguration or flat data (only the same value) we need\n // to set the high or low to 0 depending on the polarity\n if (highLow.high <= highLow.low) {\n // If both values are 0 we set high to 1\n if (highLow.low === 0) {\n highLow.high = 1;\n } else if (highLow.low < 0) {\n // If we have the same negative value for the bounds we set bounds.high to 0\n highLow.high = 0;\n } else if (highLow.high > 0) {\n // If we have the same positive value for the bounds we set bounds.low to 0\n highLow.low = 0;\n } else {\n // If data array was empty, values are Number.MAX_VALUE and -Number.MAX_VALUE. Set bounds to prevent errors\n highLow.high = 1;\n highLow.low = 0;\n }\n }\n\n return highLow;\n}\n","import type {\n Data,\n NormalizedData,\n Multi,\n AxisName,\n NormalizedMulti,\n Series,\n FlatSeries,\n NormalizedSeries,\n NormalizedFlatSeries,\n SeriesObject,\n SeriesPrimitiveValue\n} from '../types';\nimport {\n isArrayOfArrays,\n times,\n safeHasProperty,\n getNumberOrUndefined\n} from '../../utils';\nimport { isDataHoleValue, isArrayOfSeries } from './data';\n\n/**\n * Ensures that the data object passed as second argument to the charts is present and correctly initialized.\n * @param data The data object that is passed as second argument to the charts\n * @return The normalized data object\n */\nexport function normalizeData(\n data: Data<FlatSeries>,\n reverse?: boolean,\n multi?: false\n): NormalizedData<NormalizedFlatSeries>;\nexport function normalizeData(\n data: Data<(Series | SeriesObject)[]>,\n reverse: boolean | undefined,\n multi: true | AxisName\n): NormalizedData<NormalizedSeries[]>;\nexport function normalizeData(\n data: Data<FlatSeries | (Series | SeriesObject)[]>,\n reverse: boolean | undefined,\n multi: boolean | AxisName,\n distributed: true\n): NormalizedData<NormalizedSeries[]>;\nexport function normalizeData(\n data: Data<FlatSeries | (Series | SeriesObject)[]>,\n reverse?: boolean,\n multi?: boolean | AxisName\n): NormalizedData<NormalizedFlatSeries | NormalizedSeries[]>;\nexport function normalizeData(\n data: Data,\n reverse = false,\n multi?: boolean | AxisName,\n distributed?: boolean\n) {\n let labelCount: number;\n const normalized: NormalizedData = {\n labels: (data.labels || []).slice(),\n series: normalizeSeries(data.series, multi, distributed)\n };\n const inputLabelCount = normalized.labels.length;\n\n // If all elements of the normalized data array are arrays we're dealing with\n // multi series data and we need to find the largest series if they are un-even\n if (isArrayOfArrays(normalized.series)) {\n // Getting the series with the the most elements\n labelCount = Math.max(\n inputLabelCount,\n ...normalized.series.map(series => series.length)\n );\n\n normalized.series.forEach(series => {\n series.push(...times(Math.max(0, labelCount - series.length)));\n });\n } else {\n // We're dealing with Pie data so we just take the normalized array length\n labelCount = normalized.series.length;\n }\n\n // Padding the labels to labelCount with empty strings\n normalized.labels.push(\n ...times(Math.max(0, labelCount - inputLabelCount), () => '')\n );\n\n if (reverse) {\n reverseData(normalized);\n }\n\n return normalized;\n}\n\n/**\n * Reverses the series, labels and series data arrays.\n */\nfunction reverseData(data: Data) {\n data.labels?.reverse();\n data.series.reverse();\n for (const series of data.series) {\n if (safeHasProperty(series, 'data')) {\n series.data.reverse();\n } else if (Array.isArray(series)) {\n series.reverse();\n }\n }\n}\n\nfunction normalizeMulti(\n value: number | string | boolean | Date | Multi,\n multi?: boolean | AxisName\n) {\n // We need to prepare multi value output (x and y data)\n let x: number | undefined;\n let y: number | undefined;\n\n // Single series value arrays are assumed to specify the Y-Axis value\n // For example: [1, 2] => [{x: undefined, y: 1}, {x: undefined, y: 2}]\n // If multi is a string then it's assumed that it specified which dimension should be filled as default\n if (typeof value !== 'object') {\n const num = getNumberOrUndefined(value);\n\n if (multi === 'x') {\n x = num;\n } else {\n y = num;\n }\n } else {\n if (safeHasProperty(value, 'x')) {\n x = getNumberOrUndefined(value.x);\n }\n\n if (safeHasProperty(value, 'y')) {\n y = getNumberOrUndefined(value.y);\n }\n }\n\n if (x === undefined && y === undefined) {\n return undefined;\n }\n\n return { x, y } as NormalizedMulti;\n}\n\nfunction normalizePrimitive(\n value: SeriesPrimitiveValue,\n multi?: boolean | AxisName\n) {\n if (isDataHoleValue(value)) {\n // We're dealing with a hole in the data and therefore need to return undefined\n // We're also returning undefined for multi value output\n return undefined;\n }\n\n if (multi) {\n return normalizeMulti(value, multi);\n }\n\n return getNumberOrUndefined(value);\n}\n\nfunction normalizeSingleSeries(\n series: Series | SeriesObject,\n multi?: boolean | AxisName\n): NormalizedSeries {\n if (!Array.isArray(series)) {\n // We are dealing with series object notation so we need to recurse on data property\n return normalizeSingleSeries(series.data, multi);\n }\n\n return series.map(value => {\n if (safeHasProperty(value, 'value')) {\n // We are dealing with value object notation so we need to recurse on value property\n return normalizePrimitive(value.value, multi);\n }\n\n return normalizePrimitive(value, multi);\n });\n}\n\n/**\n * Convert data series into plain array\n * @param series The series object that contains the data to be visualized in the chart\n * @param multi Create a multi dimensional array from a series data array where a value object with `x` and `y` values will be created.\n * @return A plain array that contains the data to be visualized in the chart\n */\nfunction normalizeSeries(\n series: FlatSeries,\n multi?: false,\n distributed?: false\n): NormalizedFlatSeries;\nfunction normalizeSeries(\n series: (Series | SeriesObject)[],\n multi: true | AxisName,\n distributed?: false\n): NormalizedSeries[];\nfunction normalizeSeries(\n series: FlatSeries | (Series | SeriesObject)[],\n multi: boolean | undefined | AxisName,\n distributed: true\n): NormalizedSeries[];\nfunction normalizeSeries(\n series: FlatSeries | (Series | SeriesObject)[],\n multi?: boolean | undefined | AxisName,\n distributed?: boolean\n): NormalizedFlatSeries | NormalizedSeries[];\nfunction normalizeSeries(\n series: FlatSeries | (Series | SeriesObject)[],\n multi?: boolean | undefined | AxisName,\n distributed?: boolean\n) {\n if (isArrayOfSeries(series)) {\n return series.map(_ => normalizeSingleSeries(_, multi));\n }\n\n const normalizedSeries = normalizeSingleSeries(series, multi);\n\n if (distributed) {\n return normalizedSeries.map(value => [value]);\n }\n\n return normalizedSeries;\n}\n","import type { Segment, SegmentData } from '../types';\nimport { getMultiValue } from './data';\n\n/**\n * Splits a list of coordinates and associated values into segments. Each returned segment contains a pathCoordinates\n * valueData property describing the segment.\n *\n * With the default options, segments consist of contiguous sets of points that do not have an undefined value. Any\n * points with undefined values are discarded.\n *\n * **Options**\n * The following options are used to determine how segments are formed\n * ```javascript\n * var options = {\n * // If fillHoles is true, undefined values are simply discarded without creating a new segment. Assuming other options are default, this returns single segment.\n * fillHoles: false,\n * // If increasingX is true, the coordinates in all segments have strictly increasing x-values.\n * increasingX: false\n * };\n * ```\n *\n * @param pathCoordinates List of point coordinates to be split in the form [x1, y1, x2, y2 ... xn, yn]\n * @param valueData List of associated point values in the form [v1, v2 .. vn]\n * @param options Options set by user\n * @return List of segments, each containing a pathCoordinates and valueData property.\n */\nexport function splitIntoSegments(\n pathCoordinates: number[],\n valueData: SegmentData[],\n options?: {\n increasingX?: boolean;\n fillHoles?: boolean;\n }\n) {\n const finalOptions = {\n increasingX: false,\n fillHoles: false,\n ...options\n };\n\n const segments: Segment[] = [];\n let hole = true;\n\n for (let i = 0; i < pathCoordinates.length; i += 2) {\n // If this value is a \"hole\" we set the hole flag\n if (getMultiValue(valueData[i / 2].value) === undefined) {\n // if(valueData[i / 2].value === undefined) {\n if (!finalOptions.fillHoles) {\n hole = true;\n }\n } else {\n if (\n finalOptions.increasingX &&\n i >= 2 &&\n pathCoordinates[i] <= pathCoordinates[i - 2]\n ) {\n // X is not increasing, so we need to make sure we start a new segment\n hole = true;\n }\n\n // If it's a valid value we need to check if we're coming out of a hole and create a new empty segment\n if (hole) {\n segments.push({\n pathCoordinates: [],\n valueData: []\n });\n // As we have a valid value now, we are not in a \"hole\" anymore\n hole = false;\n }\n\n // Add to the segment pathCoordinates and valueData\n segments[segments.length - 1].pathCoordinates.push(\n pathCoordinates[i],\n pathCoordinates[i + 1]\n );\n segments[segments.length - 1].valueData.push(valueData[i / 2]);\n }\n }\n\n return segments;\n}\n","import { escapingMap } from '../constants';\n\n/**\n * This function serializes arbitrary data to a string. In case of data that can't be easily converted to a string, this function will create a wrapper object and serialize the data using JSON.stringify. The outcoming string will always be escaped using Chartist.escapingMap.\n * If called with null or undefined the function will return immediately with null or undefined.\n */\nexport function serialize(data: number | string | object): string;\nexport function serialize(\n data: number | string | object | null | undefined | unknown\n): string | null | undefined;\nexport function serialize(\n data: number | string | object | null | undefined | unknown\n) {\n let serialized = '';\n\n if (data === null || data === undefined) {\n return data;\n } else if (typeof data === 'number') {\n serialized = '' + data;\n } else if (typeof data === 'object') {\n serialized = JSON.stringify({ data: data });\n } else {\n serialized = String(data);\n }\n\n return Object.keys(escapingMap).reduce(\n (result, key) => result.replaceAll(key, escapingMap[key]),\n serialized\n );\n}\n\n/**\n * This function de-serializes a string previously serialized with Chartist.serialize. The string will always be unescaped using Chartist.escapingMap before it's returned. Based on the input value the return type can be Number, String or Object. JSON.parse is used with try / catch to see if the unescaped string can be parsed into an Object and this Object will be returned on success.\n */\nexport function deserialize<T extends object | number | string = object>(\n data: string\n): T;\nexport function deserialize<T extends object | number | string = object>(\n data: string | null | undefined\n): T | null | undefined;\nexport function deserialize(data: unknown) {\n if (typeof data !== 'string') {\n return data;\n }\n\n if (data === 'NaN') {\n return NaN;\n }\n\n data = Object.keys(escapingMap).reduce(\n (result, key) => result.replaceAll(escapingMap[key], key),\n data\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let parsedData: any = data;\n\n if (typeof data === 'string') {\n try {\n parsedData = JSON.parse(data);\n parsedData = parsedData.data !== undefined ? parsedData.data : parsedData;\n } catch (e) {\n /* Ingore */\n }\n }\n\n return parsedData;\n}\n","import { Svg } from './Svg';\n\ntype SvgMethods = Exclude<\n keyof Svg,\n | 'constructor'\n | 'parent'\n | 'querySelector'\n | 'querySelectorAll'\n | 'replace'\n | 'append'\n | 'classes'\n | 'height'\n | 'width'\n>;\n\ntype SvgListMethods = {\n [method in SvgMethods]: (...args: Parameters<Svg[method]>) => SvgList;\n};\n\n/**\n * This helper class is to wrap multiple `Svg` elements into a list where you can call the `Svg` functions on all elements in the list with one call. This is helpful when you'd like to perform calls with `Svg` on multiple elements.\n * An instance of this class is also returned by `Svg.querySelectorAll`.\n */\nexport class SvgList implements SvgListMethods {\n private svgElements: Svg[] = [];\n\n /**\n * @param nodeList An Array of SVG DOM nodes or a SVG DOM NodeList (as returned by document.querySelectorAll)\n */\n constructor(nodeList: ArrayLike<Element>) {\n for (let i = 0; i < nodeList.length; i++) {\n this.svgElements.push(new Svg(nodeList[i]));\n }\n }\n\n private call<T extends SvgMethods>(method: T, args: Parameters<Svg[T]>) {\n this.svgElements.forEach(element =>\n Reflect.apply(element[method], element, args)\n );\n return this;\n }\n\n attr(...args: Parameters<Svg['attr']>) {\n return this.call('attr', args);\n }\n\n elem(...args: Parameters<Svg['elem']>) {\n return this.call('elem', args);\n }\n\n root(...args: Parameters<Svg['root']>) {\n return this.call('root', args);\n }\n\n getNode(...args: Parameters<Svg['getNode']>) {\n return this.call('getNode', args);\n }\n\n foreignObject(...args: Parameters<Svg['foreignObject']>) {\n return this.call('foreignObject', args);\n }\n\n text(...args: Parameters<Svg['text']>) {\n return this.call('text', args);\n }\n\n empty(...args: Parameters<Svg['empty']>) {\n return this.call('empty', args);\n }\n\n remove(...args: Parameters<Svg['remove']>) {\n return this.call('remove', args);\n }\n\n addClass(...args: Parameters<Svg['addClass']>) {\n return this.call('addClass', args);\n }\n\n removeClass(...args: Parameters<Svg['removeClass']>) {\n return this.call('removeClass', args);\n }\n\n removeAllClasses(...args: Parameters<Svg['removeAllClasses']>) {\n return this.call('removeAllClasses', args);\n }\n\n animate(...args: Parameters<Svg['animate']>) {\n return this.call('animate', args);\n }\n}\n","import type { EventEmitter } from '../event';\nimport { ensureUnit, quantity } from '../core/lang';\nimport type { Attributes, AnimationDefinition, AnimationEvent } from './types';\nimport type { Svg } from './Svg';\n\n/**\n * This Object contains some standard easing cubic bezier curves.\n * Then can be used with their name in the `Svg.animate`.\n * You can also extend the list and use your own name in the `animate` function.\n * Click the show code button to see the available bezier functions.\n */\nexport const easings = {\n easeInSine: [0.47, 0, 0.745, 0.715],\n easeOutSine: [0.39, 0.575, 0.565, 1],\n easeInOutSine: [0.445, 0.05, 0.55, 0.95],\n easeInQuad: [0.55, 0.085, 0.68, 0.53],\n easeOutQuad: [0.25, 0.46, 0.45, 0.94],\n easeInOutQuad: [0.455, 0.03, 0.515, 0.955],\n easeInCubic: [0.55, 0.055, 0.675, 0.19],\n easeOutCubic: [0.215, 0.61, 0.355, 1],\n easeInOutCubic: [0.645, 0.045, 0.355, 1],\n easeInQuart: [0.895, 0.03, 0.685, 0.22],\n easeOutQuart: [0.165, 0.84, 0.44, 1],\n easeInOutQuart: [0.77, 0, 0.175, 1],\n easeInQuint: [0.755, 0.05, 0.855, 0.06],\n easeOutQuint: [0.23, 1, 0.32, 1],\n easeInOutQuint: [0.86, 0, 0.07, 1],\n easeInExpo: [0.95, 0.05, 0.795, 0.035],\n easeOutExpo: [0.19, 1, 0.22, 1],\n easeInOutExpo: [1, 0, 0, 1],\n easeInCirc: [0.6, 0.04, 0.98, 0.335],\n easeOutCirc: [0.075, 0.82, 0.165, 1],\n easeInOutCirc: [0.785, 0.135, 0.15, 0.86],\n easeInBack: [0.6, -0.28, 0.735, 0.045],\n easeOutBack: [0.175, 0.885, 0.32, 1.275],\n easeInOutBack: [0.68, -0.55, 0.265, 1.55]\n};\n\nexport function createAnimation(\n element: Svg,\n attribute: string,\n animationDefinition: AnimationDefinition,\n createGuided = false,\n eventEmitter?: EventEmitter\n) {\n const { easing, ...def } = animationDefinition;\n const attributeProperties: Attributes = {};\n let animationEasing;\n let timeout;\n\n // Check if an easing is specified in the definition object and delete it from the object as it will not\n // be part of the animate element attributes.\n if (easing) {\n // If already an easing Bézier curve array we take it or we lookup a easing array in the Easing object\n animationEasing = Array.isArray(easing) ? easing : easings[easing];\n }\n\n // If numeric dur or begin was provided we assume milli seconds\n def.begin = ensureUnit(def.begin, 'ms');\n def.dur = ensureUnit(def.dur, 'ms');\n\n if (animationEasing) {\n def.calcMode = 'spline';\n def.keySplines = animationEasing.join(' ');\n def.keyTimes = '0;1';\n }\n\n // Adding \"fill: freeze\" if we are in guided mode and set initial attribute values\n if (createGuided) {\n def.fill = 'freeze';\n // Animated property on our element should already be set to the animation from value in guided mode\n attributeProperties[attribute] = def.from;\n element.attr(attributeProperties);\n\n // In guided mode we also set begin to indefinite so we can trigger the start manually and put the begin\n // which needs to be in ms aside\n timeout = quantity(def.begin || 0).value;\n def.begin = 'indefinite';\n }\n\n const animate = element.elem('animate', {\n attributeName: attribute,\n ...def\n });\n\n if (createGuided) {\n // If guided we take the value that was put aside in timeout and trigger the animation manually with a timeout\n setTimeout(() => {\n // If beginElement fails we set the animated attribute to the end position and remove the animate element\n // This happens if the SMIL ElementTimeControl interface is not supported or any other problems occurred in\n // the browser. (Currently FF 34 does not support animate elements in foreignObjects)\n try {\n // @ts-expect-error Try legacy API.\n animate._node.beginElement();\n } catch (err) {\n // Set animated attribute to current animated value\n attributeProperties[attribute] = def.to;\n element.attr(attributeProperties);\n // Remove the animate element as it's no longer required\n animate.remove();\n }\n }, timeout);\n }\n\n const animateNode = animate.getNode();\n\n if (eventEmitter) {\n animateNode.addEventListener('beginEvent', () =>\n eventEmitter.emit<AnimationEvent>('animationBegin', {\n element: element,\n animate: animateNode,\n params: animationDefinition\n })\n );\n }\n\n animateNode.addEventListener('endEvent', () => {\n if (eventEmitter) {\n eventEmitter.emit<AnimationEvent>('animationEnd', {\n element: element,\n animate: animateNode,\n params: animationDefinition\n });\n }\n\n if (createGuided) {\n // Set animated attribute to current animated value\n attributeProperties[attribute] = def.to;\n element.attr(attributeProperties);\n // Remove the animate element as it's no longer required\n animate.remove();\n }\n });\n}\n","import type { EventEmitter } from '../event';\nimport { namespaces } from '../core/constants';\nimport type { Attributes, AnimationDefinition } from './types';\nimport { SvgList } from './SvgList';\nimport { createAnimation, easings } from './animation';\n\n/**\n * Svg creates a new SVG object wrapper with a starting element. You can use the wrapper to fluently create sub-elements and modify them.\n */\nexport class Svg {\n /**\n * @todo Only there for chartist <1 compatibility. Remove after deprecation warining.\n * @deprecated Use the animation module export `easings` directly.\n */\n static readonly Easing = easings;\n\n private _node: Element;\n\n /**\n * @param name The name of the SVG element to create or an SVG dom element which should be wrapped into Svg\n * @param attributes An object with properties that will be added as attributes to the SVG element that is created. Attributes with undefined values will not be added.\n * @param className This class or class list will be added to the SVG element\n * @param parent The parent SVG wrapper object where this newly created wrapper and it's element will be attached to as child\n * @param insertFirst If this param is set to true in conjunction with a parent element the newly created element will be added as first child element in the parent element\n */\n constructor(\n name: string | Element,\n attributes?: Attributes,\n className?: string,\n parent?: Svg,\n insertFirst = false\n ) {\n // If Svg is getting called with an SVG element we just return the wrapper\n if (name instanceof Element) {\n this._node = name;\n } else {\n this._node = document.createElementNS(namespaces.svg, name);\n\n // If this is an SVG element created then custom namespace\n if (name === 'svg') {\n this.attr({\n 'xmlns:ct': namespaces.ct\n });\n }\n }\n\n if (attributes) {\n this.attr(attributes);\n }\n\n if (className) {\n this.addClass(className);\n }\n\n if (parent) {\n if (insertFirst && parent._node.firstChild) {\n parent._node.insertBefore(this._node, parent._node.firstChild);\n } else {\n parent._node.appendChild(this._node);\n }\n }\n }\n\n /**\n * Set attributes on the current SVG element of the wrapper you're currently working on.\n * @param attributes An object with properties that will be added as attributes to the SVG element that is created. Attributes with undefined values will not be added. If this parameter is a String then the function is used as a getter and will return the attribute value.\n * @param ns If specified, the attribute will be obtained using getAttributeNs. In order to write namepsaced attributes you can use the namespace:attribute notation within the attributes object.\n * @return The current wrapper object will be returned so it can be used for chaining or the attribute value if used as getter function.\n */\n attr(attributes: string, ns?: string): string | null;\n attr(attributes: Attributes): this;\n attr(attributes: string | Attributes, ns?: string) {\n if (typeof attributes === 'string') {\n if (ns) {\n return this._node.getAttributeNS(ns, attributes);\n } else {\n return this._node.getAttribute(attributes);\n }\n }\n\n Object.keys(attributes).forEach(key => {\n // If the attribute value is undefined we can skip this one\n if (attributes[key] === undefined) {\n return;\n }\n\n if (key.indexOf(':') !== -1) {\n const namespacedAttribute = key.split(':');\n this._node.setAttributeNS(\n namespaces[namespacedAttribute[0]],\n key,\n String(attributes[key])\n );\n } else {\n this._node.setAttribute(key, String(attributes[key]));\n }\n });\n\n return this;\n }\n\n /**\n * Create a new SVG element whose wrapper object will be selected for further operations. This way you can also create nested groups easily.\n * @param name The name of the SVG element that should be created as child element of the currently selected element wrapper\n * @param attributes An object with properties that will be added as attributes to the SVG element that is created. Attributes with undefined values will not be added.\n * @param className This class or class list will be added to the SVG element\n * @param insertFirst If this param is set to true in conjunction with a parent element the newly created element will be added as first child element in the parent element\n * @return Returns a Svg wrapper object that can be used to modify the containing SVG data\n */\n elem(\n name: string,\n attributes?: Attributes,\n className?: string,\n insertFirst = false\n ) {\n return new Svg(name, attributes, className, this, insertFirst);\n }\n\n /**\n * Returns the parent Chartist.SVG wrapper object\n * @return Returns a Svg wrapper around the parent node of the current node. If the parent node is not existing or it's not an SVG node then this function will return null.\n */\n parent() {\n return this._node.parentNode instanceof SVGElement\n ? new Svg(this._node.parentNode)\n : null;\n }\n\n /**\n * This method returns a Svg wrapper around the root SVG element of the current tree.\n * @return The root SVG element wrapped in a Svg element\n */\n root() {\n let node = this._node;\n\n while (node.nodeName !== 'svg') {\n if (node.parentElement) {\n node = node.parentElement;\n } else {\n break;\n }\n }\n\n return new Svg(node);\n }\n\n /**\n * Find the first child SVG element of the current element that matches a CSS selector. The returned object is a Svg wrapper.\n * @param selector A CSS selector that is used to query for child SVG elements\n * @return The SVG wrapper for the element found or null if no element was found\n */\n querySelector(selector: string) {\n const foundNode = this._node.querySelector(selector);\n return foundNode ? new Svg(foundNode) : null;\n }\n\n /**\n * Find the all child SVG elements of the current element that match a CSS selector. The returned object is a Svg.List wrapper.\n * @param selector A CSS selector that is used to query for child SVG elements\n * @return The SVG wrapper list for the element found or null if no element was found\n */\n querySelectorAll(selector: string) {\n const foundNodes = this._node.querySelectorAll(selector);\n return new SvgList(foundNodes);\n }\n\n /**\n * Returns the underlying SVG node for the current element.\n */\n getNode<T extends Element = Element>() {\n return this._node as T;\n }\n\n /**\n * This method creates a foreignObject (see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject) that allows to embed HTML content into a SVG graphic. With the help of foreignObjects you can enable the usage of regular HTML elements inside of SVG where they are subject for SVG positioning and transformation but the Browser will use the HTML rendering capabilities for the containing DOM.\n * @param content The DOM Node, or HTML string that will be converted to a DOM Node, that is then placed into and wrapped by the foreignObject\n * @param attributes An object with properties that will be added as attributes to the foreignObject element that is created. Attributes with undefined values will not be added.\n * @param className This class or class list will be added to the SVG element\n * @param insertFirst Specifies if the foreignObject should be inserted as first child\n * @return New wrapper object that wraps the foreignObject element\n */\n foreignObject(\n content: string | Node,\n attributes?: Attributes,\n className?: string,\n insertFirst = false\n ) {\n let contentNode: Node;\n // If content is string then we convert it to DOM\n // TODO: Handle case where content is not a string nor a DOM Node\n if (typeof content === 'string') {\n const container = document.createElement('div');\n container.innerHTML = content;\n contentNode = container.firstChild as Node;\n } else {\n contentNode = content;\n }\n\n if (contentNode instanceof Element) {\n // Adding namespace to content element\n contentNode.setAttribute('xmlns', namespaces.xmlns);\n }\n\n // Creating the foreignObject without required extension attribute (as described here\n // http://www.w3.org/TR/SVG/extend.html#ForeignObjectElement)\n const fnObj = this.elem(\n 'foreignObject',\n attributes,\n className,\n insertFirst\n );\n\n // Add content to foreignObjectElement\n fnObj._node.appendChild(contentNode);\n\n return fnObj;\n }\n\n /**\n * This method adds a new text element to the current Svg wrapper.\n * @param t The text that should be added to the text element that is created\n * @return The same wrapper object that was used to add the newly created element\n */\n text(t: string) {\n this._node.appendChild(document.createTextNode(t));\n return this;\n }\n\n /**\n * This method will clear all child nodes of the current wrapper object.\n * @return The same wrapper object that got emptied\n */\n empty() {\n while (this._node.firstChild) {\n this._node.removeChild(this._node.firstChild);\n }\n\n return this;\n }\n\n /**\n * This method will cause the current wrapper to remove itself from its parent wrapper. Use this method if you'd like to get rid of an element in a given DOM structure.\n * @return The parent wrapper object of the element that got removed\n */\n remove() {\n this._node.parentNode?.removeChild(this._node);\n return this.parent();\n }\n\n /**\n * This method will replace the element with a new element that can be created outside of the current DOM.\n * @param newElement The new Svg object that will be used to replace the current wrapper object\n * @return The wrapper of the new element\n */\n replace(newElement: Svg) {\n this._node.parentNode?.replaceChild(newElement._node, this._node);\n return newElement;\n }\n\n /**\n * This method will append an element to the current element as a child.\n * @param element The Svg element that should be added as a child\n * @param insertFirst Specifies if the element should be inserted as first child\n * @return The wrapper of the appended object\n */\n append(element: Svg, insertFirst = false) {\n if (insertFirst && this._node.firstChild) {\n this._node.insertBefore(element._node, this._node.firstChild);\n } else {\n this._node.appendChild(element._node);\n }\n\n return this;\n }\n\n /**\n * Returns an array of class n