@martinomagnifico/tablechart 1.2.6 → 1.4.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 +52 -0
- package/README.md +5 -4
- package/dist/tablechart-additional-types.d.mts +2 -2
- package/dist/tablechart-additional-types.js +1 -1
- package/dist/tablechart-additional-types.mjs +185 -91
- package/dist/tablechart-core.d.mts +1 -1
- package/dist/tablechart-core.mjs +1 -1
- package/dist/tablechart-shared.d.mts +108 -9
- package/dist/tablechart-shared.mjs +2854 -2535
- package/dist/tablechart-summary.d.mts +3 -31
- package/dist/tablechart-summary.js +1 -1
- package/dist/tablechart-summary.mjs +299 -162
- package/dist/tablechart.css +1 -1
- package/dist/tablechart.d.mts +1 -1
- package/dist/tablechart.js +1 -1
- package/dist/tablechart.mjs +1 -1
- package/package.json +8 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0] - 2026-11-13
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `groups` in a summary, a short table of the rows in groups, each with its lowest, average and highest value.
|
|
8
|
+
- `{rank}` and `{rank.low}` for each outlier and note in a summary, and `{values}`, the number of values.
|
|
9
|
+
- Summary settings per chart, with `data-summary-` and the name, or as JSON in `data-summary-options` on the figure or a parent.
|
|
10
|
+
- `tooltip: "crosshair"` and `data-chart-tooltip="crosshair"`, lines through the row and the nearest point of a line or area chart, with its values at the side.
|
|
11
|
+
- Rows next to each other with the same short name show it once under the chart, from the first of them, such as a year over its months.
|
|
12
|
+
- `pie`, a donut without a hole. A pie, really.
|
|
13
|
+
- `candlestick`, an additional type with the open, high, low and close of each row.
|
|
14
|
+
- `axis: "right"` and `data-chart-axis="right"`, the value axis on the right of a column, line, area or waterfall chart.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- A line chart with a scale that starts above zero starts at its scale.
|
|
19
|
+
- The value axis of a line chart reaches the line under the chart.
|
|
20
|
+
- A line chart without numbers keeps no room for them above and below its lines.
|
|
21
|
+
- A chart keeps room for the top number of its value axis.
|
|
22
|
+
- A legend comes in with the first marks.
|
|
23
|
+
- `Summary.fill(figure)` uses the settings of `Summary.init()`.
|
|
24
|
+
- A table inside a summary is not read as the data of the chart.
|
|
25
|
+
- On a crowded column or bar chart, the numbers of the largest values stay, where they are clear of the other columns.
|
|
26
|
+
- On a crowded line or area chart, the last number of each line goes next to its name.
|
|
27
|
+
- A number that does not fit next to its point is left out, not placed a line further away.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## [1.3.0] - 2026-11-10
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `data-series="total"` in a summary, for the totals of the rows, such as the stacks of a stacked chart.
|
|
35
|
+
- `{highest.share}`, `{lowest.share}` and `{total}` in a summary.
|
|
36
|
+
- `chart.replay()`, builds a chart again with its animation.
|
|
37
|
+
- `chart.reset()`, takes a chart back to its start, to build when it scrolls into view.
|
|
38
|
+
- `data-chart-group-scale`, one scale for the charts of a group, so the same value is the same height.
|
|
39
|
+
- `data-chart-group`, so the baselines of charts side by side line up when their names take a different number of lines.
|
|
40
|
+
- `--tablechart-max-height` and `--tablechart-height`, so a chart stops getting higher on a wide screen.
|
|
41
|
+
- `data-tablechart-ready` and `data-summary-ready` on `html`, with the events `tablechart:ready` and `tablechart:summary-ready`, for scripts that print the page to PDF.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- A chart that builds once stops watching the scroll.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- Brackets and numbers keep clear of each other in print to PDF flows..
|
|
50
|
+
- A chart keeps the room of its `data-chart-aspect` until it is laid out.
|
|
51
|
+
- In a tagged PDF, the summary and the table are outside the figure of the chart, so a screen reader reads them.
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
3
55
|
## [1.2.6] - 2026-11-08
|
|
4
56
|
|
|
5
57
|
### Added
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
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
|
-
* **Chart types:** column, bar, line, area, waterfall and
|
|
14
|
+
* **Chart types:** column, bar, line, area, waterfall, donut and pie, and scatter and candlestick as additional types
|
|
15
15
|
* **The table:** the table is hidden from sight, but screen readers, search engines and translation scripts still read it.
|
|
16
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
17
|
* **CSS styling:** colours, sizes and animation are CSS variables. Light and dark mode follow the page.
|
|
@@ -51,7 +51,7 @@ Or with a script tag:
|
|
|
51
51
|
</script>
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
The scatter
|
|
54
|
+
The scatter and candlestick charts are additional types, in a script of their own. To use them, see [Additional types](#additional-types).
|
|
55
55
|
|
|
56
56
|
## A chart
|
|
57
57
|
|
|
@@ -74,13 +74,14 @@ Use `bar`, `line`, `area`, `waterfall` or `donut` for the other types. With more
|
|
|
74
74
|
|
|
75
75
|
### Additional types
|
|
76
76
|
|
|
77
|
-
The scatter
|
|
77
|
+
The scatter and candlestick charts are additional types, in a script of their own. If you want to use one, add it before `init()`:
|
|
78
78
|
|
|
79
79
|
```js
|
|
80
80
|
import { addType, init } from "@martinomagnifico/tablechart";
|
|
81
|
-
import { scatter } from "@martinomagnifico/tablechart/additional-types";
|
|
81
|
+
import { candlestick, scatter } from "@martinomagnifico/tablechart/additional-types";
|
|
82
82
|
|
|
83
83
|
addType("scatter", scatter);
|
|
84
|
+
addType("candlestick", candlestick);
|
|
84
85
|
init();
|
|
85
86
|
```
|
|
86
87
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { scatter };
|
|
1
|
+
import { T as candlestick, w as scatter } from "./tablechart-shared.mjs";
|
|
2
|
+
export { candlestick, scatter };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t){var o=`${t.QUADRANT_CLASS}-name`,
|
|
1
|
+
!function(t){var o="tablechart-candle",n=t=>t.flatMap((t,o)=>t.group?[]:[o]).slice(0,4),r=(t,o)=>{const n=o.map(o=>t.cells[o]);return 4===n.length&&n.every(t=>t&&!t.missing)?n.map(t=>t.value):null},a={columns:!0,numbers:!1,floats:!0,valueAxis:!0,ticks:!0,crosshair:!0,headroom:({unit:t})=>4*t,draw(a){const{rows:e,cx:s,y:c,unit:i,svg:l,barWidth:u,height:d,domain:h,config:f}=a,m=n(a.series),g=e.map(t=>r(t,m)),p=g.flatMap(t=>t?[t[2]]:[]),$=Math.max(u,i);g.forEach((n,r)=>{if(!n)return;const[a,u,d,h]=n,f=h>=a,m=(c(a)+c(h))/2,g=Math.max(Math.abs(c(a)-c(h)),i),p=m-g/2,b=(0,t.svgEl)("g",{class:(0,t.withRow)(`${t.BAR_CLASS} ${o}`,e[r]),"data-row":r+1,"data-move":f?"up":"down",...!f&&{"data-grow":"down"},style:(0,t.timing)("--i",e[r].slot)}),y=(n,a)=>{a-n>.01&&b.append((0,t.svgEl)("line",{class:`${o}-wick`,x1:s(r),x2:s(r),y1:n,y2:a}))};y(c(u),p),y(p+g,c(d)),b.append((0,t.svgEl)("rect",{class:`${o}-body`,x:s(r)-$/2,y:p,width:$,height:g})),l.append(b)});const b=p.length?-(0,t.largest)(p.map(t=>-t)):0,y=(0,t.largest)(g.flatMap(t=>t?[t[1]]:[])),x=c(h?h[0]:b);l.append((0,t.svgEl)("line",{class:t.RULE_CLASS,x1:0,x2:t.VIEWBOX_WIDTH,y1:x,y2:x}));const M=m[3],w=e.find(t=>t.cells[M]&&!t.cells[M].missing)?.cells[M],v=Math.max(0,(0,t.largest)(e.map(o=>(0,t.decimalsIn)(o.cells[M]?.number??"")))),A=c(y)-c(b)||1,k=o=>{const[n,r]=(0,t.splitSign)(b+(o-c(b))/A*(y-b),v,f.locale);return`${w?.prefix??""}${n}${r}${w?.suffix??""}`},S="right"===a.axisSide;a.crossingOf=n=>{const r=g[n];if(!r)return null;const e=l.querySelector(`.${o}[data-row="${n+1}"] .${o}-body`),u=e?getComputedStyle(e).stroke:void 0;return{x:s(n),from:h?c(h[1]):0,to:x,edge:S?t.VIEWBOX_WIDTH:0,end:S?0:t.VIEWBOX_WIDTH,axis:a.axisSide,height:d,points:m.map((o,a)=>({series:o,y:c(r[a]),dot:(0,t.svgEl)("circle",{class:"tablechart-crosshair-point",cx:s(n),cy:c(r[a]),r:3*i,style:`--tablechart-point-color:${u}`}),colour:u})),textAt:k,snap:8,around:[c(r[1]),c(r[2])]}}},tooltip(t,o,a){const e=n(t.series),s=r(t.rows[o],e);return e.map((n,r)=>{const e=t.seriesValues[n]?.[o]?.textContent??"",c=3===r&&s&&s[0]?` (${new Intl.NumberFormat(a.locale,{style:"percent",signDisplay:"exceptZero",maximumFractionDigits:1}).format((s[3]-s[0])/s[0])})`:"";return{name:t.series[n].name,value:e+c}})}},e=`${t.QUADRANT_CLASS}-name`,s=["top left","top right","bottom left","bottom right"],c=(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},i=["circle","triangle","square","diamond"],l=["right","left","above","below","right","left","right","left"],u=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)}},d=(t,o)=>o<0?[]:[...new Set(t.map(t=>t.cells[o]?.raw??""))],h=(t,o,n)=>t.cells[o]?.missing||t.cells[n]?.missing?null:`${t.cells[o]?.value} ${t.cells[n]?.value}`,f={aspect:.62,categories:"beside",columns:!0,axes:!0,numbers:!1,annotations:{quadrant:{create(o,n){for(const r of o.querySelectorAll("[data-corner]")){const o=(r.getAttribute("data-corner")??"").toLowerCase().split(/\s+/),a=`${o.find(t=>"top"===t||"bottom"===t)} ${o.find(t=>"left"===t||"right"===t)}`;if(!s.includes(a)){console.warn(`[${t.LIBRARY_NAME}] A quadrant has a corner "${r.getAttribute("data-corner")}". It takes ${s.join(", ")}.`);continue}const c=document.createElement("span");if(c.className=e,c.dataset.corner=a,c.inert=!0,r.childElementCount)for(const t of Array.from(r.children))c.append(t.cloneNode(!0));else c.textContent=(r.textContent??"").trim();for(const{name:t,value:n}of Array.from(r.attributes))"data-corner"!==t&&c.setAttribute(t,n);n.append(c)}return null},draw(o,n,r){const{frame:a,x:s,y:i,rows:l,series:u,height:d,unit:h,svg:f,plot:m,categories:g}=r;if(!a||!s)return void console.warn(`[${t.LIBRARY_NAME}] A quadrant works on a scatter chart, which has an axis across and an axis up.`);const[p,$,b,y]=a,x=u.flatMap((t,o)=>t.group?[]:[o]),M=t=>l.flatMap(o=>o.cells[x[t]]&&!o.cells[x[t]].missing?[o.cells[x[t]].value]:[]),w=c(o.getAttribute("data-x"),M(0)),v=c(o.getAttribute("data-y"),M(1)),A=null===w?p+b/2:Math.min(Math.max(s(w),p),p+b),k=null===v?$+y/2:Math.min(Math.max(i(v),$),$+y),S=f.querySelector(`:scope > .${t.GRID_CLASS}`);if(S){const t=(t,o)=>Math.abs(t-o)<h/2,o=(S.getAttribute("d")??"").match(/M[^M]+/g)??[];S.setAttribute("d",o.filter(o=>{const[,n,r,a]=o.match(/M(-?[\d.]+),(-?[\d.]+)([HV])/)??[];return!("V"===a&&t(Number(n),A)||"H"===a&&t(Number(r),k))}).join(""))}f.insertBefore((0,t.svgEl)("path",{class:t.QUADRANT_CLASS,d:`M${(0,t.short)(A)},${(0,t.short)($)}V${(0,t.short)($+y)}M${(0,t.short)(p)},${(0,t.short)(k)}H${(0,t.short)(p+b)}`}),f.querySelector(`:scope > .${t.POINT_CLASS}`));const q=6*h,z=[];for(const c of m.querySelectorAll(`:scope > .${e}`)){const o=c.dataset.corner??"",n=o.includes("right"),r=o.includes("bottom"),a=n?p+b-q:p+q,e=r?$+y-q:$+q;(0,t.placeAt)(c,a,e,t.VIEWBOX_WIDTH,d);const s=(0,t.sizeOf)(c).width*h,i=(0,t.sizeOf)(c).height*h;z.push([n?a-s:a,r?e-i:e,s,i])}r.taken.push(...z);for(const e of g){if(e.hidden||e.hasAttribute("data-left-out")||!e.style.left)continue;const o=(0,t.boxOf)(e,h,t.VIEWBOX_WIDTH,d,[0,0]);z.some(n=>(0,t.overlaps)(n,o))&&e.setAttribute("data-left-out","")}}}},text({figure:o,plot:n,rows:r,series:a,config:e}){const{x:s,y:c,size:l,group:f}=u(a),m=n.querySelector(":scope > .tablechart-categories")?.children??[],g=new Map;r.forEach((t,o)=>{const n=h(t,s,c);n&&(g.get(n)?.push(o)??g.set(n,[o]))});const p=t=>void 0===l?0:Math.abs(r[t].cells[l]?.value??0);for(const t of g.values()){if(t.length<2)continue;const o=t.reduce((t,o)=>p(o)>p(t)?o:t);for(const n of t){const r=m[n];n===o?r?.setAttribute("data-more",String(t.length-1)):r?.setAttribute("data-same","")}}const $=d(r,f);if($.length<2&&void 0===l)return;const b=document.createElement("div");b.className=t.LEGEND_CLASS,b.setAttribute("aria-hidden","true"),b.inert=!0;const y=Array.from((t=>Array.from(t.querySelectorAll("table")).find(t=>!t.closest(".chart-summary"))??null)(o)?.querySelectorAll(`tbody tr > td:nth-of-type(${f+1})`)??[]),x=(o,n)=>{const r=(0,t.htmlEl)(t.NAME_CLASS,b);return r.dataset.place="legend",r.dataset.line="none",r.textContent=o,n&&e.langattribute&&r.setAttribute(e.langattribute,n),r};$.length>1&&$.forEach((t,o)=>{const n=e.langattribute?y.find(o=>o.textContent?.trim()===t)?.getAttribute(e.langattribute):null,r=x(t,n);r.dataset.marker=i[o%i.length],r.style.setProperty("--n",String(o)),r.style.setProperty("--palette",`var(--tablechart-color-${o+1})`)}),void 0!==l&&(x(a[l].name,a[l].key).dataset.marker="size"),n.after(b)},draw(o){const{rows:n,series:r,x:a,y:e,frame:s,unit:c,svg:h,categories:f,height:m,config:g,plot:p}=o;if(!a||!s)return;const $=u(r),b=d(n,$.group),y=b.length>1,x=(o.length("--tablechart-marker-size","10px")??10*c)/2,M=(o.length("--tablechart-bubble-max","24px")??24*c)/2,w=(o.length("--tablechart-bubble-min","")??1.2*x)/2,v=t=>void 0===$.size?void 0:t.cells[$.size],A=(0,t.largest)(n.map(t=>Math.abs(v(t)?.value??0))),k=n.flatMap((t,o)=>{const n=t.cells[$.x],r=t.cells[$.y];if(!n||!r||n.missing||r.missing)return[];const s=v(t),c=s&&!s.missing&&A>0?Math.max(w,Math.sqrt(Math.abs(s.value)/A)*M):x,i=y?b.indexOf(t.cells[$.group]?.raw??""):-1;return[{i:o,px:a(n.value),py:e(r.value),r:c,g:i}]}),S=Math.min(k.length-1,6);p.style.setProperty("--count",String(S+1));const q=new Map([...k].sort((t,o)=>t.px-o.px||t.i-o.i).map((o,n)=>[o.i,S?(0,t.short)(n*S/(k.length-1)):0])),z=[...k].sort((t,o)=>o.r-t.r||t.i-o.i);for(const l of z){const o=y?i[l.g%i.length]:"circle";h.appendChild((0,t.markerEl)(o,l.px,l.py,l.r,{class:t.POINT_CLASS,"data-row":l.i+1,"data-marker":o,style:`--i:${q.get(l.i)}; ${y?`--n:${l.g}; --palette:var(--tablechart-color-${l.g+1})`:""}`}))}const N=p.parentElement?.getAttribute("data-chart-names"),H=null==N?g.names:"false"!==N,V=4*c,D=[s[0]+1.5*V,s[1],s[2]-1.5*V,s[3]-1.5*V],[I,C,F,T]=D,W=z.map(({i:o,px:n,py:r,r:a})=>{if(f[o].hasAttribute("data-same"))return[];const{width:e,height:s}=(0,t.sizeOf)(f[o]),i=e*c,l=s*c,u=.1*l,d=.7*a+V/2,h=(t,o)=>[Math.max(I,Math.min(t,I+F-i)),Math.max(C,Math.min(o,C+T-l))+u,i,l-2*u];return[h(n+a+V,r-l/2),h(n-a-V-i,r-l/2),h(n-i/2,r-a-V-l),h(n-i/2,r+a+V),h(n+d,r-d-l),h(n-d-i,r-d-l),h(n+d,r+d),h(n-d-i,r+d)]}),Z=H?(0,t.avoidCollisions)(W,k.map(({px:t,py:o,r:n})=>[t-n,o-n,2*n,2*n]),D):z.map(()=>null);for(const t of f)t.hidden=!0,t.dataset.missing="";z.forEach((o,n)=>{const r=f[o.i],a=Z[n];if(r.hidden=!1,delete r.dataset.missing,r.style.setProperty("--i",String(q.get(o.i))),r.toggleAttribute("data-left-out",null===a),null===a)return;r.dataset.side=l[a];const[e,s,,i]=W[n][a];(0,t.placeAt)(r,e,s-((0,t.sizeOf)(r).height*c-i)/2,t.VIEWBOX_WIDTH,m)}),o.rowAt=(t,o)=>{let n=null,r=6*c;for(const a of k){const e=Math.max(Math.hypot(a.px-t*c,a.py-o*c)-a.r,0);(e<r||e===r&&n&&a.r<n.r)&&(r=e,n=a)}return n?n.i:null}},tooltip(t,o){const{x:n,y:r}=u(t.series),a=h(t.rows[o],n,r),e=t.rows.flatMap((t,e)=>e===o||null!==a&&h(t,n,r)===a?[e]:[]),s=e.filter(o=>t.categories[o]?.hasAttribute("data-more"));return[...s,...e.filter(t=>!s.includes(t))].map(o=>({name:t.categories[o]?.textContent??"",value:""}))}},m=window.Tablechart;m.addType("scatter",f),m.addType("candlestick",a)}(Tablechart.kit);
|
|
@@ -1,11 +1,105 @@
|
|
|
1
|
-
import { A as e,
|
|
2
|
-
// src/js/
|
|
3
|
-
var
|
|
1
|
+
import { A as e, B as t, C as n, D as r, E as i, F as a, G as o, H as s, I as c, M as l, N as u, O as d, P as f, R as p, S as m, T as h, V as g, Y as _, Z as v, j as y, k as b, q as x, w as S, z as C } from "./tablechart-shared.mjs";
|
|
2
|
+
// src/js/charts/candlestick.ts
|
|
3
|
+
var w = "tablechart-candle", T = (e) => e.flatMap((e, t) => e.group ? [] : [t]).slice(0, 4), E = (e, t) => {
|
|
4
|
+
let n = t.map((t) => e.cells[t]);
|
|
5
|
+
return n.length === 4 && n.every((e) => e && !e.missing) ? n.map((e) => e.value) : null;
|
|
6
|
+
}, D = {
|
|
7
|
+
columns: !0,
|
|
8
|
+
numbers: !1,
|
|
9
|
+
floats: !0,
|
|
10
|
+
valueAxis: !0,
|
|
11
|
+
ticks: !0,
|
|
12
|
+
crosshair: !0,
|
|
13
|
+
headroom: ({ unit: e }) => 4 * e,
|
|
14
|
+
draw(t) {
|
|
15
|
+
let { rows: n, cx: r, y: a, unit: o, svg: s, barWidth: c, height: u, domain: d, config: f } = t, m = T(t.series), g = n.map((e) => E(e, m)), b = g.flatMap((e) => e ? [e[2]] : []), x = Math.max(c, o);
|
|
16
|
+
g.forEach((t, i) => {
|
|
17
|
+
if (!t) return;
|
|
18
|
+
let [c, u, d, f] = t, m = f >= c, h = (a(c) + a(f)) / 2, g = Math.max(Math.abs(a(c) - a(f)), o), _ = h - g / 2, v = e("g", {
|
|
19
|
+
class: y(`${p} ${w}`, n[i]),
|
|
20
|
+
"data-row": i + 1,
|
|
21
|
+
"data-move": m ? "up" : "down",
|
|
22
|
+
...!m && { "data-grow": "down" },
|
|
23
|
+
style: l("--i", n[i].slot)
|
|
24
|
+
}), b = (t, n) => {
|
|
25
|
+
n - t > .01 && v.append(e("line", {
|
|
26
|
+
class: `${w}-wick`,
|
|
27
|
+
x1: r(i),
|
|
28
|
+
x2: r(i),
|
|
29
|
+
y1: t,
|
|
30
|
+
y2: n
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
b(a(u), _), b(_ + g, a(d)), v.append(e("rect", {
|
|
34
|
+
class: `${w}-body`,
|
|
35
|
+
x: r(i) - x / 2,
|
|
36
|
+
y: _,
|
|
37
|
+
width: x,
|
|
38
|
+
height: g
|
|
39
|
+
})), s.append(v);
|
|
40
|
+
});
|
|
41
|
+
let C = b.length ? -h(b.map((e) => -e)) : 0, D = h(g.flatMap((e) => e ? [e[1]] : [])), O = a(d ? d[0] : C);
|
|
42
|
+
s.append(e("line", {
|
|
43
|
+
class: _,
|
|
44
|
+
x1: 0,
|
|
45
|
+
x2: v,
|
|
46
|
+
y1: O,
|
|
47
|
+
y2: O
|
|
48
|
+
}));
|
|
49
|
+
let k = m[3], A = n.find((e) => e.cells[k] && !e.cells[k].missing)?.cells[k], j = Math.max(0, h(n.map((e) => S(e.cells[k]?.number ?? "")))), M = a(D) - a(C) || 1, N = (e) => {
|
|
50
|
+
let [t, n] = i(C + (e - a(C)) / M * (D - C), j, f.locale);
|
|
51
|
+
return `${A?.prefix ?? ""}${t}${n}${A?.suffix ?? ""}`;
|
|
52
|
+
}, P = t.axisSide === "right";
|
|
53
|
+
t.crossingOf = (n) => {
|
|
54
|
+
let i = g[n];
|
|
55
|
+
if (!i) return null;
|
|
56
|
+
let c = s.querySelector(`.${w}[data-row="${n + 1}"] .${w}-body`), l = c ? getComputedStyle(c).stroke : void 0;
|
|
57
|
+
return {
|
|
58
|
+
x: r(n),
|
|
59
|
+
from: d ? a(d[1]) : 0,
|
|
60
|
+
to: O,
|
|
61
|
+
edge: P ? v : 0,
|
|
62
|
+
end: P ? 0 : v,
|
|
63
|
+
axis: t.axisSide,
|
|
64
|
+
height: u,
|
|
65
|
+
points: m.map((t, s) => ({
|
|
66
|
+
series: t,
|
|
67
|
+
y: a(i[s]),
|
|
68
|
+
dot: e("circle", {
|
|
69
|
+
class: "tablechart-crosshair-point",
|
|
70
|
+
cx: r(n),
|
|
71
|
+
cy: a(i[s]),
|
|
72
|
+
r: 3 * o,
|
|
73
|
+
style: `--tablechart-point-color:${l}`
|
|
74
|
+
}),
|
|
75
|
+
colour: l
|
|
76
|
+
})),
|
|
77
|
+
textAt: N,
|
|
78
|
+
snap: 8,
|
|
79
|
+
around: [a(i[1]), a(i[2])]
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
tooltip(e, t, n) {
|
|
84
|
+
let r = T(e.series), i = E(e.rows[t], r);
|
|
85
|
+
return r.map((r, a) => {
|
|
86
|
+
let o = e.seriesValues[r]?.[t]?.textContent ?? "", s = a === 3 && i && i[0] ? ` (${new Intl.NumberFormat(n.locale, {
|
|
87
|
+
style: "percent",
|
|
88
|
+
signDisplay: "exceptZero",
|
|
89
|
+
maximumFractionDigits: 1
|
|
90
|
+
}).format((i[3] - i[0]) / i[0])})` : "";
|
|
91
|
+
return {
|
|
92
|
+
name: e.series[r].name,
|
|
93
|
+
value: o + s
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}, O = `${x}-name`, k = [
|
|
4
98
|
"top left",
|
|
5
99
|
"top right",
|
|
6
100
|
"bottom left",
|
|
7
101
|
"bottom right"
|
|
8
|
-
],
|
|
102
|
+
], A = (e, t) => {
|
|
9
103
|
if (!e) return null;
|
|
10
104
|
if (e === "mean") return t.length ? t.reduce((e, t) => e + t, 0) / t.length : null;
|
|
11
105
|
if (e === "median") {
|
|
@@ -14,65 +108,65 @@ var _ = `${p}-name`, v = [
|
|
|
14
108
|
}
|
|
15
109
|
let n = Number.parseFloat(e);
|
|
16
110
|
return Number.isFinite(n) ? n : null;
|
|
17
|
-
},
|
|
111
|
+
}, j = {
|
|
18
112
|
create(e, t) {
|
|
19
113
|
for (let n of e.querySelectorAll("[data-corner]")) {
|
|
20
114
|
let e = (n.getAttribute("data-corner") ?? "").toLowerCase().split(/\s+/), r = `${e.find((e) => e === "top" || e === "bottom")} ${e.find((e) => e === "left" || e === "right")}`;
|
|
21
|
-
if (!
|
|
22
|
-
console.warn(`[${
|
|
115
|
+
if (!k.includes(r)) {
|
|
116
|
+
console.warn(`[${g}] A quadrant has a corner "${n.getAttribute("data-corner")}". It takes ${k.join(", ")}.`);
|
|
23
117
|
continue;
|
|
24
118
|
}
|
|
25
119
|
let i = document.createElement("span");
|
|
26
|
-
if (i.className =
|
|
120
|
+
if (i.className = O, i.dataset.corner = r, i.inert = !0, n.childElementCount) for (let e of Array.from(n.children)) i.append(e.cloneNode(!0));
|
|
27
121
|
else i.textContent = (n.textContent ?? "").trim();
|
|
28
122
|
for (let { name: e, value: t } of Array.from(n.attributes)) e !== "data-corner" && i.setAttribute(e, t);
|
|
29
123
|
t.append(i);
|
|
30
124
|
}
|
|
31
125
|
return null;
|
|
32
126
|
},
|
|
33
|
-
draw(
|
|
34
|
-
let { frame:
|
|
35
|
-
if (!
|
|
36
|
-
console.warn(`[${
|
|
127
|
+
draw(t, n, r) {
|
|
128
|
+
let { frame: i, x: a, y: s, rows: l, series: p, height: m, unit: h, svg: _, plot: y, categories: S } = r;
|
|
129
|
+
if (!i || !a) {
|
|
130
|
+
console.warn(`[${g}] A quadrant works on a scatter chart, which has an axis across and an axis up.`);
|
|
37
131
|
return;
|
|
38
132
|
}
|
|
39
|
-
let [
|
|
133
|
+
let [w, T, E, D] = i, k = p.flatMap((e, t) => e.group ? [] : [t]), j = (e) => l.flatMap((t) => t.cells[k[e]] && !t.cells[k[e]].missing ? [t.cells[k[e]].value] : []), M = A(t.getAttribute("data-x"), j(0)), N = A(t.getAttribute("data-y"), j(1)), P = M === null ? w + E / 2 : Math.min(Math.max(a(M), w), w + E), F = N === null ? T + D / 2 : Math.min(Math.max(s(N), T), T + D), I = _.querySelector(`:scope > .${C}`);
|
|
40
134
|
if (I) {
|
|
41
|
-
let e = (e, t) => Math.abs(e - t) <
|
|
135
|
+
let e = (e, t) => Math.abs(e - t) < h / 2, t = (I.getAttribute("d") ?? "").match(/M[^M]+/g) ?? [];
|
|
42
136
|
I.setAttribute("d", t.filter((t) => {
|
|
43
137
|
let [, n, r, i] = t.match(/M(-?[\d.]+),(-?[\d.]+)([HV])/) ?? [];
|
|
44
138
|
return !(i === "V" && e(Number(n), P)) && !(i === "H" && e(Number(r), F));
|
|
45
139
|
}).join(""));
|
|
46
140
|
}
|
|
47
|
-
|
|
48
|
-
class:
|
|
49
|
-
d: `M${
|
|
50
|
-
}),
|
|
51
|
-
let L = 6 *
|
|
52
|
-
for (let e of
|
|
53
|
-
let t = e.dataset.corner ?? "", n = t.includes("right"), r = t.includes("bottom"), i = n ?
|
|
54
|
-
|
|
55
|
-
let
|
|
141
|
+
_.insertBefore(e("path", {
|
|
142
|
+
class: x,
|
|
143
|
+
d: `M${b(P)},${b(T)}V${b(T + D)}M${b(w)},${b(F)}H${b(w + E)}`
|
|
144
|
+
}), _.querySelector(`:scope > .${o}`));
|
|
145
|
+
let L = 6 * h, R = [];
|
|
146
|
+
for (let e of y.querySelectorAll(`:scope > .${O}`)) {
|
|
147
|
+
let t = e.dataset.corner ?? "", n = t.includes("right"), r = t.includes("bottom"), i = n ? w + E - L : w + L, a = r ? T + D - L : T + L;
|
|
148
|
+
d(e, i, a, v, m);
|
|
149
|
+
let o = f(e).width * h, s = f(e).height * h;
|
|
56
150
|
R.push([
|
|
57
|
-
n ? i -
|
|
58
|
-
r ?
|
|
59
|
-
|
|
60
|
-
|
|
151
|
+
n ? i - o : i,
|
|
152
|
+
r ? a - s : a,
|
|
153
|
+
o,
|
|
154
|
+
s
|
|
61
155
|
]);
|
|
62
156
|
}
|
|
63
|
-
|
|
64
|
-
for (let e of
|
|
157
|
+
r.taken.push(...R);
|
|
158
|
+
for (let e of S) {
|
|
65
159
|
if (e.hidden || e.hasAttribute("data-left-out") || !e.style.left) continue;
|
|
66
|
-
let
|
|
67
|
-
R.some((e) =>
|
|
160
|
+
let t = u(e, h, v, m, [0, 0]);
|
|
161
|
+
R.some((e) => c(e, t)) && e.setAttribute("data-left-out", "");
|
|
68
162
|
}
|
|
69
163
|
}
|
|
70
|
-
},
|
|
164
|
+
}, M = [
|
|
71
165
|
"circle",
|
|
72
166
|
"triangle",
|
|
73
167
|
"square",
|
|
74
168
|
"diamond"
|
|
75
|
-
],
|
|
169
|
+
], N = [
|
|
76
170
|
"right",
|
|
77
171
|
"left",
|
|
78
172
|
"above",
|
|
@@ -81,7 +175,7 @@ var _ = `${p}-name`, v = [
|
|
|
81
175
|
"left",
|
|
82
176
|
"right",
|
|
83
177
|
"left"
|
|
84
|
-
],
|
|
178
|
+
], P = (e) => {
|
|
85
179
|
let t = e.flatMap((e, t) => e.group ? [] : [t]);
|
|
86
180
|
return {
|
|
87
181
|
x: t[0],
|
|
@@ -89,79 +183,79 @@ var _ = `${p}-name`, v = [
|
|
|
89
183
|
size: t[2],
|
|
90
184
|
group: e.findIndex((e) => e.group)
|
|
91
185
|
};
|
|
92
|
-
},
|
|
186
|
+
}, F = (e, t) => t < 0 ? [] : [...new Set(e.map((e) => e.cells[t]?.raw ?? ""))], I = (e, t, n) => e.cells[t]?.missing || e.cells[n]?.missing ? null : `${e.cells[t]?.value} ${e.cells[n]?.value}`, L = {
|
|
93
187
|
aspect: .62,
|
|
94
188
|
categories: "beside",
|
|
95
189
|
columns: !0,
|
|
96
190
|
axes: !0,
|
|
97
191
|
numbers: !1,
|
|
98
|
-
annotations: { quadrant:
|
|
99
|
-
text({ figure:
|
|
100
|
-
let { x:
|
|
101
|
-
|
|
102
|
-
let n =
|
|
192
|
+
annotations: { quadrant: j },
|
|
193
|
+
text({ figure: e, plot: n, rows: i, series: a, config: o }) {
|
|
194
|
+
let { x: c, y: l, size: u, group: d } = P(a), f = n.querySelector(":scope > .tablechart-categories")?.children ?? [], p = /* @__PURE__ */ new Map();
|
|
195
|
+
i.forEach((e, t) => {
|
|
196
|
+
let n = I(e, c, l);
|
|
103
197
|
n && (p.get(n)?.push(t) ?? p.set(n, [t]));
|
|
104
198
|
});
|
|
105
|
-
let
|
|
199
|
+
let h = (e) => u === void 0 ? 0 : Math.abs(i[e].cells[u]?.value ?? 0);
|
|
106
200
|
for (let e of p.values()) {
|
|
107
201
|
if (e.length < 2) continue;
|
|
108
|
-
let t = e.reduce((e, t) =>
|
|
202
|
+
let t = e.reduce((e, t) => h(t) > h(e) ? t : e);
|
|
109
203
|
for (let n of e) {
|
|
110
204
|
let r = f[n];
|
|
111
205
|
n === t ? r?.setAttribute("data-more", String(e.length - 1)) : r?.setAttribute("data-same", "");
|
|
112
206
|
}
|
|
113
207
|
}
|
|
114
|
-
let
|
|
115
|
-
if (
|
|
116
|
-
let
|
|
117
|
-
|
|
118
|
-
let
|
|
119
|
-
let
|
|
120
|
-
return
|
|
208
|
+
let g = F(i, d);
|
|
209
|
+
if (g.length < 2 && u === void 0) return;
|
|
210
|
+
let _ = document.createElement("div");
|
|
211
|
+
_.className = t, _.setAttribute("aria-hidden", "true"), _.inert = !0;
|
|
212
|
+
let v = Array.from(m(e)?.querySelectorAll(`tbody tr > td:nth-of-type(${d + 1})`) ?? []), y = (e, t) => {
|
|
213
|
+
let n = r(s, _);
|
|
214
|
+
return n.dataset.place = "legend", n.dataset.line = "none", n.textContent = e, t && o.langattribute && n.setAttribute(o.langattribute, t), n;
|
|
121
215
|
};
|
|
122
|
-
|
|
123
|
-
let n = o.langattribute ?
|
|
124
|
-
r.dataset.marker =
|
|
125
|
-
}),
|
|
216
|
+
g.length > 1 && g.forEach((e, t) => {
|
|
217
|
+
let n = o.langattribute ? v.find((t) => t.textContent?.trim() === e)?.getAttribute(o.langattribute) : null, r = y(e, n);
|
|
218
|
+
r.dataset.marker = M[t % M.length], r.style.setProperty("--n", String(t)), r.style.setProperty("--palette", `var(--tablechart-color-${t + 1})`);
|
|
219
|
+
}), u !== void 0 && (y(a[u].name, a[u].key).dataset.marker = "size"), n.after(_);
|
|
126
220
|
},
|
|
127
221
|
draw(e) {
|
|
128
|
-
let { rows: t, series:
|
|
129
|
-
if (!
|
|
130
|
-
let
|
|
131
|
-
let n = e.cells[
|
|
132
|
-
if (!n || !
|
|
133
|
-
let
|
|
222
|
+
let { rows: t, series: r, x: i, y: s, frame: c, unit: l, svg: u, categories: p, height: m, config: g, plot: _ } = e;
|
|
223
|
+
if (!i || !c) return;
|
|
224
|
+
let y = P(r), x = F(t, y.group), S = x.length > 1, C = (e.length("--tablechart-marker-size", "10px") ?? 10 * l) / 2, w = (e.length("--tablechart-bubble-max", "24px") ?? 24 * l) / 2, T = (e.length("--tablechart-bubble-min", "") ?? C * 1.2) / 2, E = (e) => y.size === void 0 ? void 0 : e.cells[y.size], D = h(t.map((e) => Math.abs(E(e)?.value ?? 0))), O = t.flatMap((e, t) => {
|
|
225
|
+
let n = e.cells[y.x], r = e.cells[y.y];
|
|
226
|
+
if (!n || !r || n.missing || r.missing) return [];
|
|
227
|
+
let a = E(e), o = a && !a.missing && D > 0 ? Math.max(T, Math.sqrt(Math.abs(a.value) / D) * w) : C, c = S ? x.indexOf(e.cells[y.group]?.raw ?? "") : -1;
|
|
134
228
|
return [{
|
|
135
229
|
i: t,
|
|
136
|
-
px:
|
|
137
|
-
py:
|
|
138
|
-
r:
|
|
230
|
+
px: i(n.value),
|
|
231
|
+
py: s(r.value),
|
|
232
|
+
r: o,
|
|
139
233
|
g: c
|
|
140
234
|
}];
|
|
141
|
-
}),
|
|
142
|
-
|
|
143
|
-
let
|
|
144
|
-
for (let e of
|
|
145
|
-
let t =
|
|
146
|
-
u.appendChild(
|
|
147
|
-
class:
|
|
235
|
+
}), k = Math.min(O.length - 1, 6);
|
|
236
|
+
_.style.setProperty("--count", String(k + 1));
|
|
237
|
+
let A = new Map([...O].sort((e, t) => e.px - t.px || e.i - t.i).map((e, t) => [e.i, k ? b(t * k / (O.length - 1)) : 0])), j = [...O].sort((e, t) => t.r - e.r || e.i - t.i);
|
|
238
|
+
for (let e of j) {
|
|
239
|
+
let t = S ? M[e.g % M.length] : "circle";
|
|
240
|
+
u.appendChild(n(t, e.px, e.py, e.r, {
|
|
241
|
+
class: o,
|
|
148
242
|
"data-row": e.i + 1,
|
|
149
243
|
"data-marker": t,
|
|
150
|
-
style: `--i:${
|
|
244
|
+
style: `--i:${A.get(e.i)}; ${S ? `--n:${e.g}; --palette:var(--tablechart-color-${e.g + 1})` : ""}`
|
|
151
245
|
}));
|
|
152
246
|
}
|
|
153
|
-
let I =
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
], [B, V, H, U] = z, W =
|
|
247
|
+
let I = _.parentElement?.getAttribute("data-chart-names"), L = I == null ? g.names : I !== "false", R = 4 * l, z = [
|
|
248
|
+
c[0] + 1.5 * R,
|
|
249
|
+
c[1],
|
|
250
|
+
c[2] - 1.5 * R,
|
|
251
|
+
c[3] - 1.5 * R
|
|
252
|
+
], [B, V, H, U] = z, W = j.map(({ i: e, px: t, py: n, r }) => {
|
|
159
253
|
if (p[e].hasAttribute("data-same")) return [];
|
|
160
|
-
let { width: i, height: a } =
|
|
254
|
+
let { width: i, height: a } = f(p[e]), o = i * l, s = a * l, c = s * .1, u = r * .7 + R / 2, d = (e, t) => [
|
|
161
255
|
Math.max(B, Math.min(e, B + H - o)),
|
|
162
|
-
Math.max(V, Math.min(t, V + U - s)) +
|
|
256
|
+
Math.max(V, Math.min(t, V + U - s)) + c,
|
|
163
257
|
o,
|
|
164
|
-
s - 2 *
|
|
258
|
+
s - 2 * c
|
|
165
259
|
];
|
|
166
260
|
return [
|
|
167
261
|
d(t + r + R, n - s / 2),
|
|
@@ -173,34 +267,34 @@ var _ = `${p}-name`, v = [
|
|
|
173
267
|
d(t + u, n + u),
|
|
174
268
|
d(t - u - o, n + u)
|
|
175
269
|
];
|
|
176
|
-
}), G = L ?
|
|
270
|
+
}), G = L ? a(W, O.map(({ px: e, py: t, r: n }) => [
|
|
177
271
|
e - n,
|
|
178
272
|
t - n,
|
|
179
273
|
2 * n,
|
|
180
274
|
2 * n
|
|
181
|
-
]), z) :
|
|
275
|
+
]), z) : j.map(() => null);
|
|
182
276
|
for (let e of p) e.hidden = !0, e.dataset.missing = "";
|
|
183
|
-
|
|
277
|
+
j.forEach((e, t) => {
|
|
184
278
|
let n = p[e.i], r = G[t];
|
|
185
|
-
if (n.hidden = !1, delete n.dataset.missing, n.style.setProperty("--i", String(
|
|
186
|
-
n.dataset.side =
|
|
187
|
-
let [i,
|
|
188
|
-
|
|
279
|
+
if (n.hidden = !1, delete n.dataset.missing, n.style.setProperty("--i", String(A.get(e.i))), n.toggleAttribute("data-left-out", r === null), r === null) return;
|
|
280
|
+
n.dataset.side = N[r];
|
|
281
|
+
let [i, a, , o] = W[t][r];
|
|
282
|
+
d(n, i, a - (f(n).height * l - o) / 2, v, m);
|
|
189
283
|
}), e.rowAt = (e, t) => {
|
|
190
|
-
let n = null, r = 6 *
|
|
191
|
-
for (let i of
|
|
192
|
-
let a = Math.max(Math.hypot(i.px - e *
|
|
284
|
+
let n = null, r = 6 * l;
|
|
285
|
+
for (let i of O) {
|
|
286
|
+
let a = Math.max(Math.hypot(i.px - e * l, i.py - t * l) - i.r, 0);
|
|
193
287
|
(a < r || a === r && n && i.r < n.r) && (r = a, n = i);
|
|
194
288
|
}
|
|
195
289
|
return n ? n.i : null;
|
|
196
290
|
};
|
|
197
291
|
},
|
|
198
292
|
tooltip(e, t) {
|
|
199
|
-
let { x: n, y: r } =
|
|
293
|
+
let { x: n, y: r } = P(e.series), i = I(e.rows[t], n, r), a = e.rows.flatMap((e, a) => a === t || i !== null && I(e, n, r) === i ? [a] : []), o = a.filter((t) => e.categories[t]?.hasAttribute("data-more"));
|
|
200
294
|
return [...o, ...a.filter((e) => !o.includes(e))].map((t) => ({
|
|
201
295
|
name: e.categories[t]?.textContent ?? "",
|
|
202
296
|
value: ""
|
|
203
297
|
}));
|
|
204
298
|
}
|
|
205
299
|
};
|
|
206
|
-
export {
|
|
300
|
+
export { D as candlestick, L as scatter };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { A as StepPlan, C as addType, D as ChartType, E as ChartState, F as PLOT_CLASS, I as PRINTING_CLASS, L as QUEUED_CLASS, M as AT_ONCE_CLASS, N as Config, O as Row, P as PLACED_CLASS, R as SHOWN_CLASS, S as unbuild, _ as prepare, b as build, c as assignKeys, d as readMarkdownRows, h as entranceOf, j as stepTo, k as Series, l as adoptTables, o as checkContrast, s as checkTextContrast, u as loadSources, v as setShown, x as relabel, y as layout, z as defaultConfig } from "./tablechart-shared.mjs";
|
|
2
2
|
export { AT_ONCE_CLASS, type ChartState, type ChartType, type Config, PLACED_CLASS, PLOT_CLASS, PRINTING_CLASS, QUEUED_CLASS, type Row, SHOWN_CLASS, type Series, type StepPlan, addType, adoptTables, assignKeys, build, checkContrast, checkTextContrast, defaultConfig, entranceOf, layout, loadSources, prepare, readMarkdownRows, relabel, setShown, stepTo, unbuild };
|
package/dist/tablechart-core.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { L as AT_ONCE_CLASS, U as PLACED_CLASS, W as PLOT_CLASS, K as PRINTING_CLASS, J as QUEUED_CLASS, X as SHOWN_CLASS, h as addType, a as adoptTables, y as assignKeys, d as build, c as checkContrast, l as checkTextContrast, Q as defaultConfig, n as entranceOf, u as layout, o as loadSources, i as prepare, s as readMarkdownRows, f as relabel, m as setShown, v as stepTo, p as unbuild } from "./tablechart-shared.mjs";
|