@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 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, id, left, maxY, reverse, right, rotate, table, top }: {
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
- }): SVGSVGElement;
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
- id,
43
- left = 15,
41
+ left = 300,
44
42
  maxY = 10,
45
43
  reverse = false,
46
- right = 200,
44
+ right = 100,
47
45
  rotate = false,
48
46
  table,
49
- top = 15
47
+ top = PADDING
50
48
  }) => {
51
- const /** @type {number[][]} */ zxY = []
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
- zxY[z] = zxY[z] ?? []
64
- zxY[z][x] = parseFloat(value)
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
- zxY.forEach(xY => xY.reverse())
72
+ zxValues.forEach(xValues => xValues.reverse())
75
73
  }
76
74
 
77
- const maxX = zxY[0].length
78
- const xi = Array.from({ length: maxX }, (_, x) => x * gapX)
79
- const yi = Array.from({ length: maxY }, (_, y) => y * gapY)
80
- const w = gapX * (maxX - 1)
81
- const h = gapY * (maxY - 1)
82
- const svgW = left + w + right
83
- const svgH = top + h + bottom
84
- const /** @type {import('./h.js').HArgs1} */ graph = ['g', { transform: `translate(${left} ${top})` },
85
- ...yi.map(y => ['line', { x1: 0, x2: w, y1: y, y2: y, stroke: '#8888' }]),
86
- ...xi.map(x => ['line', { x1: x, x2: x, y1: 0, y2: h, stroke: '#8888' }])]
87
- const /** @type {import('./h.js').HArgs1} */ colors = ['g', { transform: `translate(${left + w + 25} ${top + 15})` }]
88
- const /** @type {import('./h.js').HArgs1} */ labels = ['g', { transform: `translate(${left + w + 40} ${top + 15})` }]
89
-
90
- zxY.forEach((xY, z) => {
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 min = Math.min(...xY.filter(x => !isNaN(x)))
93
- const max = Math.max(...xY.filter(x => !isNaN(x)))
111
+ const minValue = Math.min(...values.filter(value => !isNaN(value)))
112
+ const maxValue = Math.max(...values.filter(value => !isNaN(value)))
94
113
 
95
- colors.push(['circle', { cx: 0, cy: gapY * (colors.length - 2), r: 5, fill }])
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 y = xY.map(x => isNaN(x) ? x : h * (max - x) / (max - min))
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
- xi.forEach((x, i) => {
103
- if (!isNaN(y[i])) {
104
- graph.push(['g', ['circle', { cx: x, cy: y[i], r: 5, fill }, ['title', `${zLabels[z]}: ${xY[i]}`]]])
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
- if (!isNaN(y[i - 1])) {
107
- graph.push(['line', { x1: x, x2: xi[i - 1], y1: y[i], y2: y[i - 1], stroke: fill }])
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
- return s('svg', {
114
- viewBox: `0 0 ${svgW} ${svgH}`,
115
- width: `${svgW}px`,
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
@@ -40,5 +40,5 @@
40
40
  "types": "nnn.d.ts",
41
41
  "name": "@jackens/nnn",
42
42
  "type": "module",
43
- "version": "2024.2.15"
43
+ "version": "2024.2.17"
44
44
  }
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.15</code></sub>
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, id, left, maxY, reverse, right, rotate, table, top }: {
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
- }): SVGSVGElement;
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