Skip to content

Commit c4d1b81

Browse files
authored
Merge pull request #39 from sgratzl/release/v4.2.0
Release v4.2.0
2 parents c91f47e + 037c330 commit c4d1b81

File tree

7 files changed

+286
-13
lines changed

7 files changed

+286
-13
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ npm-debug.log*
2424
.eslintcache
2525
__diff_output__
2626

27-
/samples/type_test.js
27+
/samples/type_test.js
28+
.idea
29+
.idea/*

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "chartjs-plugin-hierarchical",
33
"description": "Chart.js module for hierarchical categories",
4-
"version": "4.1.2",
4+
"version": "4.2.0",
55
"author": {
66
"name": "Samuel Gratzl",
77
"email": "[email protected]",

samples/horizontal_reverse.html

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Hierarchical Horizontal Bar Chart</title>
5+
<script src=" https://cdn.jsdelivr.net/npm/chart.js@~4.1.1"></script>
6+
<script src="../build/index.umd.js"></script>
7+
<style>
8+
canvas {
9+
-moz-user-select: none;
10+
-webkit-user-select: none;
11+
-ms-user-select: none;
12+
}
13+
</style>
14+
</head>
15+
16+
<body>
17+
<div id="container" style="width: 75%">
18+
<canvas id="canvas"></canvas>
19+
</div>
20+
<script>
21+
const data = {
22+
labels: [
23+
'A',
24+
{
25+
label: 'B1',
26+
expand: true,
27+
children: [
28+
'B1.1',
29+
{
30+
label: 'B1.2',
31+
children: ['B1.2.1', 'B1.2.2'],
32+
},
33+
'B1.3',
34+
],
35+
},
36+
{
37+
label: 'C1',
38+
children: ['C1.1', 'C1.2', 'C1.3', 'C1.4'],
39+
},
40+
'D',
41+
],
42+
datasets: [
43+
{
44+
label: 'Test',
45+
tree: [
46+
1,
47+
{
48+
value: 2,
49+
children: [
50+
3,
51+
{
52+
value: 4,
53+
children: [4.1, 4.2],
54+
},
55+
5,
56+
],
57+
},
58+
{
59+
value: 6,
60+
children: [7, 8, 9, 10],
61+
},
62+
11,
63+
],
64+
},
65+
],
66+
};
67+
window.onload = () => {
68+
const ctx = document.getElementById('canvas').getContext('2d');
69+
window.myBar = new Chart(ctx, {
70+
type: 'bar',
71+
data: data,
72+
options: {
73+
indexAxis: 'y',
74+
responsive: true,
75+
title: {
76+
display: true,
77+
text: 'Chart.js Hierarchical Horizontal Bar Chart',
78+
},
79+
layout: {
80+
padding: {
81+
// add more space at the left side for the hierarchy
82+
left: 50,
83+
},
84+
},
85+
scales: {
86+
y: {
87+
type: 'hierarchical',
88+
// tune padding setting
89+
padding: 0,
90+
reverseOrder: true,
91+
},
92+
},
93+
},
94+
});
95+
};
96+
</script>
97+
</body>
98+
</html>

samples/reverse_order.html

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Hierarchical Bar Chart</title>
5+
<script src=" https://cdn.jsdelivr.net/npm/chart.js@~4.1.1"></script>
6+
<script src="../build/index.umd.js"></script>
7+
<style>
8+
canvas {
9+
user-select: none;
10+
}
11+
</style>
12+
</head>
13+
14+
<body>
15+
<div id="container" style="width: 75%">
16+
<canvas id="canvas"></canvas>
17+
</div>
18+
<script>
19+
const data = {
20+
// define label tree
21+
labels: [
22+
'A',
23+
{
24+
label: 'B1',
25+
expand: true,
26+
children: [
27+
'B1.1',
28+
{
29+
label: 'B1.2',
30+
expand: true,
31+
children: [
32+
{
33+
label: 'B1.2.1',
34+
expand: true,
35+
children: [
36+
{
37+
label: 'B1.2.1.1',
38+
expand: true,
39+
children: [
40+
{
41+
label: 'B1.2.1.1.1',
42+
expand: true,
43+
children: [
44+
{
45+
label: 'B1.2.1.1.1.2',
46+
expand: true,
47+
children: [{ label: 'ZZ', children: ['X'] }, 'Y', 'Z'],
48+
},
49+
'XX',
50+
'X',
51+
],
52+
},
53+
'Y',
54+
'Z',
55+
],
56+
},
57+
'B1.2.1.2',
58+
'B1.2.1.3',
59+
],
60+
},
61+
'B1.2.2',
62+
],
63+
},
64+
'B1.3',
65+
],
66+
},
67+
'D',
68+
],
69+
datasets: [
70+
{
71+
label: 'Test',
72+
// store as the tree attribute for reference, the data attribute will be automatically managed
73+
tree: [
74+
1,
75+
{
76+
value: 2,
77+
children: [
78+
3,
79+
{
80+
value: 4,
81+
children: [
82+
{
83+
value: 4.1,
84+
children: [
85+
{
86+
value: 4.12,
87+
children: [
88+
{
89+
value: 4.121,
90+
children: [4.1211, 4.1212],
91+
},
92+
4.122,
93+
],
94+
},
95+
4.12,
96+
],
97+
},
98+
4.2,
99+
],
100+
},
101+
5,
102+
],
103+
},
104+
{
105+
value: 6,
106+
children: [7, 8, 9, 10],
107+
},
108+
11,
109+
],
110+
},
111+
],
112+
};
113+
window.onload = () => {
114+
const ctx = document.getElementById('canvas').getContext('2d');
115+
window.myBar = new Chart(ctx, {
116+
type: 'bar',
117+
data: data,
118+
options: {
119+
responsive: true,
120+
title: {
121+
display: true,
122+
text: 'Chart.js Hierarchical Bar Chart',
123+
},
124+
layout: {
125+
padding: {
126+
// add more space at the bottom for the hierarchy
127+
bottom: 200,
128+
},
129+
},
130+
scales: {
131+
x: {
132+
type: 'hierarchical',
133+
reverseOrder: true,
134+
},
135+
},
136+
},
137+
});
138+
};
139+
</script>
140+
</body>
141+
</html>

src/plugin/hierarchical.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
spanLogic,
1313
determineVisible,
1414
flatChildren,
15+
getMaxDepth,
1516
} from '../utils';
1617
import type { ILabelNodes, ILabelNode, IEnhancedChart, IEnhancedChartDataSet } from '../model';
1718
import type { HierarchicalScale } from '../scale';
@@ -258,7 +259,8 @@ function handleClickEvents(
258259
_event: unknown,
259260
elem: { offset: number; index: number },
260261
offsetDelta: number,
261-
inRange: (v: number) => boolean
262+
inRange: (v: number) => boolean,
263+
reverse: boolean = false
262264
) {
263265
const cc = chart as unknown as IEnhancedChart;
264266
let { offset } = elem;
@@ -270,8 +272,10 @@ function handleClickEvents(
270272
return;
271273
}
272274
const parents = parentsOf(label, flat);
275+
const maxDepth = getMaxDepth((cc.data.rootNodes || []) as Array<ILabelNode>);
276+
if (reverse) offset += maxDepth * offsetDelta;
273277

274-
for (let i = 1; i < parents.length; i += 1, offset += offsetDelta) {
278+
for (let i = 1; i < parents.length; i += 1, reverse ? (offset -= offsetDelta) : (offset += offsetDelta)) {
275279
if (!inRange(offset)) {
276280
// eslint-disable-next-line no-continue
277281
continue;
@@ -352,6 +356,7 @@ const hierarchicalPlugin: Plugin = {
352356
const isStatic = scale.options.static;
353357

354358
const scaleLabel = scale.options.title;
359+
const scaleReverse = scale.options.reverseOrder;
355360
const scaleLabelFontColor = valueOrDefault(scaleLabel.color, defaults.color as Color);
356361
const scaleLabelFont = toFont(scaleLabel.font as Partial<FontSpec>);
357362

@@ -403,11 +408,14 @@ const hierarchicalPlugin: Plugin = {
403408
ctx.fillStyle = scaleLabelFontColor!; // render in correct color
404409
ctx.font = scaleLabelFont.string;
405410

406-
const renderHorLevel = (node: ILabelNode) => {
411+
const renderHorLevel = (node: ILabelNode, maxDepth: number = 0) => {
407412
if (node.children.length === 0) {
408413
return false;
409414
}
410-
const offset = node.level * boxRow;
415+
let offset = node.level * boxRow;
416+
if (scaleReverse) {
417+
offset = maxDepth * boxRow - node.level * boxRow;
418+
}
411419

412420
if (!node.expand) {
413421
if (visibleNodes.has(node)) {
@@ -475,11 +483,14 @@ const hierarchicalPlugin: Plugin = {
475483
return true;
476484
};
477485

478-
const renderVertLevel = (node: ILabelNode) => {
486+
const renderVertLevel = (node: ILabelNode, maxDepth: number = 0) => {
479487
if (node.children.length === 0) {
480488
return false;
481489
}
482-
const offset = node.level * boxRow * -1;
490+
let offset = node.level * boxRow * -1;
491+
if (scaleReverse) {
492+
offset = (maxDepth * boxRow - node.level * boxRow) * -1;
493+
}
483494

484495
if (!node.expand) {
485496
if (visibleNodes.has(node)) {
@@ -544,17 +555,18 @@ const hierarchicalPlugin: Plugin = {
544555
return true;
545556
};
546557

558+
const maxLevel = getMaxDepth(roots as Array<ILabelNode>);
547559
if (hor) {
548560
ctx.textAlign = 'center';
549561
ctx.textBaseline = renderLabel === 'above' ? 'bottom' : 'top';
550562
ctx.translate(scale.left, scale.bottom + scale.options.padding);
551-
roots.forEach((n) => preOrderTraversal(n, renderHorLevel));
563+
roots.forEach((n) => preOrderTraversal(n, (m) => renderHorLevel(m, maxLevel)));
552564
} else {
553565
ctx.textAlign = 'right';
554566
ctx.textBaseline = 'middle';
555567
ctx.translate(scale.left - scale.options.padding, scale.top);
556568

557-
roots.forEach((n) => preOrderTraversal(n, renderVertLevel));
569+
roots.forEach((k) => preOrderTraversal(k, (l) => renderVertLevel(l, maxLevel)));
558570
}
559571

560572
ctx.restore();
@@ -578,12 +590,12 @@ const hierarchicalPlugin: Plugin = {
578590
}
579591

580592
const boxRow = scale.options.hierarchyBoxLineHeight;
581-
593+
const reverse = scale.options.reverseOrder;
582594
const inRange = hor
583595
? (o: number) => clickEvent.y >= o && clickEvent.y <= o + boxRow
584596
: (o: number) => clickEvent.x <= o && clickEvent.x >= o - boxRow;
585597
const offsetDelta = hor ? boxRow : -boxRow;
586-
handleClickEvents(chart, event, elem, offsetDelta, inRange);
598+
handleClickEvents(chart, event, elem, offsetDelta, inRange, reverse);
587599
},
588600
};
589601

src/scale/hierarchical.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface IHierarchicalScaleOptions extends CategoryScaleOptions {
1212
levelPercentage: number;
1313
/**
1414
* padding of the first collapse to the start of the x-axis
15-
* @default 5
15+
* @default 25
1616
*/
1717
padding: number;
1818
/**
@@ -66,6 +66,11 @@ export interface IHierarchicalScaleOptions extends CategoryScaleOptions {
6666
attributes: { [attribute: string]: any };
6767

6868
offset: true;
69+
/**
70+
* if reverseOrder is true the lowest hierarchy level is on axis level and the highest level is the one furthest from axis
71+
* @default false
72+
*/
73+
reverseOrder: boolean;
6974
}
7075

7176
const defaultConfig: Partial<Omit<IHierarchicalScaleOptions, 'grid'>> & {
@@ -123,6 +128,11 @@ const defaultConfig: Partial<Omit<IHierarchicalScaleOptions, 'grid'>> & {
123128
hierarchyBoxWidth: 1,
124129

125130
attributes: {},
131+
/**
132+
* if reverseOrder is true the lowest hierarchy level is on axis level and the highest level is the one furthest from axis
133+
* @default false
134+
*/
135+
reverseOrder: false,
126136
};
127137

128138
export interface IInternalScale {

0 commit comments

Comments
 (0)