@martinomagnifico/tablechart 1.1.3 → 1.2.1

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 CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.1] - 2026-10-30
4
+
5
+ ### Added
6
+
7
+ - An animation playground in the docs, under Styling: sliders for the speed, the stagger and the time of a chart, with the CSS to copy. It shows how the settings work together before you set them.
8
+ - `--tablechart-animation-min`, the shortest a chart takes to build: 0.5s by default. A chart with few columns no longer looks rushed.
9
+ - `--tablechart-waterfall-stagger-factor`, how many times the stagger a waterfall has: 2.4 by default, so it still builds column by column.
10
+ - `--tablechart-series-color-end` and `--tablechart-slice-color-end`, with `--tablechart-series-color-start` and `--tablechart-slice-color-start` as other names for the first colour: lines, parts and slices go from one colour to another in even steps. If both ends have enough contrast, every step in between has it too.
11
+ - `--tablechart-animation-speed`, to make every chart faster or slower, such as `1.25` for a quarter faster. All times scale together.
12
+
13
+
14
+ ### Changed
15
+
16
+ - The parts of a stacked column or bar have a gap of 1px between them, but not at the zero line. Two parts in similar colours stay apart. `--tablechart-stack-gap` sets it.
17
+ - The zero line darkens a bar it crosses, as a multiply does, and lightens it in dark mode. Before, it was a grey line over the bar.
18
+ - A number inside a bar has at least 4.5:1 with the bar: in the background colour if that has enough, else a bit lighter or darker, else black or white. Before, a number on a middle colour could have less than 4.5:1.
19
+ - With `debug`, the console warns about a line, slice or bar with a contrast below 3:1 with the background. That is the minimum for parts of a chart (WCAG 1.4.11).
20
+
21
+ ### Fixed
22
+
23
+ - Every chart type has the same animation defaults: 0.4s per element, a stagger of 0.25, `ease-in-out`, and 0.5s to 2s for a whole chart. In 1.2.0, a column chart with 10 or more columns took up to 3s.
24
+
25
+
26
+
27
+ ## [1.2.0] - 2026-10-28
28
+
29
+ ### Added
30
+
31
+ - Summaries! They need a separate import from `@martinomagnifico/tablechart/summary`. Summaries are important for screen readers and print.
32
+ - A line over columns, with `data-chart="line"` on a column header.
33
+ - `data-chart-values` and the `values` option: which values a line shows. A long line shows only its key values.
34
+ - Notes: a column with `data-note`, shown as a line at its row and in the tooltip.
35
+ - A `quadrant` annotation for scatter charts, and one step for a scatter with `data-fragment`.
36
+ - `--tablechart-bubble-min`, so the smallest bubble stays visible.
37
+
38
+ ### Changed
39
+
40
+ - Values on a line keep clear of the line, the points and each other, if possible.
41
+ - Line and area charts have a tick above each name. `data-chart-ticks="false"` turns them off.
42
+ - Scatter markers have an edge in the background colour, so they stand apart from lines and each other.
43
+ - Bars put their numbers after the bar by default, as columns put them above. `data-chart-labels="inside"` puts them inside, as on columns.
44
+ - Badges are rounded boxes instead of ellipses. They take less room.
45
+ - 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.
46
+
47
+ ### Fixed
48
+
49
+ - Holes in a line come with the line, not after the build.
50
+ - A legend box is no longer shown empty before its names.
51
+ - Triangle markers are centred on their point.
52
+ - Names of a long line no longer wait seconds after the build.
53
+ - Grouped columns and bars no longer grow after the first resize.
54
+
55
+
56
+
3
57
  ## [1.1.3] - 2026-10-26
4
58
 
5
59
  ### 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 stays:** it is hidden from sight, but screen readers, search engines and translation scripts still read it. Without JavaScript, the table is all there is.
16
- * **Real text:** names and numbers are HTML, so they use the font of the page and do not scale with the drawing.
17
- * **CSS for the looks:** colours, sizes and animation are CSS variables. Light and dark mode follow the page.
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
- * **Print and PDF:** every chart prints whole, and the table can be shown under a chart that leaves numbers out
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 builds when it scrolls into view
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"],r=["right","left","above","below","right","left","right","left"],n=t=>{const e=t.flatMap((t,e)=>t.group?[]:[e]);return{x:e[0],y:e[1],size:e[2],group:t.findIndex(t=>t.group)}},o=(t,e)=>e<0?[]:[...new Set(t.map(t=>t.cells[e]?.raw??""))],a=(t,e,r)=>t.cells[e]?.missing||t.cells[r]?.missing?null:`${t.cells[e]?.value} ${t.cells[r]?.value}`,s={aspect:.62,categories:"beside",columns:!0,axes:!0,numbers:!1,text({figure:r,plot:s,rows:c,series:i,config:l}){const{x:u,y:h,size:d,group:f}=n(i),g=s.querySelector(":scope > .tablechart-categories")?.children??[],p=new Map;c.forEach((t,e)=>{const r=a(t,u,h);r&&(p.get(r)?.push(e)??p.set(r,[e]))});const b=t=>void 0===d?0:Math.abs(c[t].cells[d]?.value??0);for(const t of p.values()){if(t.length<2)continue;const e=t.reduce((t,e)=>b(e)>b(t)?e:t);for(const r of t){const n=g[r];r===e?n?.setAttribute("data-more",String(t.length-1)):n?.setAttribute("data-same","")}}const m=o(c,f);if(m.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 v=Array.from(r.querySelectorAll(`tbody tr > td:nth-of-type(${f+1})`)),x=(e,r)=>{const n=(0,t.htmlEl)(t.NAME_CLASS,M);return n.dataset.place="legend",n.dataset.line="none",n.textContent=e,r&&l.langattribute&&n.setAttribute(l.langattribute,r),n};m.length>1&&m.forEach((t,r)=>{const n=l.langattribute?v.find(e=>e.textContent?.trim()===t)?.getAttribute(l.langattribute):null,o=x(t,n);o.dataset.marker=e[r%e.length],o.style.setProperty("--n",String(r)),o.style.setProperty("--palette",`var(--tablechart-color-${r+1})`)}),void 0!==d&&(x(i[d].name,i[d].key).dataset.marker="size"),r.appendChild(M)},draw(a){const{rows:s,series:c,x:i,y:l,frame:u,unit:h,svg:d,categories:f,height:g,config:p,plot:b}=a;if(!i||!u)return;const m=n(c),M=o(s,m.group),v=M.length>1,x=(a.length("--tablechart-marker-size","10px")??10*h)/2,w=(a.length("--tablechart-bubble-max","24px")??24*h)/2,y=t=>void 0===m.size?void 0:t.cells[m.size],$=(0,t.largest)(s.map(t=>Math.abs(y(t)?.value??0))),S=s.flatMap((t,e)=>{const r=t.cells[m.x],n=t.cells[m.y];if(!r||!n||r.missing||n.missing)return[];const o=y(t),a=o&&!o.missing&&$>0?Math.max(.6*x,Math.sqrt(Math.abs(o.value)/$)*w):x,s=v?M.indexOf(t.cells[m.group]?.raw??""):-1;return[{i:e,px:i(r.value),py:l(n.value),r:a,g:s}]}),z=Math.min(S.length-1,6);b.style.setProperty("--count",String(z+1));const k=new Map([...S].sort((t,e)=>t.px-e.px||t.i-e.i).map((e,r)=>[e.i,z?(0,t.short)(r*z/(S.length-1)):0])),q=[...S].sort((t,e)=>e.r-t.r||t.i-e.i);for(const r of q){const n=v?e[r.g%e.length]:"circle";d.appendChild((0,t.markerEl)(n,r.px,r.py,r.r,{class:t.POINT_CLASS,"data-row":r.i+1,"data-marker":n,style:`--i:${k.get(r.i)}; ${v?`--n:${r.g}; --palette:var(--tablechart-color-${r.g+1})`:""}`}))}const A=b.parentElement?.getAttribute("data-chart-names"),T=null==A?p.names:"false"!==A,j=4*h,B=[u[0]+1.5*j,u[1],u[2]-1.5*j,u[3]-1.5*j],[C,D,E,F]=B,G=q.map(({i:e,px:r,py:n,r:o})=>{if(f[e].hasAttribute("data-same"))return[];const{width:a,height:s}=(0,t.sizeOf)(f[e]),c=a*h,i=s*h,l=.1*i,u=.7*o+j/2,d=(t,e)=>[Math.max(C,Math.min(t,C+E-c)),Math.max(D,Math.min(e,D+F-i))+l,c,i-2*l];return[d(r+o+j,n-i/2),d(r-o-j-c,n-i/2),d(r-c/2,n-o-j-i),d(r-c/2,n+o+j),d(r+u,n-u-i),d(r-u-c,n-u-i),d(r+u,n+u),d(r-u-c,n+u)]}),H=T?(0,t.avoidCollisions)(G,S.map(({px:t,py:e,r:r})=>[t-r,e-r,2*r,2*r]),B):q.map(()=>null);for(const t of f)t.hidden=!0,t.dataset.missing="";q.forEach((e,n)=>{const o=f[e.i],a=H[n];if(o.hidden=!1,delete o.dataset.missing,o.style.setProperty("--i",String(k.get(e.i))),o.toggleAttribute("data-left-out",null===a),null===a)return;o.dataset.side=r[a];const[s,c,,i]=G[n][a];(0,t.placeAt)(o,s,c-((0,t.sizeOf)(o).height*h-i)/2,t.VIEWBOX_WIDTH,g)}),a.rowAt=(t,e)=>{let r=null,n=6*h;for(const o of S){const a=Math.max(Math.hypot(o.px-t*h,o.py-e*h)-o.r,0);(a<n||a===n&&r&&o.r<r.r)&&(n=a,r=o)}return r?r.i:null}},tooltip(t,e){const{x:r,y:o}=n(t.series),s=a(t.rows[e],r,o),c=t.rows.flatMap((t,n)=>n===e||null!==s&&a(t,r,o)===s?[n]:[]),i=c.filter(e=>t.categories[e]?.hasAttribute("data-more"));return[...i,...c.filter(t=>!i.includes(t))].map(e=>({name:t.categories[e]?.textContent??"",value:""}))}};window.Tablechart.addType("scatter",s)}(Tablechart.kit);
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 { M as e, O as t, S as n, T as r, _ as i, b as a, g as o, v as s, w as c, x as l, y as u } from "./tablechart-shared.mjs";
2
- // src/js/charts/scatter.ts
3
- var d = [
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
- ], f = [
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
- ], p = (e) => {
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
- }, m = (e, t) => t < 0 ? [] : [...new Set(e.map((e) => e.cells[t]?.raw ?? ""))], h = (e, t, n) => e.cells[t]?.missing || e.cells[n]?.missing ? null : `${e.cells[t]?.value} ${e.cells[n]?.value}`, g = {
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
- text({ figure: e, plot: t, rows: n, series: i, config: o }) {
32
- let { x: s, y: l, size: u, group: f } = p(i), g = t.querySelector(":scope > .tablechart-categories")?.children ?? [], _ = /* @__PURE__ */ new Map();
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 = h(e, s, l);
35
- n && (_.get(n)?.push(t) ?? _.set(n, [t]));
102
+ let n = T(e, c, l);
103
+ n && (p.get(n)?.push(t) ?? p.set(n, [t]));
36
104
  });
37
- let v = (e) => u === void 0 ? 0 : Math.abs(n[e].cells[u]?.value ?? 0);
38
- for (let e of _.values()) {
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) => v(t) > v(e) ? t : e);
108
+ let t = e.reduce((e, t) => m(t) > m(e) ? t : e);
41
109
  for (let n of e) {
42
- let r = g[n];
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 y = m(n, f);
47
- if (y.length < 2 && u === void 0) return;
48
- let b = document.createElement("div");
49
- b.className = c, b.setAttribute("aria-hidden", "true"), b.inert = !0;
50
- let x = Array.from(e.querySelectorAll(`tbody tr > td:nth-of-type(${f + 1})`)), S = (e, t) => {
51
- let n = a(r, b);
52
- return n.dataset.place = "legend", n.dataset.line = "none", n.textContent = e, t && o.langattribute && n.setAttribute(o.langattribute, t), n;
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
- y.length > 1 && y.forEach((e, t) => {
55
- let n = o.langattribute ? x.find((t) => t.textContent?.trim() === e)?.getAttribute(o.langattribute) : null, r = S(e, n);
56
- r.dataset.marker = d[t % d.length], r.style.setProperty("--n", String(t)), r.style.setProperty("--palette", `var(--tablechart-color-${t + 1})`);
57
- }), u !== void 0 && (S(i[u].name, i[u].key).dataset.marker = "size"), e.appendChild(b);
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: a, series: c, x: h, y: g, frame: _, unit: v, svg: y, categories: b, height: x, config: S, plot: C } = r;
61
- if (!h || !_) return;
62
- let w = p(c), T = m(a, w.group), E = T.length > 1, D = (r.length("--tablechart-marker-size", "10px") ?? 10 * v) / 2, O = (r.length("--tablechart-bubble-max", "24px") ?? 24 * v) / 2, k = (e) => w.size === void 0 ? void 0 : e.cells[w.size], A = u(a.map((e) => Math.abs(k(e)?.value ?? 0))), j = a.flatMap((e, t) => {
63
- let n = e.cells[w.x], r = e.cells[w.y];
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 = k(e), a = i && !i.missing && A > 0 ? Math.max(D * .6, Math.sqrt(Math.abs(i.value) / A) * O) : D, o = E ? T.indexOf(e.cells[w.group]?.raw ?? "") : -1;
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: h(n.value),
69
- py: g(r.value),
136
+ px: o(n.value),
137
+ py: s(r.value),
70
138
  r: a,
71
- g: o
139
+ g: c
72
140
  }];
73
- }), M = Math.min(j.length - 1, 6);
74
- C.style.setProperty("--count", String(M + 1));
75
- let N = new Map([...j].sort((e, t) => e.px - t.px || e.i - t.i).map((e, t) => [e.i, M ? n(t * M / (j.length - 1)) : 0])), P = [...j].sort((e, t) => t.r - e.r || e.i - t.i);
76
- for (let e of P) {
77
- let n = E ? d[e.g % d.length] : "circle";
78
- y.appendChild(o(n, e.px, e.py, e.r, {
79
- class: t,
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": n,
82
- style: `--i:${N.get(e.i)}; ${E ? `--n:${e.g}; --palette:var(--tablechart-color-${e.g + 1})` : ""}`
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 F = C.parentElement?.getAttribute("data-chart-names"), I = F == null ? S.names : F !== "false", L = 4 * v, R = [
86
- _[0] + 1.5 * L,
87
- _[1],
88
- _[2] - 1.5 * L,
89
- _[3] - 1.5 * L
90
- ], [z, B, V, H] = R, U = P.map(({ i: e, px: t, py: n, r }) => {
91
- if (b[e].hasAttribute("data-same")) return [];
92
- let { width: a, height: o } = i(b[e]), s = a * v, c = o * v, l = c * .1, u = r * .7 + L / 2, d = (e, t) => [
93
- Math.max(z, Math.min(e, z + V - s)),
94
- Math.max(B, Math.min(t, B + H - c)) + l,
95
- s,
96
- c - 2 * l
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 + L, n - c / 2),
100
- d(t - r - L - s, n - c / 2),
101
- d(t - s / 2, n - r - L - c),
102
- d(t - s / 2, n + r + L),
103
- d(t + u, n - u - c),
104
- d(t - u - s, n - u - c),
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 - s, n + u)
174
+ d(t - u - o, n + u)
107
175
  ];
108
- }), W = I ? s(U, j.map(({ px: e, py: t, r: n }) => [
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
- ]), R) : P.map(() => null);
114
- for (let e of b) e.hidden = !0, e.dataset.missing = "";
115
- P.forEach((t, n) => {
116
- let r = b[t.i], a = W[n];
117
- if (r.hidden = !1, delete r.dataset.missing, r.style.setProperty("--i", String(N.get(t.i))), r.toggleAttribute("data-left-out", a === null), a === null) return;
118
- r.dataset.side = f[a];
119
- let [o, s, , c] = U[n][a];
120
- l(r, o, s - (i(r).height * v - c) / 2, e, x);
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 * v;
123
- for (let i of j) {
124
- let a = Math.max(Math.hypot(i.px - e * v, i.py - t * v) - i.r, 0);
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 } = p(e.series), i = h(e.rows[t], n, r), a = e.rows.flatMap((e, a) => a === t || i !== null && h(e, n, r) === i ? [a] : []), o = a.filter((t) => e.categories[t]?.hasAttribute("data-more"));
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 { g as scatter };
206
+ export { E as scatter };
@@ -1 +1 @@
1
- export { C as AT_ONCE_CLASS, E as PLACED_CLASS, D as PLOT_CLASS, k as PRINTING_CLASS, A as QUEUED_CLASS, j as SHOWN_CLASS, c as addType, a as adoptTables, h as assignKeys, l as build, N 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";
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. */
@@ -235,6 +273,7 @@ interface ChartType {
235
273
  rows: number;
236
274
  series: number;
237
275
  stacked: boolean;
276
+ labels: Geometry["labels"];
238
277
  }) => number;
239
278
  /** Room under the baseline for numbers that hang below a mark near it. */
240
279
  footroom?: (geo: HeadroomInput) => number;
@@ -243,6 +282,8 @@ interface ChartType {
243
282
  draw: (geo: Geometry) => void;
244
283
  /** The lines of the tooltip of a row. Default: each value with the name of its series, and the total of a stack. */
245
284
  tooltip?: (state: ChartState, index: number, config: Config) => TooltipLine[];
285
+ /** Annotations that come with this type, such as `quadrant` with `scatter`; `addType` adds them. */
286
+ annotations?: Record<string, AnnotationType>;
246
287
  }
247
288
  /** One line of a tooltip, under the name of the row. */
248
289
  interface TooltipLine {
@@ -299,6 +340,8 @@ interface ChartState {
299
340
  rowAt: ((x: number, y: number) => number | null) | null;
300
341
  /** As percentages: left, top, width and height. */
301
342
  rowBox: ((index: number) => [number, number, number, number]) | null;
343
+ /** Where the tooltip of a row points, in viewBox units; set by a chart such as a line. */
344
+ pointOf: ((index: number) => [number, number] | null) | null;
302
345
  annotations: {
303
346
  spec: HTMLElement;
304
347
  type: AnnotationType;