chartjs-chart-geo
Version:
Chart.js module for charting maps
187 lines (167 loc) • 5.63 kB
text/typescript
import {
Chart,
UpdateMode,
ScriptableContext,
TooltipItem,
CommonHoverOptions,
ScriptableAndArrayOptions,
ControllerDatasetOptions,
ChartConfiguration,
ChartItem,
PointOptions,
Scale,
AnimationOptions,
} from 'chart.js';
import { merge } from 'chart.js/helpers';
import { geoDefaults, GeoController, IGeoChartOptions, IGeoDataPoint, geoOverrides } from './GeoController';
import { GeoFeature, IGeoFeatureOptions, IGeoFeatureProps } from '../elements';
import { ColorScale, ProjectionScale } from '../scales';
import patchController from './patchController';
export class ChoroplethController extends GeoController<'choropleth', GeoFeature> {
initialize(): void {
super.initialize();
this.enableOptionSharing = true;
}
linkScales(): void {
super.linkScales();
const dataset = this.getGeoDataset();
const meta = this.getMeta();
meta.vAxisID = 'color';
meta.rAxisID = 'color';
dataset.vAxisID = 'color';
dataset.rAxisID = 'color';
meta.rScale = this.getScaleForId('color');
meta.vScale = meta.rScale;
meta.iScale = meta.xScale;
meta.iAxisID = meta.xAxisID!;
dataset.iAxisID = meta.xAxisID!;
}
_getOtherScale(scale: Scale): Scale {
// for strange get min max with other scale
return scale;
}
parse(start: number, count: number): void {
const rScale = this.getMeta().rScale!;
const { data } = this.getDataset();
const meta = this._cachedMeta;
for (let i = start; i < start + count; i += 1) {
meta._parsed[i] = {
[rScale.axis]: rScale.parse(data[i], i),
};
}
}
updateElements(elems: GeoFeature[], start: number, count: number, mode: UpdateMode): void {
const firstOpts = this.resolveDataElementOptions(start, mode);
const sharedOptions = this.getSharedOptions(firstOpts)!;
const includeOptions = this.includeOptions(mode, sharedOptions);
const scale = this.getProjectionScale();
this.updateSharedOptions(sharedOptions, mode, firstOpts);
for (let i = start; i < start + count; i += 1) {
const elem = elems[i];
elem.projectionScale = scale;
elem.feature = (this as any)._data[i].feature;
elem.center = (this as any)._data[i].center;
elem.pixelRatio = this.chart.currentDevicePixelRatio;
const center = elem.getCenterPoint();
const properties: IGeoFeatureProps & { options?: PointOptions } = {
x: center.x,
y: center.y,
};
if (includeOptions) {
properties.options = (sharedOptions || this.resolveDataElementOptions(i, mode)) as unknown as PointOptions;
}
this.updateElement(elem, i, properties as unknown as Record<string, unknown>, mode);
}
}
indexToColor(index: number): string {
const rScale = this.getMeta().rScale as unknown as ColorScale;
return rScale.getColorForValue(this.getParsed(index)[rScale.axis as 'r']);
}
static readonly id = 'choropleth';
/**
* @hidden
*/
static readonly defaults: any = /* #__PURE__ */ merge({}, [
geoDefaults,
{
datasetElementType: GeoFeature.id,
dataElementType: GeoFeature.id,
},
]);
/**
* @hidden
*/
static readonly overrides: any = /* #__PURE__ */ merge({}, [
geoOverrides,
{
plugins: {
tooltip: {
callbacks: {
title() {
// Title doesn't make sense for scatter since we format the data as a point
return '';
},
label(item: TooltipItem<'choropleth'>) {
if (item.formattedValue == null) {
return item.chart.data?.labels?.[item.dataIndex];
}
return `${item.chart.data?.labels?.[item.dataIndex]}: ${item.formattedValue}`;
},
},
},
colors: {
enabled: false,
},
},
scales: {
color: {
type: ColorScale.id,
axis: 'x',
},
},
elements: {
geoFeature: {
backgroundColor(context: ScriptableContext<'choropleth'>) {
if (context.dataIndex == null) {
return null;
}
const controller = (context.chart as Chart<'choropleth'>).getDatasetMeta(context.datasetIndex)
.controller as ChoroplethController;
return controller.indexToColor(context.dataIndex);
},
},
},
},
]);
}
export interface IChoroplethControllerDatasetOptions
extends ControllerDatasetOptions,
IGeoChartOptions,
ScriptableAndArrayOptions<IGeoFeatureOptions, ScriptableContext<'choropleth'>>,
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'choropleth'>>,
AnimationOptions<'choropleth'> {}
export interface IChoroplethDataPoint extends IGeoDataPoint {
value: number;
}
declare module 'chart.js' {
export interface ChartTypeRegistry {
choropleth: {
chartOptions: IGeoChartOptions;
datasetOptions: IChoroplethControllerDatasetOptions;
defaultDataPoint: IChoroplethDataPoint;
scales: keyof (ProjectionScaleTypeRegistry & ColorScaleTypeRegistry);
metaExtensions: Record<string, never>;
parsedDataType: { r: number };
};
}
}
export class ChoroplethChart<DATA extends unknown[] = IGeoDataPoint[], LABEL = string> extends Chart<
'choropleth',
DATA,
LABEL
> {
static id = ChoroplethController.id;
constructor(item: ChartItem, config: Omit<ChartConfiguration<'choropleth', DATA, LABEL>, 'type'>) {
super(item, patchController('choropleth', config, ChoroplethController, GeoFeature, [ColorScale, ProjectionScale]));
}
}