chartjs-chart-geo
Version:
Chart.js module for charting maps
222 lines (194 loc) • 5.79 kB
text/typescript
import { LinearScale, LogarithmicScale, PointOptions, LinearScaleOptions, LogarithmicScaleOptions } from 'chart.js';
import { merge, drawPoint } from 'chart.js/helpers';
import { baseDefaults, ILegendScaleOptions, LegendScale, LogarithmicLegendScale } from './LegendScale';
export interface ISizeScaleOptions extends ILegendScaleOptions {
// support all options from linear scale -> https://www.chartjs.org/docs/latest/axes/cartesian/linear.html#linear-cartesian-axis
// e.g. for tick manipulation, ...
/**
* radius range in pixel, the minimal data value will be mapped to the
* first entry, the maximal one to the second and a linear interpolation
* for all values in between.
*
* @default [2, 20]
*/
range: [number, number];
/**
* operation mode for the scale, area means that the area is linearly increasing whereas radius the radius is.
* The area one is the default since it gives a better visual comparison of values
* @default area
*/
mode: 'radius' | 'area';
/**
* radius to render for missing values
* @default 1
*/
missing: number;
}
const scaleDefaults = {
missing: 1,
mode: 'area', // 'radius'
// mode: 'radius',
range: [2, 20],
legend: {
align: 'bottom',
length: 90,
width: 70,
indicatorWidth: 42,
},
};
export class SizeScale extends LegendScale<ISizeScaleOptions & LinearScaleOptions> {
/**
* @hidden
*/
_model: PointOptions | null = null;
/**
* @hidden
*/
getSizeForValue(value: number): number {
const v = this._getNormalizedValue(value);
if (v == null || Number.isNaN(v)) {
return this.options.missing;
}
return this.getSizeImpl(v);
}
/**
* @hidden
*/
getSizeImpl(normalized: number): number {
const [r0, r1] = this.options.range;
if (this.options.mode === 'area') {
const a1 = r1 * r1 * Math.PI;
const a0 = r0 * r0 * Math.PI;
const range = a1 - a0;
const a = normalized * range + a0;
return Math.sqrt(a / Math.PI);
}
const range = r1 - r0;
return normalized * range + r0;
}
/**
* @hidden
*/
_drawIndicator(): void {
/** @type {CanvasRenderingContext2D} */
const { ctx } = this;
const shift = this.options.legend.indicatorWidth / 2;
const isHor = this.isHorizontal();
const values = this.ticks;
const labelItems = this.getLabelItems();
const positions = labelItems
? labelItems.map((el: any) => ({ [isHor ? 'x' : 'y']: el.options.translation[isHor ? 0 : 1] }))
: values.map((_, i) => ({ [isHor ? 'x' : 'y']: this.getPixelForTick(i) }));
((this as any)._gridLineItems || []).forEach((item: any) => {
ctx.save();
ctx.strokeStyle = item.color;
ctx.lineWidth = item.width;
if (ctx.setLineDash) {
ctx.setLineDash(item.borderDash);
ctx.lineDashOffset = item.borderDashOffset;
}
ctx.beginPath();
if (this.options.grid.drawTicks) {
switch (this.options.legend.align) {
case 'left':
ctx.moveTo(0, item.ty1);
ctx.lineTo(shift, item.ty2);
break;
case 'top':
ctx.moveTo(item.tx1, 0);
ctx.lineTo(item.tx2, shift);
break;
case 'bottom':
ctx.moveTo(item.tx1, shift);
ctx.lineTo(item.tx2, shift * 2);
break;
default:
// right
ctx.moveTo(shift, item.ty1);
ctx.lineTo(shift * 2, item.ty2);
break;
}
}
ctx.stroke();
ctx.restore();
});
if (this._model) {
const props = this._model;
ctx.strokeStyle = props.borderColor;
ctx.lineWidth = props.borderWidth || 0;
ctx.fillStyle = props.backgroundColor;
} else {
ctx.fillStyle = 'blue';
}
values.forEach((v, i) => {
const pos = positions[i];
const radius = this.getSizeForValue(v.value);
const x = isHor ? pos.x : shift;
const y = isHor ? shift : pos.y;
const renderOptions = {
pointStyle: 'circle' as const,
borderWidth: 0,
...(this._model || {}),
radius,
};
drawPoint(ctx, renderOptions, x, y);
});
}
static readonly id = 'size';
/**
* @hidden
*/
static readonly defaults: any = /* #__PURE__ */ merge({}, [LinearScale.defaults, baseDefaults, scaleDefaults]);
/**
* @hidden
*/
static readonly descriptors = /* #__PURE__ */ {
_scriptable: true,
_indexable: (name: string): boolean => name !== 'range',
};
}
export class SizeLogarithmicScale extends LogarithmicLegendScale<ISizeScaleOptions & LogarithmicScaleOptions> {
/**
* @hidden
*/
_model: PointOptions | null = null;
/**
* @hidden
*/
getSizeForValue(value: number): number {
const v = this._getNormalizedValue(value);
if (v == null || Number.isNaN(v)) {
return this.options.missing;
}
return this.getSizeImpl(v);
}
/**
* @hidden
*/
getSizeImpl(normalized: number): number {
return SizeScale.prototype.getSizeImpl.call(this, normalized);
}
/**
* @hidden
*/
_drawIndicator(): void {
SizeScale.prototype._drawIndicator.call(this);
}
static readonly id = 'sizeLogarithmic';
/**
* @hidden
*/
static readonly defaults: any = /* #__PURE__ */ merge({}, [LogarithmicScale.defaults, baseDefaults, scaleDefaults]);
}
declare module 'chart.js' {
export interface SizeScaleTypeRegistry {
size: {
options: ISizeScaleOptions & LinearScaleOptions;
};
sizeLogarithmic: {
options: ISizeScaleOptions & LogarithmicScaleOptions;
};
}
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ScaleTypeRegistry extends SizeScaleTypeRegistry {}
}