@martinomagnifico/tablechart 1.1.3 → 1.2.0
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/CHANGELOG.md +30 -0
- package/README.md +7 -6
- package/dist/tablechart-additional-types.js +1 -1
- package/dist/tablechart-additional-types.mjs +143 -75
- package/dist/tablechart-core.mjs +1 -1
- package/dist/tablechart-shared.d.mts +42 -0
- package/dist/tablechart-shared.mjs +1474 -1005
- package/dist/tablechart-summary.d.mts +35 -0
- package/dist/tablechart-summary.js +1 -0
- package/dist/tablechart-summary.mjs +269 -0
- package/dist/tablechart.css +1 -1
- package/dist/tablechart.js +1 -1
- package/dist/tablechart.mjs +1 -1
- package/package.json +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2026-10-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Summaries! They need a separate import from `@martinomagnifico/tablechart/summary`. Summaries are important for screen readers and print.
|
|
8
|
+
- A line over columns, with `data-chart="line"` on a column header.
|
|
9
|
+
- `data-chart-values` and the `values` option: which values a line shows. A long line shows only its key values.
|
|
10
|
+
- Notes: a column with `data-note`, shown as a line at its row and in the tooltip.
|
|
11
|
+
- A `quadrant` annotation for scatter charts, and one step for a scatter with `data-fragment`.
|
|
12
|
+
- `--tablechart-bubble-min`, so the smallest bubble stays visible.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Values on a line keep clear of the line, the points and each other, if possible.
|
|
17
|
+
- Line and area charts have a tick above each name. `data-chart-ticks="false"` turns them off.
|
|
18
|
+
- Scatter markers have an edge in the background colour, so they stand apart from lines and each other.
|
|
19
|
+
- Bars put their numbers after the bar by default, as columns put them above. `data-chart-labels="inside"` puts them inside, as on columns.
|
|
20
|
+
- Badges are rounded boxes instead of ellipses. They take less room.
|
|
21
|
+
- A few columns or bars start one after the other again, and their numbers come when the column is nearly grown. Many columns still grow almost together.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Holes in a line come with the line, not after the build.
|
|
26
|
+
- A legend box is no longer shown empty before its names.
|
|
27
|
+
- Triangle markers are centred on their point.
|
|
28
|
+
- Names of a long line no longer wait seconds after the build.
|
|
29
|
+
- Grouped columns and bars no longer grow after the first resize.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
3
33
|
## [1.1.3] - 2026-10-26
|
|
4
34
|
|
|
5
35
|
### Changed
|
package/README.md
CHANGED
|
@@ -12,14 +12,15 @@
|
|
|
12
12
|
Tablechart makes simple charts from a table: an HTML table, a Markdown table or a JSON file. You write the table, Tablechart shows it as a chart.
|
|
13
13
|
|
|
14
14
|
* **Chart types:** column, bar, line, area, waterfall and donut, and scatter as an additional type
|
|
15
|
-
* **The table
|
|
16
|
-
* **Real text:** names and numbers are HTML, so they use the font of the page
|
|
17
|
-
* **CSS
|
|
15
|
+
* **The table:** the table is hidden from sight, but screen readers, search engines and translation scripts still read it.
|
|
16
|
+
* **Real text:** names and numbers are HTML, so they use the font of the page. *No more Times New Roman labels.* Text does not scale, but just uses the font size of the page or a size you define.
|
|
17
|
+
* **CSS styling:** colours, sizes and animation are CSS variables. Light and dark mode follow the page.
|
|
18
18
|
* **Annotations:** badges, brackets, trend arrows, reference lines and a break in the scale
|
|
19
19
|
* **Tooltips:** when a chart leaves numbers out, point at a row, or tap it, to see all its values
|
|
20
|
-
* **
|
|
20
|
+
* **Summary:** sometimes a table is just too large, for example when a screen reader talks through it, You try to remember the number that was said 4 sentences ago. In print too. A summary deals with that. See [Summary](https://martinomagnifico.github.io/tablechart/features/summary.html).
|
|
21
|
+
* **Print and PDF:** tablechart in a PDF flow builds the charts immediately
|
|
21
22
|
* **Step by step:** show the rows one at a time, with Previous and Next buttons
|
|
22
|
-
* **Animation:** a chart
|
|
23
|
+
* **Animation:** a chart can build when it scrolls into view
|
|
23
24
|
|
|
24
25
|
----
|
|
25
26
|
|
|
@@ -139,4 +140,4 @@ Tablechart is free and open source. If it saves you time, consider [sponsoring m
|
|
|
139
140
|
|
|
140
141
|
## License
|
|
141
142
|
|
|
142
|
-
MIT © [Martinomagnifico](https://github.com/martinomagnifico)
|
|
143
|
+
MIT © [Martinomagnifico](https://github.com/martinomagnifico)**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t){var e=["circle","triangle","square","diamond"],
|
|
1
|
+
!function(t){var o=`${t.QUADRANT_CLASS}-name`,n=["top left","top right","bottom left","bottom right"],r=(t,o)=>{if(!t)return null;if("mean"===t)return o.length?o.reduce((t,o)=>t+o,0)/o.length:null;if("median"===t){const t=[...o].sort((t,o)=>t-o),n=t.length/2;return t.length?t.length%2?t[Math.floor(n)]:(t[n-1]+t[n])/2:null}const n=Number.parseFloat(t);return Number.isFinite(n)?n:null},e=["circle","triangle","square","diamond"],a=["right","left","above","below","right","left","right","left"],s=t=>{const o=t.flatMap((t,o)=>t.group?[]:[o]);return{x:o[0],y:o[1],size:o[2],group:t.findIndex(t=>t.group)}},c=(t,o)=>o<0?[]:[...new Set(t.map(t=>t.cells[o]?.raw??""))],i=(t,o,n)=>t.cells[o]?.missing||t.cells[n]?.missing?null:`${t.cells[o]?.value} ${t.cells[n]?.value}`,l={aspect:.62,categories:"beside",columns:!0,axes:!0,numbers:!1,annotations:{quadrant:{create(r,e){for(const a of r.querySelectorAll("[data-corner]")){const r=(a.getAttribute("data-corner")??"").toLowerCase().split(/\s+/),s=`${r.find(t=>"top"===t||"bottom"===t)} ${r.find(t=>"left"===t||"right"===t)}`;if(!n.includes(s)){console.warn(`[${t.LIBRARY_NAME}] A quadrant has a corner "${a.getAttribute("data-corner")}". It takes ${n.join(", ")}.`);continue}const c=document.createElement("span");if(c.className=o,c.dataset.corner=s,c.inert=!0,a.childElementCount)for(const t of Array.from(a.children))c.append(t.cloneNode(!0));else c.textContent=(a.textContent??"").trim();for(const{name:t,value:o}of Array.from(a.attributes))"data-corner"!==t&&c.setAttribute(t,o);e.append(c)}return null},draw(n,e,a){const{frame:s,x:c,y:i,rows:l,series:u,height:f,unit:d,svg:h,plot:p,categories:g}=a;if(!s||!c)return void console.warn(`[${t.LIBRARY_NAME}] A quadrant works on a scatter chart, which has an axis across and an axis up.`);const[m,b,M,$]=s,x=u.flatMap((t,o)=>t.group?[]:[o]),v=t=>l.flatMap(o=>o.cells[x[t]]&&!o.cells[x[t]].missing?[o.cells[x[t]].value]:[]),w=r(n.getAttribute("data-x"),v(0)),y=r(n.getAttribute("data-y"),v(1)),A=null===w?m+M/2:Math.min(Math.max(c(w),m),m+M),S=null===y?b+$/2:Math.min(Math.max(i(y),b),b+$),k=h.querySelector(`:scope > .${t.GRID_CLASS}`);if(k){const t=(t,o)=>Math.abs(t-o)<d/2,o=(k.getAttribute("d")??"").match(/M[^M]+/g)??[];k.setAttribute("d",o.filter(o=>{const[,n,r,e]=o.match(/M(-?[\d.]+),(-?[\d.]+)([HV])/)??[];return!("V"===e&&t(Number(n),A)||"H"===e&&t(Number(r),S))}).join(""))}h.insertBefore((0,t.svgEl)("path",{class:t.QUADRANT_CLASS,d:`M${(0,t.short)(A)},${(0,t.short)(b)}V${(0,t.short)(b+$)}M${(0,t.short)(m)},${(0,t.short)(S)}H${(0,t.short)(m+M)}`}),h.querySelector(`:scope > .${t.POINT_CLASS}`));const q=6*d,z=[];for(const r of p.querySelectorAll(`:scope > .${o}`)){const o=r.dataset.corner??"",n=o.includes("right"),e=o.includes("bottom"),a=n?m+M-q:m+q,s=e?b+$-q:b+q;(0,t.placeAt)(r,a,s,t.VIEWBOX_WIDTH,f);const c=(0,t.sizeOf)(r).width*d,i=(0,t.sizeOf)(r).height*d;z.push([n?a-c:a,e?s-i:s,c,i])}a.taken.push(...z);for(const o of g){if(o.hidden||o.hasAttribute("data-left-out")||!o.style.left)continue;const n=(0,t.boxOf)(o,d,t.VIEWBOX_WIDTH,f,[0,0]);z.some(o=>(0,t.overlaps)(o,n))&&o.setAttribute("data-left-out","")}}}},text({figure:o,plot:n,rows:r,series:a,config:l}){const{x:u,y:f,size:d,group:h}=s(a),p=n.querySelector(":scope > .tablechart-categories")?.children??[],g=new Map;r.forEach((t,o)=>{const n=i(t,u,f);n&&(g.get(n)?.push(o)??g.set(n,[o]))});const m=t=>void 0===d?0:Math.abs(r[t].cells[d]?.value??0);for(const t of g.values()){if(t.length<2)continue;const o=t.reduce((t,o)=>m(o)>m(t)?o:t);for(const n of t){const r=p[n];n===o?r?.setAttribute("data-more",String(t.length-1)):r?.setAttribute("data-same","")}}const b=c(r,h);if(b.length<2&&void 0===d)return;const M=document.createElement("div");M.className=t.LEGEND_CLASS,M.setAttribute("aria-hidden","true"),M.inert=!0;const $=Array.from(o.querySelectorAll(`tbody tr > td:nth-of-type(${h+1})`)),x=(o,n)=>{const r=(0,t.htmlEl)(t.NAME_CLASS,M);return r.dataset.place="legend",r.dataset.line="none",r.textContent=o,n&&l.langattribute&&r.setAttribute(l.langattribute,n),r};b.length>1&&b.forEach((t,o)=>{const n=l.langattribute?$.find(o=>o.textContent?.trim()===t)?.getAttribute(l.langattribute):null,r=x(t,n);r.dataset.marker=e[o%e.length],r.style.setProperty("--n",String(o)),r.style.setProperty("--palette",`var(--tablechart-color-${o+1})`)}),void 0!==d&&(x(a[d].name,a[d].key).dataset.marker="size"),n.after(M)},draw(o){const{rows:n,series:r,x:i,y:l,frame:u,unit:f,svg:d,categories:h,height:p,config:g,plot:m}=o;if(!i||!u)return;const b=s(r),M=c(n,b.group),$=M.length>1,x=(o.length("--tablechart-marker-size","10px")??10*f)/2,v=(o.length("--tablechart-bubble-max","24px")??24*f)/2,w=(o.length("--tablechart-bubble-min","")??1.2*x)/2,y=t=>void 0===b.size?void 0:t.cells[b.size],A=(0,t.largest)(n.map(t=>Math.abs(y(t)?.value??0))),S=n.flatMap((t,o)=>{const n=t.cells[b.x],r=t.cells[b.y];if(!n||!r||n.missing||r.missing)return[];const e=y(t),a=e&&!e.missing&&A>0?Math.max(w,Math.sqrt(Math.abs(e.value)/A)*v):x,s=$?M.indexOf(t.cells[b.group]?.raw??""):-1;return[{i:o,px:i(n.value),py:l(r.value),r:a,g:s}]}),k=Math.min(S.length-1,6);m.style.setProperty("--count",String(k+1));const q=new Map([...S].sort((t,o)=>t.px-o.px||t.i-o.i).map((o,n)=>[o.i,k?(0,t.short)(n*k/(S.length-1)):0])),z=[...S].sort((t,o)=>o.r-t.r||t.i-o.i);for(const a of z){const o=$?e[a.g%e.length]:"circle";d.appendChild((0,t.markerEl)(o,a.px,a.py,a.r,{class:t.POINT_CLASS,"data-row":a.i+1,"data-marker":o,style:`--i:${q.get(a.i)}; ${$?`--n:${a.g}; --palette:var(--tablechart-color-${a.g+1})`:""}`}))}const N=m.parentElement?.getAttribute("data-chart-names"),H=null==N?g.names:"false"!==N,V=4*f,I=[u[0]+1.5*V,u[1],u[2]-1.5*V,u[3]-1.5*V],[T,j,B,C]=I,D=z.map(({i:o,px:n,py:r,r:e})=>{if(h[o].hasAttribute("data-same"))return[];const{width:a,height:s}=(0,t.sizeOf)(h[o]),c=a*f,i=s*f,l=.1*i,u=.7*e+V/2,d=(t,o)=>[Math.max(T,Math.min(t,T+B-c)),Math.max(j,Math.min(o,j+C-i))+l,c,i-2*l];return[d(n+e+V,r-i/2),d(n-e-V-c,r-i/2),d(n-c/2,r-e-V-i),d(n-c/2,r+e+V),d(n+u,r-u-i),d(n-u-c,r-u-i),d(n+u,r+u),d(n-u-c,r+u)]}),E=H?(0,t.avoidCollisions)(D,S.map(({px:t,py:o,r:n})=>[t-n,o-n,2*n,2*n]),I):z.map(()=>null);for(const t of h)t.hidden=!0,t.dataset.missing="";z.forEach((o,n)=>{const r=h[o.i],e=E[n];if(r.hidden=!1,delete r.dataset.missing,r.style.setProperty("--i",String(q.get(o.i))),r.toggleAttribute("data-left-out",null===e),null===e)return;r.dataset.side=a[e];const[s,c,,i]=D[n][e];(0,t.placeAt)(r,s,c-((0,t.sizeOf)(r).height*f-i)/2,t.VIEWBOX_WIDTH,p)}),o.rowAt=(t,o)=>{let n=null,r=6*f;for(const e of S){const a=Math.max(Math.hypot(e.px-t*f,e.py-o*f)-e.r,0);(a<r||a===r&&n&&e.r<n.r)&&(r=a,n=e)}return n?n.i:null}},tooltip(t,o){const{x:n,y:r}=s(t.series),e=i(t.rows[o],n,r),a=t.rows.flatMap((t,a)=>a===o||null!==e&&i(t,n,r)===e?[a]:[]),c=a.filter(o=>t.categories[o]?.hasAttribute("data-more"));return[...c,...a.filter(t=>!c.includes(t))].map(o=>({name:t.categories[o]?.textContent??"",value:""}))}};window.Tablechart.addType("scatter",l)}(Tablechart.kit);
|
|
@@ -1,11 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
// src/js/
|
|
3
|
-
var
|
|
1
|
+
import { A as e, C as t, E as n, F as r, I as i, N as a, O as o, P as s, S as c, V as l, W as u, b as d, j as f, k as p, w as m, x as h, z as g } from "./tablechart-shared.mjs";
|
|
2
|
+
// src/js/annotations/quadrant.ts
|
|
3
|
+
var _ = `${l}-name`, v = [
|
|
4
|
+
"top left",
|
|
5
|
+
"top right",
|
|
6
|
+
"bottom left",
|
|
7
|
+
"bottom right"
|
|
8
|
+
], y = (e, t) => {
|
|
9
|
+
if (!e) return null;
|
|
10
|
+
if (e === "mean") return t.length ? t.reduce((e, t) => e + t, 0) / t.length : null;
|
|
11
|
+
if (e === "median") {
|
|
12
|
+
let e = [...t].sort((e, t) => e - t), n = e.length / 2;
|
|
13
|
+
return e.length ? e.length % 2 ? e[Math.floor(n)] : (e[n - 1] + e[n]) / 2 : null;
|
|
14
|
+
}
|
|
15
|
+
let n = Number.parseFloat(e);
|
|
16
|
+
return Number.isFinite(n) ? n : null;
|
|
17
|
+
}, b = {
|
|
18
|
+
create(e, t) {
|
|
19
|
+
for (let n of e.querySelectorAll("[data-corner]")) {
|
|
20
|
+
let e = (n.getAttribute("data-corner") ?? "").toLowerCase().split(/\s+/), i = `${e.find((e) => e === "top" || e === "bottom")} ${e.find((e) => e === "left" || e === "right")}`;
|
|
21
|
+
if (!v.includes(i)) {
|
|
22
|
+
console.warn(`[${r}] A quadrant has a corner "${n.getAttribute("data-corner")}". It takes ${v.join(", ")}.`);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
let a = document.createElement("span");
|
|
26
|
+
if (a.className = _, a.dataset.corner = i, a.inert = !0, n.childElementCount) for (let e of Array.from(n.children)) a.append(e.cloneNode(!0));
|
|
27
|
+
else a.textContent = (n.textContent ?? "").trim();
|
|
28
|
+
for (let { name: e, value: t } of Array.from(n.attributes)) e !== "data-corner" && a.setAttribute(e, t);
|
|
29
|
+
t.append(a);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
},
|
|
33
|
+
draw(t, n, i) {
|
|
34
|
+
let { frame: o, x: s, y: d, rows: v, series: b, height: x, unit: S, svg: C, plot: w, categories: T } = i;
|
|
35
|
+
if (!o || !s) {
|
|
36
|
+
console.warn(`[${r}] A quadrant works on a scatter chart, which has an axis across and an axis up.`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
let [E, D, O, k] = o, A = b.flatMap((e, t) => e.group ? [] : [t]), j = (e) => v.flatMap((t) => t.cells[A[e]] && !t.cells[A[e]].missing ? [t.cells[A[e]].value] : []), M = y(t.getAttribute("data-x"), j(0)), N = y(t.getAttribute("data-y"), j(1)), P = M === null ? E + O / 2 : Math.min(Math.max(s(M), E), E + O), F = N === null ? D + k / 2 : Math.min(Math.max(d(N), D), D + k), I = C.querySelector(`:scope > .${a}`);
|
|
40
|
+
if (I) {
|
|
41
|
+
let e = (e, t) => Math.abs(e - t) < S / 2, t = (I.getAttribute("d") ?? "").match(/M[^M]+/g) ?? [];
|
|
42
|
+
I.setAttribute("d", t.filter((t) => {
|
|
43
|
+
let [, n, r, i] = t.match(/M(-?[\d.]+),(-?[\d.]+)([HV])/) ?? [];
|
|
44
|
+
return !(i === "V" && e(Number(n), P)) && !(i === "H" && e(Number(r), F));
|
|
45
|
+
}).join(""));
|
|
46
|
+
}
|
|
47
|
+
C.insertBefore(f("path", {
|
|
48
|
+
class: l,
|
|
49
|
+
d: `M${e(P)},${e(D)}V${e(D + k)}M${e(E)},${e(F)}H${e(E + O)}`
|
|
50
|
+
}), C.querySelector(`:scope > .${g}`));
|
|
51
|
+
let L = 6 * S, R = [];
|
|
52
|
+
for (let e of w.querySelectorAll(`:scope > .${_}`)) {
|
|
53
|
+
let t = e.dataset.corner ?? "", n = t.includes("right"), r = t.includes("bottom"), i = n ? E + O - L : E + L, a = r ? D + k - L : D + L;
|
|
54
|
+
p(e, i, a, u, x);
|
|
55
|
+
let o = c(e).width * S, s = c(e).height * S;
|
|
56
|
+
R.push([
|
|
57
|
+
n ? i - o : i,
|
|
58
|
+
r ? a - s : a,
|
|
59
|
+
o,
|
|
60
|
+
s
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
i.taken.push(...R);
|
|
64
|
+
for (let e of T) {
|
|
65
|
+
if (e.hidden || e.hasAttribute("data-left-out") || !e.style.left) continue;
|
|
66
|
+
let t = h(e, S, u, x, [0, 0]);
|
|
67
|
+
R.some((e) => m(e, t)) && e.setAttribute("data-left-out", "");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}, x = [
|
|
4
71
|
"circle",
|
|
5
72
|
"triangle",
|
|
6
73
|
"square",
|
|
7
74
|
"diamond"
|
|
8
|
-
],
|
|
75
|
+
], S = [
|
|
9
76
|
"right",
|
|
10
77
|
"left",
|
|
11
78
|
"above",
|
|
@@ -14,7 +81,7 @@ var d = [
|
|
|
14
81
|
"left",
|
|
15
82
|
"right",
|
|
16
83
|
"left"
|
|
17
|
-
],
|
|
84
|
+
], C = (e) => {
|
|
18
85
|
let t = e.flatMap((e, t) => e.group ? [] : [t]);
|
|
19
86
|
return {
|
|
20
87
|
x: t[0],
|
|
@@ -22,117 +89,118 @@ var d = [
|
|
|
22
89
|
size: t[2],
|
|
23
90
|
group: e.findIndex((e) => e.group)
|
|
24
91
|
};
|
|
25
|
-
},
|
|
92
|
+
}, w = (e, t) => t < 0 ? [] : [...new Set(e.map((e) => e.cells[t]?.raw ?? ""))], T = (e, t, n) => e.cells[t]?.missing || e.cells[n]?.missing ? null : `${e.cells[t]?.value} ${e.cells[n]?.value}`, E = {
|
|
26
93
|
aspect: .62,
|
|
27
94
|
categories: "beside",
|
|
28
95
|
columns: !0,
|
|
29
96
|
axes: !0,
|
|
30
97
|
numbers: !1,
|
|
31
|
-
|
|
32
|
-
|
|
98
|
+
annotations: { quadrant: b },
|
|
99
|
+
text({ figure: e, plot: t, rows: n, series: r, config: a }) {
|
|
100
|
+
let { x: c, y: l, size: u, group: d } = C(r), f = t.querySelector(":scope > .tablechart-categories")?.children ?? [], p = /* @__PURE__ */ new Map();
|
|
33
101
|
n.forEach((e, t) => {
|
|
34
|
-
let n =
|
|
35
|
-
n && (
|
|
102
|
+
let n = T(e, c, l);
|
|
103
|
+
n && (p.get(n)?.push(t) ?? p.set(n, [t]));
|
|
36
104
|
});
|
|
37
|
-
let
|
|
38
|
-
for (let e of
|
|
105
|
+
let m = (e) => u === void 0 ? 0 : Math.abs(n[e].cells[u]?.value ?? 0);
|
|
106
|
+
for (let e of p.values()) {
|
|
39
107
|
if (e.length < 2) continue;
|
|
40
|
-
let t = e.reduce((e, t) =>
|
|
108
|
+
let t = e.reduce((e, t) => m(t) > m(e) ? t : e);
|
|
41
109
|
for (let n of e) {
|
|
42
|
-
let r =
|
|
110
|
+
let r = f[n];
|
|
43
111
|
n === t ? r?.setAttribute("data-more", String(e.length - 1)) : r?.setAttribute("data-same", "");
|
|
44
112
|
}
|
|
45
113
|
}
|
|
46
|
-
let
|
|
47
|
-
if (
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
let
|
|
51
|
-
let n =
|
|
52
|
-
return n.dataset.place = "legend", n.dataset.line = "none", n.textContent = e, t &&
|
|
114
|
+
let h = w(n, d);
|
|
115
|
+
if (h.length < 2 && u === void 0) return;
|
|
116
|
+
let g = document.createElement("div");
|
|
117
|
+
g.className = s, g.setAttribute("aria-hidden", "true"), g.inert = !0;
|
|
118
|
+
let _ = Array.from(e.querySelectorAll(`tbody tr > td:nth-of-type(${d + 1})`)), v = (e, t) => {
|
|
119
|
+
let n = o(i, g);
|
|
120
|
+
return n.dataset.place = "legend", n.dataset.line = "none", n.textContent = e, t && a.langattribute && n.setAttribute(a.langattribute, t), n;
|
|
53
121
|
};
|
|
54
|
-
|
|
55
|
-
let n =
|
|
56
|
-
r.dataset.marker =
|
|
57
|
-
}), u !== void 0 && (
|
|
122
|
+
h.length > 1 && h.forEach((e, t) => {
|
|
123
|
+
let n = a.langattribute ? _.find((t) => t.textContent?.trim() === e)?.getAttribute(a.langattribute) : null, r = v(e, n);
|
|
124
|
+
r.dataset.marker = x[t % x.length], r.style.setProperty("--n", String(t)), r.style.setProperty("--palette", `var(--tablechart-color-${t + 1})`);
|
|
125
|
+
}), u !== void 0 && (v(r[u].name, r[u].key).dataset.marker = "size"), t.after(g);
|
|
58
126
|
},
|
|
59
127
|
draw(r) {
|
|
60
|
-
let { rows:
|
|
61
|
-
if (!
|
|
62
|
-
let
|
|
63
|
-
let n = e.cells[
|
|
128
|
+
let { rows: i, series: a, x: o, y: s, frame: l, unit: f, svg: m, categories: h, height: _, config: v, plot: y } = r;
|
|
129
|
+
if (!o || !l) return;
|
|
130
|
+
let b = C(a), T = w(i, b.group), E = T.length > 1, D = (r.length("--tablechart-marker-size", "10px") ?? 10 * f) / 2, O = (r.length("--tablechart-bubble-max", "24px") ?? 24 * f) / 2, k = (r.length("--tablechart-bubble-min", "") ?? D * 1.2) / 2, A = (e) => b.size === void 0 ? void 0 : e.cells[b.size], j = n(i.map((e) => Math.abs(A(e)?.value ?? 0))), M = i.flatMap((e, t) => {
|
|
131
|
+
let n = e.cells[b.x], r = e.cells[b.y];
|
|
64
132
|
if (!n || !r || n.missing || r.missing) return [];
|
|
65
|
-
let i =
|
|
133
|
+
let i = A(e), a = i && !i.missing && j > 0 ? Math.max(k, Math.sqrt(Math.abs(i.value) / j) * O) : D, c = E ? T.indexOf(e.cells[b.group]?.raw ?? "") : -1;
|
|
66
134
|
return [{
|
|
67
135
|
i: t,
|
|
68
|
-
px:
|
|
69
|
-
py:
|
|
136
|
+
px: o(n.value),
|
|
137
|
+
py: s(r.value),
|
|
70
138
|
r: a,
|
|
71
|
-
g:
|
|
139
|
+
g: c
|
|
72
140
|
}];
|
|
73
|
-
}),
|
|
74
|
-
|
|
75
|
-
let
|
|
76
|
-
for (let e of
|
|
77
|
-
let
|
|
78
|
-
|
|
79
|
-
class:
|
|
141
|
+
}), N = Math.min(M.length - 1, 6);
|
|
142
|
+
y.style.setProperty("--count", String(N + 1));
|
|
143
|
+
let P = new Map([...M].sort((e, t) => e.px - t.px || e.i - t.i).map((t, n) => [t.i, N ? e(n * N / (M.length - 1)) : 0])), F = [...M].sort((e, t) => t.r - e.r || e.i - t.i);
|
|
144
|
+
for (let e of F) {
|
|
145
|
+
let t = E ? x[e.g % x.length] : "circle";
|
|
146
|
+
m.appendChild(d(t, e.px, e.py, e.r, {
|
|
147
|
+
class: g,
|
|
80
148
|
"data-row": e.i + 1,
|
|
81
|
-
"data-marker":
|
|
82
|
-
style: `--i:${
|
|
149
|
+
"data-marker": t,
|
|
150
|
+
style: `--i:${P.get(e.i)}; ${E ? `--n:${e.g}; --palette:var(--tablechart-color-${e.g + 1})` : ""}`
|
|
83
151
|
}));
|
|
84
152
|
}
|
|
85
|
-
let
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
], [
|
|
91
|
-
if (
|
|
92
|
-
let { width:
|
|
93
|
-
Math.max(
|
|
94
|
-
Math.max(
|
|
95
|
-
|
|
96
|
-
|
|
153
|
+
let I = y.parentElement?.getAttribute("data-chart-names"), L = I == null ? v.names : I !== "false", R = 4 * f, z = [
|
|
154
|
+
l[0] + 1.5 * R,
|
|
155
|
+
l[1],
|
|
156
|
+
l[2] - 1.5 * R,
|
|
157
|
+
l[3] - 1.5 * R
|
|
158
|
+
], [B, V, H, U] = z, W = F.map(({ i: e, px: t, py: n, r }) => {
|
|
159
|
+
if (h[e].hasAttribute("data-same")) return [];
|
|
160
|
+
let { width: i, height: a } = c(h[e]), o = i * f, s = a * f, l = s * .1, u = r * .7 + R / 2, d = (e, t) => [
|
|
161
|
+
Math.max(B, Math.min(e, B + H - o)),
|
|
162
|
+
Math.max(V, Math.min(t, V + U - s)) + l,
|
|
163
|
+
o,
|
|
164
|
+
s - 2 * l
|
|
97
165
|
];
|
|
98
166
|
return [
|
|
99
|
-
d(t + r +
|
|
100
|
-
d(t - r -
|
|
101
|
-
d(t -
|
|
102
|
-
d(t -
|
|
103
|
-
d(t + u, n - u -
|
|
104
|
-
d(t - u -
|
|
167
|
+
d(t + r + R, n - s / 2),
|
|
168
|
+
d(t - r - R - o, n - s / 2),
|
|
169
|
+
d(t - o / 2, n - r - R - s),
|
|
170
|
+
d(t - o / 2, n + r + R),
|
|
171
|
+
d(t + u, n - u - s),
|
|
172
|
+
d(t - u - o, n - u - s),
|
|
105
173
|
d(t + u, n + u),
|
|
106
|
-
d(t - u -
|
|
174
|
+
d(t - u - o, n + u)
|
|
107
175
|
];
|
|
108
|
-
}),
|
|
176
|
+
}), G = L ? t(W, M.map(({ px: e, py: t, r: n }) => [
|
|
109
177
|
e - n,
|
|
110
178
|
t - n,
|
|
111
179
|
2 * n,
|
|
112
180
|
2 * n
|
|
113
|
-
]),
|
|
114
|
-
for (let e of
|
|
115
|
-
|
|
116
|
-
let
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
let [
|
|
120
|
-
|
|
181
|
+
]), z) : F.map(() => null);
|
|
182
|
+
for (let e of h) e.hidden = !0, e.dataset.missing = "";
|
|
183
|
+
F.forEach((e, t) => {
|
|
184
|
+
let n = h[e.i], r = G[t];
|
|
185
|
+
if (n.hidden = !1, delete n.dataset.missing, n.style.setProperty("--i", String(P.get(e.i))), n.toggleAttribute("data-left-out", r === null), r === null) return;
|
|
186
|
+
n.dataset.side = S[r];
|
|
187
|
+
let [i, a, , o] = W[t][r];
|
|
188
|
+
p(n, i, a - (c(n).height * f - o) / 2, u, _);
|
|
121
189
|
}), r.rowAt = (e, t) => {
|
|
122
|
-
let n = null, r = 6 *
|
|
123
|
-
for (let i of
|
|
124
|
-
let a = Math.max(Math.hypot(i.px - e *
|
|
190
|
+
let n = null, r = 6 * f;
|
|
191
|
+
for (let i of M) {
|
|
192
|
+
let a = Math.max(Math.hypot(i.px - e * f, i.py - t * f) - i.r, 0);
|
|
125
193
|
(a < r || a === r && n && i.r < n.r) && (r = a, n = i);
|
|
126
194
|
}
|
|
127
195
|
return n ? n.i : null;
|
|
128
196
|
};
|
|
129
197
|
},
|
|
130
198
|
tooltip(e, t) {
|
|
131
|
-
let { x: n, y: r } =
|
|
199
|
+
let { x: n, y: r } = C(e.series), i = T(e.rows[t], n, r), a = e.rows.flatMap((e, a) => a === t || i !== null && T(e, n, r) === i ? [a] : []), o = a.filter((t) => e.categories[t]?.hasAttribute("data-more"));
|
|
132
200
|
return [...o, ...a.filter((e) => !o.includes(e))].map((t) => ({
|
|
133
201
|
name: e.categories[t]?.textContent ?? "",
|
|
134
202
|
value: ""
|
|
135
203
|
}));
|
|
136
204
|
}
|
|
137
205
|
};
|
|
138
|
-
export {
|
|
206
|
+
export { E as scatter };
|
package/dist/tablechart-core.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { M as AT_ONCE_CLASS, L as PLACED_CLASS, R as PLOT_CLASS, B as PRINTING_CLASS, H as QUEUED_CLASS, U as SHOWN_CLASS, c as addType, a as adoptTables, h as assignKeys, l as build, G as defaultConfig, n as entranceOf, u as layout, o as loadSources, i as prepare, s as readMarkdownRows, d as relabel, f as setShown, m as stepTo, p as unbuild } from "./tablechart-shared.mjs";
|
|
@@ -21,6 +21,13 @@ interface Config {
|
|
|
21
21
|
entrance: number;
|
|
22
22
|
/** Where a column's number goes: on the bar, or inside it. A figure overrides it with `data-chart-labels`. */
|
|
23
23
|
labels: "outside" | "inside";
|
|
24
|
+
/**
|
|
25
|
+
* Which values a line or area chart shows: `"auto"`, all if they fit and else the first, last, highest and lowest; `"all"`;
|
|
26
|
+
* `"none"`; or a set, such as `"first last"`. `"auto last"` shows all if they fit, and else the last. A figure overrides it with `data-chart-values`.
|
|
27
|
+
*/
|
|
28
|
+
values: string;
|
|
29
|
+
/** A tick above each name under a line or area chart, at the place of its row. A figure overrides it with `data-chart-ticks`. */
|
|
30
|
+
ticks: boolean;
|
|
24
31
|
/** How a line or area runs between points: `straight`, `step` or `smooth`. A figure overrides it with `data-chart-shape`. */
|
|
25
32
|
shape: "straight" | "step" | "smooth";
|
|
26
33
|
/** Names of several lines in a legend instead of at the end of each line. A figure overrides it with `data-chart-legend`. */
|
|
@@ -37,6 +44,10 @@ interface Config {
|
|
|
37
44
|
tooltipparent: HTMLElement | string | null;
|
|
38
45
|
/** The table under the chart: `"always"`, only in `"print"`, in print when numbers are left out (`"auto"`), or `"never"`. A figure overrides it with `data-chart-table`. */
|
|
39
46
|
table: "never" | "auto" | "print" | "always";
|
|
47
|
+
/** With `auto`, the table is printed only up to this many values, rows times value columns; a longer one prints the summary instead. */
|
|
48
|
+
tablelimit: number;
|
|
49
|
+
/** The summary of a chart, in a `.chart-summary`: `"always"`, only in `"print"`, in print when the table is not (`"auto"`), or `"never"`, for screen readers only. A figure overrides it with `data-chart-summary`. */
|
|
50
|
+
summary: "never" | "auto" | "print" | "always";
|
|
40
51
|
/** The name of the total that a donut without a total row works out, for screen readers. */
|
|
41
52
|
totallabel: string;
|
|
42
53
|
/** Previous and Next buttons under a chart that steps (`data-fragment`). `false` leaves them out, for buttons of your own. */
|
|
@@ -108,6 +119,13 @@ interface Cell {
|
|
|
108
119
|
}
|
|
109
120
|
interface Row extends Cell {
|
|
110
121
|
label: string;
|
|
122
|
+
/** `data-short` on the row's header: the name under the chart, where room is short. The table and the summary have `label`. */
|
|
123
|
+
short: string | null;
|
|
124
|
+
/** `data-value="show"` on the row: its value is always shown on a line. */
|
|
125
|
+
pinned: boolean;
|
|
126
|
+
/** The text of its cell in a column with `data-note`, such as "Share issue", and its translation key. */
|
|
127
|
+
note: string | null;
|
|
128
|
+
noteKey: string | null;
|
|
111
129
|
key: string | null;
|
|
112
130
|
cells: Cell[];
|
|
113
131
|
/** Copied to everything drawn for the row, so one class styles all of it. */
|
|
@@ -126,12 +144,20 @@ interface Series {
|
|
|
126
144
|
line: "dashed" | "dotted" | null;
|
|
127
145
|
/** `data-group` on the header: the cells name a group, such as "Predefined". */
|
|
128
146
|
group: boolean;
|
|
147
|
+
/** `data-chart="line"` on the header, in a chart that takes it: a line over the other marks. */
|
|
148
|
+
chart: "line" | null;
|
|
129
149
|
}
|
|
130
150
|
interface Segment extends Row {
|
|
131
151
|
from: number;
|
|
132
152
|
to: number;
|
|
133
153
|
kind: "bar" | "up" | "down" | "total";
|
|
134
154
|
}
|
|
155
|
+
/** Which values a line shows, from `data-chart-values`: all, none, a set of rows, or all if they fit and else the set. */
|
|
156
|
+
interface ValuePick {
|
|
157
|
+
mode: "auto" | "all" | "set";
|
|
158
|
+
/** `first`, `last`, `highest`, `lowest`, `outliers`. Rows with `data-value="show"` are always in the set. */
|
|
159
|
+
keys: string[];
|
|
160
|
+
}
|
|
135
161
|
/** Scales only, no pixel positions, so an annotation follows the data. */
|
|
136
162
|
interface Geometry {
|
|
137
163
|
rows: Row[];
|
|
@@ -144,6 +170,8 @@ interface Geometry {
|
|
|
144
170
|
frame?: [number, number, number, number];
|
|
145
171
|
/** A chart that knows better which row is pointed at sets this, in screen pixels from the plot's corner. */
|
|
146
172
|
rowAt?: (x: number, y: number) => number | null;
|
|
173
|
+
/** Where the tooltip of a row points, in viewBox units, for a row whose mark may not be drawn, such as a point of a long line. */
|
|
174
|
+
pointOf?: (index: number) => [number, number] | null;
|
|
147
175
|
/** A CSS length set on the figure, such as `--tablechart-marker-size`, in viewBox units. */
|
|
148
176
|
length: (name: string, fallback: string) => number | null;
|
|
149
177
|
barWidth: number;
|
|
@@ -172,6 +200,8 @@ interface Geometry {
|
|
|
172
200
|
insideAlign: "start" | "center" | "end" | null;
|
|
173
201
|
stacked: boolean;
|
|
174
202
|
shape: Shape;
|
|
203
|
+
/** Which values a line or an area shows. */
|
|
204
|
+
pick: ValuePick;
|
|
175
205
|
/** Empty when the chart is not stacked or its kind shows no totals. */
|
|
176
206
|
totals: HTMLElement[];
|
|
177
207
|
svg: SVGSVGElement;
|
|
@@ -179,6 +209,8 @@ interface Geometry {
|
|
|
179
209
|
/** Made by `build`; a chart only places them. */
|
|
180
210
|
values: HTMLElement[];
|
|
181
211
|
series: Series[];
|
|
212
|
+
/** The series this drawing draws: all of them, or in a chart with columns and a line, those of its own kind. */
|
|
213
|
+
own: number[];
|
|
182
214
|
seriesValues: HTMLElement[][];
|
|
183
215
|
/** Empty with one series. */
|
|
184
216
|
names: HTMLElement[];
|
|
@@ -190,6 +222,8 @@ interface Geometry {
|
|
|
190
222
|
checks: Check[];
|
|
191
223
|
/** The labels of annotations placed so far, as left, top, width and height, so the next one finds room. */
|
|
192
224
|
taken: [number, number, number, number][];
|
|
225
|
+
/** Holes in the lines and areas, such as under an open marker: the core leaves them out with a clip, from the start. */
|
|
226
|
+
lineHoles: SVGElement[];
|
|
193
227
|
}
|
|
194
228
|
interface Check {
|
|
195
229
|
label: string;
|
|
@@ -215,6 +249,10 @@ interface ChartType {
|
|
|
215
249
|
columns?: boolean;
|
|
216
250
|
/** An axis across and an axis up, from the first two value columns, with their headers as titles. */
|
|
217
251
|
axes?: boolean;
|
|
252
|
+
/** A series with `data-chart="line"` on its header is a line over the marks, on the same scale. */
|
|
253
|
+
lines?: boolean;
|
|
254
|
+
/** Several series side by side, as groups of bars: their numbers are smaller, so the plot is marked `data-grouped` before anything is measured. */
|
|
255
|
+
groups?: boolean;
|
|
218
256
|
/** False: no numbers on the chart. They stay out of the layout, which costs a lot with many rows. */
|
|
219
257
|
numbers?: false;
|
|
220
258
|
/** Text of its own, made with the rest of the text so a translation script finds it, such as a legend. */
|
|
@@ -243,6 +281,8 @@ interface ChartType {
|
|
|
243
281
|
draw: (geo: Geometry) => void;
|
|
244
282
|
/** The lines of the tooltip of a row. Default: each value with the name of its series, and the total of a stack. */
|
|
245
283
|
tooltip?: (state: ChartState, index: number, config: Config) => TooltipLine[];
|
|
284
|
+
/** Annotations that come with this type, such as `quadrant` with `scatter`; `addType` adds them. */
|
|
285
|
+
annotations?: Record<string, AnnotationType>;
|
|
246
286
|
}
|
|
247
287
|
/** One line of a tooltip, under the name of the row. */
|
|
248
288
|
interface TooltipLine {
|
|
@@ -299,6 +339,8 @@ interface ChartState {
|
|
|
299
339
|
rowAt: ((x: number, y: number) => number | null) | null;
|
|
300
340
|
/** As percentages: left, top, width and height. */
|
|
301
341
|
rowBox: ((index: number) => [number, number, number, number]) | null;
|
|
342
|
+
/** Where the tooltip of a row points, in viewBox units; set by a chart such as a line. */
|
|
343
|
+
pointOf: ((index: number) => [number, number] | null) | null;
|
|
302
344
|
annotations: {
|
|
303
345
|
spec: HTMLElement;
|
|
304
346
|
type: AnnotationType;
|