chartjs-chart-funnel
Version:
Chart.js module for charting funnel charts
171 lines (159 loc) • 4.76 kB
text/typescript
import {
Chart,
ChartItem,
ScriptableAndArrayOptions,
ControllerDatasetOptions,
CommonHoverOptions,
UpdateMode,
ChartConfiguration,
Scale,
ScriptableContext,
CategoryScale,
BarController,
CoreChartOptions,
CartesianScaleTypeRegistry,
LinearScale,
} from 'chart.js';
import { merge } from 'chart.js/helpers';
import { pickForegroundColorToBackgroundColor, blues } from '../color';
import { TrapezoidElement, TrapezoidElementOptions } from '../elements';
import patchController from './patchController';
export interface FunnelChartOptions {
/**
* alignment of the elements one of center, left, right
* @default center
*/
align: 'center' | 'left' | 'right';
}
export class FunnelController extends BarController {
/**
* @hidden
*/
declare options: FunnelChartOptions;
static readonly id: string = 'funnel';
/**
* @hidden
*/
static readonly defaults: any = /* #__PURE__ */ merge({}, [
BarController.defaults,
{
dataElementType: TrapezoidElement.id,
barPercentage: 1,
align: 'center',
categoryPercentage: 0.98,
},
]);
/**
* @hidden
*/
static readonly overrides: any = /* #__PURE__ */ merge({}, [
(BarController as any).overrides,
{
plugins: {
legend: {
display: false,
},
colors: {
enabled: false,
},
datalabels: {
anchor: 'start',
// align: 'start',
textAlign: 'center',
font: {
size: 20,
},
color: (context: {
chart: Chart<'funnel'>;
dataset: Chart['data']['datasets'][0];
dataIndex: number;
datasetIndex: number;
}) => {
// auto pick color based on background color
const bgColor = context.chart.getDatasetMeta(context.datasetIndex).data[context.dataIndex].options
.backgroundColor as string;
return pickForegroundColorToBackgroundColor(bgColor, Chart.defaults.color as string, '#ffffff');
},
formatter: (value: number, context: { chart: Chart<'funnel'>; dataIndex: number }) => {
const label = context.chart.data.labels?.[context.dataIndex] ?? '';
return `${label}\n${(value * 100).toLocaleString()}%`;
},
},
},
scales: {
_index_: {
display: false,
padding: 10,
grid: {
display: false,
},
},
_value_: {
display: false,
beginAtZero: false,
grace: 0,
grid: {
display: false,
},
},
},
elements: {
trapezoid: {
backgroundColor(context: ScriptableContext<'funnel'>) {
const nData = context.chart.data.datasets[context.datasetIndex].data.length;
return blues(context.dataIndex, nData);
},
},
},
},
]);
/**
* @hidden
*/
getMinMax(scale: Scale, canStack?: boolean | undefined): { min: number; max: number } {
const { max } = super.getMinMax(scale, canStack);
const r = {
// fake mirroring the scale
center: { min: -max, max },
left: { min: 0, max },
right: { min: -max, max: 0 },
}[this.options.align];
return r;
}
/**
* @hidden
*/
update(mode: UpdateMode): void {
super.update(mode);
const meta = this._cachedMeta;
// create a link
const elements = (meta.data || []) as unknown as TrapezoidElement[];
for (let i = 0; i < elements.length; i++) {
elements[i].align = this.options.align;
elements[i].next = elements[i + 1];
elements[i].previous = elements[i - 1];
}
}
}
export interface FunnelControllerDatasetOptions
extends ControllerDatasetOptions,
ScriptableAndArrayOptions<TrapezoidElementOptions, ScriptableContext<'funnel'>>,
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'funnel'>> {}
declare module 'chart.js' {
interface ChartTypeRegistry {
funnel: {
chartOptions: FunnelChartOptions & CoreChartOptions<'funnel'>;
datasetOptions: FunnelControllerDatasetOptions;
defaultDataPoint: number;
metaExtensions: Record<string, never>;
parsedDataType: { x: number; y: number };
scales: keyof CartesianScaleTypeRegistry;
};
}
}
export class FunnelChart<DATA extends unknown[] = number[], LABEL = string> extends Chart<'funnel', DATA, LABEL> {
static id = FunnelController.id as 'funnel';
constructor(item: ChartItem, config: Omit<ChartConfiguration<'funnel', DATA, LABEL>, 'type'>) {
super(item, patchController('funnel', config, FunnelController, TrapezoidElement, [CategoryScale, LinearScale]));
}
}