@sgratzl/chartjs-chart-boxplot
Advanced tools
Comparing version 3.0.0-beta.9 to 3.0.0-rc.0
@@ -1,378 +0,3 @@ | ||
/** | ||
* @sgratzl/chartjs-chart-boxplot | ||
* https://github.com/sgratzl/chartjs-chart-boxplot | ||
* | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
import { TooltipModel, Element, BarController, Scale, ChartMeta, UpdateMode, ControllerDatasetOptions, ScriptableAndArrayOptions, ScriptableContext, CommonHoverOptions, CartesianScaleTypeRegistry, Chart, ChartItem, ChartConfiguration } from 'chart.js'; | ||
interface ExtendedTooltip extends TooltipModel { | ||
_tooltipOutlier?: { | ||
index: number; | ||
datasetIndex: number; | ||
}; | ||
} | ||
interface IStatsBaseOptions { | ||
/** | ||
* @default see rectangle | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
backgroundColor: string; | ||
/** | ||
* @default see rectangle | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
borderColor: string; | ||
/** | ||
* @default 1 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
borderWidth: number; | ||
/** | ||
* item style used to render outliers | ||
* @default circle | ||
*/ | ||
outlierStyle: 'circle' | 'triangle' | 'rect' | 'rectRounded' | 'rectRot' | 'cross' | 'crossRot' | 'star' | 'line' | 'dash'; | ||
/** | ||
* radius used to render outliers | ||
* @default 2 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
outlierRadius: number; | ||
/** | ||
* @default see rectangle.backgroundColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
outlierBackgroundColor: string; | ||
/** | ||
* @default see rectangle.borderColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
outlierBorderColor: string; | ||
/** | ||
* @default 1 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
outlierBorderWidth: number; | ||
/** | ||
* item style used to render items | ||
* @default circle | ||
*/ | ||
itemStyle: 'circle' | 'triangle' | 'rect' | 'rectRounded' | 'rectRot' | 'cross' | 'crossRot' | 'star' | 'line' | 'dash'; | ||
/** | ||
* radius used to render items | ||
* @default 0 so disabled | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
itemRadius: number; | ||
/** | ||
* background color for items | ||
* @default see rectangle.backgroundColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
itemBackgroundColor: string; | ||
/** | ||
* border color for items | ||
* @default see rectangle.borderColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
itemBorderColor: string; | ||
/** | ||
* border width for items | ||
* @default 0 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
itemBorderWidth: number; | ||
/** | ||
* padding that is added around the bounding box when computing a mouse hit | ||
* @default 2 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
hitPadding: number; | ||
/** | ||
* hit radius for hit test of outliers | ||
* @default 4 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
outlierHitRadius: number; | ||
} | ||
interface IStatsBaseProps { | ||
x: number; | ||
y: number; | ||
width: number; | ||
height: number; | ||
items: number[]; | ||
outliers: number[]; | ||
} | ||
declare class StatsBase<T extends IStatsBaseProps, O extends IStatsBaseOptions> extends Element<T, O> { | ||
_datasetIndex: number; | ||
_index: number; | ||
isVertical(): boolean; | ||
_drawItems(ctx: CanvasRenderingContext2D): void; | ||
_drawOutliers(ctx: CanvasRenderingContext2D): void; | ||
_getBounds(_useFinalPosition?: boolean): { | ||
left: number; | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
}; | ||
_getHitBounds(useFinalPosition?: boolean): { | ||
left: number; | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
}; | ||
inRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean; | ||
inXRange(mouseX: number, useFinalPosition?: boolean): boolean; | ||
inYRange(mouseY: number, useFinalPosition?: boolean): boolean; | ||
_outlierIndexInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): number; | ||
_boxInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean): boolean; | ||
getCenterPoint(useFinalPosition?: boolean): { | ||
x: T["x"]; | ||
y: T["y"]; | ||
}; | ||
_getOutliers(useFinalPosition?: boolean): T["outliers"]; | ||
tooltipPosition(eventPosition?: { | ||
x: number; | ||
y: number; | ||
} | boolean, tooltip?: ExtendedTooltip): { | ||
x: number; | ||
y: number; | ||
}; | ||
} | ||
interface IBoxAndWhiskersOptions extends IStatsBaseOptions { | ||
/** | ||
* separate color for the median line | ||
* @default 'transparent' takes the current borderColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
medianColor: string; | ||
/** | ||
* color the lower half (median-q3) of the box in a different color | ||
* @default 'transparent' takes the current borderColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
lowerBackgroundColor: string; | ||
} | ||
interface IBoxAndWhiskerProps extends IStatsBaseProps { | ||
q1: number; | ||
q3: number; | ||
median: number; | ||
whiskerMin: number; | ||
whiskerMax: number; | ||
mean: number; | ||
} | ||
declare class BoxAndWiskers extends StatsBase<IBoxAndWhiskerProps, IBoxAndWhiskersOptions> { | ||
draw(ctx: CanvasRenderingContext2D): void; | ||
_drawBoxPlot(ctx: CanvasRenderingContext2D): void; | ||
_drawBoxPlotVertical(ctx: CanvasRenderingContext2D): void; | ||
_drawBoxPlotHorizontal(ctx: CanvasRenderingContext2D): void; | ||
_getBounds(useFinalPosition?: boolean): { | ||
left: number; | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
}; | ||
static id: string; | ||
static defaults: any; | ||
static defaultRoutes: { | ||
[property: string]: string; | ||
} & { | ||
outlierBackgroundColor: string; | ||
outlierBorderColor: string; | ||
itemBackgroundColor: string; | ||
itemBorderColor: string; | ||
}; | ||
} | ||
interface IBaseStats { | ||
min: number; | ||
max: number; | ||
q1: number; | ||
q3: number; | ||
median: number; | ||
} | ||
interface IBoxPlot extends IBaseStats { | ||
items: readonly number[]; | ||
outliers: readonly number[]; | ||
whiskerMax: number; | ||
whiskerMin: number; | ||
mean: number; | ||
} | ||
interface IKDEPoint { | ||
v: number; | ||
estimate: number; | ||
} | ||
interface IViolin extends IBaseStats { | ||
items: readonly number[]; | ||
maxEstimate: number; | ||
coords: IKDEPoint[]; | ||
outliers: readonly number[]; | ||
} | ||
declare type QuantileMethod = 7 | 'quantiles' | 'hinges' | 'fivenum' | 'linear' | 'lower' | 'higher' | 'nearest' | 'midpoint' | ((arr: ArrayLike<number>, length?: number | undefined) => { | ||
q1: number; | ||
median: number; | ||
q3: number; | ||
}); | ||
interface IBaseOptions { | ||
/** | ||
* statistic measure that should be used for computing the minimal data limit | ||
* @default 'min' | ||
*/ | ||
minStats?: 'min' | 'q1' | 'whiskerMin'; | ||
/** | ||
* statistic measure that should be used for computing the maximal data limit | ||
* @default 'max' | ||
*/ | ||
maxStats?: 'max' | 'q3' | 'whiskerMax'; | ||
/** | ||
* from the R doc: this determines how far the plot ‘whiskers’ extend out from | ||
* the box. If coef is positive, the whiskers extend to the most extreme data | ||
* point which is no more than coef times the length of the box away from the | ||
* box. A value of zero causes the whiskers to extend to the data extremes | ||
* @default 1.5 | ||
*/ | ||
coef?: number; | ||
/** | ||
* the method to compute the quantiles. | ||
* | ||
* 7, 'quantiles': the type-7 method as used by R 'quantiles' method. | ||
* 'hinges' and 'fivenum': the method used by R 'boxplot.stats' method. | ||
* 'linear': the interpolation method 'linear' as used by 'numpy.percentile' function | ||
* 'lower': the interpolation method 'lower' as used by 'numpy.percentile' function | ||
* 'higher': the interpolation method 'higher' as used by 'numpy.percentile' function | ||
* 'nearest': the interpolation method 'nearest' as used by 'numpy.percentile' function | ||
* 'midpoint': the interpolation method 'midpoint' as used by 'numpy.percentile' function | ||
* @default 7 | ||
*/ | ||
quantiles?: QuantileMethod; | ||
} | ||
declare type IBoxplotOptions = IBaseOptions; | ||
interface IViolinOptions extends IBaseOptions { | ||
/** | ||
* number of points that should be samples of the KDE | ||
* @default 100 | ||
*/ | ||
points: number; | ||
} | ||
declare type IViolinElementOptions = IStatsBaseOptions; | ||
interface IViolinElementProps extends IStatsBaseProps { | ||
min: number; | ||
max: number; | ||
coords: IKDEPoint[]; | ||
maxEstimate: number; | ||
} | ||
declare class Violin extends StatsBase<IViolinElementProps, IViolinElementOptions> { | ||
draw(ctx: CanvasRenderingContext2D): void; | ||
_drawCoords(ctx: CanvasRenderingContext2D, props: IViolinElementProps): void; | ||
_getBounds(useFinalPosition?: boolean): { | ||
left: number; | ||
top: number; | ||
right: number; | ||
bottom: number; | ||
}; | ||
static id: string; | ||
static defaults: any; | ||
static defaultRoutes: { | ||
[property: string]: string; | ||
} & { | ||
outlierBackgroundColor: string; | ||
outlierBorderColor: string; | ||
itemBackgroundColor: string; | ||
itemBorderColor: string; | ||
}; | ||
} | ||
declare abstract class StatsBase$1<S extends { | ||
median: number; | ||
}, C extends Required<IBaseOptions>> extends BarController { | ||
_config: C; | ||
getMinMax(scale: Scale, canStack?: boolean | undefined): { | ||
min: number; | ||
max: number; | ||
}; | ||
parsePrimitiveData(meta: ChartMeta, data: any[], start: number, count: number): any[]; | ||
parseArrayData(meta: ChartMeta, data: any[], start: number, count: number): any[]; | ||
parseObjectData(meta: ChartMeta, data: any[], start: number, count: number): any[]; | ||
protected abstract _parseStats(value: any, config: C): S; | ||
getLabelAndValue(index: number): any; | ||
protected abstract _toStringStats(b: S): string; | ||
protected abstract _transformStats<T>(target: Record<keyof S, T | T[]>, source: S, mapper: (v: number) => T, mode: UpdateMode | 'string'): void; | ||
updateElement(rectangle: Element, index: number, properties: any, mode: UpdateMode): void; | ||
} | ||
declare class BoxPlotController extends StatsBase$1<IBoxPlot, Required<IBoxplotOptions>> { | ||
_parseStats(value: any, config: IBoxplotOptions): any; | ||
_toStringStats(b: IBoxPlot): string; | ||
_transformStats<T>(target: any, source: IBoxPlot, mapper: (v: number) => T): void; | ||
static readonly id = "boxplot"; | ||
static readonly defaults: any; | ||
} | ||
interface BoxPlotControllerDatasetOptions extends ControllerDatasetOptions, IBoxplotOptions, ScriptableAndArrayOptions<IBoxAndWhiskersOptions, ScriptableContext>, ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> { | ||
} | ||
declare type BoxPlotDataPoint = number[] | (Partial<IBoxPlot> & IBaseStats); | ||
interface IBoxPlotChartOptions { | ||
} | ||
declare module 'chart.js' { | ||
interface ChartTypeRegistry { | ||
boxplot: { | ||
chartOptions: IBoxPlotChartOptions; | ||
datasetOptions: BoxPlotControllerDatasetOptions; | ||
defaultDataPoint: BoxPlotDataPoint[]; | ||
scales: keyof CartesianScaleTypeRegistry; | ||
}; | ||
} | ||
} | ||
declare class BoxPlotChart<DATA extends unknown[] = BoxPlotDataPoint[], LABEL = string> extends Chart<'boxplot', DATA, LABEL> { | ||
static id: string; | ||
constructor(item: ChartItem, config: Omit<ChartConfiguration<'boxplot', DATA, LABEL>, 'type'>); | ||
} | ||
declare class ViolinController extends StatsBase$1<IViolin, Required<IViolinOptions>> { | ||
_parseStats(value: any, config: IViolinOptions): any; | ||
_toStringStats(v: IViolin): string; | ||
_transformStats<T>(target: any, source: IViolin, mapper: (v: number) => T): void; | ||
static readonly id = "violin"; | ||
static readonly defaults: any; | ||
} | ||
interface ViolinControllerDatasetOptions extends ControllerDatasetOptions, IViolinOptions, ScriptableAndArrayOptions<IViolinElementOptions, ScriptableContext>, ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> { | ||
} | ||
declare type ViolinDataPoint = number[] | (Partial<IViolin> & IBaseStats); | ||
interface IViolinChartOptions { | ||
} | ||
declare module 'chart.js' { | ||
interface ChartTypeRegistry { | ||
violin: { | ||
chartOptions: IViolinChartOptions; | ||
datasetOptions: ViolinControllerDatasetOptions; | ||
defaultDataPoint: ViolinDataPoint[]; | ||
scales: keyof CartesianScaleTypeRegistry; | ||
}; | ||
} | ||
} | ||
declare class ViolinChart<DATA extends unknown[] = ViolinDataPoint[], LABEL = string> extends Chart<'violin', DATA, LABEL> { | ||
static id: string; | ||
constructor(item: ChartItem, config: Omit<ChartConfiguration<'violin', DATA, LABEL>, 'type'>); | ||
} | ||
export { BoxAndWiskers, BoxPlotChart, BoxPlotController, BoxPlotControllerDatasetOptions, BoxPlotDataPoint, IBoxAndWhiskerProps, IBoxAndWhiskersOptions, IBoxPlotChartOptions, IStatsBaseOptions, IStatsBaseProps, IViolinChartOptions, IViolinElementOptions, IViolinElementProps, StatsBase, Violin, ViolinChart, ViolinController, ViolinControllerDatasetOptions, ViolinDataPoint }; | ||
//# sourceMappingURL=index.d.ts.map | ||
export * from './elements'; | ||
export * from './controllers'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -8,14 +8,6 @@ /** | ||
'use strict'; | ||
import { Element, BarElement, Tooltip, BarController, registry, Chart, LinearScale, CategoryScale } from 'chart.js'; | ||
import { drawPoint, merge } from 'chart.js/helpers'; | ||
import boxplots, { quantilesType7, quantilesHinges, quantilesFivenum, quantilesLinear, quantilesLower, quantilesHigher, quantilesNearest, quantilesMidpoint } from '@sgratzl/boxplots'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var chart_js = require('chart.js'); | ||
var helpers = require('chart.js/helpers'); | ||
var boxplots = require('@sgratzl/boxplots'); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var boxplots__default = /*#__PURE__*/_interopDefaultLegacy(boxplots); | ||
function gaussian(u) { | ||
@@ -89,17 +81,17 @@ return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * u * u); | ||
const lookup = { | ||
hinges: boxplots.quantilesHinges, | ||
fivenum: boxplots.quantilesFivenum, | ||
7: boxplots.quantilesType7, | ||
quantiles: boxplots.quantilesType7, | ||
linear: boxplots.quantilesLinear, | ||
lower: boxplots.quantilesLower, | ||
higher: boxplots.quantilesHigher, | ||
nearest: boxplots.quantilesNearest, | ||
midpoint: boxplots.quantilesMidpoint, | ||
hinges: quantilesHinges, | ||
fivenum: quantilesFivenum, | ||
7: quantilesType7, | ||
quantiles: quantilesType7, | ||
linear: quantilesLinear, | ||
lower: quantilesLower, | ||
higher: quantilesHigher, | ||
nearest: quantilesNearest, | ||
midpoint: quantilesMidpoint, | ||
}; | ||
return lookup[q] || boxplots.quantilesType7; | ||
return lookup[q] || quantilesType7; | ||
} | ||
function determineStatsOptions(options) { | ||
const coef = options == null || typeof options.coef !== 'number' ? defaultStatsOptions.coef : options.coef; | ||
const q = options == null || options.quantiles == null ? boxplots.quantilesType7 : options.quantiles; | ||
const q = options == null || options.quantiles == null ? quantilesType7 : options.quantiles; | ||
const quantiles = determineQuantiles(q); | ||
@@ -112,3 +104,3 @@ return { | ||
function boxplotStats(arr, options) { | ||
const r = boxplots__default['default'](arr, determineStatsOptions(options)); | ||
const r = boxplots(arr, determineStatsOptions(options)); | ||
return { | ||
@@ -132,2 +124,3 @@ items: Array.from(r.items), | ||
const items = arr.filter((v) => typeof v === 'number' && !Number.isNaN(v)).sort((a, b) => a - b); | ||
const mean = items.reduce((acc, v) => acc + v, 0) / items.length; | ||
const { quantiles } = determineStatsOptions(options); | ||
@@ -152,2 +145,3 @@ const stats = quantiles(items); | ||
items, | ||
mean, | ||
max, | ||
@@ -197,3 +191,3 @@ coords, | ||
const baseDefaults = { | ||
const baseDefaults$1 = { | ||
borderWidth: 1, | ||
@@ -206,2 +200,5 @@ outlierStyle: 'circle', | ||
itemBorderWidth: 0, | ||
meanStyle: 'circle', | ||
meanRadius: 3, | ||
meanBorderWidth: 1, | ||
hitPadding: 2, | ||
@@ -215,5 +212,7 @@ outlierHitRadius: 4, | ||
itemBorderColor: 'borderColor', | ||
meanBackgroundColor: 'backgroundColor', | ||
meanBorderColor: 'borderColor', | ||
}; | ||
const baseOptionKeys = (() => Object.keys(baseDefaults).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase extends chart_js.Element { | ||
const baseOptionKeys = (() => Object.keys(baseDefaults$1).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase$1 extends Element { | ||
isVertical() { | ||
@@ -224,3 +223,3 @@ return this.getProps(['height']).height == null; | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height']); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height', 'outliers']); | ||
const options = this.options; | ||
@@ -240,5 +239,8 @@ if (options.itemRadius <= 0 || !props.items || props.items.length <= 0) { | ||
}; | ||
const outliers = new Set(props.outliers || []); | ||
if (vert) { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
if (!outliers.has(v)) { | ||
drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
} | ||
}); | ||
@@ -248,3 +250,5 @@ } | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
if (!outliers.has(v)) { | ||
drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
} | ||
}); | ||
@@ -272,3 +276,3 @@ } | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x, v); | ||
drawPoint(ctx, pointOptions, props.x, v); | ||
}); | ||
@@ -278,3 +282,3 @@ } | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y); | ||
drawPoint(ctx, pointOptions, v, props.y); | ||
}); | ||
@@ -284,2 +288,26 @@ } | ||
} | ||
_drawMeanDot(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'mean']); | ||
const options = this.options; | ||
if (options.meanRadius <= 0 || props.mean == null || Number.isNaN(props.mean)) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.fillStyle = options.meanBackgroundColor; | ||
ctx.strokeStyle = options.meanBorderColor; | ||
ctx.lineWidth = options.meanBorderWidth; | ||
const pointOptions = { | ||
pointStyle: options.meanStyle, | ||
radius: options.meanRadius, | ||
borderWidth: options.meanBorderWidth, | ||
}; | ||
if (vert) { | ||
drawPoint(ctx, pointOptions, props.x, props.mean); | ||
} | ||
else { | ||
drawPoint(ctx, pointOptions, props.mean, props.y); | ||
} | ||
ctx.restore(); | ||
} | ||
_getBounds(_useFinalPosition) { | ||
@@ -379,3 +407,3 @@ return { | ||
const boxOptionsKeys = baseOptionKeys.concat(['medianColor', 'lowerBackgroundColor']); | ||
class BoxAndWiskers extends StatsBase { | ||
class BoxAndWiskers extends StatsBase$1 { | ||
draw(ctx) { | ||
@@ -388,2 +416,3 @@ ctx.save(); | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
ctx.restore(); | ||
@@ -535,9 +564,9 @@ this._drawItems(ctx); | ||
BoxAndWiskers.id = 'boxandwhiskers'; | ||
BoxAndWiskers.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults, { | ||
BoxAndWiskers.defaults = Object.assign({}, BarElement.defaults, baseDefaults$1, { | ||
medianColor: 'transparent', | ||
lowerBackgroundColor: 'transparent', | ||
}); | ||
BoxAndWiskers.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
BoxAndWiskers.defaultRoutes = Object.assign({}, BarElement.defaultRoutes, baseRoutes); | ||
class Violin extends StatsBase { | ||
class Violin extends StatsBase$1 { | ||
draw(ctx) { | ||
@@ -549,3 +578,3 @@ ctx.save(); | ||
const props = this.getProps(['x', 'y', 'width', 'height', 'min', 'max', 'coords', 'maxEstimate']); | ||
helpers.drawPoint(ctx, { | ||
drawPoint(ctx, { | ||
pointStyle: 'rectRot', | ||
@@ -559,2 +588,3 @@ radius: 5, | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
ctx.restore(); | ||
@@ -621,4 +651,4 @@ this._drawItems(ctx); | ||
Violin.id = 'violin'; | ||
Violin.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults); | ||
Violin.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
Violin.defaults = Object.assign({}, BarElement.defaults, baseDefaults$1); | ||
Violin.defaultRoutes = Object.assign({}, BarElement.defaultRoutes, baseRoutes); | ||
@@ -688,20 +718,22 @@ const interpolators = { | ||
outlierPositioner.register = () => { | ||
chart_js.Tooltip.positioners.averageInstance = outlierPositioner; | ||
Tooltip.positioners.averageInstance = outlierPositioner; | ||
return outlierPositioner; | ||
}; | ||
function baseDefaults$1(keys) { | ||
function baseDefaults(keys) { | ||
const colorKeys = ['borderColor', 'backgroundColor'].concat(keys.filter((c) => c.endsWith('Color'))); | ||
return { | ||
datasets: Object.assign({ | ||
animation: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
show: { | ||
return Object.assign({ | ||
animations: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
}, | ||
transitions: { | ||
show: { | ||
animations: { | ||
colors: { | ||
@@ -713,3 +745,5 @@ type: 'color', | ||
}, | ||
hide: { | ||
}, | ||
hide: { | ||
animations: { | ||
colors: { | ||
@@ -722,9 +756,15 @@ type: 'color', | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions), | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions); | ||
} | ||
function defaultOverrides() { | ||
return { | ||
plugins: { | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
}, | ||
@@ -734,6 +774,19 @@ }, | ||
} | ||
class StatsBase$1 extends chart_js.BarController { | ||
class StatsBase extends BarController { | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'mean']) { | ||
const v = source[key]; | ||
if (typeof v === 'number') { | ||
target[key] = mapper(v); | ||
} | ||
} | ||
for (const key of ['outliers', 'items']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
} | ||
getMinMax(scale, canStack) { | ||
const bak = scale.axis; | ||
const config = this._config; | ||
const config = this.options; | ||
scale.axis = config.minStats; | ||
@@ -755,3 +808,3 @@ const min = super.getMinMax(scale, canStack).min; | ||
parsed[iScale.axis] = iScale.parse(labels[index], index); | ||
const stats = this._parseStats(data == null ? null : data[index], this._config); | ||
const stats = this._parseStats(data == null ? null : data[index], this.options); | ||
if (stats) { | ||
@@ -782,3 +835,3 @@ Object.assign(parsed, stats); | ||
}; | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v), 'string'); | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v)); | ||
const s = this._toStringStats(r.value); | ||
@@ -793,2 +846,6 @@ r.value.toString = function () { | ||
} | ||
_toStringStats(b) { | ||
const f = (v) => (v == null ? 'NaN' : v.toLocaleString()); | ||
return `(min: ${f(b.min)}, 25% quantile: ${f(b.q1)}, median: ${f(b.median)}, mean: ${f(b.mean)}, 75% quantile: ${f(b.q3)}, max: ${f(b.max)})`; | ||
} | ||
updateElement(rectangle, index, properties, mode) { | ||
@@ -801,3 +858,3 @@ const reset = mode === 'reset'; | ||
properties._index = index; | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index)), mode); | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index))); | ||
super.updateElement(rectangle, index, properties, mode); | ||
@@ -808,14 +865,14 @@ } | ||
function patchController(type, config, controller, elements = [], scales = []) { | ||
chart_js.registry.addControllers(controller); | ||
registry.addControllers(controller); | ||
if (Array.isArray(elements)) { | ||
chart_js.registry.addElements(...elements); | ||
registry.addElements(...elements); | ||
} | ||
else { | ||
chart_js.registry.addElements(elements); | ||
registry.addElements(elements); | ||
} | ||
if (Array.isArray(scales)) { | ||
chart_js.registry.addScales(...scales); | ||
registry.addScales(...scales); | ||
} | ||
else { | ||
chart_js.registry.addScales(scales); | ||
registry.addScales(scales); | ||
} | ||
@@ -827,40 +884,31 @@ const c = config; | ||
class BoxPlotController extends StatsBase$1 { | ||
class BoxPlotController extends StatsBase { | ||
_parseStats(value, config) { | ||
return asBoxPlotStats(value, config); | ||
} | ||
_toStringStats(b) { | ||
return `(min: ${b.min}, 25% quantile: ${b.q1}, median: ${b.median}, 75% quantile: ${b.q3}, max: ${b.max})`; | ||
} | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax', 'mean']) { | ||
super._transformStats(target, source, mapper); | ||
for (const key of ['whiskerMin', 'whiskerMax']) { | ||
target[key] = mapper(source[key]); | ||
} | ||
for (const key of ['outliers', 'items']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
} | ||
} | ||
BoxPlotController.id = 'boxplot'; | ||
BoxPlotController.defaults = helpers.merge({}, [ | ||
chart_js.BarController.defaults, | ||
baseDefaults$1(boxOptionsKeys), | ||
BoxPlotController.defaults = merge({}, [ | ||
BarController.defaults, | ||
baseDefaults(boxOptionsKeys), | ||
{ | ||
datasets: { | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: BarController.defaults.animations.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
}, | ||
dataElementType: BoxAndWiskers.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(boxOptionsKeys), | ||
}, | ||
]); | ||
class BoxPlotChart extends chart_js.Chart { | ||
BoxPlotController.overrides = merge({}, [BarController.overrides, defaultOverrides()]); | ||
class BoxPlotChart extends Chart { | ||
constructor(item, config) { | ||
super(item, patchController('boxplot', config, BoxPlotController, BoxAndWiskers, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
super(item, patchController('boxplot', config, BoxPlotController, BoxAndWiskers, [LinearScale, CategoryScale])); | ||
} | ||
@@ -870,21 +918,11 @@ } | ||
class ViolinController extends StatsBase$1 { | ||
class ViolinController extends StatsBase { | ||
_parseStats(value, config) { | ||
return asViolinStats(value, config); | ||
} | ||
_toStringStats(v) { | ||
return `(min: ${v.min}, 25% quantile: ${v.q1}, median: ${v.median}, 75% quantile: ${v.q3}, max: ${v.max})`; | ||
} | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1']) { | ||
target[key] = mapper(source[key]); | ||
} | ||
super._transformStats(target, source, mapper); | ||
target.maxEstimate = source.maxEstimate; | ||
for (const key of ['items', 'outliers']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
if (Array.isArray(source.coords)) { | ||
target.coords = source.coords.map((coord) => Object.assign({}, coord, { v: mapper(coord.v) })); | ||
target.coords = source.coords.map((c) => Object.assign({}, c, { v: mapper(c.v) })); | ||
} | ||
@@ -894,26 +932,24 @@ } | ||
ViolinController.id = 'violin'; | ||
ViolinController.defaults = helpers.merge({}, [ | ||
chart_js.BarController.defaults, | ||
baseDefaults$1(baseOptionKeys), | ||
ViolinController.defaults = merge({}, [ | ||
BarController.defaults, | ||
baseDefaults(baseOptionKeys), | ||
{ | ||
datasets: { | ||
points: 100, | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], baseOptionKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
points: 100, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: BarController.defaults.animations.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], baseOptionKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
}, | ||
dataElementType: Violin.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(baseOptionKeys), | ||
}, | ||
]); | ||
class ViolinChart extends chart_js.Chart { | ||
ViolinController.overrides = merge({}, [BarController.overrides, defaultOverrides()]); | ||
class ViolinChart extends Chart { | ||
constructor(item, config) { | ||
super(item, patchController('violin', config, ViolinController, Violin, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
super(item, patchController('violin', config, ViolinController, Violin, [LinearScale, CategoryScale])); | ||
} | ||
@@ -923,9 +959,3 @@ } | ||
exports.BoxAndWiskers = BoxAndWiskers; | ||
exports.BoxPlotChart = BoxPlotChart; | ||
exports.BoxPlotController = BoxPlotController; | ||
exports.StatsBase = StatsBase; | ||
exports.Violin = Violin; | ||
exports.ViolinChart = ViolinChart; | ||
exports.ViolinController = ViolinController; | ||
export { BoxAndWiskers, BoxPlotChart, BoxPlotController, StatsBase$1 as StatsBase, Violin, ViolinChart, ViolinController }; | ||
//# sourceMappingURL=index.js.map |
@@ -323,2 +323,3 @@ /** | ||
const items = arr.filter((v) => typeof v === 'number' && !Number.isNaN(v)).sort((a, b) => a - b); | ||
const mean = items.reduce((acc, v) => acc + v, 0) / items.length; | ||
const { quantiles } = determineStatsOptions(options); | ||
@@ -343,2 +344,3 @@ const stats = quantiles(items); | ||
items, | ||
mean, | ||
max, | ||
@@ -455,16 +457,18 @@ coords, | ||
function baseDefaults(keys) { | ||
function baseDefaults$1(keys) { | ||
const colorKeys = ['borderColor', 'backgroundColor'].concat(keys.filter((c) => c.endsWith('Color'))); | ||
return { | ||
datasets: Object.assign({ | ||
animation: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
show: { | ||
return Object.assign({ | ||
animations: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
}, | ||
transitions: { | ||
show: { | ||
animations: { | ||
colors: { | ||
@@ -476,3 +480,5 @@ type: 'color', | ||
}, | ||
hide: { | ||
}, | ||
hide: { | ||
animations: { | ||
colors: { | ||
@@ -485,9 +491,15 @@ type: 'color', | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions), | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions); | ||
} | ||
function defaultOverrides() { | ||
return { | ||
plugins: { | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
}, | ||
@@ -497,6 +509,19 @@ }, | ||
} | ||
class StatsBase extends chart_js.BarController { | ||
class StatsBase$1 extends chart_js.BarController { | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'mean']) { | ||
const v = source[key]; | ||
if (typeof v === 'number') { | ||
target[key] = mapper(v); | ||
} | ||
} | ||
for (const key of ['outliers', 'items']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
} | ||
getMinMax(scale, canStack) { | ||
const bak = scale.axis; | ||
const config = this._config; | ||
const config = this.options; | ||
scale.axis = config.minStats; | ||
@@ -518,3 +543,3 @@ const min = super.getMinMax(scale, canStack).min; | ||
parsed[iScale.axis] = iScale.parse(labels[index], index); | ||
const stats = this._parseStats(data == null ? null : data[index], this._config); | ||
const stats = this._parseStats(data == null ? null : data[index], this.options); | ||
if (stats) { | ||
@@ -545,3 +570,3 @@ Object.assign(parsed, stats); | ||
}; | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v), 'string'); | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v)); | ||
const s = this._toStringStats(r.value); | ||
@@ -556,2 +581,6 @@ r.value.toString = function () { | ||
} | ||
_toStringStats(b) { | ||
const f = (v) => (v == null ? 'NaN' : v.toLocaleString()); | ||
return `(min: ${f(b.min)}, 25% quantile: ${f(b.q1)}, median: ${f(b.median)}, mean: ${f(b.mean)}, 75% quantile: ${f(b.q3)}, max: ${f(b.max)})`; | ||
} | ||
updateElement(rectangle, index, properties, mode) { | ||
@@ -564,3 +593,3 @@ const reset = mode === 'reset'; | ||
properties._index = index; | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index)), mode); | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index))); | ||
super.updateElement(rectangle, index, properties, mode); | ||
@@ -570,3 +599,3 @@ } | ||
const baseDefaults$1 = { | ||
const baseDefaults = { | ||
borderWidth: 1, | ||
@@ -579,2 +608,5 @@ outlierStyle: 'circle', | ||
itemBorderWidth: 0, | ||
meanStyle: 'circle', | ||
meanRadius: 3, | ||
meanBorderWidth: 1, | ||
hitPadding: 2, | ||
@@ -588,5 +620,7 @@ outlierHitRadius: 4, | ||
itemBorderColor: 'borderColor', | ||
meanBackgroundColor: 'backgroundColor', | ||
meanBorderColor: 'borderColor', | ||
}; | ||
const baseOptionKeys = (() => Object.keys(baseDefaults$1).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase$1 extends chart_js.Element { | ||
const baseOptionKeys = (() => Object.keys(baseDefaults).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase extends chart_js.Element { | ||
isVertical() { | ||
@@ -597,3 +631,3 @@ return this.getProps(['height']).height == null; | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height']); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height', 'outliers']); | ||
const options = this.options; | ||
@@ -613,5 +647,8 @@ if (options.itemRadius <= 0 || !props.items || props.items.length <= 0) { | ||
}; | ||
const outliers = new Set(props.outliers || []); | ||
if (vert) { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
if (!outliers.has(v)) { | ||
helpers.drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
} | ||
}); | ||
@@ -621,3 +658,5 @@ } | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
if (!outliers.has(v)) { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
} | ||
}); | ||
@@ -655,2 +694,26 @@ } | ||
} | ||
_drawMeanDot(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'mean']); | ||
const options = this.options; | ||
if (options.meanRadius <= 0 || props.mean == null || Number.isNaN(props.mean)) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.fillStyle = options.meanBackgroundColor; | ||
ctx.strokeStyle = options.meanBorderColor; | ||
ctx.lineWidth = options.meanBorderWidth; | ||
const pointOptions = { | ||
pointStyle: options.meanStyle, | ||
radius: options.meanRadius, | ||
borderWidth: options.meanBorderWidth, | ||
}; | ||
if (vert) { | ||
helpers.drawPoint(ctx, pointOptions, props.x, props.mean); | ||
} | ||
else { | ||
helpers.drawPoint(ctx, pointOptions, props.mean, props.y); | ||
} | ||
ctx.restore(); | ||
} | ||
_getBounds(_useFinalPosition) { | ||
@@ -750,3 +813,3 @@ return { | ||
const boxOptionsKeys = baseOptionKeys.concat(['medianColor', 'lowerBackgroundColor']); | ||
class BoxAndWiskers extends StatsBase$1 { | ||
class BoxAndWiskers extends StatsBase { | ||
draw(ctx) { | ||
@@ -759,2 +822,3 @@ ctx.save(); | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
ctx.restore(); | ||
@@ -906,3 +970,3 @@ this._drawItems(ctx); | ||
BoxAndWiskers.id = 'boxandwhiskers'; | ||
BoxAndWiskers.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1, { | ||
BoxAndWiskers.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults, { | ||
medianColor: 'transparent', | ||
@@ -913,3 +977,3 @@ lowerBackgroundColor: 'transparent', | ||
class Violin extends StatsBase$1 { | ||
class Violin extends StatsBase { | ||
draw(ctx) { | ||
@@ -930,2 +994,3 @@ ctx.save(); | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
ctx.restore(); | ||
@@ -992,3 +1057,3 @@ this._drawItems(ctx); | ||
Violin.id = 'violin'; | ||
Violin.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1); | ||
Violin.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults); | ||
Violin.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
@@ -1015,18 +1080,11 @@ | ||
class BoxPlotController extends StatsBase { | ||
class BoxPlotController extends StatsBase$1 { | ||
_parseStats(value, config) { | ||
return asBoxPlotStats(value, config); | ||
} | ||
_toStringStats(b) { | ||
return `(min: ${b.min}, 25% quantile: ${b.q1}, median: ${b.median}, 75% quantile: ${b.q3}, max: ${b.max})`; | ||
} | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax', 'mean']) { | ||
super._transformStats(target, source, mapper); | ||
for (const key of ['whiskerMin', 'whiskerMax']) { | ||
target[key] = mapper(source[key]); | ||
} | ||
for (const key of ['outliers', 'items']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
} | ||
@@ -1037,16 +1095,14 @@ } | ||
chart_js.BarController.defaults, | ||
baseDefaults(boxOptionsKeys), | ||
baseDefaults$1(boxOptionsKeys), | ||
{ | ||
datasets: { | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.animations.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
}, | ||
dataElementType: BoxAndWiskers.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(boxOptionsKeys), | ||
}, | ||
]); | ||
BoxPlotController.overrides = helpers.merge({}, [chart_js.BarController.overrides, defaultOverrides()]); | ||
class BoxPlotChart extends chart_js.Chart { | ||
@@ -1059,21 +1115,11 @@ constructor(item, config) { | ||
class ViolinController extends StatsBase { | ||
class ViolinController extends StatsBase$1 { | ||
_parseStats(value, config) { | ||
return asViolinStats(value, config); | ||
} | ||
_toStringStats(v) { | ||
return `(min: ${v.min}, 25% quantile: ${v.q1}, median: ${v.median}, 75% quantile: ${v.q3}, max: ${v.max})`; | ||
} | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1']) { | ||
target[key] = mapper(source[key]); | ||
} | ||
super._transformStats(target, source, mapper); | ||
target.maxEstimate = source.maxEstimate; | ||
for (const key of ['items', 'outliers']) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
if (Array.isArray(source.coords)) { | ||
target.coords = source.coords.map((coord) => Object.assign({}, coord, { v: mapper(coord.v) })); | ||
target.coords = source.coords.map((c) => Object.assign({}, c, { v: mapper(c.v) })); | ||
} | ||
@@ -1085,21 +1131,19 @@ } | ||
chart_js.BarController.defaults, | ||
baseDefaults(baseOptionKeys), | ||
baseDefaults$1(baseOptionKeys), | ||
{ | ||
datasets: { | ||
points: 100, | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], baseOptionKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
points: 100, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.animations.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], baseOptionKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
}, | ||
dataElementType: Violin.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(baseOptionKeys), | ||
}, | ||
]); | ||
ViolinController.overrides = helpers.merge({}, [chart_js.BarController.overrides, defaultOverrides()]); | ||
class ViolinChart extends chart_js.Chart { | ||
@@ -1118,3 +1162,3 @@ constructor(item, config) { | ||
exports.BoxPlotController = BoxPlotController; | ||
exports.StatsBase = StatsBase$1; | ||
exports.StatsBase = StatsBase; | ||
exports.Violin = Violin; | ||
@@ -1121,0 +1165,0 @@ exports.ViolinChart = ViolinChart; |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).ChartBoxPlot={},t.Chart,t.Chart.helpers)}(this,(function(t,e,r){"use strict";function i(t,e,r){const i=e-1,o=e=>{const o=e*i,n=Math.floor(o),s=o-n,a=t[n];return 0===s?a:r(a,t[Math.min(n+1,i)],s)};return{q1:o(.25),median:o(.5),q3:o(.75)}}function o(t,e=t.length){return i(t,e,(t,e,r)=>t+r*(e-t))}function n(t,e=t.length){return i(t,e,(t,e,r)=>t+(e-t)*r)}function s(t,e=t.length){return i(t,e,t=>t)}function a(t,e=t.length){return i(t,e,(t,e)=>e)}function l(t,e=t.length){return i(t,e,(t,e,r)=>r<.5?t:e)}function u(t,e=t.length){return i(t,e,(t,e)=>.5*(t+e))}function h(t,e=t.length){const r=e,i=Math.floor((r+3)/2)/2,o=e=>.5*(t[Math.floor(e)-1]+t[Math.ceil(e)-1]);return{q1:o(i),median:o((r+1)/2),q3:o(r+1-i)}}function d(t,e=t.length){return h(t,e)}function m(t,e={}){const{quantiles:r,validAndSorted:i,coef:n,whiskersMode:s,eps:a}=Object.assign({coef:1.5,eps:.01,quantiles:o,validAndSorted:!1,whiskersMode:"nearest"},e),{missing:l,s:u,min:h,max:d,sum:m}=i?function(t){if(0===t.length)return{sum:Number.NaN,min:Number.NaN,max:Number.NaN,missing:0,s:[]};const e=t[0],r=t[t.length-1],i=(t,e)=>t+e;return{sum:(t instanceof Float32Array||Float64Array,t.reduce(i,0)),min:e,max:r,missing:0,s:t}}(t):function(t){let e=Number.POSITIVE_INFINITY,r=Number.NEGATIVE_INFINITY,i=0,o=0;const n=t.length,s=t instanceof Float64Array?new Float64Array(n):new Float32Array(n);for(let a=0;a<n;++a){const n=t[a];null==n||Number.isNaN(n)||(s[o]=n,o++,n<e&&(e=n),n>r&&(r=n),i+=n)}const a=n-o;if(0===o)return{sum:i,min:e,max:r,missing:a,s:[]};const l=o===n?s:s.subarray(0,o);return l.sort((t,e)=>t===e?0:t<e?-1:1),{sum:i,min:l[0],max:l[l.length-1],missing:a,s:l}}(t),c={min:Number.NaN,max:Number.NaN,mean:Number.NaN,missing:l,iqr:Number.NaN,count:t.length,whiskerHigh:Number.NaN,whiskerLow:Number.NaN,outlier:[],median:Number.NaN,q1:Number.NaN,q3:Number.NaN,items:[]},f=(t,e)=>Math.abs(t-e)<a,g=t.length-l;if(0===g)return c;const{median:p,q1:x,q3:y}=r(u,g),b=y-x,q="number"==typeof n&&n>0;let w=q?Math.max(h,x-n*b):h,k=q?Math.min(d,y+n*b):d;const M=[];for(let t=0;t<g;++t){const e=u[t];if(e>=w||f(e,w)){"nearest"===s&&(w=e);break}0!==M.length&&f(M[M.length-1],e)||M.push(e)}const N=[];for(let t=g-1;t>=0;--t){const e=u[t];if(e<=k||f(e,k)){"nearest"===s&&(k=e);break}0!==N.length&&f(N[N.length-1],e)||0!==M.length&&f(M[M.length-1],e)||N.push(e)}return{min:h,max:d,count:t.length,missing:l,mean:m/g,whiskerHigh:k,whiskerLow:w,outlier:M.concat(N.reverse()),median:p,q1:x,q3:y,iqr:b,items:u}}function c(t){const e=t.length;if(e<1)return Number.NaN;if(1===e)return 0;const r=function(t){return 0===t.length?Number.NaN:t.reduce((t,e,r)=>t+(e-t)/(r+1),0)}(t);return t.reduce((t,e)=>t+(e-r)*(e-r),0)/(t.length-1)}function f(t,e,r){const i=function(t,e){const r=e(t),i=(r.q3-r.q1)/1.34;return 1.06*Math.min(Math.sqrt(c(t)),i)*Math.pow(t.length,-.2)}(e,r);return t.map(t=>{const r=e.reduce((e,r)=>{return e+(o=(t-r)/i,1/Math.sqrt(2*Math.PI)*Math.exp(-.5*o*o));var o},0);return{v:t,estimate:r/i/e.length}})}const g={coef:1.5,quantiles:7};function p(t){return{coef:null==t||"number"!=typeof t.coef?g.coef:t.coef,quantiles:function(t){return"function"==typeof t?t:{hinges:d,fivenum:h,7:o,quantiles:o,linear:n,lower:s,higher:a,nearest:l,midpoint:u}[t]||o}(null==t||null==t.quantiles?o:t.quantiles)}}function x(t,e){if(t){if("number"==typeof t.median&&"number"==typeof t.q1&&"number"==typeof t.q3){if(void 0===t.whiskerMin){const{coef:r}=p(e),{whiskerMin:i,whiskerMax:o}=function(t,e,r=1.5){const i=t.q3-t.q1,o="number"==typeof r&&r>0;let n=o?Math.max(t.min,t.q1-r*i):t.min,s=o?Math.min(t.max,t.q3+r*i):t.max;if(Array.isArray(e)){for(let t=0;t<e.length;t++){const r=e[t];if(r>=n){n=r;break}}for(let t=e.length-1;t>=0;t--){const r=e[t];if(r<=s){s=r;break}}}return{whiskerMin:n,whiskerMax:s}}(t,Array.isArray(t.items)?t.items.slice().sort((t,e)=>t-e):null,r);t.whiskerMin=i,t.whiskerMax=o}return t}if(Array.isArray(t))return function(t,e){const r=m(t,p(e));return{items:Array.from(r.items),outliers:r.outlier,whiskerMax:r.whiskerHigh,whiskerMin:r.whiskerLow,max:r.max,median:r.median,mean:r.mean,min:r.min,q1:r.q1,q3:r.q3}}(t,e)}}function y(t,e){if(t){if("number"==typeof t.median&&Array.isArray(t.coords))return t;if(Array.isArray(t))return function(t,e){if(0===t.length)return;const r=t.filter(t=>"number"==typeof t&&!Number.isNaN(t)).sort((t,e)=>t-e),{quantiles:i}=p(e),o=i(r),n=r[0],s=r[r.length-1],a=[],l=(s-n)/e.points;for(let t=n;t<=s&&l>0;t+=l)a.push(t);a[a.length-1]!==s&&a.push(s);const u=f(r,a,i),h=u.reduce((t,e)=>Math.max(t,e.estimate),Number.NEGATIVE_INFINITY);return{...o,min:n,items:r,max:s,coords:u,outliers:[],maxEstimate:h}}(t,e)}}const b={number:(t,e,r)=>t===e||null==t?e:null==e?t:t+(e-t)*r};function q(t,e,r){return"number"==typeof t&&"number"==typeof e?b.number(t,e,r):Array.isArray(t)&&Array.isArray(e)?e.map((e,i)=>b.number(t[i],e,r)):e}function w(t){const e=t.formattedValue,r=this;e&&null!=r._tooltipOutlier&&t.datasetIndex===r._tooltipOutlier.datasetIndex&&(e.hoveredOutlierIndex=r._tooltipOutlier.index)}function k(t,e){if(!t.length)return!1;let r=0,i=0,o=0;for(let n=0;n<t.length;++n){const s=t[n].element;if(s&&s.hasValue()){const t=s.tooltipPosition(e,this);r+=t.x,i+=t.y,++o}}return{x:r/o,y:i/o}}function M(t){const e=["borderColor","backgroundColor"].concat(t.filter(t=>t.endsWith("Color")));return{datasets:Object.assign({animation:{numberArray:{fn:q,properties:["outliers","items"]},colors:{type:"color",properties:e},show:{colors:{type:"color",properties:e,from:"transparent"}},hide:{colors:{type:"color",properties:e,to:"transparent"}}},minStats:"min",maxStats:"max"},g),tooltips:{position:k.register().id,callbacks:{beforeLabel:w}}}}k.id="averageInstance",k.register=()=>(e.Tooltip.positioners.averageInstance=k,k);class N extends e.BarController{getMinMax(t,e){const r=t.axis,i=this._config;t.axis=i.minStats;const o=super.getMinMax(t,e).min;t.axis=i.maxStats;const n=super.getMinMax(t,e).max;return t.axis=r,{min:o,max:n}}parsePrimitiveData(t,e,r,i){const o=t.vScale,n=t.iScale,s=n.getLabels(),a=[];for(let t=0;t<i;t++){const i=t+r,l={};l[n.axis]=n.parse(s[i],i);const u=this._parseStats(null==e?null:e[i],this._config);u&&(Object.assign(l,u),l[o.axis]=u.median),a.push(l)}return a}parseArrayData(t,e,r,i){return this.parsePrimitiveData(t,e,r,i)}parseObjectData(t,e,r,i){return this.parsePrimitiveData(t,e,r,i)}getLabelAndValue(t){const e=super.getLabelAndValue(t),r=this._cachedMeta.vScale,i=this.getParsed(t);if(!r||!i||"NaN"===e.value)return e;e.value={raw:i,hoveredOutlierIndex:-1},this._transformStats(e.value,i,t=>r.getLabelForValue(t),"string");const o=this._toStringStats(e.value);return e.value.toString=function(){return this.hoveredOutlierIndex>=0?`(outlier: ${this.outliers[this.hoveredOutlierIndex]})`:o},e}updateElement(t,e,r,i){const o="reset"===i,n=this._cachedMeta.vScale,s=this.getParsed(e),a=n.getBasePixel();r._datasetIndex=this.index,r._index=e,this._transformStats(r,s,t=>o?a:n.getPixelForValue(t,e),i),super.updateElement(t,e,r,i)}}const C={borderWidth:1,outlierStyle:"circle",outlierRadius:2,outlierBorderWidth:1,itemStyle:"circle",itemRadius:0,itemBorderWidth:0,hitPadding:2,outlierHitRadius:4},_={outlierBackgroundColor:"backgroundColor",outlierBorderColor:"borderColor",itemBackgroundColor:"backgroundColor",itemBorderColor:"borderColor"},v=Object.keys(C).concat(Object.keys(_));class P extends e.Element{isVertical(){return null==this.getProps(["height"]).height}_drawItems(t){const e=this.isVertical(),i=this.getProps(["x","y","items","width","height"]),o=this.options;if(o.itemRadius<=0||!i.items||i.items.length<=0)return;t.save(),t.strokeStyle=o.itemBorderColor,t.fillStyle=o.itemBackgroundColor,t.lineWidth=o.itemBorderWidth;const n=function(t=Date.now()){let e=t;return()=>(e=(9301*e+49297)%233280,e/233280)}(1e3*this._datasetIndex+this._index),s={pointStyle:o.itemStyle,radius:o.itemRadius,borderWidth:o.itemBorderWidth};e?i.items.forEach(e=>{r.drawPoint(t,s,i.x-i.width/2+n()*i.width,e)}):i.items.forEach(e=>{r.drawPoint(t,s,e,i.y-i.height/2+n()*i.height)}),t.restore()}_drawOutliers(t){const e=this.isVertical(),i=this.getProps(["x","y","outliers"]),o=this.options;if(o.outlierRadius<=0||!i.outliers||0===i.outliers.length)return;t.save(),t.fillStyle=o.outlierBackgroundColor,t.strokeStyle=o.outlierBorderColor,t.lineWidth=o.outlierBorderWidth;const n={pointStyle:o.outlierStyle,radius:o.outlierRadius,borderWidth:o.outlierBorderWidth};e?i.outliers.forEach(e=>{r.drawPoint(t,n,i.x,e)}):i.outliers.forEach(e=>{r.drawPoint(t,n,e,i.y)}),t.restore()}_getBounds(t){return{left:0,top:0,right:0,bottom:0}}_getHitBounds(t){const e=this.options.hitPadding,r=this._getBounds(t);return{left:r.left-e,top:r.top-e,right:r.right+e,bottom:r.bottom+e}}inRange(t,e,r){return(!Number.isNaN(this.x)||!Number.isNaN(this.y))&&(this._boxInRange(t,e,r)||this._outlierIndexInRange(t,e,r)>=0)}inXRange(t,e){const r=this._getHitBounds(e);return t>=r.left&&t<=r.right}inYRange(t,e){const r=this._getHitBounds(e);return t>=r.top&&t<=r.bottom}_outlierIndexInRange(t,e,r){const i=this.getProps(["x","y"],r),o=this.options.outlierHitRadius,n=this._getOutliers(r),s=this.isVertical();if(s&&Math.abs(t-i.x)>o||!s&&Math.abs(e-i.y)>o)return-1;const a=s?e:t;for(let t=0;t<n.length;t++)if(Math.abs(n[t]-a)<=o)return t;return-1}_boxInRange(t,e,r){const i=this._getHitBounds(r);return t>=i.left&&t<=i.right&&e>=i.top&&e<=i.bottom}getCenterPoint(t){const e=this.getProps(["x","y"],t);return{x:e.x,y:e.y}}_getOutliers(t){return this.getProps(["outliers"],t).outliers||[]}tooltipPosition(t,e){if(!t||"boolean"==typeof t)return this.getCenterPoint();e&&delete e._tooltipOutlier;const r=this.getProps(["x","y"]),i=this._outlierIndexInRange(t.x,t.y);return i<0||!e?this.getCenterPoint():(e._tooltipOutlier={index:i,datasetIndex:this._datasetIndex},this.isVertical()?{x:r.x,y:this._getOutliers()[i]}:{x:this._getOutliers()[i],y:r.y})}}const B=v.concat(["medianColor","lowerBackgroundColor"]);class S extends P{draw(t){t.save(),t.fillStyle=this.options.backgroundColor,t.strokeStyle=this.options.borderColor,t.lineWidth=this.options.borderWidth,this._drawBoxPlot(t),this._drawOutliers(t),t.restore(),this._drawItems(t)}_drawBoxPlot(t){this.isVertical()?this._drawBoxPlotVertical(t):this._drawBoxPlotHorizontal(t)}_drawBoxPlotVertical(t){const e=this.options,r=this.getProps(["x","width","q1","q3","median","whiskerMin","whiskerMax"]),i=r.x,o=r.width,n=i-o/2;r.q3>r.q1?t.fillRect(n,r.q1,o,r.q3-r.q1):t.fillRect(n,r.q3,o,r.q1-r.q3),t.save(),e.medianColor&&"transparent"!==e.medianColor&&"#0000"!==e.medianColor&&(t.strokeStyle=e.medianColor),t.beginPath(),t.moveTo(n,r.median),t.lineTo(n+o,r.median),t.closePath(),t.stroke(),t.restore(),t.save(),e.lowerBackgroundColor&&"transparent"!==e.lowerBackgroundColor&&"#0000"!==e.lowerBackgroundColor&&(t.fillStyle=e.lowerBackgroundColor,r.q3>r.q1?t.fillRect(n,r.median,o,r.q3-r.median):t.fillRect(n,r.median,o,r.q1-r.median)),t.restore(),r.q3>r.q1?t.strokeRect(n,r.q1,o,r.q3-r.q1):t.strokeRect(n,r.q3,o,r.q1-r.q3),t.beginPath(),t.moveTo(n,r.whiskerMin),t.lineTo(n+o,r.whiskerMin),t.moveTo(i,r.whiskerMin),t.lineTo(i,r.q1),t.moveTo(n,r.whiskerMax),t.lineTo(n+o,r.whiskerMax),t.moveTo(i,r.whiskerMax),t.lineTo(i,r.q3),t.closePath(),t.stroke()}_drawBoxPlotHorizontal(t){const e=this.options,r=this.getProps(["y","height","q1","q3","median","whiskerMin","whiskerMax"]),i=r.y,o=r.height,n=i-o/2;r.q3>r.q1?t.fillRect(r.q1,n,r.q3-r.q1,o):t.fillRect(r.q3,n,r.q1-r.q3,o),t.save(),e.medianColor&&"transparent"!==e.medianColor&&(t.strokeStyle=e.medianColor),t.beginPath(),t.moveTo(r.median,n),t.lineTo(r.median,n+o),t.closePath(),t.stroke(),t.restore(),t.save(),e.lowerBackgroundColor&&"transparent"!==e.lowerBackgroundColor&&(t.fillStyle=e.lowerBackgroundColor,r.q3>r.q1?t.fillRect(r.median,n,r.q3-r.median,o):t.fillRect(r.median,n,r.q1-r.median,o)),t.restore(),r.q3>r.q1?t.strokeRect(r.q1,n,r.q3-r.q1,o):t.strokeRect(r.q3,n,r.q1-r.q3,o),t.beginPath(),t.moveTo(r.whiskerMin,n),t.lineTo(r.whiskerMin,n+o),t.moveTo(r.whiskerMin,i),t.lineTo(r.q1,i),t.moveTo(r.whiskerMax,n),t.lineTo(r.whiskerMax,n+o),t.moveTo(r.whiskerMax,i),t.lineTo(r.q3,i),t.closePath(),t.stroke()}_getBounds(t){const e=this.isVertical();if(null==this.x)return{left:0,top:0,right:0,bottom:0};if(e){const{x:e,width:r,whiskerMax:i,whiskerMin:o}=this.getProps(["x","width","whiskerMin","whiskerMax"],t),n=e-r/2;return{left:n,top:i,right:n+r,bottom:o}}const{y:r,height:i,whiskerMax:o,whiskerMin:n}=this.getProps(["y","height","whiskerMin","whiskerMax"],t),s=r-i/2;return{left:n,top:s,right:o,bottom:s+i}}}S.id="boxandwhiskers",S.defaults=Object.assign({},e.BarElement.defaults,C,{medianColor:"transparent",lowerBackgroundColor:"transparent"}),S.defaultRoutes=Object.assign({},e.BarElement.defaultRoutes,_);class A extends P{draw(t){t.save(),t.fillStyle=this.options.backgroundColor,t.strokeStyle=this.options.borderColor,t.lineWidth=this.options.borderWidth;const e=this.getProps(["x","y","width","height","min","max","coords","maxEstimate"]);r.drawPoint(t,{pointStyle:"rectRot",radius:5,borderWidth:this.options.borderWidth},e.x,e.y),e.coords&&e.coords.length>0&&this._drawCoords(t,e),this._drawOutliers(t),t.restore(),this._drawItems(t)}_drawCoords(t,e){if(t.beginPath(),this.isVertical()){const r=e.x,i=e.width/2/e.maxEstimate;t.moveTo(r,e.min),e.coords.forEach(e=>{t.lineTo(r-e.estimate*i,e.v)}),t.lineTo(r,e.max),t.moveTo(r,e.min),e.coords.forEach(e=>{t.lineTo(r+e.estimate*i,e.v)}),t.lineTo(r,e.max)}else{const r=e.y,i=e.height/2/e.maxEstimate;t.moveTo(e.min,r),e.coords.forEach(e=>{t.lineTo(e.v,r-e.estimate*i)}),t.lineTo(e.max,r),t.moveTo(e.min,r),e.coords.forEach(e=>{t.lineTo(e.v,r+e.estimate*i)}),t.lineTo(e.max,r)}t.closePath(),t.stroke(),t.fill()}_getBounds(t){if(this.isVertical()){const{x:e,width:r,min:i,max:o}=this.getProps(["x","width","min","max"],t),n=e-r/2;return{left:n,top:o,right:n+r,bottom:i}}const{y:e,height:r,min:i,max:o}=this.getProps(["y","height","min","max"],t),n=e-r/2;return{left:i,top:n,right:o,bottom:n+r}}}function T(t,r,i,o=[],n=[]){e.registry.addControllers(i),Array.isArray(o)?e.registry.addElements(...o):e.registry.addElements(o),Array.isArray(n)?e.registry.addScales(...n):e.registry.addScales(n);const s=r;return s.type=t,s}A.id="violin",A.defaults=Object.assign({},e.BarElement.defaults,C),A.defaultRoutes=Object.assign({},e.BarElement.defaultRoutes,_);class I extends N{_parseStats(t,e){return x(t,e)}_toStringStats(t){return`(min: ${t.min}, 25% quantile: ${t.q1}, median: ${t.median}, 75% quantile: ${t.q3}, max: ${t.max})`}_transformStats(t,e,r){for(const i of["min","max","median","q3","q1","whiskerMin","whiskerMax","mean"])t[i]=r(e[i]);for(const i of["outliers","items"])Array.isArray(e[i])&&(t[i]=e[i].map(r))}}I.id="boxplot",I.defaults=r.merge({},[e.BarController.defaults,M(B),{datasets:{animation:{numbers:{type:"number",properties:e.BarController.defaults.datasets.animation.numbers.properties.concat(["q1","q3","min","max","median","whiskerMin","whiskerMax","mean"],B.filter(t=>!t.endsWith("Color")))}}},dataElementType:S.id,dataElementOptions:e.BarController.defaults.dataElementOptions.concat(B)}]);class E extends e.Chart{constructor(t,r){super(t,T("boxplot",r,I,S,[e.LinearScale,e.CategoryScale]))}}E.id=I.id;class O extends N{_parseStats(t,e){return y(t,e)}_toStringStats(t){return`(min: ${t.min}, 25% quantile: ${t.q1}, median: ${t.median}, 75% quantile: ${t.q3}, max: ${t.max})`}_transformStats(t,e,r){for(const i of["min","max","median","q3","q1"])t[i]=r(e[i]);t.maxEstimate=e.maxEstimate;for(const i of["items","outliers"])Array.isArray(e[i])&&(t[i]=e[i].map(r));Array.isArray(e.coords)&&(t.coords=e.coords.map(t=>Object.assign({},t,{v:r(t.v)})))}}O.id="violin",O.defaults=r.merge({},[e.BarController.defaults,M(v),{datasets:{points:100,animation:{numbers:{type:"number",properties:e.BarController.defaults.datasets.animation.numbers.properties.concat(["q1","q3","min","max","median","maxEstimate"],v.filter(t=>!t.endsWith("Color")))},kdeCoords:{fn:function(t,e,r){return Array.isArray(t)&&Array.isArray(e)?e.map((e,i)=>({v:b.number(t[i]?t[i].v:null,e.v,r),estimate:b.number(t[i]?t[i].estimate:null,e.estimate,r)})):e},properties:["coords"]}}},dataElementType:A.id,dataElementOptions:e.BarController.defaults.dataElementOptions.concat(v)}]);class R extends e.Chart{constructor(t,r){super(t,T("violin",r,O,A,[e.LinearScale,e.CategoryScale]))}}R.id=O.id,e.registry.addControllers(I,O),e.registry.addElements(S,A),t.BoxAndWiskers=S,t.BoxPlotChart=E,t.BoxPlotController=I,t.StatsBase=P,t.Violin=A,t.ViolinChart=R,t.ViolinController=O,Object.defineProperty(t,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("chart.js"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["exports","chart.js","chart.js/helpers"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ChartBoxPlot={},e.Chart,e.Chart.helpers)}(this,(function(e,t,r){"use strict";function i(e,t,r){const i=t-1,o=t=>{const o=t*i,n=Math.floor(o),s=o-n,a=e[n];return 0===s?a:r(a,e[Math.min(n+1,i)],s)};return{q1:o(.25),median:o(.5),q3:o(.75)}}function o(e,t=e.length){return i(e,t,((e,t,r)=>e+r*(t-e)))}function n(e,t=e.length){return i(e,t,((e,t,r)=>e+(t-e)*r))}function s(e,t=e.length){return i(e,t,(e=>e))}function a(e,t=e.length){return i(e,t,((e,t)=>t))}function l(e,t=e.length){return i(e,t,((e,t,r)=>r<.5?e:t))}function u(e,t=e.length){return i(e,t,((e,t)=>.5*(e+t)))}function h(e,t=e.length){const r=t,i=Math.floor((r+3)/2)/2,o=t=>.5*(e[Math.floor(t)-1]+e[Math.ceil(t)-1]);return{q1:o(i),median:o((r+1)/2),q3:o(r+1-i)}}function d(e,t=e.length){return h(e,t)}function m(e,t={}){const{quantiles:r,validAndSorted:i,coef:n,whiskersMode:s,eps:a}=Object.assign({coef:1.5,eps:.01,quantiles:o,validAndSorted:!1,whiskersMode:"nearest"},t),{missing:l,s:u,min:h,max:d,sum:m}=i?function(e){if(0===e.length)return{sum:Number.NaN,min:Number.NaN,max:Number.NaN,missing:0,s:[]};const t=e[0],r=e[e.length-1],i=(e,t)=>e+t;return{sum:(e instanceof Float32Array||Float64Array,e.reduce(i,0)),min:t,max:r,missing:0,s:e}}(e):function(e){let t=Number.POSITIVE_INFINITY,r=Number.NEGATIVE_INFINITY,i=0,o=0;const n=e.length,s=e instanceof Float64Array?new Float64Array(n):new Float32Array(n);for(let a=0;a<n;++a){const n=e[a];null==n||Number.isNaN(n)||(s[o]=n,o++,n<t&&(t=n),n>r&&(r=n),i+=n)}const a=n-o;if(0===o)return{sum:i,min:t,max:r,missing:a,s:[]};const l=o===n?s:s.subarray(0,o);return l.sort(((e,t)=>e===t?0:e<t?-1:1)),{sum:i,min:l[0],max:l[l.length-1],missing:a,s:l}}(e),c={min:Number.NaN,max:Number.NaN,mean:Number.NaN,missing:l,iqr:Number.NaN,count:e.length,whiskerHigh:Number.NaN,whiskerLow:Number.NaN,outlier:[],median:Number.NaN,q1:Number.NaN,q3:Number.NaN,items:[]},f=(e,t)=>Math.abs(e-t)<a,g=e.length-l;if(0===g)return c;const{median:p,q1:x,q3:y}=r(u,g),b=y-x,w="number"==typeof n&&n>0;let q=w?Math.max(h,x-n*b):h,k=w?Math.min(d,y+n*b):d;const N=[];for(let e=0;e<g;++e){const t=u[e];if(t>=q||f(t,q)){"nearest"===s&&(q=t);break}0!==N.length&&f(N[N.length-1],t)||N.push(t)}const C=[];for(let e=g-1;e>=0;--e){const t=u[e];if(t<=k||f(t,k)){"nearest"===s&&(k=t);break}0!==C.length&&f(C[C.length-1],t)||0!==N.length&&f(N[N.length-1],t)||C.push(t)}return{min:h,max:d,count:e.length,missing:l,mean:m/g,whiskerHigh:k,whiskerLow:q,outlier:N.concat(C.reverse()),median:p,q1:x,q3:y,iqr:b,items:u}}function c(e){const t=e.length;if(t<1)return Number.NaN;if(1===t)return 0;const r=function(e){return 0===e.length?Number.NaN:e.reduce(((e,t,r)=>e+(t-e)/(r+1)),0)}(e);return e.reduce(((e,t)=>e+(t-r)*(t-r)),0)/(e.length-1)}function f(e,t,r){const i=function(e,t){const r=t(e),i=(r.q3-r.q1)/1.34;return 1.06*Math.min(Math.sqrt(c(e)),i)*Math.pow(e.length,-.2)}(t,r);return e.map((e=>{const r=t.reduce(((t,r)=>{return t+(o=(e-r)/i,1/Math.sqrt(2*Math.PI)*Math.exp(-.5*o*o));var o}),0);return{v:e,estimate:r/i/t.length}}))}const g={coef:1.5,quantiles:7};function p(e){return{coef:null==e||"number"!=typeof e.coef?g.coef:e.coef,quantiles:function(e){return"function"==typeof e?e:{hinges:d,fivenum:h,7:o,quantiles:o,linear:n,lower:s,higher:a,nearest:l,midpoint:u}[e]||o}(null==e||null==e.quantiles?o:e.quantiles)}}function x(e,t){if(e){if("number"==typeof e.median&&"number"==typeof e.q1&&"number"==typeof e.q3){if(void 0===e.whiskerMin){const{coef:r}=p(t),{whiskerMin:i,whiskerMax:o}=function(e,t,r=1.5){const i=e.q3-e.q1,o="number"==typeof r&&r>0;let n=o?Math.max(e.min,e.q1-r*i):e.min,s=o?Math.min(e.max,e.q3+r*i):e.max;if(Array.isArray(t)){for(let e=0;e<t.length;e++){const r=t[e];if(r>=n){n=r;break}}for(let e=t.length-1;e>=0;e--){const r=t[e];if(r<=s){s=r;break}}}return{whiskerMin:n,whiskerMax:s}}(e,Array.isArray(e.items)?e.items.slice().sort(((e,t)=>e-t)):null,r);e.whiskerMin=i,e.whiskerMax=o}return e}if(Array.isArray(e))return function(e,t){const r=m(e,p(t));return{items:Array.from(r.items),outliers:r.outlier,whiskerMax:r.whiskerHigh,whiskerMin:r.whiskerLow,max:r.max,median:r.median,mean:r.mean,min:r.min,q1:r.q1,q3:r.q3}}(e,t)}}function y(e,t){if(e){if("number"==typeof e.median&&Array.isArray(e.coords))return e;if(Array.isArray(e))return function(e,t){if(0===e.length)return;const r=e.filter((e=>"number"==typeof e&&!Number.isNaN(e))).sort(((e,t)=>e-t)),i=r.reduce(((e,t)=>e+t),0)/r.length,{quantiles:o}=p(t),n=o(r),s=r[0],a=r[r.length-1],l=[],u=(a-s)/t.points;for(let e=s;e<=a&&u>0;e+=u)l.push(e);l[l.length-1]!==a&&l.push(a);const h=f(r,l,o),d=h.reduce(((e,t)=>Math.max(e,t.estimate)),Number.NEGATIVE_INFINITY);return{...n,min:s,items:r,mean:i,max:a,coords:h,outliers:[],maxEstimate:d}}(e,t)}}const b={number:(e,t,r)=>e===t||null==e?t:null==t?e:e+(t-e)*r};function w(e,t,r){return"number"==typeof e&&"number"==typeof t?b.number(e,t,r):Array.isArray(e)&&Array.isArray(t)?t.map(((t,i)=>b.number(e[i],t,r))):t}function q(e){const t=e.formattedValue,r=this;t&&null!=r._tooltipOutlier&&e.datasetIndex===r._tooltipOutlier.datasetIndex&&(t.hoveredOutlierIndex=r._tooltipOutlier.index)}function k(e,t){if(!e.length)return!1;let r=0,i=0,o=0;for(let n=0;n<e.length;++n){const s=e[n].element;if(s&&s.hasValue()){const e=s.tooltipPosition(t,this);r+=e.x,i+=e.y,++o}}return{x:r/o,y:i/o}}function N(e){const t=["borderColor","backgroundColor"].concat(e.filter((e=>e.endsWith("Color"))));return Object.assign({animations:{numberArray:{fn:w,properties:["outliers","items"]},colors:{type:"color",properties:t}},transitions:{show:{animations:{colors:{type:"color",properties:t,from:"transparent"}}},hide:{animations:{colors:{type:"color",properties:t,to:"transparent"}}}},minStats:"min",maxStats:"max"},g)}function C(){return{plugins:{tooltips:{position:k.register().id,callbacks:{beforeLabel:q}}}}}k.id="averageInstance",k.register=()=>(t.Tooltip.positioners.averageInstance=k,k);class M extends t.BarController{_transformStats(e,t,r){for(const i of["min","max","median","q3","q1","mean"]){const o=t[i];"number"==typeof o&&(e[i]=r(o))}for(const i of["outliers","items"])Array.isArray(t[i])&&(e[i]=t[i].map(r))}getMinMax(e,t){const r=e.axis,i=this.options;e.axis=i.minStats;const o=super.getMinMax(e,t).min;e.axis=i.maxStats;const n=super.getMinMax(e,t).max;return e.axis=r,{min:o,max:n}}parsePrimitiveData(e,t,r,i){const o=e.vScale,n=e.iScale,s=n.getLabels(),a=[];for(let e=0;e<i;e++){const i=e+r,l={};l[n.axis]=n.parse(s[i],i);const u=this._parseStats(null==t?null:t[i],this.options);u&&(Object.assign(l,u),l[o.axis]=u.median),a.push(l)}return a}parseArrayData(e,t,r,i){return this.parsePrimitiveData(e,t,r,i)}parseObjectData(e,t,r,i){return this.parsePrimitiveData(e,t,r,i)}getLabelAndValue(e){const t=super.getLabelAndValue(e),r=this._cachedMeta.vScale,i=this.getParsed(e);if(!r||!i||"NaN"===t.value)return t;t.value={raw:i,hoveredOutlierIndex:-1},this._transformStats(t.value,i,(e=>r.getLabelForValue(e)));const o=this._toStringStats(t.value);return t.value.toString=function(){return this.hoveredOutlierIndex>=0?`(outlier: ${this.outliers[this.hoveredOutlierIndex]})`:o},t}_toStringStats(e){const t=e=>null==e?"NaN":e.toLocaleString();return`(min: ${t(e.min)}, 25% quantile: ${t(e.q1)}, median: ${t(e.median)}, mean: ${t(e.mean)}, 75% quantile: ${t(e.q3)}, max: ${t(e.max)})`}updateElement(e,t,r,i){const o="reset"===i,n=this._cachedMeta.vScale,s=this.getParsed(t),a=n.getBasePixel();r._datasetIndex=this.index,r._index=t,this._transformStats(r,s,(e=>o?a:n.getPixelForValue(e,t))),super.updateElement(e,t,r,i)}}const v={borderWidth:1,outlierStyle:"circle",outlierRadius:2,outlierBorderWidth:1,itemStyle:"circle",itemRadius:0,itemBorderWidth:0,meanStyle:"circle",meanRadius:3,meanBorderWidth:1,hitPadding:2,outlierHitRadius:4},_={outlierBackgroundColor:"backgroundColor",outlierBorderColor:"borderColor",itemBackgroundColor:"backgroundColor",itemBorderColor:"borderColor",meanBackgroundColor:"backgroundColor",meanBorderColor:"borderColor"},B=Object.keys(v).concat(Object.keys(_));class S extends t.Element{isVertical(){return null==this.getProps(["height"]).height}_drawItems(e){const t=this.isVertical(),i=this.getProps(["x","y","items","width","height","outliers"]),o=this.options;if(o.itemRadius<=0||!i.items||i.items.length<=0)return;e.save(),e.strokeStyle=o.itemBorderColor,e.fillStyle=o.itemBackgroundColor,e.lineWidth=o.itemBorderWidth;const n=function(e=Date.now()){let t=e;return()=>(t=(9301*t+49297)%233280,t/233280)}(1e3*this._datasetIndex+this._index),s={pointStyle:o.itemStyle,radius:o.itemRadius,borderWidth:o.itemBorderWidth},a=new Set(i.outliers||[]);t?i.items.forEach((t=>{a.has(t)||r.drawPoint(e,s,i.x-i.width/2+n()*i.width,t)})):i.items.forEach((t=>{a.has(t)||r.drawPoint(e,s,t,i.y-i.height/2+n()*i.height)})),e.restore()}_drawOutliers(e){const t=this.isVertical(),i=this.getProps(["x","y","outliers"]),o=this.options;if(o.outlierRadius<=0||!i.outliers||0===i.outliers.length)return;e.save(),e.fillStyle=o.outlierBackgroundColor,e.strokeStyle=o.outlierBorderColor,e.lineWidth=o.outlierBorderWidth;const n={pointStyle:o.outlierStyle,radius:o.outlierRadius,borderWidth:o.outlierBorderWidth};t?i.outliers.forEach((t=>{r.drawPoint(e,n,i.x,t)})):i.outliers.forEach((t=>{r.drawPoint(e,n,t,i.y)})),e.restore()}_drawMeanDot(e){const t=this.isVertical(),i=this.getProps(["x","y","mean"]),o=this.options;if(o.meanRadius<=0||null==i.mean||Number.isNaN(i.mean))return;e.save(),e.fillStyle=o.meanBackgroundColor,e.strokeStyle=o.meanBorderColor,e.lineWidth=o.meanBorderWidth;const n={pointStyle:o.meanStyle,radius:o.meanRadius,borderWidth:o.meanBorderWidth};t?r.drawPoint(e,n,i.x,i.mean):r.drawPoint(e,n,i.mean,i.y),e.restore()}_getBounds(e){return{left:0,top:0,right:0,bottom:0}}_getHitBounds(e){const t=this.options.hitPadding,r=this._getBounds(e);return{left:r.left-t,top:r.top-t,right:r.right+t,bottom:r.bottom+t}}inRange(e,t,r){return(!Number.isNaN(this.x)||!Number.isNaN(this.y))&&(this._boxInRange(e,t,r)||this._outlierIndexInRange(e,t,r)>=0)}inXRange(e,t){const r=this._getHitBounds(t);return e>=r.left&&e<=r.right}inYRange(e,t){const r=this._getHitBounds(t);return e>=r.top&&e<=r.bottom}_outlierIndexInRange(e,t,r){const i=this.getProps(["x","y"],r),o=this.options.outlierHitRadius,n=this._getOutliers(r),s=this.isVertical();if(s&&Math.abs(e-i.x)>o||!s&&Math.abs(t-i.y)>o)return-1;const a=s?t:e;for(let e=0;e<n.length;e++)if(Math.abs(n[e]-a)<=o)return e;return-1}_boxInRange(e,t,r){const i=this._getHitBounds(r);return e>=i.left&&e<=i.right&&t>=i.top&&t<=i.bottom}getCenterPoint(e){const t=this.getProps(["x","y"],e);return{x:t.x,y:t.y}}_getOutliers(e){return this.getProps(["outliers"],e).outliers||[]}tooltipPosition(e,t){if(!e||"boolean"==typeof e)return this.getCenterPoint();t&&delete t._tooltipOutlier;const r=this.getProps(["x","y"]),i=this._outlierIndexInRange(e.x,e.y);return i<0||!t?this.getCenterPoint():(t._tooltipOutlier={index:i,datasetIndex:this._datasetIndex},this.isVertical()?{x:r.x,y:this._getOutliers()[i]}:{x:this._getOutliers()[i],y:r.y})}}const P=B.concat(["medianColor","lowerBackgroundColor"]);class T extends S{draw(e){e.save(),e.fillStyle=this.options.backgroundColor,e.strokeStyle=this.options.borderColor,e.lineWidth=this.options.borderWidth,this._drawBoxPlot(e),this._drawOutliers(e),this._drawMeanDot(e),e.restore(),this._drawItems(e)}_drawBoxPlot(e){this.isVertical()?this._drawBoxPlotVertical(e):this._drawBoxPlotHorizontal(e)}_drawBoxPlotVertical(e){const t=this.options,r=this.getProps(["x","width","q1","q3","median","whiskerMin","whiskerMax"]),i=r.x,o=r.width,n=i-o/2;r.q3>r.q1?e.fillRect(n,r.q1,o,r.q3-r.q1):e.fillRect(n,r.q3,o,r.q1-r.q3),e.save(),t.medianColor&&"transparent"!==t.medianColor&&"#0000"!==t.medianColor&&(e.strokeStyle=t.medianColor),e.beginPath(),e.moveTo(n,r.median),e.lineTo(n+o,r.median),e.closePath(),e.stroke(),e.restore(),e.save(),t.lowerBackgroundColor&&"transparent"!==t.lowerBackgroundColor&&"#0000"!==t.lowerBackgroundColor&&(e.fillStyle=t.lowerBackgroundColor,r.q3>r.q1?e.fillRect(n,r.median,o,r.q3-r.median):e.fillRect(n,r.median,o,r.q1-r.median)),e.restore(),r.q3>r.q1?e.strokeRect(n,r.q1,o,r.q3-r.q1):e.strokeRect(n,r.q3,o,r.q1-r.q3),e.beginPath(),e.moveTo(n,r.whiskerMin),e.lineTo(n+o,r.whiskerMin),e.moveTo(i,r.whiskerMin),e.lineTo(i,r.q1),e.moveTo(n,r.whiskerMax),e.lineTo(n+o,r.whiskerMax),e.moveTo(i,r.whiskerMax),e.lineTo(i,r.q3),e.closePath(),e.stroke()}_drawBoxPlotHorizontal(e){const t=this.options,r=this.getProps(["y","height","q1","q3","median","whiskerMin","whiskerMax"]),i=r.y,o=r.height,n=i-o/2;r.q3>r.q1?e.fillRect(r.q1,n,r.q3-r.q1,o):e.fillRect(r.q3,n,r.q1-r.q3,o),e.save(),t.medianColor&&"transparent"!==t.medianColor&&(e.strokeStyle=t.medianColor),e.beginPath(),e.moveTo(r.median,n),e.lineTo(r.median,n+o),e.closePath(),e.stroke(),e.restore(),e.save(),t.lowerBackgroundColor&&"transparent"!==t.lowerBackgroundColor&&(e.fillStyle=t.lowerBackgroundColor,r.q3>r.q1?e.fillRect(r.median,n,r.q3-r.median,o):e.fillRect(r.median,n,r.q1-r.median,o)),e.restore(),r.q3>r.q1?e.strokeRect(r.q1,n,r.q3-r.q1,o):e.strokeRect(r.q3,n,r.q1-r.q3,o),e.beginPath(),e.moveTo(r.whiskerMin,n),e.lineTo(r.whiskerMin,n+o),e.moveTo(r.whiskerMin,i),e.lineTo(r.q1,i),e.moveTo(r.whiskerMax,n),e.lineTo(r.whiskerMax,n+o),e.moveTo(r.whiskerMax,i),e.lineTo(r.q3,i),e.closePath(),e.stroke()}_getBounds(e){const t=this.isVertical();if(null==this.x)return{left:0,top:0,right:0,bottom:0};if(t){const{x:t,width:r,whiskerMax:i,whiskerMin:o}=this.getProps(["x","width","whiskerMin","whiskerMax"],e),n=t-r/2;return{left:n,top:i,right:n+r,bottom:o}}const{y:r,height:i,whiskerMax:o,whiskerMin:n}=this.getProps(["y","height","whiskerMin","whiskerMax"],e),s=r-i/2;return{left:n,top:s,right:o,bottom:s+i}}}T.id="boxandwhiskers",T.defaults=Object.assign({},t.BarElement.defaults,v,{medianColor:"transparent",lowerBackgroundColor:"transparent"}),T.defaultRoutes=Object.assign({},t.BarElement.defaultRoutes,_);class A extends S{draw(e){e.save(),e.fillStyle=this.options.backgroundColor,e.strokeStyle=this.options.borderColor,e.lineWidth=this.options.borderWidth;const t=this.getProps(["x","y","width","height","min","max","coords","maxEstimate"]);r.drawPoint(e,{pointStyle:"rectRot",radius:5,borderWidth:this.options.borderWidth},t.x,t.y),t.coords&&t.coords.length>0&&this._drawCoords(e,t),this._drawOutliers(e),this._drawMeanDot(e),e.restore(),this._drawItems(e)}_drawCoords(e,t){if(e.beginPath(),this.isVertical()){const r=t.x,i=t.width/2/t.maxEstimate;e.moveTo(r,t.min),t.coords.forEach((t=>{e.lineTo(r-t.estimate*i,t.v)})),e.lineTo(r,t.max),e.moveTo(r,t.min),t.coords.forEach((t=>{e.lineTo(r+t.estimate*i,t.v)})),e.lineTo(r,t.max)}else{const r=t.y,i=t.height/2/t.maxEstimate;e.moveTo(t.min,r),t.coords.forEach((t=>{e.lineTo(t.v,r-t.estimate*i)})),e.lineTo(t.max,r),e.moveTo(t.min,r),t.coords.forEach((t=>{e.lineTo(t.v,r+t.estimate*i)})),e.lineTo(t.max,r)}e.closePath(),e.stroke(),e.fill()}_getBounds(e){if(this.isVertical()){const{x:t,width:r,min:i,max:o}=this.getProps(["x","width","min","max"],e),n=t-r/2;return{left:n,top:o,right:n+r,bottom:i}}const{y:t,height:r,min:i,max:o}=this.getProps(["y","height","min","max"],e),n=t-r/2;return{left:i,top:n,right:o,bottom:n+r}}}function I(e,r,i,o=[],n=[]){t.registry.addControllers(i),Array.isArray(o)?t.registry.addElements(...o):t.registry.addElements(o),Array.isArray(n)?t.registry.addScales(...n):t.registry.addScales(n);const s=r;return s.type=e,s}A.id="violin",A.defaults=Object.assign({},t.BarElement.defaults,v),A.defaultRoutes=Object.assign({},t.BarElement.defaultRoutes,_);class R extends M{_parseStats(e,t){return x(e,t)}_transformStats(e,t,r){super._transformStats(e,t,r);for(const i of["whiskerMin","whiskerMax"])e[i]=r(t[i])}}R.id="boxplot",R.defaults=r.merge({},[t.BarController.defaults,N(P),{animations:{numbers:{type:"number",properties:t.BarController.defaults.animations.numbers.properties.concat(["q1","q3","min","max","median","whiskerMin","whiskerMax","mean"],P.filter((e=>!e.endsWith("Color"))))}},dataElementType:T.id}]),R.overrides=r.merge({},[t.BarController.overrides,C()]);class E extends t.Chart{constructor(e,r){super(e,I("boxplot",r,R,T,[t.LinearScale,t.CategoryScale]))}}E.id=R.id;class O extends M{_parseStats(e,t){return y(e,t)}_transformStats(e,t,r){super._transformStats(e,t,r),e.maxEstimate=t.maxEstimate,Array.isArray(t.coords)&&(e.coords=t.coords.map((e=>Object.assign({},e,{v:r(e.v)}))))}}O.id="violin",O.defaults=r.merge({},[t.BarController.defaults,N(B),{points:100,animations:{numbers:{type:"number",properties:t.BarController.defaults.animations.numbers.properties.concat(["q1","q3","min","max","median","maxEstimate"],B.filter((e=>!e.endsWith("Color"))))},kdeCoords:{fn:function(e,t,r){return Array.isArray(e)&&Array.isArray(t)?t.map(((t,i)=>({v:b.number(e[i]?e[i].v:null,t.v,r),estimate:b.number(e[i]?e[i].estimate:null,t.estimate,r)}))):t},properties:["coords"]}},dataElementType:A.id}]),O.overrides=r.merge({},[t.BarController.overrides,C()]);class W extends t.Chart{constructor(e,r){super(e,I("violin",r,O,A,[t.LinearScale,t.CategoryScale]))}}W.id=O.id,t.registry.addControllers(R,O),t.registry.addElements(T,A),e.BoxAndWiskers=T,e.BoxPlotChart=E,e.BoxPlotController=R,e.StatsBase=S,e.Violin=A,e.ViolinChart=W,e.ViolinController=O,Object.defineProperty(e,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=index.umd.min.js.map |
{ | ||
"name": "@sgratzl/chartjs-chart-boxplot", | ||
"description": "Chart.js module for charting boxplots and violin charts", | ||
"version": "3.0.0-beta.9", | ||
"version": "3.0.0-rc.0", | ||
"publishConfig": { | ||
@@ -40,4 +40,5 @@ "access": "public" | ||
"global": "ChartBoxPlot", | ||
"module": "build/index.esm.js", | ||
"main": "build/index.js", | ||
"module": "build/index.js", | ||
"main": "build/index.cjs.js", | ||
"browser": "build/index.umd.js", | ||
"unpkg": "build/index.umd.min.js", | ||
@@ -49,6 +50,7 @@ "jsdelivr": "build/index.umd.min.js", | ||
"build", | ||
"src/**/*.ts" | ||
"src/**/*.ts", | ||
"src/**/*.tsx" | ||
], | ||
"peerDependencies": { | ||
"chart.js": "^3.0.0-beta" | ||
"chart.js": "^3.0.0-rc" | ||
}, | ||
@@ -64,19 +66,19 @@ "browserslist": [ | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^17.0.0", | ||
"@rollup/plugin-node-resolve": "^11.1.0", | ||
"@rollup/plugin-replace": "^2.3.4", | ||
"@rollup/plugin-typescript": "^8.1.0", | ||
"@types/jest": "^26.0.20", | ||
"@types/jest-image-snapshot": "^4.1.3", | ||
"@types/node": "^14.14.21", | ||
"@typescript-eslint/eslint-plugin": "^4.13.0", | ||
"@typescript-eslint/parser": "^4.13.0", | ||
"@rollup/plugin-commonjs": "^17.1.0", | ||
"@rollup/plugin-node-resolve": "^11.2.0", | ||
"@rollup/plugin-replace": "^2.4.1", | ||
"@rollup/plugin-typescript": "^8.2.0", | ||
"@types/jest": "^26.0.21", | ||
"@types/jest-image-snapshot": "^4.3.0", | ||
"@types/node": "^14.14.35", | ||
"@typescript-eslint/eslint-plugin": "^4.18.0", | ||
"@typescript-eslint/parser": "^4.18.0", | ||
"@yarnpkg/pnpify": "^2.4.0", | ||
"canvas": "^2.6.1", | ||
"canvas": "^2.7.0", | ||
"canvas-5-polyfill": "^0.1.5", | ||
"chart.js": "^3.0.0-beta.9", | ||
"eslint": "^7.18.0", | ||
"eslint-config-prettier": "^7.1.0", | ||
"chart.js": "^3.0.0-rc", | ||
"eslint": "^7.22.0", | ||
"eslint-config-prettier": "^8.1.0", | ||
"eslint-config-react-app": "^6.0.0", | ||
"eslint-plugin-flowtype": "^5.2.0", | ||
"eslint-plugin-flowtype": "^5.4.0", | ||
"eslint-plugin-import": "^2.22.1", | ||
@@ -88,36 +90,36 @@ "eslint-plugin-jsx-a11y": "^6.4.1", | ||
"jest": "^26.6.3", | ||
"jest-image-snapshot": "^4.3.0", | ||
"jest-image-snapshot": "^4.4.0", | ||
"prettier": "^2.2.1", | ||
"release-it": "^14.2.2", | ||
"release-it": "^14.4.1", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.36.2", | ||
"rollup": "^2.42.1", | ||
"rollup-plugin-cleanup": "^3.2.1", | ||
"rollup-plugin-dts": "^2.0.1", | ||
"rollup-plugin-dts": "^3.0.1", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"ts-jest": "^26.4.4", | ||
"ts-jest": "^26.5.4", | ||
"tslib": "^2.1.0", | ||
"typedoc": "^0.20.16", | ||
"typescript": "^4.1.3" | ||
"typedoc": "^0.20.32", | ||
"typescript": "^4.2.3" | ||
}, | ||
"scripts": { | ||
"clean": "rimraf build node_modules \"*.tgz\"", | ||
"compile": "tsc -p tsconfig.json --noEmit", | ||
"start": "npm run watch", | ||
"clean": "rimraf build docs node_modules \"*.tgz\" \"*.tsbuildinfo\"", | ||
"compile": "tsc -b tsconfig.c.json", | ||
"compile:types": "tsc -p tsconfig.c.json --emitDeclarationOnly", | ||
"start": "yarn run watch", | ||
"watch": "rollup -c -w", | ||
"build": "rollup -c", | ||
"build": "rollup -c && yarn run compile:types", | ||
"test": "jest --passWithNoTests", | ||
"test:watch": "jest --passWithNoTests --watch", | ||
"test:coverage": "jest --passWithNoTests --coverage", | ||
"lint": "npm run eslint && npm run prettier", | ||
"fix": "npm run eslint:fix && npm run prettier:write", | ||
"prettier:write": "prettier */** --write", | ||
"prettier": "prettier */** --check", | ||
"lint": "yarn run eslint && yarn run prettier", | ||
"fix": "yarn run eslint:fix && yarn run prettier:write", | ||
"prettier:write": "prettier \"*\" \"*/**\" --write", | ||
"prettier": "prettier \"*\" \"*/**\" --check", | ||
"eslint": "eslint src --ext .ts,.tsx", | ||
"eslint:fix": "npm run eslint -- --fix", | ||
"docs": "typedoc", | ||
"prepare": "npm run build", | ||
"eslint:fix": "yarn run eslint --fix", | ||
"docs": "typedoc src/index.ts", | ||
"prepare": "yarn run build", | ||
"release": "release-it --disable-metrics --npm.skipChecks", | ||
"release:pre": "release-it --disable-metrics --npm.skipChecks --preRelease=alpha --npm.tag=next", | ||
"release:beta": "release-it --disable-metrics --npm.skipChecks --preRelease=beta --npm.tag=next" | ||
"release:pre": "release-it --disable-metrics --npm.skipChecks --preRelease=alpha --npm.tag=next" | ||
} | ||
} |
@@ -105,4 +105,4 @@ # Chart.js Box and Violin Plot | ||
cat .yarnrc_patch.yml >> .yarnrc.yml | ||
yarn | ||
yarn pnpify --sdk | ||
yarn install | ||
yarn pnpify --sdk vscode | ||
``` | ||
@@ -109,0 +109,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { IKDEPoint } from './data'; | ||
import type { IKDEPoint } from './data'; | ||
@@ -3,0 +3,0 @@ const interpolators = { |
@@ -93,2 +93,4 @@ const Months = [ | ||
max: base[shift + 4]!, | ||
items: base, | ||
mean: base.reduce((acc, v) => acc + v, 0) / base.length, | ||
outliers: base.slice(0, 3).concat(base.slice(shift + 5)), | ||
@@ -95,0 +97,0 @@ }; |
import { interpolateNumberArray } from '../animation'; | ||
import { outlierPositioner, patchInHoveredOutlier } from '../tooltip'; | ||
import { BarController, Element, ChartMeta, LinearScale, Scale, UpdateMode } from 'chart.js'; | ||
import { defaultStatsOptions, IBaseOptions } from '../data'; | ||
import { defaultStatsOptions, IBaseOptions, IBaseStats } from '../data'; | ||
export /*#__PURE__*/ function baseDefaults(keys: string[]) { | ||
const colorKeys = ['borderColor', 'backgroundColor'].concat(keys.filter((c) => c.endsWith('Color'))); | ||
return { | ||
datasets: Object.assign( | ||
{ | ||
animation: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
show: { | ||
return Object.assign( | ||
{ | ||
animations: { | ||
numberArray: { | ||
fn: interpolateNumberArray, | ||
properties: ['outliers', 'items'], | ||
}, | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
}, | ||
}, | ||
transitions: { | ||
show: { | ||
animations: { | ||
colors: { | ||
@@ -27,3 +29,5 @@ type: 'color', | ||
}, | ||
hide: { | ||
}, | ||
hide: { | ||
animations: { | ||
colors: { | ||
@@ -36,11 +40,18 @@ type: 'color', | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, | ||
defaultStatsOptions | ||
), | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, | ||
defaultStatsOptions | ||
); | ||
} | ||
export function defaultOverrides() { | ||
return { | ||
plugins: { | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
}, | ||
@@ -51,8 +62,22 @@ }, | ||
export abstract class StatsBase<S extends { median: number }, C extends Required<IBaseOptions>> extends BarController { | ||
declare _config: C; | ||
export abstract class StatsBase<S extends IBaseStats, C extends Required<IBaseOptions>> extends BarController { | ||
declare options: C; | ||
protected _transformStats<T>(target: any, source: S, mapper: (v: number) => T): void { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'mean'] as const) { | ||
const v = source[key]; | ||
if (typeof v === 'number') { | ||
target[key] = mapper(v); | ||
} | ||
} | ||
for (const key of ['outliers', 'items'] as const) { | ||
if (Array.isArray(source[key])) { | ||
target[key] = source[key].map(mapper); | ||
} | ||
} | ||
} | ||
getMinMax(scale: Scale, canStack?: boolean | undefined) { | ||
const bak = scale.axis; | ||
const config = this._config; | ||
const config = this.options; | ||
scale.axis = config.minStats; | ||
@@ -75,3 +100,3 @@ const min = super.getMinMax(scale, canStack).min; | ||
parsed[iScale.axis] = iScale.parse(labels[index], index); | ||
const stats = this._parseStats(data == null ? null : data[index], this._config); | ||
const stats = this._parseStats(data == null ? null : data[index], this.options); | ||
if (stats) { | ||
@@ -94,3 +119,3 @@ Object.assign(parsed, stats); | ||
protected abstract _parseStats(value: any, config: C): S; | ||
protected abstract _parseStats(value: any, options: C): S; | ||
@@ -100,3 +125,3 @@ getLabelAndValue(index: number) { | ||
const vScale = this._cachedMeta.vScale; | ||
const parsed = this.getParsed(index); | ||
const parsed = (this.getParsed(index) as unknown) as S; | ||
if (!vScale || !parsed || r.value === 'NaN') { | ||
@@ -109,3 +134,3 @@ return r; | ||
}; | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v), 'string'); | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v)); | ||
const s = this._toStringStats(r.value); | ||
@@ -115,2 +140,3 @@ r.value.toString = function () { | ||
if (this.hoveredOutlierIndex >= 0) { | ||
// TODO formatter | ||
return `(outlier: ${this.outliers[this.hoveredOutlierIndex]})`; | ||
@@ -123,21 +149,20 @@ } | ||
protected abstract _toStringStats(b: S): string; | ||
protected _toStringStats(b: S) { | ||
// TODO formatter | ||
const f = (v: number) => (v == null ? 'NaN' : v.toLocaleString()); | ||
return `(min: ${f(b.min)}, 25% quantile: ${f(b.q1)}, median: ${f(b.median)}, mean: ${f(b.mean)}, 75% quantile: ${f( | ||
b.q3 | ||
)}, max: ${f(b.max)})`; | ||
} | ||
protected abstract _transformStats<T>( | ||
target: Record<keyof S, T | T[]>, | ||
source: S, | ||
mapper: (v: number) => T, | ||
mode: UpdateMode | 'string' | ||
): void; | ||
updateElement(rectangle: Element, index: number, properties: any, mode: UpdateMode) { | ||
const reset = mode === 'reset'; | ||
const scale = this._cachedMeta.vScale as LinearScale; | ||
const parsed = this.getParsed(index); | ||
const parsed = (this.getParsed(index) as unknown) as S; | ||
const base = scale.getBasePixel(); | ||
properties._datasetIndex = this.index; | ||
properties._index = index; | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index)), mode); | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index))); | ||
super.updateElement(rectangle, index, properties, mode); | ||
} | ||
} |
@@ -199,3 +199,3 @@ import createChart from '../__tests__/createChart'; | ||
fill: 1, | ||
data: samples.numbers({ count: 7, max: 150 }).map((d) => ({ y: d })), | ||
data: samples.numbers({ count: 7, max: 150 }) as any, | ||
}, | ||
@@ -202,0 +202,0 @@ ], |
@@ -13,6 +13,5 @@ import { asBoxPlotStats, IBaseStats, IBoxPlot, IBoxplotOptions } from '../data'; | ||
CartesianScaleTypeRegistry, | ||
ScriptableContext, | ||
} from 'chart.js'; | ||
import { merge } from 'chart.js/helpers'; | ||
import { baseDefaults, StatsBase } from './base'; | ||
import { baseDefaults, StatsBase, defaultOverrides } from './base'; | ||
import { BoxAndWiskers, IBoxAndWhiskersOptions } from '../elements'; | ||
@@ -23,21 +22,11 @@ import patchController from './patchController'; | ||
export class BoxPlotController extends StatsBase<IBoxPlot, Required<IBoxplotOptions>> { | ||
_parseStats(value: any, config: IBoxplotOptions) { | ||
protected _parseStats(value: any, config: IBoxplotOptions) { | ||
return asBoxPlotStats(value, config); | ||
} | ||
_toStringStats(b: IBoxPlot) { | ||
return `(min: ${b.min}, 25% quantile: ${b.q1}, median: ${b.median}, 75% quantile: ${b.q3}, max: ${b.max})`; | ||
} | ||
_transformStats<T>(target: any, source: IBoxPlot, mapper: (v: number) => T) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax', 'mean']) { | ||
target[key] = mapper( | ||
source[key as 'min' | 'max' | 'median' | 'q3' | 'q1' | 'whiskerMin' | 'whiskerMax' | 'mean'] | ||
); | ||
protected _transformStats<T>(target: any, source: IBoxPlot, mapper: (v: number) => T) { | ||
super._transformStats(target, source, mapper); | ||
for (const key of ['whiskerMin', 'whiskerMax']) { | ||
target[key] = mapper(source[key as 'whiskerMin' | 'whiskerMax']); | ||
} | ||
for (const key of ['outliers', 'items']) { | ||
if (Array.isArray(source[key as keyof IBoxPlot])) { | ||
target[key] = source[key as 'outliers' | 'items'].map(mapper); | ||
} | ||
} | ||
} | ||
@@ -50,17 +39,15 @@ | ||
{ | ||
datasets: { | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: BarController.defaults.datasets.animation.numbers.properties.concat( | ||
['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], | ||
boxOptionsKeys.filter((c) => !c.endsWith('Color')) | ||
), | ||
}, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: (BarController.defaults as any).animations.numbers.properties.concat( | ||
['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], | ||
boxOptionsKeys.filter((c) => !c.endsWith('Color')) | ||
), | ||
}, | ||
}, | ||
dataElementType: BoxAndWiskers.id, | ||
dataElementOptions: BarController.defaults.dataElementOptions.concat(boxOptionsKeys), | ||
}, | ||
]); | ||
static readonly overrides: any = /*#__PURE__*/ merge({}, [(BarController as any).overrides, defaultOverrides()]); | ||
} | ||
@@ -71,4 +58,4 @@ | ||
IBoxplotOptions, | ||
ScriptableAndArrayOptions<IBoxAndWhiskersOptions, ScriptableContext>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> {} | ||
ScriptableAndArrayOptions<IBoxAndWhiskersOptions, 'boxplot'>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, 'boxplot'> {} | ||
@@ -85,4 +72,5 @@ export type BoxPlotDataPoint = number[] | (Partial<IBoxPlot> & IBaseStats); | ||
datasetOptions: BoxPlotControllerDatasetOptions; | ||
defaultDataPoint: BoxPlotDataPoint[]; | ||
defaultDataPoint: BoxPlotDataPoint; | ||
scales: keyof CartesianScaleTypeRegistry; | ||
parsedDataType: IBoxPlot & ChartTypeRegistry['bar']['parsedDataType']; | ||
}; | ||
@@ -89,0 +77,0 @@ } |
@@ -13,6 +13,5 @@ import { asViolinStats, IBaseStats, IViolin, IViolinOptions } from '../data'; | ||
CartesianScaleTypeRegistry, | ||
ScriptableContext, | ||
} from 'chart.js'; | ||
import { merge } from 'chart.js/helpers'; | ||
import { StatsBase, baseDefaults } from './base'; | ||
import { StatsBase, baseDefaults, defaultOverrides } from './base'; | ||
import { baseOptionKeys } from '../elements/base'; | ||
@@ -24,22 +23,11 @@ import { IViolinElementOptions, Violin } from '../elements'; | ||
export class ViolinController extends StatsBase<IViolin, Required<IViolinOptions>> { | ||
_parseStats(value: any, config: IViolinOptions) { | ||
protected _parseStats(value: any, config: IViolinOptions) { | ||
return asViolinStats(value, config); | ||
} | ||
_toStringStats(v: IViolin) { | ||
return `(min: ${v.min}, 25% quantile: ${v.q1}, median: ${v.median}, 75% quantile: ${v.q3}, max: ${v.max})`; | ||
} | ||
_transformStats<T>(target: any, source: IViolin, mapper: (v: number) => T) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1']) { | ||
target[key] = mapper(source[key as 'min' | 'max' | 'median' | 'q3' | 'q1']); | ||
} | ||
protected _transformStats<T>(target: any, source: IViolin, mapper: (v: number) => T) { | ||
super._transformStats(target, source, mapper); | ||
target.maxEstimate = source.maxEstimate; | ||
for (const key of ['items', 'outliers']) { | ||
if (Array.isArray(source[key as keyof IViolin])) { | ||
target[key] = source[key as 'items' | 'outliers'].map(mapper); | ||
} | ||
} | ||
if (Array.isArray(source.coords)) { | ||
target.coords = source.coords.map((coord) => Object.assign({}, coord, { v: mapper(coord.v) })); | ||
target.coords = source.coords.map((c) => Object.assign({}, c, { v: mapper(c.v) })); | ||
} | ||
@@ -53,23 +41,22 @@ } | ||
{ | ||
datasets: { | ||
points: 100, | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: BarController.defaults.datasets.animation.numbers.properties.concat( | ||
['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], | ||
baseOptionKeys.filter((c) => !c.endsWith('Color')) | ||
), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
points: 100, | ||
animations: { | ||
numbers: { | ||
type: 'number', | ||
properties: (BarController.defaults as any).animations.numbers.properties.concat( | ||
['q1', 'q3', 'min', 'max', 'median', 'maxEstimate'], | ||
baseOptionKeys.filter((c) => !c.endsWith('Color')) | ||
), | ||
}, | ||
kdeCoords: { | ||
fn: interpolateKdeCoords, | ||
properties: ['coords'], | ||
}, | ||
}, | ||
dataElementType: Violin.id, | ||
dataElementOptions: BarController.defaults.dataElementOptions.concat(baseOptionKeys), | ||
}, | ||
]); | ||
static readonly overrides: any = /*#__PURE__*/ merge({}, [(BarController as any).overrides, defaultOverrides()]); | ||
} | ||
export type ViolinDataPoint = number[] | (Partial<IViolin> & IBaseStats); | ||
@@ -79,7 +66,5 @@ export interface ViolinControllerDatasetOptions | ||
IViolinOptions, | ||
ScriptableAndArrayOptions<IViolinElementOptions, ScriptableContext>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> {} | ||
ScriptableAndArrayOptions<IViolinElementOptions, 'violin'>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, 'violin'> {} | ||
export type ViolinDataPoint = number[] | (Partial<IViolin> & IBaseStats); | ||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
@@ -93,4 +78,5 @@ export interface IViolinChartOptions {} | ||
datasetOptions: ViolinControllerDatasetOptions; | ||
defaultDataPoint: ViolinDataPoint[]; | ||
defaultDataPoint: ViolinDataPoint; | ||
scales: keyof CartesianScaleTypeRegistry; | ||
parsedDataType: IViolin & ChartTypeRegistry['bar']['parsedDataType']; | ||
}; | ||
@@ -97,0 +83,0 @@ } |
@@ -29,10 +29,10 @@ import boxplots, { | ||
median: number; | ||
mean: number; | ||
items: readonly number[]; | ||
outliers: readonly number[]; | ||
} | ||
export interface IBoxPlot extends IBaseStats { | ||
items: readonly number[]; | ||
outliers: readonly number[]; | ||
whiskerMax: number; | ||
whiskerMin: number; | ||
mean: number; | ||
} | ||
@@ -46,6 +46,4 @@ | ||
export interface IViolin extends IBaseStats { | ||
items: readonly number[]; | ||
maxEstimate: number; | ||
coords: IKDEPoint[]; | ||
outliers: readonly number[]; | ||
} | ||
@@ -205,2 +203,4 @@ | ||
const mean = items.reduce((acc, v) => acc + v, 0) / items.length; | ||
const { quantiles } = determineStatsOptions(options); | ||
@@ -229,2 +229,3 @@ | ||
items, | ||
mean, | ||
max, | ||
@@ -231,0 +232,0 @@ coords, |
import { Element } from 'chart.js'; | ||
import { drawPoint } from 'chart.js/helpers'; | ||
import { rnd } from '../data'; | ||
import { ExtendedTooltip } from '../tooltip'; | ||
import type { ExtendedTooltip } from '../tooltip'; | ||
@@ -135,2 +135,50 @@ export interface IStatsBaseOptions { | ||
outlierHitRadius: number; | ||
/** | ||
* item style used to render mean dot | ||
* @default circle | ||
*/ | ||
meanStyle: | ||
| 'circle' | ||
| 'triangle' | ||
| 'rect' | ||
| 'rectRounded' | ||
| 'rectRot' | ||
| 'cross' | ||
| 'crossRot' | ||
| 'star' | ||
| 'line' | ||
| 'dash'; | ||
/** | ||
* radius used to mean dots | ||
* @default 3 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
meanRadius: number; | ||
/** | ||
* background color for mean dot | ||
* @default see rectangle.backgroundColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
meanBackgroundColor: string; | ||
/** | ||
* border color for mean dot | ||
* @default see rectangle.borderColor | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
meanBorderColor: string; | ||
/** | ||
* border width for mean dot | ||
* @default 0 | ||
* @scriptable | ||
* @indexable | ||
*/ | ||
meanBorderWidth: number; | ||
} | ||
@@ -149,2 +197,6 @@ | ||
meanStyle: 'circle', | ||
meanRadius: 3, | ||
meanBorderWidth: 1, | ||
hitPadding: 2, | ||
@@ -159,2 +211,4 @@ outlierHitRadius: 4, | ||
itemBorderColor: 'borderColor', | ||
meanBackgroundColor: 'backgroundColor', | ||
meanBorderColor: 'borderColor', | ||
}; | ||
@@ -171,2 +225,3 @@ | ||
outliers: number[]; | ||
mean: number; | ||
} | ||
@@ -182,5 +237,5 @@ | ||
_drawItems(ctx: CanvasRenderingContext2D) { | ||
protected _drawItems(ctx: CanvasRenderingContext2D) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height']); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height', 'outliers']); | ||
const options = this.options; | ||
@@ -204,10 +259,15 @@ | ||
}; | ||
const outliers = new Set(props.outliers || []); | ||
if (vert) { | ||
props.items.forEach((v) => { | ||
drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
if (!outliers.has(v)) { | ||
drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
} | ||
}); | ||
} else { | ||
props.items.forEach((v) => { | ||
drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
if (!outliers.has(v)) { | ||
drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
} | ||
}); | ||
@@ -218,3 +278,3 @@ } | ||
_drawOutliers(ctx: CanvasRenderingContext2D) { | ||
protected _drawOutliers(ctx: CanvasRenderingContext2D) { | ||
const vert = this.isVertical(); | ||
@@ -250,2 +310,29 @@ const props = this.getProps(['x', 'y', 'outliers']); | ||
protected _drawMeanDot(ctx: CanvasRenderingContext2D) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'mean']); | ||
const options = this.options; | ||
if (options.meanRadius <= 0 || props.mean == null || Number.isNaN(props.mean)) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.fillStyle = options.meanBackgroundColor; | ||
ctx.strokeStyle = options.meanBorderColor; | ||
ctx.lineWidth = options.meanBorderWidth; | ||
const pointOptions = { | ||
pointStyle: options.meanStyle, | ||
radius: options.meanRadius, | ||
borderWidth: options.meanBorderWidth, | ||
}; | ||
if (vert) { | ||
drawPoint(ctx, pointOptions, props.x, props.mean); | ||
} else { | ||
drawPoint(ctx, pointOptions, props.mean, props.y); | ||
} | ||
ctx.restore(); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
@@ -293,3 +380,3 @@ _getBounds(_useFinalPosition?: boolean) { | ||
_outlierIndexInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) { | ||
protected _outlierIndexInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) { | ||
const props = this.getProps(['x', 'y'], useFinalPosition); | ||
@@ -313,3 +400,3 @@ const hitRadius = this.options.outlierHitRadius; | ||
_boxInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) { | ||
protected _boxInRange(mouseX: number, mouseY: number, useFinalPosition?: boolean) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
@@ -327,3 +414,3 @@ return mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
_getOutliers(useFinalPosition?: boolean) { | ||
protected _getOutliers(useFinalPosition?: boolean) { | ||
const props = this.getProps(['outliers'], useFinalPosition); | ||
@@ -330,0 +417,0 @@ return props.outliers || []; |
@@ -43,2 +43,3 @@ import { BarElement } from 'chart.js'; | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
@@ -50,3 +51,3 @@ ctx.restore(); | ||
_drawBoxPlot(ctx: CanvasRenderingContext2D) { | ||
protected _drawBoxPlot(ctx: CanvasRenderingContext2D) { | ||
if (this.isVertical()) { | ||
@@ -59,3 +60,3 @@ this._drawBoxPlotVertical(ctx); | ||
_drawBoxPlotVertical(ctx: CanvasRenderingContext2D) { | ||
protected _drawBoxPlotVertical(ctx: CanvasRenderingContext2D) { | ||
const options = this.options; | ||
@@ -123,3 +124,3 @@ const props = this.getProps(['x', 'width', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); | ||
_drawBoxPlotHorizontal(ctx: CanvasRenderingContext2D) { | ||
protected _drawBoxPlotHorizontal(ctx: CanvasRenderingContext2D) { | ||
const options = this.options; | ||
@@ -126,0 +127,0 @@ const props = this.getProps(['y', 'height', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); |
import { BarElement } from 'chart.js'; | ||
import { drawPoint } from 'chart.js/helpers'; | ||
import { IKDEPoint } from '../data'; | ||
import type { IKDEPoint } from '../data'; | ||
import { StatsBase, baseDefaults, baseRoutes, IStatsBaseOptions, IStatsBaseProps } from './base'; | ||
@@ -40,2 +40,3 @@ | ||
this._drawOutliers(ctx); | ||
this._drawMeanDot(ctx); | ||
@@ -47,3 +48,3 @@ ctx.restore(); | ||
_drawCoords(ctx: CanvasRenderingContext2D, props: IViolinElementProps) { | ||
protected _drawCoords(ctx: CanvasRenderingContext2D, props: IViolinElementProps) { | ||
ctx.beginPath(); | ||
@@ -50,0 +51,0 @@ if (this.isVertical()) { |
import { InteractionItem, TooltipItem, Tooltip, TooltipModel } from 'chart.js'; | ||
export interface ExtendedTooltip extends TooltipModel { | ||
export interface ExtendedTooltip extends TooltipModel<'boxplot' | 'violin'> { | ||
_tooltipOutlier?: { | ||
@@ -10,3 +10,6 @@ index: number; | ||
export function patchInHoveredOutlier(this: TooltipModel, item: TooltipItem) { | ||
export function patchInHoveredOutlier( | ||
this: TooltipModel<'boxplot' | 'violin'>, | ||
item: TooltipItem<'boxplot' | 'violin'> | ||
) { | ||
const value = item.formattedValue as any; | ||
@@ -21,3 +24,3 @@ const that = this as ExtendedTooltip; | ||
export function outlierPositioner( | ||
this: TooltipModel, | ||
this: TooltipModel<'boxplot' | 'violin'>, | ||
items: readonly InteractionItem[], | ||
@@ -24,0 +27,0 @@ eventPosition: { x: number; y: number } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
371737
68
5438