Skip to content

Commit 8b28243

Browse files
committed
horizontal bar chart
1 parent 383b723 commit 8b28243

File tree

13 files changed

+206
-139
lines changed

13 files changed

+206
-139
lines changed

README.md

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -29,56 +29,54 @@ four new types: `boxplot`, `horizontalBoxplot`, `violin`, and `horizontalViolin`
2929

3030
```typescript
3131
interface IChartJSOptions {
32-
/**
33-
* Limit decimal digits by an optional config option
34-
**/
35-
tooltipDecimals?: number;
36-
3732
boxplot: {
38-
/**
39-
* statistic measure that should be used for computing the minimal data limit
40-
* @default 'min'
41-
*/
42-
minStats: 'min' | 'q1' | 'whiskerMin';
43-
44-
/**
45-
* statistic measure that should be used for computing the maximal data limit
46-
* @default 'max'
47-
*/
48-
maxStats: 'max' | 'q3' | 'whiskerMax';
49-
50-
/**
51-
* from the R doc: this determines how far the plot ‘whiskers’ extend out from
52-
* the box. If coef is positive, the whiskers extend to the most extreme data
53-
* point which is no more than coef times the length of the box away from the
54-
* box. A value of zero causes the whiskers to extend to the data extremes
55-
* @default 1.5
56-
*/
57-
coef: number;
58-
59-
/**
60-
* the method to compute the quantiles.
61-
*
62-
* 7, 'quantiles': the type-7 method as used by R 'quantiles' method.
63-
* 'hinges' and 'fivenum': the method used by R 'boxplot.stats' method.
64-
* 'linear': the interpolation method 'linear' as used by 'numpy.percentile' function
65-
* 'lower': the interpolation method 'lower' as used by 'numpy.percentile' function
66-
* 'higher': the interpolation method 'higher' as used by 'numpy.percentile' function
67-
* 'nearest': the interpolation method 'nearest' as used by 'numpy.percentile' function
68-
* 'midpoint': the interpolation method 'midpoint' as used by 'numpy.percentile' function
69-
* @default 7
70-
*/
71-
quantiles:
72-
| 7
73-
| 'quantiles'
74-
| 'hinges'
75-
| 'fivenum'
76-
| 'linear'
77-
| 'lower'
78-
| 'higher'
79-
| 'nearest'
80-
| 'midpoint'
81-
| ((sortedArr: number[]) => { min: number; q1: number; median: number; q3: number; max: number });
33+
datasets: {
34+
/**
35+
* statistic measure that should be used for computing the minimal data limit
36+
* @default 'min'
37+
*/
38+
minStats: 'min' | 'q1' | 'whiskerMin';
39+
40+
/**
41+
* statistic measure that should be used for computing the maximal data limit
42+
* @default 'max'
43+
*/
44+
maxStats: 'max' | 'q3' | 'whiskerMax';
45+
46+
/**
47+
* from the R doc: this determines how far the plot ‘whiskers’ extend out from
48+
* the box. If coef is positive, the whiskers extend to the most extreme data
49+
* point which is no more than coef times the length of the box away from the
50+
* box. A value of zero causes the whiskers to extend to the data extremes
51+
* @default 1.5
52+
*/
53+
coef: number;
54+
55+
/**
56+
* the method to compute the quantiles.
57+
*
58+
* 7, 'quantiles': the type-7 method as used by R 'quantiles' method.
59+
* 'hinges' and 'fivenum': the method used by R 'boxplot.stats' method.
60+
* 'linear': the interpolation method 'linear' as used by 'numpy.percentile' function
61+
* 'lower': the interpolation method 'lower' as used by 'numpy.percentile' function
62+
* 'higher': the interpolation method 'higher' as used by 'numpy.percentile' function
63+
* 'nearest': the interpolation method 'nearest' as used by 'numpy.percentile' function
64+
* 'midpoint': the interpolation method 'midpoint' as used by 'numpy.percentile' function
65+
* @default 7
66+
*/
67+
quantiles:
68+
| 7
69+
| 'quantiles'
70+
| 'hinges'
71+
| 'fivenum'
72+
| 'linear'
73+
| 'lower'
74+
| 'higher'
75+
| 'nearest'
76+
| 'midpoint'
77+
| ((sortedArr: number[]) => { min: number; q1: number; median: number; q3: number; max: number });
78+
};
79+
};
8280
}
8381
```
8482

samples/fivenum.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@
5252
display: true,
5353
text: 'Quantiles type 7',
5454
},
55-
datasets: {
56-
quantiles: 'quantiles',
55+
boxplot: {
56+
datasets: {
57+
quantiles: 'quantiles',
58+
},
5759
},
5860
},
5961
});
@@ -70,8 +72,10 @@
7072
display: true,
7173
text: 'Quantiles Fivenum',
7274
},
73-
datasets: {
74-
quantiles: 'fivenum',
75+
boxplot: {
76+
datasets: {
77+
quantiles: 'fivenum',
78+
},
7579
},
7680
},
7781
});

samples/horizontalBoxplot.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
display: true,
6262
text: 'Chart.js Box Plot Chart',
6363
},
64-
tooltipDecimals: 4,
6564
},
6665
});
6766
};

samples/violin.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
display: true,
6464
text: 'Chart.js Violin Chart',
6565
},
66-
tooltipDecimals: 3,
6766
},
6867
});
6968
};

samples/violinSingle.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@
6161
display: true,
6262
text: 'Chart.js Violin Chart',
6363
},
64-
tooltipDecimals: 3,
6564
},
6665
});
6766
};

src/bundle.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from '.';
22

3-
import { BoxPlot } from './controllers';
3+
import { BoxPlot, HorizontalBoxPlot } from './controllers';
44

55
BoxPlot.register();
6+
HorizontalBoxPlot.register();

src/controllers/base.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { interpolateNumberArray } from '../animation';
22
import { outlierPositioner, patchInHoveredOutlier } from '../tooltip';
3+
import { controllers } from 'chart.js';
34

45
export const configKeys = [
56
'outlierRadius',
@@ -42,6 +43,8 @@ export function baseDefaults() {
4243
},
4344
},
4445
},
46+
minStats: 'min',
47+
maxStats: 'max',
4548
},
4649
tooltips: {
4750
position: outlierPositioner.register().id,
@@ -51,3 +54,86 @@ export function baseDefaults() {
5154
},
5255
};
5356
}
57+
58+
export class StatsBase extends controllers.bar {
59+
getMinMax(scale, canStack) {
60+
const bak = scale.axis;
61+
const config = this._config;
62+
scale.axis = config.minStats;
63+
const min = super.getMinMax(scale, canStack).min;
64+
scale.axis = config.maxStats;
65+
const max = super.getMinMax(scale, canStack).max;
66+
scale.axis = bak;
67+
return { min, max };
68+
}
69+
70+
parseArrayData(meta, data, start, count) {
71+
const vScale = meta.vScale;
72+
const iScale = meta.iScale;
73+
const labels = iScale.getLabels();
74+
const r = [];
75+
for (let i = 0; i < count; i++) {
76+
const index = i + start;
77+
const parsed = {};
78+
parsed[iScale.axis] = iScale.parse(labels[index], index);
79+
const stats = this._parseStats(data[index], this._config);
80+
if (stats) {
81+
Object.assign(parsed, stats);
82+
parsed[vScale.axis] = stats.median;
83+
}
84+
r.push(parsed);
85+
}
86+
return r;
87+
}
88+
89+
_parseStats(_value, _config) {
90+
// abstract
91+
return {};
92+
}
93+
94+
parseObjectData(meta, data, start, count) {
95+
return this.parseArrayData(meta, data, start, count);
96+
}
97+
98+
getLabelAndValue(index) {
99+
const r = super.getLabelAndValue(index);
100+
const vScale = this._cachedMeta.vScale;
101+
const parsed = this.getParsed(index);
102+
if (!vScale || !parsed) {
103+
return r;
104+
}
105+
r.value = {
106+
raw: parsed,
107+
hoveredOutlierIndex: -1,
108+
};
109+
this._transformStats(r.value, parsed, (v) => vScale.getLabelForValue(v));
110+
const s = this._toStringStats(r.value);
111+
r.value.toString = function () {
112+
// custom to string function for the 'value'
113+
if (this.hoveredOutlierIndex >= 0) {
114+
return `(outlier: ${this.outliers[this.hoveredOutlierIndex]})`;
115+
}
116+
return s;
117+
};
118+
return r;
119+
}
120+
121+
_toStringStats(_b) {
122+
// abstract
123+
return '';
124+
}
125+
126+
_transformStats(_target, _source, _mapper) {
127+
// abstract
128+
}
129+
130+
updateElement(rectangle, index, properties, mode) {
131+
const reset = mode === 'reset';
132+
const scale = this._cachedMeta.vScale;
133+
const parsed = this.getParsed(index);
134+
const base = scale.getBasePixel();
135+
properties._datasetIndex = this.index;
136+
this._transformStats(properties, parsed, (v) => (reset ? base : scale.getPixelForValue(v)));
137+
super.updateElement(rectangle, index, properties, mode);
138+
}
139+
}

0 commit comments

Comments
 (0)