chartjs-chart-geo
Version:
Chart.js module for charting maps
1 lines • 61 kB
Source Map (JSON)
{"version":3,"file":"index.d.ts","sources":["../src/scales/ProjectionScale.ts","../src/scales/LegendScale.ts","../src/scales/ColorScale.ts","../src/scales/SizeScale.ts","../src/elements/GeoFeature.ts","../src/controllers/GeoController.ts","../src/controllers/ChoroplethController.ts","../src/controllers/BubbleMapController.ts"],"sourcesContent":["import { Scale, CoreScaleOptions } from 'chart.js';\nimport {\n geoPath,\n geoAzimuthalEqualArea,\n geoAzimuthalEquidistant,\n geoGnomonic,\n geoOrthographic,\n geoStereographic,\n geoEqualEarth,\n geoAlbers,\n geoAlbersUsa,\n geoConicConformal,\n geoConicEqualArea,\n geoConicEquidistant,\n geoEquirectangular,\n geoMercator,\n geoTransverseMercator,\n geoNaturalEarth1,\n GeoProjection,\n GeoPath,\n GeoPermissibleObjects,\n ExtendedFeatureCollection,\n ExtendedFeature,\n GeoGeometryObjects,\n ExtendedGeometryCollection,\n} from 'd3-geo';\n\nconst lookup: { [key: string]: () => GeoProjection } = {\n geoAzimuthalEqualArea,\n geoAzimuthalEquidistant,\n geoGnomonic,\n geoOrthographic,\n geoStereographic,\n geoEqualEarth,\n geoAlbers,\n geoAlbersUsa,\n geoConicConformal,\n geoConicEqualArea,\n geoConicEquidistant,\n geoEquirectangular,\n geoMercator,\n geoTransverseMercator,\n geoNaturalEarth1,\n};\nObject.keys(lookup).forEach((key) => {\n lookup[`${key.charAt(3).toLowerCase()}${key.slice(4)}`] = lookup[key];\n});\n\nexport interface IProjectionScaleOptions extends CoreScaleOptions {\n /**\n * projection method used\n * @default albersUsa\n */\n projection:\n | GeoProjection\n | 'azimuthalEqualArea'\n | 'azimuthalEquidistant'\n | 'gnomonic'\n | 'orthographic'\n | 'stereographic'\n | 'equalEarth'\n | 'albers'\n | 'albersUsa'\n | 'conicConformal'\n | 'conicEqualArea'\n | 'conicEquidistant'\n | 'equirectangular'\n | 'mercator'\n | 'transverseMercator'\n | 'naturalEarth1';\n\n /**\n * extra scale factor applied to projection\n */\n projectionScale: number;\n /**\n * extra offset applied after projection\n */\n projectionOffset: [number, number];\n /**\n * padding applied during auto scaling of the map in pixels\n * i.e. the chart size is reduce by the padding before fitting the map\n */\n padding: number | { top: number; left: number; right: number; bottom: number };\n}\n\nexport class ProjectionScale extends Scale<IProjectionScaleOptions> {\n /**\n * @hidden\n */\n readonly geoPath: GeoPath<any, GeoPermissibleObjects>;\n\n /**\n * @hidden\n */\n projection!: GeoProjection;\n\n private outlineBounds: {\n refX: number;\n refY: number;\n refScale: number;\n width: number;\n height: number;\n aspectRatio: number;\n } | null = null;\n\n private oldChartBounds: { chartWidth: number; chartHeight: number } | null = null;\n\n constructor(cfg: any) {\n super(cfg);\n this.geoPath = geoPath();\n }\n\n /**\n * @hidden\n */\n init(options: IProjectionScaleOptions): void {\n (options as any).position = 'chartArea';\n super.init(options);\n if (typeof options.projection === 'function') {\n this.projection = options.projection;\n } else {\n this.projection = (lookup[options.projection] || lookup.albersUsa)();\n }\n this.geoPath.projection(this.projection);\n\n this.outlineBounds = null;\n this.oldChartBounds = null;\n }\n\n /**\n * @hidden\n */\n computeBounds(outline: ExtendedFeature): void;\n computeBounds(outline: ExtendedFeatureCollection): void;\n computeBounds(outline: GeoGeometryObjects): void;\n computeBounds(outline: ExtendedGeometryCollection): void;\n\n computeBounds(outline: any): void {\n const bb = geoPath(this.projection.fitWidth(1000, outline)).bounds(outline);\n const bHeight = Math.ceil(bb[1][1] - bb[0][1]);\n const bWidth = Math.ceil(bb[1][0] - bb[0][0]);\n const t = this.projection.translate();\n\n this.outlineBounds = {\n width: bWidth,\n height: bHeight,\n aspectRatio: bWidth / bHeight,\n refScale: this.projection.scale(),\n refX: t[0],\n refY: t[1],\n };\n }\n\n /**\n * @hidden\n */\n updateBounds(): boolean {\n const area = this.chart.chartArea;\n\n const bb = this.outlineBounds;\n\n if (!bb) {\n return false;\n }\n const padding = this.options.padding;\n const paddingTop = typeof padding === 'number' ? padding : padding.top;\n const paddingLeft = typeof padding === 'number' ? padding : padding.left;\n const paddingBottom = typeof padding === 'number' ? padding : padding.bottom;\n const paddingRight = typeof padding === 'number' ? padding : padding.right;\n\n const chartWidth = area.right - area.left - paddingLeft - paddingRight;\n const chartHeight = area.bottom - area.top - paddingTop - paddingBottom;\n\n const bak = this.oldChartBounds;\n this.oldChartBounds = {\n chartWidth,\n chartHeight,\n };\n\n const scale = Math.min(chartWidth / bb.width, chartHeight / bb.height);\n const viewWidth = bb.width * scale;\n const viewHeight = bb.height * scale;\n\n const x = (chartWidth - viewWidth) * 0.5 + area.left + paddingLeft;\n const y = (chartHeight - viewHeight) * 0.5 + area.top + paddingTop;\n\n // this.mapScale = scale;\n // this.mapTranslate = {x, y};\n\n const o = this.options;\n\n this.projection\n .scale(bb.refScale * scale * o.projectionScale)\n .translate([scale * bb.refX + x + o.projectionOffset[0], scale * bb.refY + y + o.projectionOffset[1]]);\n\n return (\n !bak || bak.chartWidth !== this.oldChartBounds.chartWidth || bak.chartHeight !== this.oldChartBounds.chartHeight\n );\n }\n\n static readonly id = 'projection';\n\n /**\n * @hidden\n */\n static readonly defaults: Partial<IProjectionScaleOptions> = {\n projection: 'albersUsa',\n projectionScale: 1,\n projectionOffset: [0, 0],\n padding: 0,\n };\n\n /**\n * @hidden\n */\n static readonly descriptors = /* #__PURE__ */ {\n _scriptable: (name: keyof IProjectionScaleOptions): boolean => name !== 'projection',\n _indexable: (name: keyof IProjectionScaleOptions): boolean => name !== 'projectionOffset',\n };\n}\n\ndeclare module 'chart.js' {\n export interface ProjectionScaleTypeRegistry {\n projection: {\n options: IProjectionScaleOptions;\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n export interface ScaleTypeRegistry extends ProjectionScaleTypeRegistry {}\n}\n","import {\n ChartArea,\n CartesianScaleOptions,\n LinearScale,\n LinearScaleOptions,\n LogarithmicScale,\n LogarithmicScaleOptions,\n} from 'chart.js';\n\nexport interface ILegendScaleOptions extends CartesianScaleOptions {\n /**\n * whether to render a color legend\n * @default true\n */\n display: boolean;\n\n /**\n * the property name that stores the value in the data elements\n * @default value\n */\n property: string;\n\n legend: {\n /**\n * location of the legend on the chart area\n * @default bottom-right\n */\n position:\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n | 'top-left'\n | 'top-right'\n | 'top-right'\n | 'bottom-right'\n | 'bottom-left'\n | { x: number; y: number };\n /**\n * alignment of the scale, e.g., `right` means that it is a vertical scale\n * with the ticks on the right side\n * @default right\n */\n align: 'left' | 'right' | 'top' | 'bottom';\n /**\n * length of the legend, i.e., for a horizontal scale the width\n * if a value < 1 is given, is it assume to be a ratio of the corresponding\n * chart area\n * @default 100\n */\n length: number;\n /**\n * how wide the scale is, i.e., for a horizontal scale the height\n * if a value < 1 is given, is it assume to be a ratio of the corresponding\n * chart area\n * @default 50\n */\n width: number;\n /**\n * how many pixels should be used for the color bar\n * @default 10\n */\n indicatorWidth: number;\n /**\n * margin pixels such that it doesn't stick to the edge of the chart\n * @default 8\n */\n margin: number | ChartArea;\n };\n}\n\nexport const baseDefaults = {\n position: 'chartArea',\n property: 'value',\n grid: {\n z: 1,\n drawOnChartArea: false,\n },\n ticks: {\n z: 1,\n },\n legend: {\n align: 'right',\n position: 'bottom-right',\n length: 100,\n width: 50,\n margin: 8,\n indicatorWidth: 10,\n },\n};\n\ninterface IPositionOption {\n position?: string;\n}\n\nfunction computeLegendMargin(legend: ILegendScaleOptions['legend']): {\n left: number;\n top: number;\n right: number;\n bottom: number;\n} {\n const { indicatorWidth, align: pos, margin } = legend;\n\n const left = (typeof margin === 'number' ? margin : margin.left) + (pos === 'right' ? indicatorWidth : 0);\n const top = (typeof margin === 'number' ? margin : margin.top) + (pos === 'bottom' ? indicatorWidth : 0);\n const right = (typeof margin === 'number' ? margin : margin.right) + (pos === 'left' ? indicatorWidth : 0);\n const bottom = (typeof margin === 'number' ? margin : margin.bottom) + (pos === 'top' ? indicatorWidth : 0);\n return { left, top, right, bottom };\n}\n\nfunction computeLegendPosition(\n chartArea: ChartArea,\n legend: ILegendScaleOptions['legend'],\n width: number,\n height: number,\n legendSize: { w: number; h: number }\n): [number, number] {\n const { indicatorWidth, align: axisPos, position: pos } = legend;\n const isHor = axisPos === 'top' || axisPos === 'bottom';\n const w = (axisPos === 'left' ? legendSize.w : width) + (isHor ? indicatorWidth : 0);\n const h = (axisPos === 'top' ? legendSize.h : height) + (!isHor ? indicatorWidth : 0);\n const margin = computeLegendMargin(legend);\n\n if (typeof pos === 'string') {\n switch (pos) {\n case 'top-left':\n return [margin.left, margin.top];\n case 'top':\n return [(chartArea.right - w) / 2, margin.top];\n case 'left':\n return [margin.left, (chartArea.bottom - h) / 2];\n case 'top-right':\n return [chartArea.right - w - margin.right, margin.top];\n case 'bottom-right':\n return [chartArea.right - w - margin.right, chartArea.bottom - h - margin.bottom];\n case 'bottom':\n return [(chartArea.right - w) / 2, chartArea.bottom - h - margin.bottom];\n case 'bottom-left':\n return [margin.left, chartArea.bottom - h - margin.bottom];\n default:\n // right\n return [chartArea.right - w - margin.right, (chartArea.bottom - h) / 2];\n }\n }\n return [pos.x, pos.y];\n}\n\nexport class LegendScale<O extends ILegendScaleOptions & LinearScaleOptions> extends LinearScale<O> {\n /**\n * @hidden\n */\n legendSize: { w: number; h: number } = { w: 0, h: 0 };\n\n /**\n * @hidden\n */\n init(options: O): void {\n (options as unknown as IPositionOption).position = 'chartArea';\n super.init(options);\n this.axis = 'r';\n }\n\n /**\n * @hidden\n */\n\n parse(raw: any, index: number): number {\n if (raw && typeof raw[this.options.property] === 'number') {\n return raw[this.options.property];\n }\n return super.parse(raw, index) as number;\n }\n\n /**\n * @hidden\n */\n isHorizontal(): boolean {\n return this.options.legend.align === 'top' || this.options.legend.align === 'bottom';\n }\n\n protected _getNormalizedValue(v: number): number | null {\n if (v == null || Number.isNaN(v)) {\n return null;\n }\n return (v - (this as any)._startValue) / (this as any)._valueRange;\n }\n\n /**\n * @hidden\n */\n update(maxWidth: number, maxHeight: number, margins: ChartArea): void {\n const ch = Math.min(maxHeight, this.bottom == null ? Number.POSITIVE_INFINITY : this.bottom);\n const cw = Math.min(maxWidth, this.right == null ? Number.POSITIVE_INFINITY : this.right);\n\n const l = this.options.legend;\n const isHor = this.isHorizontal();\n const factor = (v: number, full: number) => (v < 1 ? full * v : v);\n const w = Math.min(cw, factor(isHor ? l.length : l.width, cw)) - (!isHor ? l.indicatorWidth : 0);\n const h = Math.min(ch, factor(!isHor ? l.length : l.width, ch)) - (isHor ? l.indicatorWidth : 0);\n this.legendSize = { w, h };\n this.bottom = h;\n this.height = h;\n this.right = w;\n this.width = w;\n\n const bak = (this.options as IPositionOption).position;\n (this.options as IPositionOption).position = this.options.legend.align;\n const r = super.update(w, h, margins);\n (this.options as IPositionOption).position = bak;\n this.height = Math.min(h, this.height);\n this.width = Math.min(w, this.width);\n return r;\n }\n\n /**\n * @hidden\n */\n\n _computeLabelArea(): void {\n return undefined;\n }\n\n /**\n * @hidden\n */\n draw(chartArea: ChartArea): void {\n if (!(this as any)._isVisible()) {\n return;\n }\n const pos = computeLegendPosition(chartArea, this.options.legend, this.width, this.height, this.legendSize);\n /** @type {CanvasRenderingContext2D} */\n const { ctx } = this;\n ctx.save();\n ctx.translate(pos[0], pos[1]);\n\n const bak = (this.options as IPositionOption).position;\n (this.options as IPositionOption).position = this.options.legend.align;\n super.draw({ ...chartArea, bottom: this.height + 10, right: this.width });\n (this.options as IPositionOption).position = bak;\n const { indicatorWidth } = this.options.legend;\n switch (this.options.legend.align) {\n case 'left':\n ctx.translate(this.legendSize.w, 0);\n break;\n case 'top':\n ctx.translate(0, this.legendSize.h);\n break;\n case 'bottom':\n ctx.translate(0, -indicatorWidth);\n break;\n default:\n ctx.translate(-indicatorWidth, 0);\n break;\n }\n this._drawIndicator();\n ctx.restore();\n }\n\n /**\n * @hidden\n */\n\n protected _drawIndicator(): void {\n // hook\n }\n}\n\nexport class LogarithmicLegendScale<\n O extends ILegendScaleOptions & LogarithmicScaleOptions,\n> extends LogarithmicScale<O> {\n /**\n * @hidden\n */\n legendSize: { w: number; h: number } = { w: 0, h: 0 };\n\n /**\n * @hidden\n */\n init(options: O): void {\n LegendScale.prototype.init.call(this, options);\n }\n\n /**\n * @hidden\n */\n\n parse(raw: any, index: number): number {\n return LegendScale.prototype.parse.call(this, raw, index);\n }\n\n /**\n * @hidden\n */\n isHorizontal(): boolean {\n return this.options.legend.align === 'top' || this.options.legend.align === 'bottom';\n }\n\n protected _getNormalizedValue(v: number): number | null {\n if (v == null || Number.isNaN(v)) {\n return null;\n }\n return (Math.log10(v) - (this as any)._startValue) / (this as any)._valueRange;\n }\n\n /**\n * @hidden\n */\n update(maxWidth: number, maxHeight: number, margins: ChartArea): void {\n return LegendScale.prototype.update.call(this, maxWidth, maxHeight, margins);\n }\n\n /**\n * @hidden\n */\n\n _computeLabelArea(): void {\n return undefined;\n }\n\n /**\n * @hidden\n */\n draw(chartArea: ChartArea): void {\n return LegendScale.prototype.draw.call(this, chartArea);\n }\n\n protected _drawIndicator(): void {\n // hook\n }\n}\n","import { LinearScale, LogarithmicScale, LogarithmicScaleOptions, LinearScaleOptions } from 'chart.js';\nimport { merge } from 'chart.js/helpers';\nimport {\n interpolateBlues,\n interpolateBrBG,\n interpolateBuGn,\n interpolateBuPu,\n interpolateCividis,\n interpolateCool,\n interpolateCubehelixDefault,\n interpolateGnBu,\n interpolateGreens,\n interpolateGreys,\n interpolateInferno,\n interpolateMagma,\n interpolateOrRd,\n interpolateOranges,\n interpolatePRGn,\n interpolatePiYG,\n interpolatePlasma,\n interpolatePuBu,\n interpolatePuBuGn,\n interpolatePuOr,\n interpolatePuRd,\n interpolatePurples,\n interpolateRainbow,\n interpolateRdBu,\n interpolateRdGy,\n interpolateRdPu,\n interpolateRdYlBu,\n interpolateRdYlGn,\n interpolateReds,\n interpolateSinebow,\n interpolateSpectral,\n interpolateTurbo,\n interpolateViridis,\n interpolateWarm,\n interpolateYlGn,\n interpolateYlGnBu,\n interpolateYlOrBr,\n interpolateYlOrRd,\n} from 'd3-scale-chromatic';\nimport { baseDefaults, LegendScale, LogarithmicLegendScale, ILegendScaleOptions } from './LegendScale';\n\nconst lookup: { [key: string]: (normalizedValue: number) => string } = {\n interpolateBlues,\n interpolateBrBG,\n interpolateBuGn,\n interpolateBuPu,\n interpolateCividis,\n interpolateCool,\n interpolateCubehelixDefault,\n interpolateGnBu,\n interpolateGreens,\n interpolateGreys,\n interpolateInferno,\n interpolateMagma,\n interpolateOrRd,\n interpolateOranges,\n interpolatePRGn,\n interpolatePiYG,\n interpolatePlasma,\n interpolatePuBu,\n interpolatePuBuGn,\n interpolatePuOr,\n interpolatePuRd,\n interpolatePurples,\n interpolateRainbow,\n interpolateRdBu,\n interpolateRdGy,\n interpolateRdPu,\n interpolateRdYlBu,\n interpolateRdYlGn,\n interpolateReds,\n interpolateSinebow,\n interpolateSpectral,\n interpolateTurbo,\n interpolateViridis,\n interpolateWarm,\n interpolateYlGn,\n interpolateYlGnBu,\n interpolateYlOrBr,\n interpolateYlOrRd,\n};\n\nObject.keys(lookup).forEach((key) => {\n lookup[`${key.charAt(11).toLowerCase()}${key.slice(12)}`] = lookup[key];\n lookup[key.slice(11)] = lookup[key];\n});\n\nfunction quantize(v: number, steps: number) {\n const perStep = 1 / steps;\n if (v <= perStep) {\n return 0;\n }\n if (v >= 1 - perStep) {\n return 1;\n }\n for (let acc = 0; acc < 1; acc += perStep) {\n if (v < acc) {\n return acc - perStep / 2; // center\n }\n }\n return v;\n}\n\nexport interface IColorScaleOptions extends ILegendScaleOptions {\n // support all options from linear scale -> https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#linear-cartesian-axis\n // e.g. for tick manipulation, ...\n\n /**\n * color interpolation method which is either a function\n * converting a normalized value to string or a\n * well defined string of all the interpolation scales\n * from https://github.com/d3/d3-scale-chromatic.\n * e.g. interpolateBlues -> blues\n *\n * @default blues\n */\n interpolate:\n | ((normalizedValue: number) => string)\n | 'blues'\n | 'brBG'\n | 'buGn'\n | 'buPu'\n | 'cividis'\n | 'cool'\n | 'cubehelixDefault'\n | 'gnBu'\n | 'greens'\n | 'greys'\n | 'inferno'\n | 'magma'\n | 'orRd'\n | 'oranges'\n | 'pRGn'\n | 'piYG'\n | 'plasma'\n | 'puBu'\n | 'puBuGn'\n | 'puOr'\n | 'puRd'\n | 'purples'\n | 'rainbow'\n | 'rdBu'\n | 'rdGy'\n | 'rdPu'\n | 'rdYlBu'\n | 'rdYlGn'\n | 'reds'\n | 'sinebow'\n | 'spectral'\n | 'turbo'\n | 'viridis'\n | 'warm'\n | 'ylGn'\n | 'ylGnBu'\n | 'ylOrBr'\n | 'ylOrRd';\n\n /**\n * color value to render for missing values\n * @default transparent\n */\n missing: string;\n\n /**\n * allows to split the color scale in N quantized equal bins.\n * @default 0\n */\n quantize: number;\n}\n\nconst colorScaleDefaults = {\n interpolate: 'blues',\n missing: 'transparent',\n quantize: 0,\n};\n\nexport class ColorScale extends LegendScale<IColorScaleOptions & LinearScaleOptions> {\n /**\n * @hidden\n */\n get interpolate(): (v: number) => string {\n const o = this.options as IColorScaleOptions & LinearScaleOptions;\n if (!o) {\n return (v: number) => `rgb(${v},${v},${v})`;\n }\n if (typeof o.interpolate === 'function') {\n return o.interpolate;\n }\n return lookup[o.interpolate] || lookup.blues;\n }\n\n /**\n * @hidden\n */\n getColorForValue(value: number): string {\n const v = this._getNormalizedValue(value);\n if (v == null || Number.isNaN(v)) {\n return this.options.missing;\n }\n return this.getColor(v);\n }\n\n /**\n * @hidden\n */\n getColor(normalized: number): string {\n let v = normalized;\n if (this.options.quantize > 0) {\n v = quantize(v, this.options.quantize);\n }\n return this.interpolate(v);\n }\n\n /**\n * @hidden\n */\n _drawIndicator(): void {\n const { indicatorWidth: indicatorSize } = this.options.legend;\n const reverse = (this as any)._reversePixels;\n\n if (this.isHorizontal()) {\n const w = this.width;\n if (this.options.quantize > 0) {\n const stepWidth = w / this.options.quantize;\n const offset = !reverse ? (i: number) => i : (i: number) => w - stepWidth - i;\n for (let i = 0; i < w; i += stepWidth) {\n const v = (i + stepWidth / 2) / w;\n this.ctx.fillStyle = this.getColor(v);\n this.ctx.fillRect(offset(i), 0, stepWidth, indicatorSize);\n }\n } else {\n const offset = !reverse ? (i: number) => i : (i: number) => w - 1 - i;\n for (let i = 0; i < w; i += 1) {\n this.ctx.fillStyle = this.getColor((i + 0.5) / w);\n this.ctx.fillRect(offset(i), 0, 1, indicatorSize);\n }\n }\n } else {\n const h = this.height;\n if (this.options.quantize > 0) {\n const stepWidth = h / this.options.quantize;\n const offset = !reverse ? (i: number) => i : (i: number) => h - stepWidth - i;\n for (let i = 0; i < h; i += stepWidth) {\n const v = (i + stepWidth / 2) / h;\n this.ctx.fillStyle = this.getColor(v);\n this.ctx.fillRect(0, offset(i), indicatorSize, stepWidth);\n }\n } else {\n const offset = !reverse ? (i: number) => i : (i: number) => h - 1 - i;\n for (let i = 0; i < h; i += 1) {\n this.ctx.fillStyle = this.getColor((i + 0.5) / h);\n this.ctx.fillRect(0, offset(i), indicatorSize, 1);\n }\n }\n }\n }\n\n static readonly id = 'color';\n\n /**\n * @hidden\n */\n static readonly defaults: any = /* #__PURE__ */ merge({}, [LinearScale.defaults, baseDefaults, colorScaleDefaults]);\n\n /**\n * @hidden\n */\n static readonly descriptors = /* #__PURE__ */ {\n _scriptable: (name: string): boolean => name !== 'interpolate',\n _indexable: false,\n };\n}\n\nexport class ColorLogarithmicScale extends LogarithmicLegendScale<IColorScaleOptions & LogarithmicScaleOptions> {\n private interpolate = (v: number) => `rgb(${v},${v},${v})`;\n\n /**\n * @hidden\n */\n init(options: IColorScaleOptions & LinearScaleOptions): void {\n super.init(options);\n if (typeof options.interpolate === 'function') {\n this.interpolate = options.interpolate;\n } else {\n this.interpolate = lookup[options.interpolate] || lookup.blues;\n }\n }\n\n /**\n * @hidden\n */\n getColorForValue(value: number): string {\n return ColorScale.prototype.getColorForValue.call(this, value);\n }\n\n /**\n * @hidden\n */\n getColor(normalized: number): string {\n let v = normalized;\n if (this.options.quantize > 0) {\n v = quantize(v, this.options.quantize);\n }\n return this.interpolate(v);\n }\n\n protected _drawIndicator(): void {\n return ColorScale.prototype._drawIndicator.call(this);\n }\n\n static readonly id = 'colorLogarithmic';\n\n /**\n * @hidden\n */\n static readonly defaults: any = /* #__PURE__ */ merge({}, [\n LogarithmicScale.defaults,\n baseDefaults,\n colorScaleDefaults,\n ]);\n\n /**\n * @hidden\n */\n static readonly descriptors = /* #__PURE__ */ {\n _scriptable: (name: string): boolean => name !== 'interpolate',\n _indexable: false,\n };\n}\n\ndeclare module 'chart.js' {\n export interface ColorScaleTypeRegistry {\n color: {\n options: IColorScaleOptions & LinearScaleOptions;\n };\n colorLogarithmic: {\n options: IColorScaleOptions & LogarithmicScaleOptions;\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n export interface ScaleTypeRegistry extends ColorScaleTypeRegistry {}\n}\n","import { LinearScale, LogarithmicScale, PointOptions, LinearScaleOptions, LogarithmicScaleOptions } from 'chart.js';\nimport { merge, drawPoint } from 'chart.js/helpers';\nimport { baseDefaults, ILegendScaleOptions, LegendScale, LogarithmicLegendScale } from './LegendScale';\n\nexport interface ISizeScaleOptions extends ILegendScaleOptions {\n // support all options from linear scale -> https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#linear-cartesian-axis\n // e.g. for tick manipulation, ...\n\n /**\n * radius range in pixel, the minimal data value will be mapped to the\n * first entry, the maximal one to the second and a linear interpolation\n * for all values in between.\n *\n * @default [2, 20]\n */\n range: [number, number];\n\n /**\n * operation mode for the scale, area means that the area is linearly increasing whereas radius the radius is.\n * The area one is the default since it gives a better visual comparison of values\n * @default area\n */\n mode: 'radius' | 'area';\n\n /**\n * radius to render for missing values\n * @default 1\n */\n missing: number;\n}\n\nconst scaleDefaults = {\n missing: 1,\n mode: 'area', // 'radius'\n // mode: 'radius',\n range: [2, 20],\n legend: {\n align: 'bottom',\n length: 90,\n width: 70,\n indicatorWidth: 42,\n },\n};\n\nexport class SizeScale extends LegendScale<ISizeScaleOptions & LinearScaleOptions> {\n /**\n * @hidden\n */\n _model: PointOptions | null = null;\n\n /**\n * @hidden\n */\n getSizeForValue(value: number): number {\n const v = this._getNormalizedValue(value);\n if (v == null || Number.isNaN(v)) {\n return this.options.missing;\n }\n return this.getSizeImpl(v);\n }\n\n /**\n * @hidden\n */\n getSizeImpl(normalized: number): number {\n const [r0, r1] = this.options.range;\n if (this.options.mode === 'area') {\n const a1 = r1 * r1 * Math.PI;\n const a0 = r0 * r0 * Math.PI;\n const range = a1 - a0;\n const a = normalized * range + a0;\n return Math.sqrt(a / Math.PI);\n }\n const range = r1 - r0;\n return normalized * range + r0;\n }\n\n /**\n * @hidden\n */\n _drawIndicator(): void {\n /** @type {CanvasRenderingContext2D} */\n const { ctx } = this;\n const shift = this.options.legend.indicatorWidth / 2;\n\n const isHor = this.isHorizontal();\n const values = this.ticks;\n const labelItems = this.getLabelItems();\n const positions = labelItems\n ? labelItems.map((el: any) => ({ [isHor ? 'x' : 'y']: el.options.translation[isHor ? 0 : 1] }))\n : values.map((_, i) => ({ [isHor ? 'x' : 'y']: this.getPixelForTick(i) }));\n\n ((this as any)._gridLineItems || []).forEach((item: any) => {\n ctx.save();\n ctx.strokeStyle = item.color;\n ctx.lineWidth = item.width;\n\n if (ctx.setLineDash) {\n ctx.setLineDash(item.borderDash);\n ctx.lineDashOffset = item.borderDashOffset;\n }\n\n ctx.beginPath();\n\n if (this.options.grid.drawTicks) {\n switch (this.options.legend.align) {\n case 'left':\n ctx.moveTo(0, item.ty1);\n ctx.lineTo(shift, item.ty2);\n break;\n case 'top':\n ctx.moveTo(item.tx1, 0);\n ctx.lineTo(item.tx2, shift);\n break;\n case 'bottom':\n ctx.moveTo(item.tx1, shift);\n ctx.lineTo(item.tx2, shift * 2);\n break;\n default:\n // right\n ctx.moveTo(shift, item.ty1);\n ctx.lineTo(shift * 2, item.ty2);\n break;\n }\n }\n ctx.stroke();\n ctx.restore();\n });\n\n if (this._model) {\n const props = this._model;\n ctx.strokeStyle = props.borderColor;\n ctx.lineWidth = props.borderWidth || 0;\n ctx.fillStyle = props.backgroundColor;\n } else {\n ctx.fillStyle = 'blue';\n }\n\n values.forEach((v, i) => {\n const pos = positions[i];\n const radius = this.getSizeForValue(v.value);\n const x = isHor ? pos.x : shift;\n const y = isHor ? shift : pos.y;\n const renderOptions = {\n pointStyle: 'circle' as const,\n borderWidth: 0,\n ...(this._model || {}),\n radius,\n };\n drawPoint(ctx, renderOptions, x, y);\n });\n }\n\n static readonly id = 'size';\n\n /**\n * @hidden\n */\n static readonly defaults: any = /* #__PURE__ */ merge({}, [LinearScale.defaults, baseDefaults, scaleDefaults]);\n\n /**\n * @hidden\n */\n static readonly descriptors = /* #__PURE__ */ {\n _scriptable: true,\n _indexable: (name: string): boolean => name !== 'range',\n };\n}\n\nexport class SizeLogarithmicScale extends LogarithmicLegendScale<ISizeScaleOptions & LogarithmicScaleOptions> {\n /**\n * @hidden\n */\n _model: PointOptions | null = null;\n\n /**\n * @hidden\n */\n getSizeForValue(value: number): number {\n const v = this._getNormalizedValue(value);\n if (v == null || Number.isNaN(v)) {\n return this.options.missing;\n }\n return this.getSizeImpl(v);\n }\n\n /**\n * @hidden\n */\n getSizeImpl(normalized: number): number {\n return SizeScale.prototype.getSizeImpl.call(this, normalized);\n }\n\n /**\n * @hidden\n */\n _drawIndicator(): void {\n SizeScale.prototype._drawIndicator.call(this);\n }\n\n static readonly id = 'sizeLogarithmic';\n\n /**\n * @hidden\n */\n static readonly defaults: any = /* #__PURE__ */ merge({}, [LogarithmicScale.defaults, baseDefaults, scaleDefaults]);\n}\n\ndeclare module 'chart.js' {\n export interface SizeScaleTypeRegistry {\n size: {\n options: ISizeScaleOptions & LinearScaleOptions;\n };\n sizeLogarithmic: {\n options: ISizeScaleOptions & LogarithmicScaleOptions;\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n export interface ScaleTypeRegistry extends SizeScaleTypeRegistry {}\n}\n","import {\n Element,\n BarElement,\n BarOptions,\n VisualElement,\n Point,\n ChartType,\n ScriptableAndArrayOptions,\n CommonHoverOptions,\n ScriptableContext,\n UpdateMode,\n} from 'chart.js';\nimport { geoContains, GeoPath, GeoProjection } from 'd3-geo';\nimport type { ProjectionScale } from '../scales';\n\nexport interface IGeoFeatureOptions extends Omit<BarOptions, 'borderWidth'>, Record<string, unknown> {\n /**\n * Width of the border\n * @default 0\n */\n borderWidth: number;\n\n /**\n * background color for the outline\n * @default null\n */\n outlineBackgroundColor: string | null;\n /**\n * border color for the outline\n * @default defaultColor of Chart.js\n */\n outlineBorderColor: string;\n /**\n * border width for the outline\n * @default 0\n */\n outlineBorderWidth: number;\n\n /**\n * border color for the graticule\n * @default #CCCCCC\n */\n graticuleBorderColor: string;\n /**\n * border width for the graticule\n * @default 0\n */\n graticuleBorderWidth: number;\n}\n\nexport type Feature = any;\n\ntype GeoBounds = ReturnType<GeoPath['bounds']>;\n\nfunction growGeoBounds(bounds: GeoBounds, amount: number): GeoBounds {\n return [\n [bounds[0][0] - amount, bounds[0][1] - amount],\n [bounds[1][0] + amount, bounds[1][1] + amount],\n ];\n}\n\nexport interface IGeoFeatureProps {\n x: number;\n y: number;\n}\n\nexport class GeoFeature extends Element<IGeoFeatureProps, IGeoFeatureOptions> implements VisualElement {\n /**\n * @hidden\n */\n cache?:\n | {\n center?: Point;\n bounds?: {\n x: number;\n y: number;\n width: number;\n height: number;\n x2: number;\n y2: number;\n };\n canvasKey?: string;\n canvas?: HTMLCanvasElement;\n }\n | undefined = undefined;\n\n /**\n * @hidden\n */\n projectionScale!: ProjectionScale;\n\n /**\n * @hidden\n */\n feature!: Feature;\n\n /**\n * @hidden\n */\n center?: { longitude: number; latitude: number };\n\n /**\n * @hidden\n */\n pixelRatio?: number;\n\n updateExtras({\n scale,\n feature,\n center,\n pixelRatio,\n mode,\n }: {\n scale: ProjectionScale;\n feature: Feature;\n center?: { longitude: number; latitude: number };\n pixelRatio: number;\n mode: UpdateMode;\n }): Point {\n const changed =\n mode === 'resize' ||\n mode === 'reset' ||\n this.projectionScale !== scale ||\n this.feature !== feature ||\n this.center?.longitude !== center?.longitude ||\n this.center?.latitude !== center?.latitude ||\n this.pixelRatio !== pixelRatio;\n this.projectionScale = scale;\n this.feature = feature;\n this.center = center;\n this.pixelRatio = pixelRatio;\n if (changed) {\n this.cache = undefined;\n }\n return this.getCenterPoint();\n }\n\n /**\n * @hidden\n */\n inRange(mouseX: number, mouseY: number): boolean {\n const bb = this.getBounds();\n const r =\n (Number.isNaN(mouseX) || (mouseX >= bb.x && mouseX <= bb.x2)) &&\n (Number.isNaN(mouseY) || (mouseY >= bb.y && mouseY <= bb.y2));\n\n const projection = this.projectionScale.geoPath.projection() as unknown as GeoProjection;\n if (r && !Number.isNaN(mouseX) && !Number.isNaN(mouseY) && typeof projection.invert === 'function') {\n // test for real if within the bounds\n const longLat = projection.invert([mouseX, mouseY]);\n return longLat != null && geoContains(this.feature, longLat);\n }\n\n return r;\n }\n\n /**\n * @hidden\n */\n inXRange(mouseX: number): boolean {\n return this.inRange(mouseX, Number.NaN);\n }\n\n /**\n * @hidden\n */\n inYRange(mouseY: number): boolean {\n return this.inRange(Number.NaN, mouseY);\n }\n\n /**\n * @hidden\n */\n getCenterPoint(): { x: number; y: number } {\n if (this.cache && this.cache.center) {\n return this.cache.center;\n }\n let center: { x: number; y: number };\n if (this.center) {\n const p = this.projectionScale.projection([this.center.longitude, this.center.latitude])!;\n center = {\n x: p[0]!,\n y: p[1]!,\n };\n } else {\n const centroid = this.projectionScale.geoPath.centroid(this.feature);\n center = {\n x: centroid[0],\n y: centroid[1],\n };\n }\n this.cache = { ...(this.cache || {}), center };\n return center;\n }\n\n /**\n * @hidden\n */\n getBounds(): { x: number; y: number; x2: number; y2: number; width: number; height: number } {\n if (this.cache && this.cache.bounds) {\n return this.cache.bounds;\n }\n const bb = growGeoBounds(this.projectionScale.geoPath.bounds(this.feature), this.options.borderWidth / 2);\n const bounds = {\n x: bb[0][0],\n x2: bb[1][0],\n y: bb[0][1],\n y2: bb[1][1],\n width: bb[1][0] - bb[0][0],\n height: bb[1][1] - bb[0][1],\n };\n this.cache = { ...(this.cache || {}), bounds };\n return bounds;\n }\n\n /**\n * @hidden\n */\n _drawInCache(doc: Document): void {\n const bounds = this.getBounds();\n if (!Number.isFinite(bounds.x)) {\n return;\n }\n const canvas = this.cache && this.cache.canvas ? this.cache.canvas : doc.createElement('canvas');\n const x1 = Math.floor(bounds.x);\n const y1 = Math.floor(bounds.y);\n const x2 = Math.ceil(bounds.x + bounds.width);\n const y2 = Math.ceil(bounds.y + bounds.height);\n const pixelRatio = this.pixelRatio || 1;\n const width = Math.ceil(Math.max(x2 - x1, 1) * pixelRatio);\n const height = Math.ceil(Math.max(y2 - y1, 1) * pixelRatio);\n if (width <= 0 || height <= 0) {\n return;\n }\n canvas.width = width;\n canvas.height = height;\n\n const ctx = canvas.getContext('2d');\n if (ctx) {\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n ctx.save();\n ctx.scale(pixelRatio, pixelRatio);\n ctx.translate(-x1, -y1);\n this._drawImpl(ctx);\n ctx.restore();\n\n this.cache = { ...(this.cache || {}), canvas, canvasKey: this._optionsToKey() };\n }\n }\n\n /**\n * @hidden\n */\n _optionsToKey(): string {\n const { options } = this;\n return `${options.backgroundColor};${options.borderColor};${options.borderWidth};${this.pixelRatio}`;\n }\n\n /**\n * @hidden\n */\n _drawImpl(ctx: CanvasRenderingContext2D): void {\n const { feature } = this;\n const { options } = this;\n ctx.beginPath();\n this.projectionScale.geoPath.context(ctx)(feature);\n if (options.backgroundColor) {\n ctx.fillStyle = options.backgroundColor;\n ctx.fill();\n }\n if (options.borderColor) {\n ctx.strokeStyle = options.borderColor;\n ctx.lineWidth = options.borderWidth as number;\n ctx.stroke();\n }\n }\n\n /**\n * @hidden\n */\n draw(ctx: CanvasRenderingContext2D): void {\n const { feature } = this;\n if (!feature) {\n return;\n }\n if ((!this.cache || this.cache.canvasKey !== this._optionsToKey()) && ctx.canvas.ownerDocument != null) {\n this._drawInCache(ctx.canvas.ownerDocument);\n }\n const bounds = this.getBounds();\n if (this.cache && this.cache.canvas && this.cache.canvas.width > 0 && this.cache.canvas.height > 0) {\n const x1 = Math.floor(bounds.x);\n const y1 = Math.floor(bounds.y);\n const x2 = Math.ceil(bounds.x + bounds.width);\n const y2 = Math.ceil(bounds.y + bounds.height);\n const width = x2 - x1;\n const height = y2 - y1;\n if (width > 0 && height > 0) {\n ctx.drawImage(this.cache.canvas, x1, y1, x2 - x1, y2 - y1);\n }\n } else if (Number.isFinite(bounds.x)) {\n ctx.save();\n this._drawImpl(ctx);\n ctx.restore();\n }\n }\n\n static id = 'geoFeature';\n\n /**\n * @hidden\n */\n static defaults = /* #__PURE__ */ {\n ...BarElement.defaults,\n outlineBackgroundColor: null,\n outlineBorderWidth: 0,\n\n graticuleBorderColor: '#CCCCCC',\n graticuleBorderWidth: 0,\n };\n\n /**\n * @hidden\n */\n static defaultRoutes = /* #__PURE__ */ {\n outlineBorderColor: 'borderColor',\n ...(BarElement.defaultRoutes || {}),\n };\n}\n\ndeclare module 'chart.js' {\n export interface ElementOptionsByType<TType extends ChartType> {\n geoFeature: ScriptableAndArrayOptions<IGeoFeatureOptions & CommonHoverOptions, ScriptableContext<TType>>;\n }\n}\n","import {\n DatasetController,\n ChartDataset,\n ScriptableAndArrayOptions,\n UpdateMode,\n Element,\n VisualElement,\n ScriptableContext,\n ChartTypeRegistry,\n AnimationOptions,\n} from 'chart.js';\nimport { clipArea, unclipArea, valueOrDefault } from 'chart.js/helpers';\nimport { geoGraticule, geoGraticule10, ExtendedFeature } from 'd3-geo';\nimport { ProjectionScale } from '../scales';\nimport type { GeoFeature, IGeoFeatureOptions } from '../elements';\n\nexport const geoDefaults = {\n showOutline: false,\n showGraticule: false,\n clipMap: true,\n};\n\nexport const geoOverrides = {\n scales: {\n projection: {\n axis: 'x',\n type: ProjectionScale.id,\n position: 'chartArea',\n display: false,\n },\n },\n};\n\nfunction patchDatasetElementOptions(options: any) {\n // patch the options by removing the `outline` or `hoverOutline` option;\n // see https://github.com/chartjs/Chart.js/issues/7362\n const r: any = { ...options };\n Object.keys(options).forEach((key) => {\n let targetKey = key;\n if (key.startsWith('outline')) {\n const sub = key.slice('outline'.length);\n targetKey = sub[0].toLowerCase() + sub.slice(1);\n } else if (key.startsWith('hoverOutline')) {\n targetKey = `hover${key.slice('hoverOutline'.length)}`;\n } else {\n return;\n }\n delete r[key];\n r[targetKey] = options[key];\n });\n return r;\n}\n\nexport class GeoController<\n TYPE extends keyof ChartTypeRegistry,\n TElement extends Element & VisualElement,\n> extends DatasetController<TYPE, TElement, GeoFeature> {\n getGeoDataset(): ChartDataset<'choropleth' | 'bubbleMap'> & IGeoControllerDatasetOptions {\n return super.getDataset() as unknown as ChartDataset<'choropleth' | 'bubbleMap'> & IGeoControllerDatasetOptions;\n }\n\n getGeoOptions(): IGeoChartOptions {\n return this.chart.options as unknown as IGeoChartOptions;\n }\n\n getProjectionScale(): ProjectionScale {\n return this.getScaleForId('projection') as ProjectionScale;\n }\n\n linkScales(): void {\n const dataset = this.getGeoDataset();\n const meta = this.getMeta();\n meta.xAxisID = 'projection';\n dataset.xAxisID = 'projection';\n meta.yAxisID = 'projection';\n dataset.yAxisID = 'projection';\n meta.xScale = this.getScaleForId('projection');\n meta.yScale = this.getScaleForId('projection');\n\n this.getProjectionScale().computeBounds(this.resolveOutline());\n }\n\n showOutline(): IGeoChartOptions['showOutline'] {\n return valueOrDefault(this.getGeoDataset().showOutline, this.getGeoOptions().showOutline);\n }\n\n clipMap(): IGeoChartOptions['clipMap'] {\n return valueOrDefault(this.getGeoDataset().clipMap, this.getGeoOptions().clipMap);\n }\n\n getGraticule(): IGeoChartOptions['showGraticule'] {\n return valueOrDefault(this.getGeoDataset().showGraticule, this.getGeoOptions().showGraticule);\n }\n\n update(mode: UpdateMode): void {\n super.update(mode);\n\n const meta = this.getMeta();\n\n const scale = this.getProjectionScale();\n const dirtyCache = scale.updateBounds() || mode === 'resize' || mode === 'reset';\n\n if (this.showOutline()) {\n const elem = meta.dataset!;\n if (dirtyCache) {\n delete elem.cache;\n }\n elem.projectionScale = scale;\n elem.pixelRatio = this.chart.currentDevicePixelRatio;\n if (mode !== 'resize') {\n const options = patchDatasetElementOptions(this.resolveDatasetElementOptions(mode));\n const properties = {\n feature: this.resolveOutline(),\n options,\n };\n this.updateElement(elem, undefined, properties, mode);\n if (this.getGraticule()) {\n (meta as any).graticule = options;\n }\n }\n } else if (this.getGraticule() && mode !== 'resize') {\n (meta as any).graticule = patchDatasetElementOptions(this.resolveDatasetElementOptions(mode));\n }\n\n if (dirtyCache) {\n meta.data.forEach((elem) => delete (elem as any).cache);\n }\n this.updateElements(meta.data, 0, meta.data.length, mode);\n }\n\n resolveOutline(): any {\n const ds = this.getGeoDataset();\n const outline = ds.outline || { type: 'Sphere' };\n if (Array.isArray(outline)) {\n return {\n type: 'FeatureCollection',\n features: outline,\n };\n }\n return outline;\n }\n\n showGraticule(): void {\n const g = this.getGraticule();\n const options = (this.getMeta() as any).graticule;\n if (!g || !options) {\n return;\n }\n const { ctx } = this.chart;\n const scale = this.getProjectionScale();\n const path = scale.geoPath.context(ctx);\n\n ctx.save();\n ctx.beginPath();\n\n if (typeof g === 'boolean') {\n if (g) {\n path(geoGraticule10());\n }\n } else {\n const geo = geoGraticule();\n if (g.stepMajor) {\n geo.stepMajor(g.stepMajor as unknown as [number, number]);\n }\n if (g.stepMinor) {\n geo.stepMinor(g.stepMinor as unknown as [number, number]);\n }\n path(geo());\n }\n\n ctx.strokeStyle = options.graticuleBorderColor;\n ctx.lineWidth = options.graticuleBorderWidth;\n ctx.stroke();\n ctx.restore();\n }\n\n draw(): void {\n const { chart } = this;\n\n const clipMap = this.clipMap();\n\n // enable clipping based on the option\n let enabled = false;\n if (clipMap === true || clipMap === 'outline' || clipMap === 'outline+graticule') {\n enabled = true;\n clipArea(chart.ctx, chart.chartArea);\n }\n\n if (this.showOutline() && this.getMeta().dataset) {\n (this.getMeta().dataset!.draw.call as any)(this.getMeta().dataset!, chart.ctx, chart.chartArea);\n }\n\n if (clipMap === true || clipMap === 'graticule' || clipMap === 'outline+graticule') {\n if (!enabled) {\n clipArea(chart.ctx, chart.chartArea);\n }\n } else if (enabled) {\n enabled = false;\n unclipArea(chart.ctx);\n }\n\n this.showGraticule();\n\n if (clipMap === true || clipMap === 'items') {\n if (!enabled) {\n clipArea(chart.ctx, chart.chartArea);\n }\n } else if (enabled) {\n enabled = false;\n unclipArea(chart.ctx);\n }\n\n this.getMeta().data.forEach((elem) => (elem.draw.call as any)(elem, chart.ctx, chart.chartArea));\n\n if (enabled) {\n enabled = false;\n unclipArea(chart.ctx);\n }\n }\n}\n\nexport interface IGeoChartOptions {\n /**\n * Outline used to scale and centralize the projection in the chart area.\n * By default a sphere is used\n * @default { type: 'Sphere\" }\n */\n outline: any[];\n /**\n * option to render the outline in the background, see also the outline... styling option\n * @default false\n */\n showOutline: boolean;\n\n /**\n * option to render a graticule in the background, see also the outline... styling option\n * @default false\n */\n showGraticule:\n | boolean\n | {\n stepMajor: [number, number];\n stepMinor: [number, number];\n };\n\n /**\n * option whether to clip the rendering to the chartArea of the graph\n * @default choropleth: true bubbleMap: 'outline+graticule'\n */\n clipMap: boolean | 'outline' | 'graticule' | 'outline+graticule' | 'items';\n}\n\nexport interface IGeoControllerDatasetOptions\n extends IGeoChartOptions,\n ScriptableAndArrayOptions<IGeoFeatureOptions, ScriptableContext<'choropleth' | 'bubbleMap'>>,\n AnimationOptions<'choropleth' | 'bubbleMap'> {\n xAxisID?: string;\n yAxisID?: string;\n rAxisID?: string;\n iAxisID?: string;\n vAxisID?: string;\n}\n\nexport interface IGeoDataPoint {\n feature: ExtendedFeature;\n center?: {\n longitude: number;\n latitude: number;\n };\n}\n","import {\n Chart,\n UpdateMode,\n ScriptableContext,\n TooltipItem,\n CommonHoverOptions,\n ScriptableAndArrayOptions,\n ControllerDatasetOptions,\n ChartConfiguration,\n ChartItem,\n PointOptions,\n Scale,\n AnimationOptions,\n} from 'chart.js';\nimport { merge } from 'chart.js/helpers';\nimport { geoDefaults, GeoController, IGeoChartOptions, IGeoDataPoint, geoOverrides } from './GeoController';\nimport { GeoFeature, IGeoFeatureOptions, IGeoFeatureProps } from '../elements';\nimport { ColorScale, ProjectionScale } from '../scales';\nimport patchController from './patchController';\n\nexport class ChoroplethController extends GeoController<'choropleth', GeoFeature> {\n initialize(): void {\n super.initialize();\n this.enableOptionSharing = true;\n }\n\n linkScales(): void {\n super.linkScales();\n const dataset = this.getGeoDataset();\n const meta = this.getMeta();\n meta.vAxisID = 'color';\n meta.rAxisID = 'color';\n dataset.vAxisID = 'color';\n dataset.rAxisID = 'color';\n meta.rScale = this.getScaleForId('color');\n meta.vScale = meta.rScale;\n meta.iScale = meta.xScale;\n\n meta.iAxisID = meta.xAxisID!;\n\n dataset.iAxisID = meta.xAxisID!;\n }\n\n _getOtherScale(scale: Scale): Scale {\n // for strange get min max with other scale\n return scale;\n }\n\n parse(start: number, count: number): void {\n const rScale = this.getMeta().rScale!;\n const { data } = this.getDataset();\n const meta = this._cachedMeta;\n for (let i = start; i < start + count; i += 1) {\n meta._parsed[i] = {\n [rScale.axis]: rScale.parse(data[i], i),\n };\n }\n }\n\n updateElements(elems: GeoFeature[], start: number, count: number, mode: UpdateMode): void {\n const firstOpts = this.resolveDataElementOptions(start, mode);\n\n const sharedOptions = this.getSharedOptions(firstOpts)!;\n const includeOptions = this.includeOptions(mode, sharedOptions);\n const scale = this.getProjectionScale();\n this.updateSharedOptions(sharedOptions, mode, firstOpts);\n\n for (let i = start; i < start + count; i += 1) {\n const elem = elems[i];\n elem.projectionScale = scale;\n const center = elem.updateExtras({\n scale,\n feature: (this as any)._data[i].feature,\n center: (this as any)._data[i].center,\n pixelRatio: this.chart.currentDevicePixelRatio,\n mode,\n });\n\n const properties: IGeoFeatureProps & { options?: PointOptions } = {\n x: center.x,\n y: center.y,\n };\n if (includeOptions) {\n properties.options = (sharedOptions || this.resolveDataElementOptions(i, mode)) as unknown as PointOptions;\n }\n this.updateElement(elem, i, properties as unknown as Record<string, unknown>, mode);\n }\n }\n\n indexToColor(index: number): string {\n const rScale = this.getMeta().rScale as unknown as ColorScale;\n return rScale.getColorForValue(this.getParsed(index)[rScale.axis as 'r']);\n }\n\n static readonly id = 'choropleth';\n\n /**\n * @hidden\n */\n static readonly defaults: any = /* #__PURE__ */ merge({}, [\n geoDefaults,\n {\n datasetElementType: GeoFeature.id,\n dataElementType: GeoFeature.id,\n },\n ]);\n\n /**\n * @hidden\n */\n static readonly overrides: any = /* #__PURE__ */ merge({}, [\n geoOverrides,\n {\n plugins: {\n tooltip: {\n callbacks: {\n title()