@jackens/nnn 2024.2.15 → 2024.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chartable.d.ts +2 -4
- package/chartable.js +79 -51
- package/package.json +1 -1
- package/readme.md +3 -5
package/chartable.d.ts
CHANGED
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* - `gapX`: X axis spacing
|
|
7
7
|
* - `gapY`: Y axis spacing
|
|
8
8
|
* - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
|
|
9
|
-
* - `id`: chart id
|
|
10
9
|
* - `left`: left padding
|
|
11
10
|
* - `maxY`: number of Y axis lines
|
|
12
11
|
* - `reverse`: flag to reverse all data series
|
|
@@ -15,12 +14,11 @@
|
|
|
15
14
|
* - `table`: `HTMLTableElement` to extract data, data series labels and X axis labels
|
|
16
15
|
* - `top`: top padding
|
|
17
16
|
*/
|
|
18
|
-
export function chartable({ bottom, gapX, gapY, headerColumn,
|
|
17
|
+
export function chartable({ bottom, gapX, gapY, headerColumn, left, maxY, reverse, right, rotate, table, top }: {
|
|
19
18
|
bottom?: number;
|
|
20
19
|
gapX?: number;
|
|
21
20
|
gapY?: number;
|
|
22
21
|
headerColumn?: boolean;
|
|
23
|
-
id?: string;
|
|
24
22
|
left?: number;
|
|
25
23
|
maxY?: number;
|
|
26
24
|
reverse?: boolean;
|
|
@@ -28,6 +26,6 @@ export function chartable({ bottom, gapX, gapY, headerColumn, id, left, maxY, re
|
|
|
28
26
|
rotate?: boolean;
|
|
29
27
|
table: HTMLTableElement;
|
|
30
28
|
top?: number;
|
|
31
|
-
}):
|
|
29
|
+
}): Node;
|
|
32
30
|
|
|
33
31
|
export const tests: {};
|
package/chartable.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { s } from './h.js'
|
|
2
2
|
|
|
3
3
|
const COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
|
|
4
|
+
const PADDING = 15
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
|
|
@@ -10,7 +11,6 @@ const COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
|
|
|
10
11
|
* - `gapX`: X axis spacing
|
|
11
12
|
* - `gapY`: Y axis spacing
|
|
12
13
|
* - `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
|
|
13
|
-
* - `id`: chart id
|
|
14
14
|
* - `left`: left padding
|
|
15
15
|
* - `maxY`: number of Y axis lines
|
|
16
16
|
* - `reverse`: flag to reverse all data series
|
|
@@ -24,7 +24,6 @@ const COLORS = ['#e22', '#e73', '#fc3', '#ad4', '#4d9', '#3be', '#45d', '#c3e']
|
|
|
24
24
|
* gapX?: number;
|
|
25
25
|
* gapY?: number;
|
|
26
26
|
* headerColumn?: boolean;
|
|
27
|
-
* id?: string;
|
|
28
27
|
* left?: number;
|
|
29
28
|
* maxY?: number;
|
|
30
29
|
* reverse?: boolean;
|
|
@@ -39,16 +38,15 @@ export const chartable = ({
|
|
|
39
38
|
gapX = 70,
|
|
40
39
|
gapY = 30,
|
|
41
40
|
headerColumn = false,
|
|
42
|
-
|
|
43
|
-
left = 15,
|
|
41
|
+
left = 300,
|
|
44
42
|
maxY = 10,
|
|
45
43
|
reverse = false,
|
|
46
|
-
right =
|
|
44
|
+
right = 100,
|
|
47
45
|
rotate = false,
|
|
48
46
|
table,
|
|
49
|
-
top =
|
|
47
|
+
top = PADDING
|
|
50
48
|
}) => {
|
|
51
|
-
const /** @type {number[][]} */
|
|
49
|
+
const /** @type {number[][]} */ zxValues = []
|
|
52
50
|
const /** @type {string[]} */ xLabels = []
|
|
53
51
|
const /** @type {string[]} */ zLabels = []
|
|
54
52
|
|
|
@@ -60,8 +58,8 @@ export const chartable = ({
|
|
|
60
58
|
const value = col.innerText
|
|
61
59
|
|
|
62
60
|
if (x >= 0 && z >= 0) {
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
zxValues[z] = zxValues[z] ?? []
|
|
62
|
+
zxValues[z][x] = parseFloat(value)
|
|
65
63
|
} else if (x >= 0 && z < 0) {
|
|
66
64
|
xLabels[x] = value
|
|
67
65
|
} else if (x < 0 && z >= 0) {
|
|
@@ -71,60 +69,90 @@ export const chartable = ({
|
|
|
71
69
|
|
|
72
70
|
if (reverse) {
|
|
73
71
|
xLabels.reverse()
|
|
74
|
-
|
|
72
|
+
zxValues.forEach(xValues => xValues.reverse())
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
const maxX =
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const svgW = left +
|
|
83
|
-
const svgH = top +
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
...
|
|
87
|
-
|
|
88
|
-
const /** @type {import('./h.js').HArgs1} */
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
const maxX = zxValues[0].length
|
|
76
|
+
const xPx = Array.from({ length: maxX }, (_, x) => x * gapX)
|
|
77
|
+
const yPx = Array.from({ length: maxY }, (_, y) => y * gapY)
|
|
78
|
+
const width = gapX * (maxX - 1)
|
|
79
|
+
const height = gapY * (maxY - 1)
|
|
80
|
+
const svgW = left + width + right
|
|
81
|
+
const svgH = top + height + bottom
|
|
82
|
+
|
|
83
|
+
const /** @type {import('./h.js').HArgs1} */ gGraph = ['g', { transform: `translate(${left} ${top})` },
|
|
84
|
+
...yPx.map(px => ['line', { x1: 0, x2: width, y1: px, y2: px, stroke: '#8888' }]),
|
|
85
|
+
...xPx.map(px => ['line', { x1: px, x2: px, y1: 0, y2: height, stroke: '#8888' }])]
|
|
86
|
+
const /** @type {import('./h.js').HArgs1} */ gZColors = ['g', { transform: `translate(${PADDING} ${top})` }]
|
|
87
|
+
const /** @type {import('./h.js').HArgs1} */ gZLabels = ['g', { transform: `translate(${2 * PADDING} ${top})` }]
|
|
88
|
+
const /** @type {import('./h.js').HArgs} */ svg = ['svg',
|
|
89
|
+
{ viewBox: `0 0 ${svgW} ${svgH}`, width: `${svgW}px`, height: `${svgH}px`, class: 'chartable' },
|
|
90
|
+
gGraph, gZColors, gZLabels]
|
|
91
|
+
|
|
92
|
+
if (xLabels.length > 0) {
|
|
93
|
+
svg.push(['g', { transform: `translate(${left} ${top + height + PADDING})` },
|
|
94
|
+
...xPx.slice(0).map((xp, x) => ['text',
|
|
95
|
+
rotate
|
|
96
|
+
? { x: 0, y: -xp, 'text-anchor': 'start', 'alignment-baseline': 'middle', transform: 'rotate(90)' }
|
|
97
|
+
: { x: xp, y: PADDING, 'text-anchor': 'middle', 'alignment-baseline': 'middle' },
|
|
98
|
+
xLabels[x]])])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const /** @type {[SVGElement, SVGElement, SVGTextElement][]} */ toUpdate = []
|
|
102
|
+
|
|
103
|
+
const onclick = (/** @type {number} */ z) => toUpdate.forEach(([gLeftYLabels, gRightYLabels, zLabel], z2) => {
|
|
104
|
+
s(gLeftYLabels, { $style: { display: z2 === z ? 'block' : 'none' } })
|
|
105
|
+
s(gRightYLabels, { $style: { display: z2 === z ? 'block' : 'none' } })
|
|
106
|
+
s(zLabel, { $style: { textDecoration: z2 === z ? 'underline' : 'none', cursor: 'pointer' } })
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
zxValues.forEach((values, z) => {
|
|
91
110
|
const fill = COLORS[z % COLORS.length]
|
|
92
|
-
const
|
|
93
|
-
const
|
|
111
|
+
const minValue = Math.min(...values.filter(value => !isNaN(value)))
|
|
112
|
+
const maxValue = Math.max(...values.filter(value => !isNaN(value)))
|
|
94
113
|
|
|
95
|
-
|
|
96
|
-
labels.push(['text',
|
|
97
|
-
{ x: 0, y: gapY * (labels.length - 2), 'text-anchor': 'start', 'alignment-baseline': 'middle' },
|
|
98
|
-
zLabels[z]])
|
|
114
|
+
const valuesPx = values.map(value => isNaN(value) ? value : height * (maxValue - value) / (maxValue - minValue))
|
|
99
115
|
|
|
100
|
-
const
|
|
116
|
+
const gLeftYLabels = s('g', { transform: `translate(${left - PADDING} ${top})` },
|
|
117
|
+
...yPx.map((yp, y) => ['text',
|
|
118
|
+
{ x: 0, y: yp, 'text-anchor': 'end', 'alignment-baseline': 'middle' },
|
|
119
|
+
(maxValue - (maxValue - minValue) / maxY * (y + 1)).toFixed(2)]))
|
|
101
120
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
121
|
+
const gRightYLabels = s('g', { transform: `translate(${left + width + PADDING} ${top})` },
|
|
122
|
+
...yPx.map((yp, y) => ['text',
|
|
123
|
+
{ x: 0, y: yp, 'text-anchor': 'start', 'alignment-baseline': 'middle' },
|
|
124
|
+
(maxValue - (maxValue - minValue) / maxY * (y + 1)).toFixed(2)]))
|
|
105
125
|
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
svg.push(gLeftYLabels, gRightYLabels)
|
|
127
|
+
|
|
128
|
+
const zLabel = s('text', {
|
|
129
|
+
x: 0,
|
|
130
|
+
y: gapY * (gZLabels.length - 2),
|
|
131
|
+
'text-anchor': 'start',
|
|
132
|
+
'alignment-baseline': 'middle',
|
|
133
|
+
$onclick: () => onclick(z)
|
|
134
|
+
},
|
|
135
|
+
zLabels[z])
|
|
136
|
+
|
|
137
|
+
gZLabels.push(zLabel)
|
|
138
|
+
gZColors.push(['circle', { cx: 0, cy: gapY * (gZColors.length - 2), r: 5, fill }])
|
|
139
|
+
|
|
140
|
+
toUpdate.push([gLeftYLabels, gRightYLabels, zLabel])
|
|
141
|
+
|
|
142
|
+
xPx.forEach((px, x) => {
|
|
143
|
+
if (!isNaN(valuesPx[x])) {
|
|
144
|
+
gGraph.push(['g', ['circle', { cx: px, cy: valuesPx[x], r: 5, fill }]])
|
|
145
|
+
|
|
146
|
+
if (!isNaN(valuesPx[x - 1])) {
|
|
147
|
+
gGraph.push(['line', { x1: px, x2: xPx[x - 1], y1: valuesPx[x], y2: valuesPx[x - 1], stroke: fill }])
|
|
108
148
|
}
|
|
109
149
|
}
|
|
110
150
|
})
|
|
111
151
|
})
|
|
112
152
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
height: `${svgH}px`,
|
|
117
|
-
class: 'chartable'
|
|
118
|
-
}, id != null ? { id } : null,
|
|
119
|
-
graph, colors, labels,
|
|
120
|
-
xLabels.length > 0
|
|
121
|
-
? ['g', { transform: `translate(${left} ${top + h + 15})` },
|
|
122
|
-
...xi.slice(0).map((x, i) => ['text', rotate
|
|
123
|
-
? { x: 0, y: -x, 'text-anchor': 'start', 'alignment-baseline': 'hanging', transform: 'rotate(90)' }
|
|
124
|
-
: { x, y: 0, 'text-anchor': 'middle', 'alignment-baseline': 'hanging' },
|
|
125
|
-
xLabels[i]])]
|
|
126
|
-
: null
|
|
127
|
-
)
|
|
153
|
+
onclick(0)
|
|
154
|
+
|
|
155
|
+
return s(...svg)
|
|
128
156
|
}
|
|
129
157
|
|
|
130
158
|
export const tests = {}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Jackens’ JavaScript helpers.
|
|
4
4
|
|
|
5
|
-
<sub>Version: <code class="version">2024.2.
|
|
5
|
+
<sub>Version: <code class="version">2024.2.17</code></sub>
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -103,12 +103,11 @@ The type of arguments of the `jcss` helper.
|
|
|
103
103
|
### chartable
|
|
104
104
|
|
|
105
105
|
```ts
|
|
106
|
-
export function chartable({ bottom, gapX, gapY, headerColumn,
|
|
106
|
+
export function chartable({ bottom, gapX, gapY, headerColumn, left, maxY, reverse, right, rotate, table, top }: {
|
|
107
107
|
bottom?: number;
|
|
108
108
|
gapX?: number;
|
|
109
109
|
gapY?: number;
|
|
110
110
|
headerColumn?: boolean;
|
|
111
|
-
id?: string;
|
|
112
111
|
left?: number;
|
|
113
112
|
maxY?: number;
|
|
114
113
|
reverse?: boolean;
|
|
@@ -116,7 +115,7 @@ export function chartable({ bottom, gapX, gapY, headerColumn, id, left, maxY, re
|
|
|
116
115
|
rotate?: boolean;
|
|
117
116
|
table: HTMLTableElement;
|
|
118
117
|
top?: number;
|
|
119
|
-
}):
|
|
118
|
+
}): Node;
|
|
120
119
|
```
|
|
121
120
|
|
|
122
121
|
A helper for creating a chart based on a table (conf. <https://jackens.github.io/nnn/chartable/>).
|
|
@@ -126,7 +125,6 @@ Options:
|
|
|
126
125
|
- `gapX`: X axis spacing
|
|
127
126
|
- `gapY`: Y axis spacing
|
|
128
127
|
- `headerColumn`: flag indicating that `table` has a header column (with X axis labels)
|
|
129
|
-
- `id`: chart id
|
|
130
128
|
- `left`: left padding
|
|
131
129
|
- `maxY`: number of Y axis lines
|
|
132
130
|
- `reverse`: flag to reverse all data series
|