@sgratzl/chartjs-chart-boxplot
Advanced tools
Comparing version 3.0.0-beta.7 to 3.0.0-beta.9
@@ -5,6 +5,6 @@ /** | ||
* | ||
* Copyright (c) 2020 Samuel Gratzl <sam@sgratzl.com> | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
import { TooltipModel, Element, BarController, Scale, ChartMeta, UpdateMode, ControllerDatasetOptions, ScriptableAndArrayOptions, CommonHoverOptions, CartesianScaleTypeRegistry, Chart, ChartItem, ChartConfiguration } from 'chart.js'; | ||
import { TooltipModel, Element, BarController, Scale, ChartMeta, UpdateMode, ControllerDatasetOptions, ScriptableAndArrayOptions, ScriptableContext, CommonHoverOptions, CartesianScaleTypeRegistry, Chart, ChartItem, ChartConfiguration } from 'chart.js'; | ||
@@ -182,2 +182,3 @@ interface ExtendedTooltip extends TooltipModel { | ||
whiskerMax: number; | ||
mean: number; | ||
} | ||
@@ -219,2 +220,3 @@ declare class BoxAndWiskers extends StatsBase<IBoxAndWhiskerProps, IBoxAndWhiskersOptions> { | ||
whiskerMin: number; | ||
mean: number; | ||
} | ||
@@ -331,3 +333,3 @@ interface IKDEPoint { | ||
} | ||
interface BoxPlotControllerDatasetOptions extends ControllerDatasetOptions, IBoxplotOptions, ScriptableAndArrayOptions<IBoxAndWhiskersOptions>, ScriptableAndArrayOptions<CommonHoverOptions> { | ||
interface BoxPlotControllerDatasetOptions extends ControllerDatasetOptions, IBoxplotOptions, ScriptableAndArrayOptions<IBoxAndWhiskersOptions, ScriptableContext>, ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> { | ||
} | ||
@@ -359,3 +361,3 @@ declare type BoxPlotDataPoint = number[] | (Partial<IBoxPlot> & IBaseStats); | ||
} | ||
interface ViolinControllerDatasetOptions extends ControllerDatasetOptions, IViolinOptions, ScriptableAndArrayOptions<IViolinElementOptions>, ScriptableAndArrayOptions<CommonHoverOptions> { | ||
interface ViolinControllerDatasetOptions extends ControllerDatasetOptions, IViolinOptions, ScriptableAndArrayOptions<IViolinElementOptions, ScriptableContext>, ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> { | ||
} | ||
@@ -362,0 +364,0 @@ declare type ViolinDataPoint = number[] | (Partial<IViolin> & IBaseStats); |
@@ -5,3 +5,3 @@ /** | ||
* | ||
* Copyright (c) 2020 Samuel Gratzl <sam@sgratzl.com> | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
@@ -111,2 +111,3 @@ | ||
median: r.median, | ||
mean: r.mean, | ||
min: r.min, | ||
@@ -799,3 +800,3 @@ q1: r.q1, | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax']) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax', 'mean']) { | ||
target[key] = mapper(source[key]); | ||
@@ -819,3 +820,3 @@ } | ||
type: 'number', | ||
properties: BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
properties: BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
@@ -822,0 +823,0 @@ }, |
@@ -5,3 +5,3 @@ /** | ||
* | ||
* Copyright (c) 2020 Samuel Gratzl <sam@sgratzl.com> | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
@@ -119,2 +119,3 @@ | ||
median: r.median, | ||
mean: r.mean, | ||
min: r.min, | ||
@@ -807,3 +808,3 @@ q1: r.q1, | ||
_transformStats(target, source, mapper) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax']) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax', 'mean']) { | ||
target[key] = mapper(source[key]); | ||
@@ -827,3 +828,3 @@ } | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
@@ -830,0 +831,0 @@ }, |
@@ -5,1104 +5,1096 @@ /** | ||
* | ||
* Copyright (c) 2020 Samuel Gratzl <sam@sgratzl.com> | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chart.js'), require('chart.js/helpers')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'chart.js/helpers'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ChartBoxPlot = {}, global.Chart, global.Chart.helpers)); | ||
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('chart.js'), require('chart.js/helpers')) : | ||
typeof define === 'function' && define.amd ? define(['exports', 'chart.js', 'chart.js/helpers'], factory) : | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ChartBoxPlot = {}, global.Chart, global.Chart.helpers)); | ||
}(this, (function (exports, chart_js, helpers) { 'use strict'; | ||
/** | ||
* boxplots | ||
* https://github.com/sgratzl/boxplots | ||
* | ||
* Copyright (c) 2020 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
function quantilesInterpolate(arr, length, interpolate) { | ||
const n1 = length - 1; | ||
const compute = q => { | ||
const index = q * n1; | ||
const lo = Math.floor(index); | ||
const h = index - lo; | ||
const a = arr[lo]; | ||
return h === 0 ? a : interpolate(a, arr[Math.min(lo + 1, n1)], h); | ||
}; | ||
return { | ||
q1: compute(0.25), | ||
median: compute(0.5), | ||
q3: compute(0.75) | ||
}; | ||
} | ||
function quantilesType7(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (a, b, alpha) => a + alpha * (b - a)); | ||
} | ||
function quantilesLinear(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j, fraction) => i + (j - i) * fraction); | ||
} | ||
function quantilesLower(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, i => i); | ||
} | ||
function quantilesHigher(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (_, j) => j); | ||
} | ||
function quantilesNearest(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j, fraction) => fraction < 0.5 ? i : j); | ||
} | ||
function quantilesMidpoint(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j) => (i + j) * 0.5); | ||
} | ||
function quantilesFivenum(arr, length = arr.length) { | ||
const n = length; | ||
const n4 = Math.floor((n + 3) / 2) / 2; | ||
const compute = d => 0.5 * (arr[Math.floor(d) - 1] + arr[Math.ceil(d) - 1]); | ||
return { | ||
q1: compute(n4), | ||
median: compute((n + 1) / 2), | ||
q3: compute(n + 1 - n4) | ||
}; | ||
} | ||
function quantilesHinges(arr, length = arr.length) { | ||
return quantilesFivenum(arr, length); | ||
} | ||
/** | ||
* @sgratzl/boxplots | ||
* https://github.com/sgratzl/boxplots | ||
* | ||
* Copyright (c) 2021 Samuel Gratzl <sam@sgratzl.com> | ||
*/ | ||
function createSortedData(data) { | ||
let min = Number.POSITIVE_INFINITY; | ||
let max = Number.NEGATIVE_INFINITY; | ||
let sum = 0; | ||
let valid = 0; | ||
let length = data.length; | ||
const vs = data instanceof Float64Array ? new Float64Array(length) : new Float32Array(length); | ||
for (let i = 0; i < length; ++i) { | ||
const v = data[i]; | ||
if (v == null || Number.isNaN(v)) { | ||
continue; | ||
} | ||
vs[valid] = v; | ||
valid++; | ||
if (v < min) { | ||
min = v; | ||
} | ||
if (v > max) { | ||
max = v; | ||
} | ||
sum += v; | ||
function quantilesInterpolate(arr, length, interpolate) { | ||
const n1 = length - 1; | ||
const compute = (q) => { | ||
const index = q * n1; | ||
const lo = Math.floor(index); | ||
const h = index - lo; | ||
const a = arr[lo]; | ||
return h === 0 ? a : interpolate(a, arr[Math.min(lo + 1, n1)], h); | ||
}; | ||
return { | ||
q1: compute(0.25), | ||
median: compute(0.5), | ||
q3: compute(0.75), | ||
}; | ||
} | ||
const missing = length - valid; | ||
if (valid === 0) { | ||
return { | ||
sum, | ||
min, | ||
max, | ||
missing, | ||
s: [] | ||
}; | ||
function quantilesType7(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (a, b, alpha) => a + alpha * (b - a)); | ||
} | ||
const s = (valid === length ? vs : vs.subarray(0, valid)).sort((a, b) => a === b ? 0 : a < b ? -1 : 1); | ||
return { | ||
sum, | ||
min: s[0], | ||
max: s[s.length - 1], | ||
missing, | ||
s | ||
}; | ||
} | ||
function withSortedData(data) { | ||
if (data.length === 0) { | ||
return { | ||
sum: Number.NaN, | ||
min: Number.NaN, | ||
max: Number.NaN, | ||
missing: 0, | ||
s: [] | ||
}; | ||
function quantilesLinear(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j, fraction) => i + (j - i) * fraction); | ||
} | ||
const min = data[0]; | ||
const max = data[data.length - 1]; | ||
const red = (acc, v) => acc + v; | ||
const sum = data instanceof Float32Array ? data.reduce(red, 0) : data instanceof Float64Array ? data.reduce(red, 0) : data.reduce(red, 0); | ||
return { | ||
sum, | ||
min, | ||
max, | ||
missing: 0, | ||
s: data | ||
}; | ||
} | ||
function boxplot(data, options = {}) { | ||
const { | ||
quantiles, | ||
validAndSorted, | ||
coef, | ||
whiskersMode, | ||
eps | ||
} = Object.assign({ | ||
coef: 1.5, | ||
eps: 10e-3, | ||
quantiles: quantilesType7, | ||
validAndSorted: false, | ||
whiskersMode: 'nearest' | ||
}, options); | ||
const { | ||
missing, | ||
s, | ||
min, | ||
max, | ||
sum | ||
} = validAndSorted ? withSortedData(data) : createSortedData(data); | ||
const invalid = { | ||
min: Number.NaN, | ||
max: Number.NaN, | ||
mean: Number.NaN, | ||
missing, | ||
iqr: Number.NaN, | ||
count: data.length, | ||
whiskerHigh: Number.NaN, | ||
whiskerLow: Number.NaN, | ||
outlier: [], | ||
median: Number.NaN, | ||
q1: Number.NaN, | ||
q3: Number.NaN, | ||
items: [] | ||
}; | ||
const same = (a, b) => Math.abs(a - b) < eps; | ||
const valid = data.length - missing; | ||
if (valid === 0) { | ||
return invalid; | ||
function quantilesLower(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i) => i); | ||
} | ||
const { | ||
median, | ||
q1, | ||
q3 | ||
} = quantiles(s, valid); | ||
const iqr = q3 - q1; | ||
const isCoefValid = typeof coef === 'number' && coef > 0; | ||
let whiskerLow = isCoefValid ? Math.max(min, q1 - coef * iqr) : min; | ||
let whiskerHigh = isCoefValid ? Math.min(max, q3 + coef * iqr) : max; | ||
const outlier = []; | ||
for (let i = 0; i < valid; ++i) { | ||
const v = s[i]; | ||
if (v >= whiskerLow || same(v, whiskerLow)) { | ||
if (whiskersMode === 'nearest') { | ||
whiskerLow = v; | ||
function quantilesHigher(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (_, j) => j); | ||
} | ||
function quantilesNearest(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j, fraction) => (fraction < 0.5 ? i : j)); | ||
} | ||
function quantilesMidpoint(arr, length = arr.length) { | ||
return quantilesInterpolate(arr, length, (i, j) => (i + j) * 0.5); | ||
} | ||
function quantilesFivenum(arr, length = arr.length) { | ||
const n = length; | ||
const n4 = Math.floor((n + 3) / 2) / 2; | ||
const compute = (d) => 0.5 * (arr[Math.floor(d) - 1] + arr[Math.ceil(d) - 1]); | ||
return { | ||
q1: compute(n4), | ||
median: compute((n + 1) / 2), | ||
q3: compute(n + 1 - n4), | ||
}; | ||
} | ||
function quantilesHinges(arr, length = arr.length) { | ||
return quantilesFivenum(arr, length); | ||
} | ||
function createSortedData(data) { | ||
let min = Number.POSITIVE_INFINITY; | ||
let max = Number.NEGATIVE_INFINITY; | ||
let sum = 0; | ||
let valid = 0; | ||
const length = data.length; | ||
const vs = data instanceof Float64Array ? new Float64Array(length) : new Float32Array(length); | ||
for (let i = 0; i < length; ++i) { | ||
const v = data[i]; | ||
if (v == null || Number.isNaN(v)) { | ||
continue; | ||
} | ||
vs[valid] = v; | ||
valid++; | ||
if (v < min) { | ||
min = v; | ||
} | ||
if (v > max) { | ||
max = v; | ||
} | ||
sum += v; | ||
} | ||
break; | ||
} | ||
if (outlier.length === 0 || !same(outlier[outlier.length - 1], v)) { | ||
outlier.push(v); | ||
} | ||
const missing = length - valid; | ||
if (valid === 0) { | ||
return { | ||
sum, | ||
min, | ||
max, | ||
missing, | ||
s: [], | ||
}; | ||
} | ||
const s = valid === length ? vs : vs.subarray(0, valid); | ||
s.sort((a, b) => (a === b ? 0 : a < b ? -1 : 1)); | ||
return { | ||
sum, | ||
min: s[0], | ||
max: s[s.length - 1], | ||
missing, | ||
s, | ||
}; | ||
} | ||
const reversedOutliers = []; | ||
for (let i = valid - 1; i >= 0; --i) { | ||
const v = s[i]; | ||
if (v <= whiskerHigh || same(v, whiskerHigh)) { | ||
if (whiskersMode === 'nearest') { | ||
whiskerHigh = v; | ||
function withSortedData(data) { | ||
if (data.length === 0) { | ||
return { | ||
sum: Number.NaN, | ||
min: Number.NaN, | ||
max: Number.NaN, | ||
missing: 0, | ||
s: [], | ||
}; | ||
} | ||
break; | ||
} | ||
if ((reversedOutliers.length === 0 || !same(reversedOutliers[reversedOutliers.length - 1], v)) && (outlier.length === 0 || !same(outlier[outlier.length - 1], v))) { | ||
reversedOutliers.push(v); | ||
} | ||
const min = data[0]; | ||
const max = data[data.length - 1]; | ||
const red = (acc, v) => acc + v; | ||
const sum = data instanceof Float32Array | ||
? data.reduce(red, 0) | ||
: data instanceof Float64Array | ||
? data.reduce(red, 0) | ||
: data.reduce(red, 0); | ||
return { | ||
sum, | ||
min, | ||
max, | ||
missing: 0, | ||
s: data, | ||
}; | ||
} | ||
return { | ||
min, | ||
max, | ||
count: data.length, | ||
missing, | ||
mean: sum / valid, | ||
whiskerHigh, | ||
whiskerLow, | ||
outlier: outlier.concat(reversedOutliers.reverse()), | ||
median, | ||
q1, | ||
q3, | ||
iqr, | ||
items: s | ||
}; | ||
} | ||
function boxplot(data, options = {}) { | ||
const { quantiles, validAndSorted, coef, whiskersMode, eps } = Object.assign({ | ||
coef: 1.5, | ||
eps: 10e-3, | ||
quantiles: quantilesType7, | ||
validAndSorted: false, | ||
whiskersMode: 'nearest', | ||
}, options); | ||
const { missing, s, min, max, sum } = validAndSorted ? withSortedData(data) : createSortedData(data); | ||
const invalid = { | ||
min: Number.NaN, | ||
max: Number.NaN, | ||
mean: Number.NaN, | ||
missing, | ||
iqr: Number.NaN, | ||
count: data.length, | ||
whiskerHigh: Number.NaN, | ||
whiskerLow: Number.NaN, | ||
outlier: [], | ||
median: Number.NaN, | ||
q1: Number.NaN, | ||
q3: Number.NaN, | ||
items: [], | ||
}; | ||
const same = (a, b) => Math.abs(a - b) < eps; | ||
const valid = data.length - missing; | ||
if (valid === 0) { | ||
return invalid; | ||
} | ||
const { median, q1, q3 } = quantiles(s, valid); | ||
const iqr = q3 - q1; | ||
const isCoefValid = typeof coef === 'number' && coef > 0; | ||
let whiskerLow = isCoefValid ? Math.max(min, q1 - coef * iqr) : min; | ||
let whiskerHigh = isCoefValid ? Math.min(max, q3 + coef * iqr) : max; | ||
const outlier = []; | ||
for (let i = 0; i < valid; ++i) { | ||
const v = s[i]; | ||
if (v >= whiskerLow || same(v, whiskerLow)) { | ||
if (whiskersMode === 'nearest') { | ||
whiskerLow = v; | ||
} | ||
break; | ||
} | ||
if (outlier.length === 0 || !same(outlier[outlier.length - 1], v)) { | ||
outlier.push(v); | ||
} | ||
} | ||
const reversedOutliers = []; | ||
for (let i = valid - 1; i >= 0; --i) { | ||
const v = s[i]; | ||
if (v <= whiskerHigh || same(v, whiskerHigh)) { | ||
if (whiskersMode === 'nearest') { | ||
whiskerHigh = v; | ||
} | ||
break; | ||
} | ||
if ((reversedOutliers.length === 0 || !same(reversedOutliers[reversedOutliers.length - 1], v)) && | ||
(outlier.length === 0 || !same(outlier[outlier.length - 1], v))) { | ||
reversedOutliers.push(v); | ||
} | ||
} | ||
return { | ||
min, | ||
max, | ||
count: data.length, | ||
missing, | ||
mean: sum / valid, | ||
whiskerHigh, | ||
whiskerLow, | ||
outlier: outlier.concat(reversedOutliers.reverse()), | ||
median, | ||
q1, | ||
q3, | ||
iqr, | ||
items: s, | ||
}; | ||
} | ||
function gaussian(u) { | ||
return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * u * u); | ||
} | ||
function mean(x) { | ||
if (x.length === 0) { | ||
return Number.NaN; | ||
} | ||
return x.reduce((m, xi, i) => m + (xi - m) / (i + 1), 0); | ||
} | ||
function variance(x) { | ||
const n = x.length; | ||
if (n < 1) { | ||
return Number.NaN; | ||
} | ||
if (n === 1) { | ||
return 0; | ||
} | ||
const m = mean(x); | ||
return x.reduce((acc, x) => acc + (x - m) * (x - m), 0) / (x.length - 1); | ||
} | ||
function nrd(sample, quantiles) { | ||
const q = quantiles(sample); | ||
const h = (q.q3 - q.q1) / 1.34; | ||
return 1.06 * Math.min(Math.sqrt(variance(sample)), h) * Math.pow(sample.length, -1 / 5); | ||
} | ||
function kde(points, sample, quantiles) { | ||
const bw = nrd(sample, quantiles); | ||
return points.map((v) => { | ||
const y = sample.reduce((acc, s) => acc + gaussian((v - s) / bw), 0); | ||
return { v, estimate: y / bw / sample.length }; | ||
}); | ||
} | ||
function gaussian(u) { | ||
return (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * u * u); | ||
} | ||
function mean(x) { | ||
if (x.length === 0) { | ||
return Number.NaN; | ||
} | ||
return x.reduce((m, xi, i) => m + (xi - m) / (i + 1), 0); | ||
} | ||
function variance(x) { | ||
const n = x.length; | ||
if (n < 1) { | ||
return Number.NaN; | ||
} | ||
if (n === 1) { | ||
return 0; | ||
} | ||
const m = mean(x); | ||
return x.reduce((acc, x) => acc + (x - m) * (x - m), 0) / (x.length - 1); | ||
} | ||
function nrd(sample, quantiles) { | ||
const q = quantiles(sample); | ||
const h = (q.q3 - q.q1) / 1.34; | ||
return 1.06 * Math.min(Math.sqrt(variance(sample)), h) * Math.pow(sample.length, -1 / 5); | ||
} | ||
function kde(points, sample, quantiles) { | ||
const bw = nrd(sample, quantiles); | ||
return points.map((v) => { | ||
const y = sample.reduce((acc, s) => acc + gaussian((v - s) / bw), 0); | ||
return { v, estimate: y / bw / sample.length }; | ||
}); | ||
} | ||
function whiskers(boxplot, arr, coef = 1.5) { | ||
const iqr = boxplot.q3 - boxplot.q1; | ||
const coefValid = typeof coef === 'number' && coef > 0; | ||
let whiskerMin = coefValid ? Math.max(boxplot.min, boxplot.q1 - coef * iqr) : boxplot.min; | ||
let whiskerMax = coefValid ? Math.min(boxplot.max, boxplot.q3 + coef * iqr) : boxplot.max; | ||
if (Array.isArray(arr)) { | ||
for (let i = 0; i < arr.length; i++) { | ||
const v = arr[i]; | ||
if (v >= whiskerMin) { | ||
whiskerMin = v; | ||
break; | ||
} | ||
} | ||
for (let i = arr.length - 1; i >= 0; i--) { | ||
const v = arr[i]; | ||
if (v <= whiskerMax) { | ||
whiskerMax = v; | ||
break; | ||
} | ||
} | ||
} | ||
return { | ||
whiskerMin, | ||
whiskerMax, | ||
}; | ||
} | ||
const defaultStatsOptions = { | ||
coef: 1.5, | ||
quantiles: 7, | ||
}; | ||
function determineQuantiles(q) { | ||
if (typeof q === 'function') { | ||
return q; | ||
} | ||
const lookup = { | ||
hinges: quantilesHinges, | ||
fivenum: quantilesFivenum, | ||
7: quantilesType7, | ||
quantiles: quantilesType7, | ||
linear: quantilesLinear, | ||
lower: quantilesLower, | ||
higher: quantilesHigher, | ||
nearest: quantilesNearest, | ||
midpoint: quantilesMidpoint, | ||
}; | ||
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 ? quantilesType7 : options.quantiles; | ||
const quantiles = determineQuantiles(q); | ||
return { | ||
coef, | ||
quantiles, | ||
}; | ||
} | ||
function boxplotStats(arr, options) { | ||
const r = boxplot(arr, determineStatsOptions(options)); | ||
return { | ||
items: Array.from(r.items), | ||
outliers: r.outlier, | ||
whiskerMax: r.whiskerHigh, | ||
whiskerMin: r.whiskerLow, | ||
max: r.max, | ||
median: r.median, | ||
min: r.min, | ||
q1: r.q1, | ||
q3: r.q3, | ||
}; | ||
} | ||
function violinStats(arr, options) { | ||
if (arr.length === 0) { | ||
return undefined; | ||
} | ||
const items = arr.filter((v) => typeof v === 'number' && !Number.isNaN(v)).sort((a, b) => a - b); | ||
const { quantiles } = determineStatsOptions(options); | ||
const stats = quantiles(items); | ||
const min = items[0]; | ||
const max = items[items.length - 1]; | ||
const range = max - min; | ||
const samples = []; | ||
const inc = range / options.points; | ||
for (let v = min; v <= max && inc > 0; v += inc) { | ||
samples.push(v); | ||
} | ||
if (samples[samples.length - 1] !== max) { | ||
samples.push(max); | ||
} | ||
const coords = kde(items, samples, quantiles); | ||
const maxEstimate = coords.reduce((a, d) => Math.max(a, d.estimate), Number.NEGATIVE_INFINITY); | ||
return { | ||
...stats, | ||
min, | ||
items, | ||
max, | ||
coords, | ||
outliers: [], | ||
maxEstimate, | ||
}; | ||
} | ||
function asBoxPlotStats(value, options) { | ||
if (!value) { | ||
return undefined; | ||
} | ||
if (typeof value.median === 'number' && typeof value.q1 === 'number' && typeof value.q3 === 'number') { | ||
if (typeof value.whiskerMin === 'undefined') { | ||
const { coef } = determineStatsOptions(options); | ||
const { whiskerMin, whiskerMax } = whiskers(value, Array.isArray(value.items) ? value.items.slice().sort((a, b) => a - b) : null, coef); | ||
value.whiskerMin = whiskerMin; | ||
value.whiskerMax = whiskerMax; | ||
} | ||
return value; | ||
} | ||
if (!Array.isArray(value)) { | ||
return undefined; | ||
} | ||
return boxplotStats(value, options); | ||
} | ||
function asViolinStats(value, options) { | ||
if (!value) { | ||
return undefined; | ||
} | ||
if (typeof value.median === 'number' && Array.isArray(value.coords)) { | ||
return value; | ||
} | ||
if (!Array.isArray(value)) { | ||
return undefined; | ||
} | ||
return violinStats(value, options); | ||
} | ||
function rnd(seed = Date.now()) { | ||
let s = seed; | ||
return () => { | ||
s = (s * 9301 + 49297) % 233280; | ||
return s / 233280; | ||
}; | ||
} | ||
function whiskers(boxplot, arr, coef = 1.5) { | ||
const iqr = boxplot.q3 - boxplot.q1; | ||
const coefValid = typeof coef === 'number' && coef > 0; | ||
let whiskerMin = coefValid ? Math.max(boxplot.min, boxplot.q1 - coef * iqr) : boxplot.min; | ||
let whiskerMax = coefValid ? Math.min(boxplot.max, boxplot.q3 + coef * iqr) : boxplot.max; | ||
if (Array.isArray(arr)) { | ||
for (let i = 0; i < arr.length; i++) { | ||
const v = arr[i]; | ||
if (v >= whiskerMin) { | ||
whiskerMin = v; | ||
break; | ||
} | ||
} | ||
for (let i = arr.length - 1; i >= 0; i--) { | ||
const v = arr[i]; | ||
if (v <= whiskerMax) { | ||
whiskerMax = v; | ||
break; | ||
} | ||
} | ||
} | ||
return { | ||
whiskerMin, | ||
whiskerMax, | ||
}; | ||
} | ||
const defaultStatsOptions = { | ||
coef: 1.5, | ||
quantiles: 7, | ||
}; | ||
function determineQuantiles(q) { | ||
if (typeof q === 'function') { | ||
return q; | ||
} | ||
const lookup = { | ||
hinges: quantilesHinges, | ||
fivenum: quantilesFivenum, | ||
7: quantilesType7, | ||
quantiles: quantilesType7, | ||
linear: quantilesLinear, | ||
lower: quantilesLower, | ||
higher: quantilesHigher, | ||
nearest: quantilesNearest, | ||
midpoint: quantilesMidpoint, | ||
}; | ||
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 ? quantilesType7 : options.quantiles; | ||
const quantiles = determineQuantiles(q); | ||
return { | ||
coef, | ||
quantiles, | ||
}; | ||
} | ||
function boxplotStats(arr, options) { | ||
const r = boxplot(arr, determineStatsOptions(options)); | ||
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, | ||
}; | ||
} | ||
function violinStats(arr, options) { | ||
if (arr.length === 0) { | ||
return undefined; | ||
} | ||
const items = arr.filter((v) => typeof v === 'number' && !Number.isNaN(v)).sort((a, b) => a - b); | ||
const { quantiles } = determineStatsOptions(options); | ||
const stats = quantiles(items); | ||
const min = items[0]; | ||
const max = items[items.length - 1]; | ||
const range = max - min; | ||
const samples = []; | ||
const inc = range / options.points; | ||
for (let v = min; v <= max && inc > 0; v += inc) { | ||
samples.push(v); | ||
} | ||
if (samples[samples.length - 1] !== max) { | ||
samples.push(max); | ||
} | ||
const coords = kde(items, samples, quantiles); | ||
const maxEstimate = coords.reduce((a, d) => Math.max(a, d.estimate), Number.NEGATIVE_INFINITY); | ||
return { | ||
...stats, | ||
min, | ||
items, | ||
max, | ||
coords, | ||
outliers: [], | ||
maxEstimate, | ||
}; | ||
} | ||
function asBoxPlotStats(value, options) { | ||
if (!value) { | ||
return undefined; | ||
} | ||
if (typeof value.median === 'number' && typeof value.q1 === 'number' && typeof value.q3 === 'number') { | ||
if (typeof value.whiskerMin === 'undefined') { | ||
const { coef } = determineStatsOptions(options); | ||
const { whiskerMin, whiskerMax } = whiskers(value, Array.isArray(value.items) ? value.items.slice().sort((a, b) => a - b) : null, coef); | ||
value.whiskerMin = whiskerMin; | ||
value.whiskerMax = whiskerMax; | ||
} | ||
return value; | ||
} | ||
if (!Array.isArray(value)) { | ||
return undefined; | ||
} | ||
return boxplotStats(value, options); | ||
} | ||
function asViolinStats(value, options) { | ||
if (!value) { | ||
return undefined; | ||
} | ||
if (typeof value.median === 'number' && Array.isArray(value.coords)) { | ||
return value; | ||
} | ||
if (!Array.isArray(value)) { | ||
return undefined; | ||
} | ||
return violinStats(value, options); | ||
} | ||
function rnd(seed = Date.now()) { | ||
let s = seed; | ||
return () => { | ||
s = (s * 9301 + 49297) % 233280; | ||
return s / 233280; | ||
}; | ||
} | ||
const interpolators = { | ||
number(from, to, factor) { | ||
if (from === to) { | ||
return to; | ||
} | ||
if (from == null) { | ||
return to; | ||
} | ||
if (to == null) { | ||
return from; | ||
} | ||
return from + (to - from) * factor; | ||
}, | ||
}; | ||
function interpolateNumberArray(from, to, factor) { | ||
if (typeof from === 'number' && typeof to === 'number') { | ||
return interpolators.number(from, to, factor); | ||
} | ||
if (Array.isArray(from) && Array.isArray(to)) { | ||
return to.map((t, i) => interpolators.number(from[i], t, factor)); | ||
} | ||
return to; | ||
} | ||
function interpolateKdeCoords(from, to, factor) { | ||
if (Array.isArray(from) && Array.isArray(to)) { | ||
return to.map((t, i) => ({ | ||
v: interpolators.number(from[i] ? from[i].v : null, t.v, factor), | ||
estimate: interpolators.number(from[i] ? from[i].estimate : null, t.estimate, factor), | ||
})); | ||
} | ||
return to; | ||
} | ||
const interpolators = { | ||
number(from, to, factor) { | ||
if (from === to) { | ||
return to; | ||
} | ||
if (from == null) { | ||
return to; | ||
} | ||
if (to == null) { | ||
return from; | ||
} | ||
return from + (to - from) * factor; | ||
}, | ||
}; | ||
function interpolateNumberArray(from, to, factor) { | ||
if (typeof from === 'number' && typeof to === 'number') { | ||
return interpolators.number(from, to, factor); | ||
} | ||
if (Array.isArray(from) && Array.isArray(to)) { | ||
return to.map((t, i) => interpolators.number(from[i], t, factor)); | ||
} | ||
return to; | ||
} | ||
function interpolateKdeCoords(from, to, factor) { | ||
if (Array.isArray(from) && Array.isArray(to)) { | ||
return to.map((t, i) => ({ | ||
v: interpolators.number(from[i] ? from[i].v : null, t.v, factor), | ||
estimate: interpolators.number(from[i] ? from[i].estimate : null, t.estimate, factor), | ||
})); | ||
} | ||
return to; | ||
} | ||
function patchInHoveredOutlier(item) { | ||
const value = item.formattedValue; | ||
const that = this; | ||
if (value && that._tooltipOutlier != null && item.datasetIndex === that._tooltipOutlier.datasetIndex) { | ||
value.hoveredOutlierIndex = that._tooltipOutlier.index; | ||
} | ||
} | ||
function outlierPositioner(items, eventPosition) { | ||
if (!items.length) { | ||
return false; | ||
} | ||
let x = 0; | ||
let y = 0; | ||
let count = 0; | ||
for (let i = 0; i < items.length; ++i) { | ||
const el = items[i].element; | ||
if (el && el.hasValue()) { | ||
const pos = el.tooltipPosition(eventPosition, this); | ||
x += pos.x; | ||
y += pos.y; | ||
++count; | ||
} | ||
} | ||
return { | ||
x: x / count, | ||
y: y / count, | ||
}; | ||
} | ||
outlierPositioner.id = 'averageInstance'; | ||
outlierPositioner.register = () => { | ||
chart_js.Tooltip.positioners.averageInstance = outlierPositioner; | ||
return outlierPositioner; | ||
}; | ||
function patchInHoveredOutlier(item) { | ||
const value = item.formattedValue; | ||
const that = this; | ||
if (value && that._tooltipOutlier != null && item.datasetIndex === that._tooltipOutlier.datasetIndex) { | ||
value.hoveredOutlierIndex = that._tooltipOutlier.index; | ||
} | ||
} | ||
function outlierPositioner(items, eventPosition) { | ||
if (!items.length) { | ||
return false; | ||
} | ||
let x = 0; | ||
let y = 0; | ||
let count = 0; | ||
for (let i = 0; i < items.length; ++i) { | ||
const el = items[i].element; | ||
if (el && el.hasValue()) { | ||
const pos = el.tooltipPosition(eventPosition, this); | ||
x += pos.x; | ||
y += pos.y; | ||
++count; | ||
} | ||
} | ||
return { | ||
x: x / count, | ||
y: y / count, | ||
}; | ||
} | ||
outlierPositioner.id = 'averageInstance'; | ||
outlierPositioner.register = () => { | ||
chart_js.Tooltip.positioners.averageInstance = outlierPositioner; | ||
return outlierPositioner; | ||
}; | ||
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: { | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
from: 'transparent', | ||
}, | ||
}, | ||
hide: { | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
to: 'transparent', | ||
}, | ||
}, | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions), | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
}, | ||
}; | ||
} | ||
class StatsBase extends chart_js.BarController { | ||
getMinMax(scale, canStack) { | ||
const bak = scale.axis; | ||
const config = this._config; | ||
scale.axis = config.minStats; | ||
const min = super.getMinMax(scale, canStack).min; | ||
scale.axis = config.maxStats; | ||
const max = super.getMinMax(scale, canStack).max; | ||
scale.axis = bak; | ||
return { min, max }; | ||
} | ||
parsePrimitiveData(meta, data, start, count) { | ||
const vScale = meta.vScale; | ||
const iScale = meta.iScale; | ||
const labels = iScale.getLabels(); | ||
const r = []; | ||
for (let i = 0; i < count; i++) { | ||
const index = i + start; | ||
const parsed = {}; | ||
parsed[iScale.axis] = iScale.parse(labels[index], index); | ||
const stats = this._parseStats(data == null ? null : data[index], this._config); | ||
if (stats) { | ||
Object.assign(parsed, stats); | ||
parsed[vScale.axis] = stats.median; | ||
} | ||
r.push(parsed); | ||
} | ||
return r; | ||
} | ||
parseArrayData(meta, data, start, count) { | ||
return this.parsePrimitiveData(meta, data, start, count); | ||
} | ||
parseObjectData(meta, data, start, count) { | ||
return this.parsePrimitiveData(meta, data, start, count); | ||
} | ||
getLabelAndValue(index) { | ||
const r = super.getLabelAndValue(index); | ||
const vScale = this._cachedMeta.vScale; | ||
const parsed = this.getParsed(index); | ||
if (!vScale || !parsed || r.value === 'NaN') { | ||
return r; | ||
} | ||
r.value = { | ||
raw: parsed, | ||
hoveredOutlierIndex: -1, | ||
}; | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v), 'string'); | ||
const s = this._toStringStats(r.value); | ||
r.value.toString = function () { | ||
if (this.hoveredOutlierIndex >= 0) { | ||
return `(outlier: ${this.outliers[this.hoveredOutlierIndex]})`; | ||
} | ||
return s; | ||
}; | ||
return r; | ||
} | ||
updateElement(rectangle, index, properties, mode) { | ||
const reset = mode === 'reset'; | ||
const scale = this._cachedMeta.vScale; | ||
const parsed = this.getParsed(index); | ||
const base = scale.getBasePixel(); | ||
properties._datasetIndex = this.index; | ||
properties._index = index; | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index)), mode); | ||
super.updateElement(rectangle, index, properties, mode); | ||
} | ||
} | ||
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: { | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
from: 'transparent', | ||
}, | ||
}, | ||
hide: { | ||
colors: { | ||
type: 'color', | ||
properties: colorKeys, | ||
to: 'transparent', | ||
}, | ||
}, | ||
}, | ||
minStats: 'min', | ||
maxStats: 'max', | ||
}, defaultStatsOptions), | ||
tooltips: { | ||
position: outlierPositioner.register().id, | ||
callbacks: { | ||
beforeLabel: patchInHoveredOutlier, | ||
}, | ||
}, | ||
}; | ||
} | ||
class StatsBase extends chart_js.BarController { | ||
getMinMax(scale, canStack) { | ||
const bak = scale.axis; | ||
const config = this._config; | ||
scale.axis = config.minStats; | ||
const min = super.getMinMax(scale, canStack).min; | ||
scale.axis = config.maxStats; | ||
const max = super.getMinMax(scale, canStack).max; | ||
scale.axis = bak; | ||
return { min, max }; | ||
} | ||
parsePrimitiveData(meta, data, start, count) { | ||
const vScale = meta.vScale; | ||
const iScale = meta.iScale; | ||
const labels = iScale.getLabels(); | ||
const r = []; | ||
for (let i = 0; i < count; i++) { | ||
const index = i + start; | ||
const parsed = {}; | ||
parsed[iScale.axis] = iScale.parse(labels[index], index); | ||
const stats = this._parseStats(data == null ? null : data[index], this._config); | ||
if (stats) { | ||
Object.assign(parsed, stats); | ||
parsed[vScale.axis] = stats.median; | ||
} | ||
r.push(parsed); | ||
} | ||
return r; | ||
} | ||
parseArrayData(meta, data, start, count) { | ||
return this.parsePrimitiveData(meta, data, start, count); | ||
} | ||
parseObjectData(meta, data, start, count) { | ||
return this.parsePrimitiveData(meta, data, start, count); | ||
} | ||
getLabelAndValue(index) { | ||
const r = super.getLabelAndValue(index); | ||
const vScale = this._cachedMeta.vScale; | ||
const parsed = this.getParsed(index); | ||
if (!vScale || !parsed || r.value === 'NaN') { | ||
return r; | ||
} | ||
r.value = { | ||
raw: parsed, | ||
hoveredOutlierIndex: -1, | ||
}; | ||
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v), 'string'); | ||
const s = this._toStringStats(r.value); | ||
r.value.toString = function () { | ||
if (this.hoveredOutlierIndex >= 0) { | ||
return `(outlier: ${this.outliers[this.hoveredOutlierIndex]})`; | ||
} | ||
return s; | ||
}; | ||
return r; | ||
} | ||
updateElement(rectangle, index, properties, mode) { | ||
const reset = mode === 'reset'; | ||
const scale = this._cachedMeta.vScale; | ||
const parsed = this.getParsed(index); | ||
const base = scale.getBasePixel(); | ||
properties._datasetIndex = this.index; | ||
properties._index = index; | ||
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v, index)), mode); | ||
super.updateElement(rectangle, index, properties, mode); | ||
} | ||
} | ||
const baseDefaults$1 = { | ||
borderWidth: 1, | ||
outlierStyle: 'circle', | ||
outlierRadius: 2, | ||
outlierBorderWidth: 1, | ||
itemStyle: 'circle', | ||
itemRadius: 0, | ||
itemBorderWidth: 0, | ||
hitPadding: 2, | ||
outlierHitRadius: 4, | ||
}; | ||
const baseRoutes = { | ||
outlierBackgroundColor: 'backgroundColor', | ||
outlierBorderColor: 'borderColor', | ||
itemBackgroundColor: 'backgroundColor', | ||
itemBorderColor: 'borderColor', | ||
}; | ||
const baseOptionKeys = (() => Object.keys(baseDefaults$1).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase$1 extends chart_js.Element { | ||
isVertical() { | ||
return this.getProps(['height']).height == null; | ||
} | ||
_drawItems(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height']); | ||
const options = this.options; | ||
if (options.itemRadius <= 0 || !props.items || props.items.length <= 0) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.strokeStyle = options.itemBorderColor; | ||
ctx.fillStyle = options.itemBackgroundColor; | ||
ctx.lineWidth = options.itemBorderWidth; | ||
const random = rnd(this._datasetIndex * 1000 + this._index); | ||
const pointOptions = { | ||
pointStyle: options.itemStyle, | ||
radius: options.itemRadius, | ||
borderWidth: options.itemBorderWidth, | ||
}; | ||
if (vert) { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
}); | ||
} | ||
else { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
}); | ||
} | ||
ctx.restore(); | ||
} | ||
_drawOutliers(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'outliers']); | ||
const options = this.options; | ||
if (options.outlierRadius <= 0 || !props.outliers || props.outliers.length === 0) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.fillStyle = options.outlierBackgroundColor; | ||
ctx.strokeStyle = options.outlierBorderColor; | ||
ctx.lineWidth = options.outlierBorderWidth; | ||
const pointOptions = { | ||
pointStyle: options.outlierStyle, | ||
radius: options.outlierRadius, | ||
borderWidth: options.outlierBorderWidth, | ||
}; | ||
if (vert) { | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x, v); | ||
}); | ||
} | ||
else { | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y); | ||
}); | ||
} | ||
ctx.restore(); | ||
} | ||
_getBounds(_useFinalPosition) { | ||
return { | ||
left: 0, | ||
top: 0, | ||
right: 0, | ||
bottom: 0, | ||
}; | ||
} | ||
_getHitBounds(useFinalPosition) { | ||
const padding = this.options.hitPadding; | ||
const b = this._getBounds(useFinalPosition); | ||
return { | ||
left: b.left - padding, | ||
top: b.top - padding, | ||
right: b.right + padding, | ||
bottom: b.bottom + padding, | ||
}; | ||
} | ||
inRange(mouseX, mouseY, useFinalPosition) { | ||
if (Number.isNaN(this.x) && Number.isNaN(this.y)) { | ||
return false; | ||
} | ||
return (this._boxInRange(mouseX, mouseY, useFinalPosition) || | ||
this._outlierIndexInRange(mouseX, mouseY, useFinalPosition) >= 0); | ||
} | ||
inXRange(mouseX, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseX >= bounds.left && mouseX <= bounds.right; | ||
} | ||
inYRange(mouseY, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
} | ||
_outlierIndexInRange(mouseX, mouseY, useFinalPosition) { | ||
const props = this.getProps(['x', 'y'], useFinalPosition); | ||
const hitRadius = this.options.outlierHitRadius; | ||
const outliers = this._getOutliers(useFinalPosition); | ||
const vertical = this.isVertical(); | ||
if ((vertical && Math.abs(mouseX - props.x) > hitRadius) || (!vertical && Math.abs(mouseY - props.y) > hitRadius)) { | ||
return -1; | ||
} | ||
const toCompare = vertical ? mouseY : mouseX; | ||
for (let i = 0; i < outliers.length; i++) { | ||
if (Math.abs(outliers[i] - toCompare) <= hitRadius) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
_boxInRange(mouseX, mouseY, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
} | ||
getCenterPoint(useFinalPosition) { | ||
const props = this.getProps(['x', 'y'], useFinalPosition); | ||
return { | ||
x: props.x, | ||
y: props.y, | ||
}; | ||
} | ||
_getOutliers(useFinalPosition) { | ||
const props = this.getProps(['outliers'], useFinalPosition); | ||
return props.outliers || []; | ||
} | ||
tooltipPosition(eventPosition, tooltip) { | ||
if (!eventPosition || typeof eventPosition === 'boolean') { | ||
return this.getCenterPoint(); | ||
} | ||
if (tooltip) { | ||
delete tooltip._tooltipOutlier; | ||
} | ||
const props = this.getProps(['x', 'y']); | ||
const index = this._outlierIndexInRange(eventPosition.x, eventPosition.y); | ||
if (index < 0 || !tooltip) { | ||
return this.getCenterPoint(); | ||
} | ||
tooltip._tooltipOutlier = { | ||
index, | ||
datasetIndex: this._datasetIndex, | ||
}; | ||
if (this.isVertical()) { | ||
return { | ||
x: props.x, | ||
y: this._getOutliers()[index], | ||
}; | ||
} | ||
return { | ||
x: this._getOutliers()[index], | ||
y: props.y, | ||
}; | ||
} | ||
} | ||
const baseDefaults$1 = { | ||
borderWidth: 1, | ||
outlierStyle: 'circle', | ||
outlierRadius: 2, | ||
outlierBorderWidth: 1, | ||
itemStyle: 'circle', | ||
itemRadius: 0, | ||
itemBorderWidth: 0, | ||
hitPadding: 2, | ||
outlierHitRadius: 4, | ||
}; | ||
const baseRoutes = { | ||
outlierBackgroundColor: 'backgroundColor', | ||
outlierBorderColor: 'borderColor', | ||
itemBackgroundColor: 'backgroundColor', | ||
itemBorderColor: 'borderColor', | ||
}; | ||
const baseOptionKeys = (() => Object.keys(baseDefaults$1).concat(Object.keys(baseRoutes)))(); | ||
class StatsBase$1 extends chart_js.Element { | ||
isVertical() { | ||
return this.getProps(['height']).height == null; | ||
} | ||
_drawItems(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'items', 'width', 'height']); | ||
const options = this.options; | ||
if (options.itemRadius <= 0 || !props.items || props.items.length <= 0) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.strokeStyle = options.itemBorderColor; | ||
ctx.fillStyle = options.itemBackgroundColor; | ||
ctx.lineWidth = options.itemBorderWidth; | ||
const random = rnd(this._datasetIndex * 1000 + this._index); | ||
const pointOptions = { | ||
pointStyle: options.itemStyle, | ||
radius: options.itemRadius, | ||
borderWidth: options.itemBorderWidth, | ||
}; | ||
if (vert) { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x - props.width / 2 + random() * props.width, v); | ||
}); | ||
} | ||
else { | ||
props.items.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y - props.height / 2 + random() * props.height); | ||
}); | ||
} | ||
ctx.restore(); | ||
} | ||
_drawOutliers(ctx) { | ||
const vert = this.isVertical(); | ||
const props = this.getProps(['x', 'y', 'outliers']); | ||
const options = this.options; | ||
if (options.outlierRadius <= 0 || !props.outliers || props.outliers.length === 0) { | ||
return; | ||
} | ||
ctx.save(); | ||
ctx.fillStyle = options.outlierBackgroundColor; | ||
ctx.strokeStyle = options.outlierBorderColor; | ||
ctx.lineWidth = options.outlierBorderWidth; | ||
const pointOptions = { | ||
pointStyle: options.outlierStyle, | ||
radius: options.outlierRadius, | ||
borderWidth: options.outlierBorderWidth, | ||
}; | ||
if (vert) { | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, props.x, v); | ||
}); | ||
} | ||
else { | ||
props.outliers.forEach((v) => { | ||
helpers.drawPoint(ctx, pointOptions, v, props.y); | ||
}); | ||
} | ||
ctx.restore(); | ||
} | ||
_getBounds(_useFinalPosition) { | ||
return { | ||
left: 0, | ||
top: 0, | ||
right: 0, | ||
bottom: 0, | ||
}; | ||
} | ||
_getHitBounds(useFinalPosition) { | ||
const padding = this.options.hitPadding; | ||
const b = this._getBounds(useFinalPosition); | ||
return { | ||
left: b.left - padding, | ||
top: b.top - padding, | ||
right: b.right + padding, | ||
bottom: b.bottom + padding, | ||
}; | ||
} | ||
inRange(mouseX, mouseY, useFinalPosition) { | ||
if (Number.isNaN(this.x) && Number.isNaN(this.y)) { | ||
return false; | ||
} | ||
return (this._boxInRange(mouseX, mouseY, useFinalPosition) || | ||
this._outlierIndexInRange(mouseX, mouseY, useFinalPosition) >= 0); | ||
} | ||
inXRange(mouseX, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseX >= bounds.left && mouseX <= bounds.right; | ||
} | ||
inYRange(mouseY, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
} | ||
_outlierIndexInRange(mouseX, mouseY, useFinalPosition) { | ||
const props = this.getProps(['x', 'y'], useFinalPosition); | ||
const hitRadius = this.options.outlierHitRadius; | ||
const outliers = this._getOutliers(useFinalPosition); | ||
const vertical = this.isVertical(); | ||
if ((vertical && Math.abs(mouseX - props.x) > hitRadius) || (!vertical && Math.abs(mouseY - props.y) > hitRadius)) { | ||
return -1; | ||
} | ||
const toCompare = vertical ? mouseY : mouseX; | ||
for (let i = 0; i < outliers.length; i++) { | ||
if (Math.abs(outliers[i] - toCompare) <= hitRadius) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
_boxInRange(mouseX, mouseY, useFinalPosition) { | ||
const bounds = this._getHitBounds(useFinalPosition); | ||
return mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; | ||
} | ||
getCenterPoint(useFinalPosition) { | ||
const props = this.getProps(['x', 'y'], useFinalPosition); | ||
return { | ||
x: props.x, | ||
y: props.y, | ||
}; | ||
} | ||
_getOutliers(useFinalPosition) { | ||
const props = this.getProps(['outliers'], useFinalPosition); | ||
return props.outliers || []; | ||
} | ||
tooltipPosition(eventPosition, tooltip) { | ||
if (!eventPosition || typeof eventPosition === 'boolean') { | ||
return this.getCenterPoint(); | ||
} | ||
if (tooltip) { | ||
delete tooltip._tooltipOutlier; | ||
} | ||
const props = this.getProps(['x', 'y']); | ||
const index = this._outlierIndexInRange(eventPosition.x, eventPosition.y); | ||
if (index < 0 || !tooltip) { | ||
return this.getCenterPoint(); | ||
} | ||
tooltip._tooltipOutlier = { | ||
index, | ||
datasetIndex: this._datasetIndex, | ||
}; | ||
if (this.isVertical()) { | ||
return { | ||
x: props.x, | ||
y: this._getOutliers()[index], | ||
}; | ||
} | ||
return { | ||
x: this._getOutliers()[index], | ||
y: props.y, | ||
}; | ||
} | ||
} | ||
const boxOptionsKeys = baseOptionKeys.concat(['medianColor', 'lowerBackgroundColor']); | ||
class BoxAndWiskers extends StatsBase$1 { | ||
draw(ctx) { | ||
ctx.save(); | ||
ctx.fillStyle = this.options.backgroundColor; | ||
ctx.strokeStyle = this.options.borderColor; | ||
ctx.lineWidth = this.options.borderWidth; | ||
this._drawBoxPlot(ctx); | ||
this._drawOutliers(ctx); | ||
ctx.restore(); | ||
this._drawItems(ctx); | ||
} | ||
_drawBoxPlot(ctx) { | ||
if (this.isVertical()) { | ||
this._drawBoxPlotVertical(ctx); | ||
} | ||
else { | ||
this._drawBoxPlotHorizontal(ctx); | ||
} | ||
} | ||
_drawBoxPlotVertical(ctx) { | ||
const options = this.options; | ||
const props = this.getProps(['x', 'width', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); | ||
const x = props.x; | ||
const width = props.width; | ||
const x0 = x - width / 2; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(x0, props.q1, width, props.q3 - props.q1); | ||
} | ||
else { | ||
ctx.fillRect(x0, props.q3, width, props.q1 - props.q3); | ||
} | ||
ctx.save(); | ||
if (options.medianColor && options.medianColor !== 'transparent' && options.medianColor !== '#0000') { | ||
ctx.strokeStyle = options.medianColor; | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(x0, props.median); | ||
ctx.lineTo(x0 + width, props.median); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.restore(); | ||
ctx.save(); | ||
if (options.lowerBackgroundColor && | ||
options.lowerBackgroundColor !== 'transparent' && | ||
options.lowerBackgroundColor !== '#0000') { | ||
ctx.fillStyle = options.lowerBackgroundColor; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(x0, props.median, width, props.q3 - props.median); | ||
} | ||
else { | ||
ctx.fillRect(x0, props.median, width, props.q1 - props.median); | ||
} | ||
} | ||
ctx.restore(); | ||
if (props.q3 > props.q1) { | ||
ctx.strokeRect(x0, props.q1, width, props.q3 - props.q1); | ||
} | ||
else { | ||
ctx.strokeRect(x0, props.q3, width, props.q1 - props.q3); | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(x0, props.whiskerMin); | ||
ctx.lineTo(x0 + width, props.whiskerMin); | ||
ctx.moveTo(x, props.whiskerMin); | ||
ctx.lineTo(x, props.q1); | ||
ctx.moveTo(x0, props.whiskerMax); | ||
ctx.lineTo(x0 + width, props.whiskerMax); | ||
ctx.moveTo(x, props.whiskerMax); | ||
ctx.lineTo(x, props.q3); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
} | ||
_drawBoxPlotHorizontal(ctx) { | ||
const options = this.options; | ||
const props = this.getProps(['y', 'height', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); | ||
const y = props.y; | ||
const height = props.height; | ||
const y0 = y - height / 2; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(props.q1, y0, props.q3 - props.q1, height); | ||
} | ||
else { | ||
ctx.fillRect(props.q3, y0, props.q1 - props.q3, height); | ||
} | ||
ctx.save(); | ||
if (options.medianColor && options.medianColor !== 'transparent') { | ||
ctx.strokeStyle = options.medianColor; | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(props.median, y0); | ||
ctx.lineTo(props.median, y0 + height); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.restore(); | ||
ctx.save(); | ||
if (options.lowerBackgroundColor && options.lowerBackgroundColor !== 'transparent') { | ||
ctx.fillStyle = options.lowerBackgroundColor; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(props.median, y0, props.q3 - props.median, height); | ||
} | ||
else { | ||
ctx.fillRect(props.median, y0, props.q1 - props.median, height); | ||
} | ||
} | ||
ctx.restore(); | ||
if (props.q3 > props.q1) { | ||
ctx.strokeRect(props.q1, y0, props.q3 - props.q1, height); | ||
} | ||
else { | ||
ctx.strokeRect(props.q3, y0, props.q1 - props.q3, height); | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(props.whiskerMin, y0); | ||
ctx.lineTo(props.whiskerMin, y0 + height); | ||
ctx.moveTo(props.whiskerMin, y); | ||
ctx.lineTo(props.q1, y); | ||
ctx.moveTo(props.whiskerMax, y0); | ||
ctx.lineTo(props.whiskerMax, y0 + height); | ||
ctx.moveTo(props.whiskerMax, y); | ||
ctx.lineTo(props.q3, y); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
} | ||
_getBounds(useFinalPosition) { | ||
const vert = this.isVertical(); | ||
if (this.x == null) { | ||
return { | ||
left: 0, | ||
top: 0, | ||
right: 0, | ||
bottom: 0, | ||
}; | ||
} | ||
if (vert) { | ||
const { x, width, whiskerMax, whiskerMin } = this.getProps(['x', 'width', 'whiskerMin', 'whiskerMax'], useFinalPosition); | ||
const x0 = x - width / 2; | ||
return { | ||
left: x0, | ||
top: whiskerMax, | ||
right: x0 + width, | ||
bottom: whiskerMin, | ||
}; | ||
} | ||
const { y, height, whiskerMax, whiskerMin } = this.getProps(['y', 'height', 'whiskerMin', 'whiskerMax'], useFinalPosition); | ||
const y0 = y - height / 2; | ||
return { | ||
left: whiskerMin, | ||
top: y0, | ||
right: whiskerMax, | ||
bottom: y0 + height, | ||
}; | ||
} | ||
} | ||
BoxAndWiskers.id = 'boxandwhiskers'; | ||
BoxAndWiskers.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1, { | ||
medianColor: 'transparent', | ||
lowerBackgroundColor: 'transparent', | ||
}); | ||
BoxAndWiskers.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
const boxOptionsKeys = baseOptionKeys.concat(['medianColor', 'lowerBackgroundColor']); | ||
class BoxAndWiskers extends StatsBase$1 { | ||
draw(ctx) { | ||
ctx.save(); | ||
ctx.fillStyle = this.options.backgroundColor; | ||
ctx.strokeStyle = this.options.borderColor; | ||
ctx.lineWidth = this.options.borderWidth; | ||
this._drawBoxPlot(ctx); | ||
this._drawOutliers(ctx); | ||
ctx.restore(); | ||
this._drawItems(ctx); | ||
} | ||
_drawBoxPlot(ctx) { | ||
if (this.isVertical()) { | ||
this._drawBoxPlotVertical(ctx); | ||
} | ||
else { | ||
this._drawBoxPlotHorizontal(ctx); | ||
} | ||
} | ||
_drawBoxPlotVertical(ctx) { | ||
const options = this.options; | ||
const props = this.getProps(['x', 'width', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); | ||
const x = props.x; | ||
const width = props.width; | ||
const x0 = x - width / 2; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(x0, props.q1, width, props.q3 - props.q1); | ||
} | ||
else { | ||
ctx.fillRect(x0, props.q3, width, props.q1 - props.q3); | ||
} | ||
ctx.save(); | ||
if (options.medianColor && options.medianColor !== 'transparent' && options.medianColor !== '#0000') { | ||
ctx.strokeStyle = options.medianColor; | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(x0, props.median); | ||
ctx.lineTo(x0 + width, props.median); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.restore(); | ||
ctx.save(); | ||
if (options.lowerBackgroundColor && | ||
options.lowerBackgroundColor !== 'transparent' && | ||
options.lowerBackgroundColor !== '#0000') { | ||
ctx.fillStyle = options.lowerBackgroundColor; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(x0, props.median, width, props.q3 - props.median); | ||
} | ||
else { | ||
ctx.fillRect(x0, props.median, width, props.q1 - props.median); | ||
} | ||
} | ||
ctx.restore(); | ||
if (props.q3 > props.q1) { | ||
ctx.strokeRect(x0, props.q1, width, props.q3 - props.q1); | ||
} | ||
else { | ||
ctx.strokeRect(x0, props.q3, width, props.q1 - props.q3); | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(x0, props.whiskerMin); | ||
ctx.lineTo(x0 + width, props.whiskerMin); | ||
ctx.moveTo(x, props.whiskerMin); | ||
ctx.lineTo(x, props.q1); | ||
ctx.moveTo(x0, props.whiskerMax); | ||
ctx.lineTo(x0 + width, props.whiskerMax); | ||
ctx.moveTo(x, props.whiskerMax); | ||
ctx.lineTo(x, props.q3); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
} | ||
_drawBoxPlotHorizontal(ctx) { | ||
const options = this.options; | ||
const props = this.getProps(['y', 'height', 'q1', 'q3', 'median', 'whiskerMin', 'whiskerMax']); | ||
const y = props.y; | ||
const height = props.height; | ||
const y0 = y - height / 2; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(props.q1, y0, props.q3 - props.q1, height); | ||
} | ||
else { | ||
ctx.fillRect(props.q3, y0, props.q1 - props.q3, height); | ||
} | ||
ctx.save(); | ||
if (options.medianColor && options.medianColor !== 'transparent') { | ||
ctx.strokeStyle = options.medianColor; | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(props.median, y0); | ||
ctx.lineTo(props.median, y0 + height); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.restore(); | ||
ctx.save(); | ||
if (options.lowerBackgroundColor && options.lowerBackgroundColor !== 'transparent') { | ||
ctx.fillStyle = options.lowerBackgroundColor; | ||
if (props.q3 > props.q1) { | ||
ctx.fillRect(props.median, y0, props.q3 - props.median, height); | ||
} | ||
else { | ||
ctx.fillRect(props.median, y0, props.q1 - props.median, height); | ||
} | ||
} | ||
ctx.restore(); | ||
if (props.q3 > props.q1) { | ||
ctx.strokeRect(props.q1, y0, props.q3 - props.q1, height); | ||
} | ||
else { | ||
ctx.strokeRect(props.q3, y0, props.q1 - props.q3, height); | ||
} | ||
ctx.beginPath(); | ||
ctx.moveTo(props.whiskerMin, y0); | ||
ctx.lineTo(props.whiskerMin, y0 + height); | ||
ctx.moveTo(props.whiskerMin, y); | ||
ctx.lineTo(props.q1, y); | ||
ctx.moveTo(props.whiskerMax, y0); | ||
ctx.lineTo(props.whiskerMax, y0 + height); | ||
ctx.moveTo(props.whiskerMax, y); | ||
ctx.lineTo(props.q3, y); | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
} | ||
_getBounds(useFinalPosition) { | ||
const vert = this.isVertical(); | ||
if (this.x == null) { | ||
return { | ||
left: 0, | ||
top: 0, | ||
right: 0, | ||
bottom: 0, | ||
}; | ||
} | ||
if (vert) { | ||
const { x, width, whiskerMax, whiskerMin } = this.getProps(['x', 'width', 'whiskerMin', 'whiskerMax'], useFinalPosition); | ||
const x0 = x - width / 2; | ||
return { | ||
left: x0, | ||
top: whiskerMax, | ||
right: x0 + width, | ||
bottom: whiskerMin, | ||
}; | ||
} | ||
const { y, height, whiskerMax, whiskerMin } = this.getProps(['y', 'height', 'whiskerMin', 'whiskerMax'], useFinalPosition); | ||
const y0 = y - height / 2; | ||
return { | ||
left: whiskerMin, | ||
top: y0, | ||
right: whiskerMax, | ||
bottom: y0 + height, | ||
}; | ||
} | ||
} | ||
BoxAndWiskers.id = 'boxandwhiskers'; | ||
BoxAndWiskers.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1, { | ||
medianColor: 'transparent', | ||
lowerBackgroundColor: 'transparent', | ||
}); | ||
BoxAndWiskers.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
class Violin extends StatsBase$1 { | ||
draw(ctx) { | ||
ctx.save(); | ||
ctx.fillStyle = this.options.backgroundColor; | ||
ctx.strokeStyle = this.options.borderColor; | ||
ctx.lineWidth = this.options.borderWidth; | ||
const props = this.getProps(['x', 'y', 'width', 'height', 'min', 'max', 'coords', 'maxEstimate']); | ||
helpers.drawPoint(ctx, { | ||
pointStyle: 'rectRot', | ||
radius: 5, | ||
borderWidth: this.options.borderWidth, | ||
}, props.x, props.y); | ||
if (props.coords && props.coords.length > 0) { | ||
this._drawCoords(ctx, props); | ||
} | ||
this._drawOutliers(ctx); | ||
ctx.restore(); | ||
this._drawItems(ctx); | ||
} | ||
_drawCoords(ctx, props) { | ||
ctx.beginPath(); | ||
if (this.isVertical()) { | ||
const x = props.x; | ||
const width = props.width; | ||
const factor = width / 2 / props.maxEstimate; | ||
ctx.moveTo(x, props.min); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(x - c.estimate * factor, c.v); | ||
}); | ||
ctx.lineTo(x, props.max); | ||
ctx.moveTo(x, props.min); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(x + c.estimate * factor, c.v); | ||
}); | ||
ctx.lineTo(x, props.max); | ||
} | ||
else { | ||
const y = props.y; | ||
const height = props.height; | ||
const factor = height / 2 / props.maxEstimate; | ||
ctx.moveTo(props.min, y); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(c.v, y - c.estimate * factor); | ||
}); | ||
ctx.lineTo(props.max, y); | ||
ctx.moveTo(props.min, y); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(c.v, y + c.estimate * factor); | ||
}); | ||
ctx.lineTo(props.max, y); | ||
} | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.fill(); | ||
} | ||
_getBounds(useFinalPosition) { | ||
if (this.isVertical()) { | ||
const { x, width, min, max } = this.getProps(['x', 'width', 'min', 'max'], useFinalPosition); | ||
const x0 = x - width / 2; | ||
return { | ||
left: x0, | ||
top: max, | ||
right: x0 + width, | ||
bottom: min, | ||
}; | ||
} | ||
const { y, height, min, max } = this.getProps(['y', 'height', 'min', 'max'], useFinalPosition); | ||
const y0 = y - height / 2; | ||
return { | ||
left: min, | ||
top: y0, | ||
right: max, | ||
bottom: y0 + height, | ||
}; | ||
} | ||
} | ||
Violin.id = 'violin'; | ||
Violin.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1); | ||
Violin.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
class Violin extends StatsBase$1 { | ||
draw(ctx) { | ||
ctx.save(); | ||
ctx.fillStyle = this.options.backgroundColor; | ||
ctx.strokeStyle = this.options.borderColor; | ||
ctx.lineWidth = this.options.borderWidth; | ||
const props = this.getProps(['x', 'y', 'width', 'height', 'min', 'max', 'coords', 'maxEstimate']); | ||
helpers.drawPoint(ctx, { | ||
pointStyle: 'rectRot', | ||
radius: 5, | ||
borderWidth: this.options.borderWidth, | ||
}, props.x, props.y); | ||
if (props.coords && props.coords.length > 0) { | ||
this._drawCoords(ctx, props); | ||
} | ||
this._drawOutliers(ctx); | ||
ctx.restore(); | ||
this._drawItems(ctx); | ||
} | ||
_drawCoords(ctx, props) { | ||
ctx.beginPath(); | ||
if (this.isVertical()) { | ||
const x = props.x; | ||
const width = props.width; | ||
const factor = width / 2 / props.maxEstimate; | ||
ctx.moveTo(x, props.min); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(x - c.estimate * factor, c.v); | ||
}); | ||
ctx.lineTo(x, props.max); | ||
ctx.moveTo(x, props.min); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(x + c.estimate * factor, c.v); | ||
}); | ||
ctx.lineTo(x, props.max); | ||
} | ||
else { | ||
const y = props.y; | ||
const height = props.height; | ||
const factor = height / 2 / props.maxEstimate; | ||
ctx.moveTo(props.min, y); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(c.v, y - c.estimate * factor); | ||
}); | ||
ctx.lineTo(props.max, y); | ||
ctx.moveTo(props.min, y); | ||
props.coords.forEach((c) => { | ||
ctx.lineTo(c.v, y + c.estimate * factor); | ||
}); | ||
ctx.lineTo(props.max, y); | ||
} | ||
ctx.closePath(); | ||
ctx.stroke(); | ||
ctx.fill(); | ||
} | ||
_getBounds(useFinalPosition) { | ||
if (this.isVertical()) { | ||
const { x, width, min, max } = this.getProps(['x', 'width', 'min', 'max'], useFinalPosition); | ||
const x0 = x - width / 2; | ||
return { | ||
left: x0, | ||
top: max, | ||
right: x0 + width, | ||
bottom: min, | ||
}; | ||
} | ||
const { y, height, min, max } = this.getProps(['y', 'height', 'min', 'max'], useFinalPosition); | ||
const y0 = y - height / 2; | ||
return { | ||
left: min, | ||
top: y0, | ||
right: max, | ||
bottom: y0 + height, | ||
}; | ||
} | ||
} | ||
Violin.id = 'violin'; | ||
Violin.defaults = Object.assign({}, chart_js.BarElement.defaults, baseDefaults$1); | ||
Violin.defaultRoutes = Object.assign({}, chart_js.BarElement.defaultRoutes, baseRoutes); | ||
function patchController(type, config, controller, elements = [], scales = []) { | ||
chart_js.registry.addControllers(controller); | ||
if (Array.isArray(elements)) { | ||
chart_js.registry.addElements(...elements); | ||
} | ||
else { | ||
chart_js.registry.addElements(elements); | ||
} | ||
if (Array.isArray(scales)) { | ||
chart_js.registry.addScales(...scales); | ||
} | ||
else { | ||
chart_js.registry.addScales(scales); | ||
} | ||
const c = config; | ||
c.type = type; | ||
return c; | ||
} | ||
function patchController(type, config, controller, elements = [], scales = []) { | ||
chart_js.registry.addControllers(controller); | ||
if (Array.isArray(elements)) { | ||
chart_js.registry.addElements(...elements); | ||
} | ||
else { | ||
chart_js.registry.addElements(elements); | ||
} | ||
if (Array.isArray(scales)) { | ||
chart_js.registry.addScales(...scales); | ||
} | ||
else { | ||
chart_js.registry.addScales(scales); | ||
} | ||
const c = config; | ||
c.type = type; | ||
return c; | ||
} | ||
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']) { | ||
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(boxOptionsKeys), | ||
{ | ||
datasets: { | ||
animation: { | ||
numbers: { | ||
type: 'number', | ||
properties: chart_js.BarController.defaults.datasets.animation.numbers.properties.concat(['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax'], boxOptionsKeys.filter((c) => !c.endsWith('Color'))), | ||
}, | ||
}, | ||
}, | ||
dataElementType: BoxAndWiskers.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(boxOptionsKeys), | ||
}, | ||
]); | ||
class BoxPlotChart extends chart_js.Chart { | ||
constructor(item, config) { | ||
super(item, patchController('boxplot', config, BoxPlotController, BoxAndWiskers, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
} | ||
} | ||
BoxPlotChart.id = BoxPlotController.id; | ||
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']) { | ||
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(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'))), | ||
}, | ||
}, | ||
}, | ||
dataElementType: BoxAndWiskers.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(boxOptionsKeys), | ||
}, | ||
]); | ||
class BoxPlotChart extends chart_js.Chart { | ||
constructor(item, config) { | ||
super(item, patchController('boxplot', config, BoxPlotController, BoxAndWiskers, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
} | ||
} | ||
BoxPlotChart.id = BoxPlotController.id; | ||
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]); | ||
} | ||
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) })); | ||
} | ||
} | ||
} | ||
ViolinController.id = 'violin'; | ||
ViolinController.defaults = helpers.merge({}, [ | ||
chart_js.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'], | ||
}, | ||
}, | ||
}, | ||
dataElementType: Violin.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(baseOptionKeys), | ||
}, | ||
]); | ||
class ViolinChart extends chart_js.Chart { | ||
constructor(item, config) { | ||
super(item, patchController('violin', config, ViolinController, Violin, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
} | ||
} | ||
ViolinChart.id = ViolinController.id; | ||
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]); | ||
} | ||
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) })); | ||
} | ||
} | ||
} | ||
ViolinController.id = 'violin'; | ||
ViolinController.defaults = helpers.merge({}, [ | ||
chart_js.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'], | ||
}, | ||
}, | ||
}, | ||
dataElementType: Violin.id, | ||
dataElementOptions: chart_js.BarController.defaults.dataElementOptions.concat(baseOptionKeys), | ||
}, | ||
]); | ||
class ViolinChart extends chart_js.Chart { | ||
constructor(item, config) { | ||
super(item, patchController('violin', config, ViolinController, Violin, [chart_js.LinearScale, chart_js.CategoryScale])); | ||
} | ||
} | ||
ViolinChart.id = ViolinController.id; | ||
chart_js.registry.addControllers(BoxPlotController, ViolinController); | ||
chart_js.registry.addElements(BoxAndWiskers, Violin); | ||
chart_js.registry.addControllers(BoxPlotController, ViolinController); | ||
chart_js.registry.addElements(BoxAndWiskers, Violin); | ||
exports.BoxAndWiskers = BoxAndWiskers; | ||
exports.BoxPlotChart = BoxPlotChart; | ||
exports.BoxPlotController = BoxPlotController; | ||
exports.StatsBase = StatsBase$1; | ||
exports.Violin = Violin; | ||
exports.ViolinChart = ViolinChart; | ||
exports.ViolinController = ViolinController; | ||
exports.BoxAndWiskers = BoxAndWiskers; | ||
exports.BoxPlotChart = BoxPlotChart; | ||
exports.BoxPlotController = BoxPlotController; | ||
exports.StatsBase = StatsBase$1; | ||
exports.Violin = Violin; | ||
exports.ViolinChart = ViolinChart; | ||
exports.ViolinController = ViolinController; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
}))); | ||
//# sourceMappingURL=index.umd.js.map |
@@ -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,n=t.length;const 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)).sort((t,e)=>t===e?0:t<e?-1:1);return{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,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"])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"],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(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})})); | ||
//# 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.7", | ||
"version": "3.0.0-beta.9", | ||
"publishConfig": { | ||
@@ -59,19 +59,20 @@ "access": "public" | ||
"dependencies": { | ||
"@sgratzl/boxplots": "^1.2.1" | ||
"@sgratzl/boxplots": "^1.2.2" | ||
}, | ||
"devDependencies": { | ||
"@rollup/plugin-commonjs": "^17.0.0", | ||
"@rollup/plugin-node-resolve": "^11.0.0", | ||
"@rollup/plugin-node-resolve": "^11.1.0", | ||
"@rollup/plugin-replace": "^2.3.4", | ||
"@rollup/plugin-typescript": "^8.0.0", | ||
"@types/jest": "^26.0.18", | ||
"@rollup/plugin-typescript": "^8.1.0", | ||
"@types/jest": "^26.0.20", | ||
"@types/jest-image-snapshot": "^4.1.3", | ||
"@types/node": "^14.14.11", | ||
"@typescript-eslint/eslint-plugin": "^4.9.1", | ||
"@typescript-eslint/parser": "^4.9.1", | ||
"@types/node": "^14.14.21", | ||
"@typescript-eslint/eslint-plugin": "^4.13.0", | ||
"@typescript-eslint/parser": "^4.13.0", | ||
"@yarnpkg/pnpify": "^2.4.0", | ||
"canvas": "^2.6.1", | ||
"chart.js": "^3.0.0-beta.7", | ||
"eslint": "^7.15.0", | ||
"eslint-config-prettier": "^7.0.0", | ||
"canvas-5-polyfill": "^0.1.5", | ||
"chart.js": "^3.0.0-beta.9", | ||
"eslint": "^7.18.0", | ||
"eslint-config-prettier": "^7.1.0", | ||
"eslint-config-react-app": "^6.0.0", | ||
@@ -81,18 +82,18 @@ "eslint-plugin-flowtype": "^5.2.0", | ||
"eslint-plugin-jsx-a11y": "^6.4.1", | ||
"eslint-plugin-prettier": "^3.2.0", | ||
"eslint-plugin-react": "^7.21.5", | ||
"eslint-plugin-prettier": "^3.3.1", | ||
"eslint-plugin-react": "^7.22.0", | ||
"eslint-plugin-react-hooks": "^4.2.0", | ||
"jest": "^26.6.3", | ||
"jest-image-snapshot": "^4.2.0", | ||
"jest-image-snapshot": "^4.3.0", | ||
"prettier": "^2.2.1", | ||
"release-it": "^14.2.2", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.34.2", | ||
"rollup": "^2.36.2", | ||
"rollup-plugin-cleanup": "^3.2.1", | ||
"rollup-plugin-dts": "^2.0.0", | ||
"rollup-plugin-dts": "^2.0.1", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"ts-jest": "^26.4.4", | ||
"tslib": "^2.0.3", | ||
"typedoc": "^0.19.2", | ||
"typescript": "^4.1.2" | ||
"tslib": "^2.1.0", | ||
"typedoc": "^0.20.16", | ||
"typescript": "^4.1.3" | ||
}, | ||
@@ -99,0 +100,0 @@ "scripts": { |
@@ -6,2 +6,3 @@ /// <reference types="jest" /> | ||
import { toMatchImageSnapshot, MatchImageSnapshotOptions } from 'jest-image-snapshot'; | ||
import 'canvas-5-polyfill'; | ||
@@ -34,3 +35,3 @@ expect.extend({ toMatchImageSnapshot }); | ||
defaults.font.family = 'Courier New'; | ||
defaults.font.color = 'transparent'; | ||
defaults.color = 'transparent'; | ||
config.options = Object.assign( | ||
@@ -37,0 +38,0 @@ { |
@@ -13,2 +13,3 @@ import { asBoxPlotStats, IBaseStats, IBoxPlot, IBoxplotOptions } from '../data'; | ||
CartesianScaleTypeRegistry, | ||
ScriptableContext, | ||
} from 'chart.js'; | ||
@@ -31,4 +32,6 @@ import { merge } from 'chart.js/helpers'; | ||
_transformStats<T>(target: any, source: IBoxPlot, mapper: (v: number) => T) { | ||
for (const key of ['min', 'max', 'median', 'q3', 'q1', 'whiskerMin', 'whiskerMax']) { | ||
target[key] = mapper(source[key as 'min' | 'max' | 'median' | 'q3' | 'q1' | 'whiskerMin' | 'whiskerMax']); | ||
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'] | ||
); | ||
} | ||
@@ -52,3 +55,3 @@ for (const key of ['outliers', 'items']) { | ||
properties: BarController.defaults.datasets.animation.numbers.properties.concat( | ||
['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax'], | ||
['q1', 'q3', 'min', 'max', 'median', 'whiskerMin', 'whiskerMax', 'mean'], | ||
boxOptionsKeys.filter((c) => !c.endsWith('Color')) | ||
@@ -68,4 +71,4 @@ ), | ||
IBoxplotOptions, | ||
ScriptableAndArrayOptions<IBoxAndWhiskersOptions>, | ||
ScriptableAndArrayOptions<CommonHoverOptions> {} | ||
ScriptableAndArrayOptions<IBoxAndWhiskersOptions, ScriptableContext>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> {} | ||
@@ -72,0 +75,0 @@ export type BoxPlotDataPoint = number[] | (Partial<IBoxPlot> & IBaseStats); |
@@ -13,2 +13,3 @@ import { asViolinStats, IBaseStats, IViolin, IViolinOptions } from '../data'; | ||
CartesianScaleTypeRegistry, | ||
ScriptableContext, | ||
} from 'chart.js'; | ||
@@ -76,4 +77,4 @@ import { merge } from 'chart.js/helpers'; | ||
IViolinOptions, | ||
ScriptableAndArrayOptions<IViolinElementOptions>, | ||
ScriptableAndArrayOptions<CommonHoverOptions> {} | ||
ScriptableAndArrayOptions<IViolinElementOptions, ScriptableContext>, | ||
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext> {} | ||
@@ -80,0 +81,0 @@ export type ViolinDataPoint = number[] | (Partial<IViolin> & IBaseStats); |
@@ -36,2 +36,3 @@ import boxplots, { | ||
whiskerMin: number; | ||
mean: number; | ||
} | ||
@@ -189,2 +190,3 @@ | ||
median: r.median, | ||
mean: r.mean, | ||
min: r.min, | ||
@@ -191,0 +193,0 @@ q1: r.q1, |
@@ -30,2 +30,3 @@ import { BarElement } from 'chart.js'; | ||
whiskerMax: number; | ||
mean: number; | ||
} | ||
@@ -32,0 +33,0 @@ |
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
338737
5112
35
Updated@sgratzl/boxplots@^1.2.2