@grafloria/element 0.4.14 → 0.4.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafloria/element",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "type": "module",
5
5
  "main": "./src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -344,6 +344,14 @@ const CSS = `
344
344
  flex: 0 0 auto; width: auto; height: 100%; max-height: 260px; max-width: 60%; aspect-ratio: 1 / 1;
345
345
  }
346
346
 
347
+ /* READABILITY TIERS (widgets.ts chartTier): the text is always in the DOM;
348
+ what a short body cannot afford is hidden, not dropped. */
349
+ .axdb-tier-1 .axdb-yt--q, .axdb-tier-1 .axdb-yl--q,
350
+ .axdb-tier-2 .axdb-yt--q, .axdb-tier-2 .axdb-yl--q,
351
+ .axdb-tier-2 .axdb-yt--h, .axdb-tier-2 .axdb-yl--h,
352
+ .axdb-tier-2 .axdb-xt, .axdb-tier-2 .axdb-vt,
353
+ .axdb-lg--off { display: none; }
354
+
347
355
  /* legend chips, shared by line and donut */
348
356
  .axdb-lg { display: flex; flex-wrap: wrap; gap: 4px 12px; margin-top: 9px; }
349
357
  .axdb-lg--col { flex-direction: column; flex-wrap: nowrap; gap: 6px; margin-top: 0; }
@@ -97,6 +97,21 @@ export declare const BUILT_IN_WIDGET_KINDS: readonly ["kpi", "line", "bar", "don
97
97
  * the classic 640×250 stands in, and the size watcher below repaints once the
98
98
  * real box exists.
99
99
  */
100
+ /**
101
+ * READABILITY TIERS. A squeezed chart — a one-row line on a fit board, a
102
+ * 60-px row after a pull — piled its five y labels onto each other and, under
103
+ * 45 px, gave its whole body to the legend (kit lab L03/L05/L07, scenario
104
+ * s11/s23). The text set never changes with the box (the save/load gate's
105
+ * contract: a board reloaded at another size paints the same text), so the
106
+ * SVG carries a tier class and the stylesheet HIDES what does not fit:
107
+ * tier 1 drops the quarter ticks, tier 2 keeps only min and max, drops the x
108
+ * labels and the bar values; a legend on a body under 64 px is hidden and the
109
+ * chart takes the room. 0 = unmeasured (jsdom, a detached host): full tier.
110
+ */
111
+ export declare function chartTier(bodyH: number, hasLegend: boolean): {
112
+ tier: 0 | 1 | 2;
113
+ legendShown: boolean;
114
+ };
100
115
  export declare function chartBox(body: {
101
116
  clientWidth: number;
102
117
  clientHeight: number;
@@ -101,6 +101,24 @@ const data = (widget) => { var _a; return ((_a = widget.data) !== null && _a !==
101
101
  * the classic 640×250 stands in, and the size watcher below repaints once the
102
102
  * real box exists.
103
103
  */
104
+ /**
105
+ * READABILITY TIERS. A squeezed chart — a one-row line on a fit board, a
106
+ * 60-px row after a pull — piled its five y labels onto each other and, under
107
+ * 45 px, gave its whole body to the legend (kit lab L03/L05/L07, scenario
108
+ * s11/s23). The text set never changes with the box (the save/load gate's
109
+ * contract: a board reloaded at another size paints the same text), so the
110
+ * SVG carries a tier class and the stylesheet HIDES what does not fit:
111
+ * tier 1 drops the quarter ticks, tier 2 keeps only min and max, drops the x
112
+ * labels and the bar values; a legend on a body under 64 px is hidden and the
113
+ * chart takes the room. 0 = unmeasured (jsdom, a detached host): full tier.
114
+ */
115
+ export function chartTier(bodyH, hasLegend) {
116
+ if (!bodyH)
117
+ return { tier: 0, legendShown: hasLegend };
118
+ const legendShown = hasLegend && bodyH >= 64;
119
+ const h = bodyH - (legendShown ? 26 : 0);
120
+ return { tier: h < 60 ? 2 : h < 120 ? 1 : 0, legendShown };
121
+ }
104
122
  export function chartBox(body, legend = false) {
105
123
  const w = body.clientWidth || 0;
106
124
  const h = (body.clientHeight || 0) - (legend ? 26 : 0);
@@ -154,7 +172,7 @@ const srTable = (caption, columns, rows) => `<table class="axdb-sr"><caption>${e
154
172
  .join('')}</tr></thead><tbody>${rows
155
173
  .map((r) => `<tr>${r.map((v) => `<td>${esc(v)}</td>`).join('')}</tr>`)
156
174
  .join('')}</tbody></table>`;
157
- const legend = (items, column = false) => `<div class="axdb-lg${column ? ' axdb-lg--col' : ''}">` +
175
+ const legend = (items, column = false, off = false) => `<div class="axdb-lg${column ? ' axdb-lg--col' : ''}${off ? ' axdb-lg--off' : ''}">` +
158
176
  items.map((i) => `<i><b style="background:${esc(i.color)}"></b>${esc(i.label)}</i>`).join('') +
159
177
  '</div>';
160
178
  // -- kpi ----------------------------------------------------------------------
@@ -218,10 +236,11 @@ function layoutLine(widget, body) {
218
236
  if (!series.length)
219
237
  return empty(body);
220
238
  const named = series.filter((s) => s.name);
221
- if (named.length)
222
- body.classList.add('axdb-has-lg');
223
- const { W, H } = chartBox(body, named.length > 0);
224
- const pad = { l: 34, r: 12, t: 12, b: 22 };
239
+ const { tier, legendShown } = chartTier(body.clientHeight || 0, named.length > 0);
240
+ body.classList.toggle('axdb-has-lg', legendShown);
241
+ const { W, H } = chartBox(body, legendShown);
242
+ // Tier 2 shows no x labels, so the band they lived in goes to the plot.
243
+ const pad = tier === 2 ? { l: 34, r: 12, t: 4, b: 6 } : { l: 34, r: 12, t: 12, b: 22 };
225
244
  const iw = W - pad.l - pad.r;
226
245
  const ih = H - pad.t - pad.b;
227
246
  const all = series.flatMap((s) => s.values);
@@ -233,9 +252,10 @@ function layoutLine(widget, body) {
233
252
  const grid = [0, 0.25, 0.5, 0.75, 1]
234
253
  .map((f) => {
235
254
  const y = pad.t + ih - f * ih;
236
- return (`<line x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
255
+ const cls = f === 0.5 ? ' axdb-yt--h' : f === 0.25 || f === 0.75 ? ' axdb-yt--q' : '';
256
+ return (`<line class="axdb-yl${cls.replace('yt', 'yl')}" x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
237
257
  `stroke="var(--axdb-grid)" stroke-width="1"></line>` +
238
- `<text x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
258
+ `<text class="axdb-yt${cls}" x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
239
259
  `fill="var(--axdb-muted)">${esc(compact(min + f * (max - min)))}</text>`);
240
260
  })
241
261
  .join('');
@@ -247,7 +267,7 @@ function layoutLine(widget, body) {
247
267
  const ticks = labels
248
268
  .slice(0, count)
249
269
  .map((l, i) => i % every === 0
250
- ? `<text x="${xAt(i).toFixed(1)}" y="${H - 6}" text-anchor="middle" font-size="9" ` +
270
+ ? `<text class="axdb-xt" x="${xAt(i).toFixed(1)}" y="${H - 6}" text-anchor="middle" font-size="9" ` +
251
271
  `fill="var(--axdb-muted)">${esc(l)}</text>`
252
272
  : '')
253
273
  .join('');
@@ -276,9 +296,9 @@ function layoutLine(widget, body) {
276
296
  })
277
297
  .join('');
278
298
  body.innerHTML =
279
- `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
299
+ `<svg class="axdb-tier-${tier}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
280
300
  `aria-label="${esc(titleOf(widget))}">${grid}${ticks}${marks}</svg>` +
281
- (named.length ? legend(series.map((s, i) => { var _a; return ({ label: String((_a = s.name) !== null && _a !== void 0 ? _a : ''), color: colorAt(i) }); })) : '') +
301
+ (named.length ? legend(series.map((s, i) => { var _a; return ({ label: String((_a = s.name) !== null && _a !== void 0 ? _a : ''), color: colorAt(i) }); }), false, !legendShown) : '') +
282
302
  srTable(titleOf(widget), ['', ...series.map((s, i) => { var _a; return String((_a = s.name) !== null && _a !== void 0 ? _a : `Series ${i + 1}`); })], Array.from({ length: count }, (_, i) => { var _a; return [(_a = labels[i]) !== null && _a !== void 0 ? _a : String(i + 1), ...series.map((s) => { var _a; return (_a = s.values[i]) !== null && _a !== void 0 ? _a : ''; })]; }));
283
303
  }
284
304
  // -- bar ----------------------------------------------------------------------
@@ -294,8 +314,9 @@ function layoutBar(widget, body) {
294
314
  const bars = (Array.isArray(d.bars) ? d.bars : []).filter((b) => !!b);
295
315
  if (!bars.length)
296
316
  return empty(body);
317
+ const { tier } = chartTier(body.clientHeight || 0, false);
297
318
  const { W, H } = chartBox(body);
298
- const pad = { l: 34, r: 12, t: 12, b: 26 };
319
+ const pad = tier === 2 ? { l: 34, r: 12, t: 4, b: 6 } : { l: 34, r: 12, t: 12, b: 26 };
299
320
  const iw = W - pad.l - pad.r;
300
321
  const ih = H - pad.t - pad.b;
301
322
  const max = niceMax(Math.max(...bars.map((b) => num(b.value))));
@@ -304,9 +325,10 @@ function layoutBar(widget, body) {
304
325
  const grid = [0, 0.5, 1]
305
326
  .map((f) => {
306
327
  const y = pad.t + ih - f * ih;
307
- return (`<line x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
328
+ const cls = f === 0.5 ? ' axdb-yt--h' : '';
329
+ return (`<line class="axdb-yl${cls.replace('yt', 'yl')}" x1="${pad.l}" y1="${y.toFixed(1)}" x2="${W - pad.r}" y2="${y.toFixed(1)}" ` +
308
330
  `stroke="var(--axdb-grid)" stroke-width="1"></line>` +
309
- `<text x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
331
+ `<text class="axdb-yt${cls}" x="${pad.l - 6}" y="${(y + 3).toFixed(1)}" text-anchor="end" font-size="9" ` +
310
332
  `fill="var(--axdb-muted)">${esc(compact(f * max))}</text>`);
311
333
  })
312
334
  .join('');
@@ -319,14 +341,14 @@ function layoutBar(widget, body) {
319
341
  const y = pad.t + ih - h;
320
342
  return (`<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${h.toFixed(1)}" ` +
321
343
  `rx="4" fill="${colorAt(i)}"></rect>` +
322
- `<text x="${(x + bw / 2).toFixed(1)}" y="${(y - 4).toFixed(1)}" text-anchor="middle" font-size="9.5" ` +
344
+ `<text class="axdb-vt" x="${(x + bw / 2).toFixed(1)}" y="${(y - 4).toFixed(1)}" text-anchor="middle" font-size="9.5" ` +
323
345
  `font-weight="600" fill="var(--axdb-ink)">${esc(compact(num(b.value)))}</text>` +
324
- `<text x="${(x + bw / 2).toFixed(1)}" y="${H - 8}" text-anchor="middle" font-size="9" ` +
346
+ `<text class="axdb-xt" x="${(x + bw / 2).toFixed(1)}" y="${H - 8}" text-anchor="middle" font-size="9" ` +
325
347
  `fill="var(--axdb-muted)">${esc((_a = b.label) !== null && _a !== void 0 ? _a : '')}</text>`);
326
348
  })
327
349
  .join('');
328
350
  body.innerHTML =
329
- `<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
351
+ `<svg class="axdb-tier-${tier}" viewBox="0 0 ${W} ${H}" preserveAspectRatio="xMidYMid meet" role="img" ` +
330
352
  `aria-label="${esc(titleOf(widget))}">${grid}${marks}</svg>` +
331
353
  srTable(titleOf(widget), ['Category', 'Value'], bars.map((b) => { var _a; return [(_a = b.label) !== null && _a !== void 0 ? _a : '', num(b.value)]; }));
332
354
  }