waveform-analysis 0.1.41 → 0.1.42

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/dist/index.js CHANGED
@@ -1032,6 +1032,7 @@ function tn(e) {
1032
1032
  rowCount: en(e?.rowCount, 2),
1033
1033
  columnCount: en(e?.columnCount, 1),
1034
1034
  showPagination: e?.showPagination ?? !0,
1035
+ fillIncompleteLastRow: e?.fillIncompleteLastRow ?? !1,
1035
1036
  trackLines: t
1036
1037
  };
1037
1038
  }
@@ -1049,32 +1050,33 @@ function on(e) {
1049
1050
  return e === "compact" ? 0 : e === "separated" ? 16 : 14;
1050
1051
  }
1051
1052
  function sn(e, t, n, r, i = [], a, o = !0) {
1052
- let s = on(r), c = Number.isFinite(a) ? Math.max(0, a) : s, l = Math.max(0, n.columnCount - 1) * c, u = /* @__PURE__ */ new Set();
1053
+ let s = nn(n), c = i.filter(Boolean).length, l = n.fillIncompleteLastRow && c > 0 && c < s, u = l ? Math.ceil(c / n.columnCount) : n.rowCount, d = on(r), f = Number.isFinite(a) ? Math.max(0, a) : d, p = /* @__PURE__ */ new Set();
1053
1054
  if (o) {
1054
- if (r === "independent") for (let e = 0; e < n.rowCount; e += 1) u.add(e);
1055
- else if (r === "compact") u.add(n.rowCount - 1);
1055
+ if (r === "independent") for (let e = 0; e < u; e += 1) p.add(e);
1056
+ else if (r === "compact") p.add(u - 1);
1056
1057
  else for (let e = 0; e < n.columnCount; e += 1) for (let t = nn(n) - 1; t >= 0; --t) if (t % n.columnCount === e && i[t]) {
1057
- u.add(Math.floor(t / n.columnCount));
1058
+ p.add(Math.floor(t / n.columnCount));
1058
1059
  break;
1059
1060
  }
1060
1061
  }
1061
- let d = Math.max(0, n.rowCount - 1) * s, f = u.size * 30, p = Math.max(1, (e - l) / n.columnCount), m = Math.max(1, (t - d - f) / n.rowCount);
1062
- return Array.from({ length: nn(n) }, (e, t) => {
1063
- let r = Math.floor(t / n.columnCount), i = t % n.columnCount, a = u.has(r) ? 30 : 0, o = m + a, l = Array.from({ length: r }, (e, t) => {
1064
- let n = u.has(t) ? 30 : 0;
1065
- return m + n + s;
1062
+ let m = Math.max(0, u - 1) * d, h = p.size * 30, g = Math.max(1, (t - m - h) / u);
1063
+ return Array.from({ length: l ? c : s }, (t, r) => {
1064
+ let i = Math.floor(r / n.columnCount), a = r % n.columnCount, o = l && i === u - 1 ? c - i * n.columnCount : n.columnCount, s = Math.max(0, o - 1) * f, m = Math.max(1, (e - s) / o), h = p.has(i) ? 30 : 0, _ = g + h, v = Array.from({ length: i }, (e, t) => {
1065
+ let n = p.has(t) ? 30 : 0;
1066
+ return g + n + d;
1066
1067
  }).reduce((e, t) => e + t, 0);
1067
1068
  return {
1068
- slotIndex: t,
1069
- row: r,
1070
- column: i,
1071
- left: i * (p + c),
1072
- top: l,
1073
- width: p,
1074
- height: m,
1075
- plotHeight: m,
1076
- cellHeight: o,
1077
- xAxisBand: a
1069
+ slotIndex: r,
1070
+ row: i,
1071
+ column: a,
1072
+ left: a * (m + f),
1073
+ top: v,
1074
+ width: m,
1075
+ height: g,
1076
+ plotHeight: g,
1077
+ cellHeight: _,
1078
+ xAxisBand: h,
1079
+ isLastRow: i === u - 1
1078
1080
  };
1079
1081
  });
1080
1082
  }
@@ -1210,7 +1212,7 @@ function pn(e) {
1210
1212
  horizontal: !0,
1211
1213
  vertical: !0
1212
1214
  },
1213
- showXAxis: (i || s) && (e.displayMode === "independent" || (e.displayMode === "compact" ? t.row === e.grid.rowCount - 1 : n.has(t.slotIndex)))
1215
+ showXAxis: (i || s) && (e.displayMode === "independent" || (e.displayMode === "compact" ? t.isLastRow ?? t.row === e.grid.rowCount - 1 : n.has(t.slotIndex)))
1214
1216
  };
1215
1217
  });
1216
1218
  }
@@ -2152,6 +2154,7 @@ function Qn(e) {
2152
2154
  v,
2153
2155
  () => x.value.rowCount,
2154
2156
  () => x.value.columnCount,
2157
+ () => x.value.fillIncompleteLastRow,
2155
2158
  j
2156
2159
  ], async () => {
2157
2160
  await d(), H();
@@ -2161,7 +2164,8 @@ function Qn(e) {
2161
2164
  }), E([
2162
2165
  y,
2163
2166
  () => t.grid?.rowCount,
2164
- () => t.grid?.columnCount
2167
+ () => t.grid?.columnCount,
2168
+ () => t.grid?.fillIncompleteLastRow
2165
2169
  ], () => {
2166
2170
  let e = v.value;
2167
2171
  v.value = v.value > y.value ? y.value : v.value === 1 ? v.value : 1, e !== v.value && n("page-change", v.value, y.value), B(), d(H);
@@ -2768,6 +2772,7 @@ var lr = ["id"], ur = { class: "waveform-annotation-editor__content" }, dr = {
2768
2772
  "cancel-text": "取消",
2769
2773
  "ok-text": "保存标注",
2770
2774
  "ok-button-props": { disabled: !M.value },
2775
+ "root-class-name": "waveform-annotation-editor-root",
2771
2776
  "wrap-class-name": "waveform-annotation-editor",
2772
2777
  onCancel: o[4] ||= (e) => u("cancel"),
2773
2778
  onOk: R
@@ -2874,7 +2879,7 @@ var lr = ["id"], ur = { class: "waveform-annotation-editor__content" }, dr = {
2874
2879
  _: 1
2875
2880
  }, 8, ["ok-button-props"]));
2876
2881
  }
2877
- }), [["__scopeId", "data-v-8ce79dc8"]]), Or = { class: "waveform-tooltip__series-content" }, kr = { class: "waveform-tooltip__series-label" }, Ar = { class: "waveform-tooltip__value" }, jr = 12, Mr = 8, Nr = 238, Pr = 560, Fr = 20, Ir = 20, Lr = /*#__PURE__*/ Fe(/* @__PURE__ */ c({
2882
+ }), [["__scopeId", "data-v-42466cf9"]]), Or = { class: "waveform-tooltip__series-content" }, kr = { class: "waveform-tooltip__series-label" }, Ar = { class: "waveform-tooltip__value" }, jr = 12, Mr = 8, Nr = 238, Pr = 560, Fr = 20, Ir = 20, Lr = /*#__PURE__*/ Fe(/* @__PURE__ */ c({
2878
2883
  __name: "WaveformTooltip",
2879
2884
  props: {
2880
2885
  visible: { type: Boolean },
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .waveform-annotation-layer[data-v-7254383e]{pointer-events:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]{pointer-events:auto;cursor:grab;touch-action:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]:active{cursor:grabbing}.waveform-annotation__arrow[data-v-7254383e]{stroke-width:2px;stroke-linecap:round;pointer-events:none}.waveform-annotation__box[data-v-7254383e]{stroke-width:1px;rx:3}.waveform-annotation-layer--interactive .waveform-annotation__box[data-v-7254383e]{pointer-events:auto}.waveform-annotation__text[data-v-7254383e]{pointer-events:none;-webkit-user-select:none;user-select:none}.waveform-annotation-context-menu[data-v-c70c98a1]{z-index:30;background:#fff;border:1px solid #dfe5ef;border-radius:4px;min-width:112px;padding:4px;display:grid;position:absolute;box-shadow:0 4px 14px #10182829}.waveform-annotation-context-menu button[data-v-c70c98a1]{color:#344054;text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:3px;padding:7px 10px;font-size:12px}.waveform-annotation-context-menu button[data-v-c70c98a1]:hover{background:#f2f4f7}.waveform-annotation-editor__content[data-v-8ce79dc8]{color:#344054;gap:20px;font-size:13px;display:grid}.waveform-annotation-editor__coordinates[data-v-8ce79dc8]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-8ce79dc8],.waveform-annotation-editor__series[data-v-8ce79dc8]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-8ce79dc8]{color:#667085}.waveform-annotation-editor__coordinate-input[data-v-8ce79dc8]{color:#667085;background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinate-input[data-v-8ce79dc8] .ant-input-number{color:#344054;width:auto;min-width:96px;font-family:SFMono-Regular,Consolas,monospace}.waveform-annotation-editor__coordinate-value[data-v-8ce79dc8]{display:none}.waveform-annotation-editor__coordinate-error[data-v-8ce79dc8]{color:#d92d20;grid-column:1/-1;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinate-hint[data-v-8ce79dc8]{color:#98a2b3;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinates b[data-v-8ce79dc8]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-8ce79dc8]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-8ce79dc8]{gap:10px}.waveform-annotation-editor__series i[data-v-8ce79dc8]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-8ce79dc8]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-8ce79dc8]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-8ce79dc8]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-8ce79dc8]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-8ce79dc8]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-8ce79dc8],.waveform-annotation-editor__color-field[data-v-8ce79dc8]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-8ce79dc8]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-8ce79dc8],.waveform-annotation-editor__colors legend[data-v-8ce79dc8]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-8ce79dc8]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-8ce79dc8]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-8ce79dc8]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-8ce79dc8]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-8ce79dc8]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-8ce79dc8]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-8ce79dc8] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-8ce79dc8] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}@media (width<=420px){.waveform-annotation-editor__content[data-v-8ce79dc8]{gap:16px}.waveform-annotation-editor__coordinates[data-v-8ce79dc8]{grid-template-columns:1fr}}.waveform-tooltip[data-v-1f55d6c8]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-1f55d6c8]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-1f55d6c8]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-1f55d6c8]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-1f55d6c8]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-1f55d6c8]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-1f55d6c8]{font-weight:600}.waveform-tooltip__value[data-v-1f55d6c8]{color:#555}.waveform-tooltip__series small[data-v-1f55d6c8]{color:#667085}.waveform-track__line[data-v-42a3fd47]{fill:none;stroke-width:1.5px;stroke-linejoin:round;stroke-linecap:round}.waveform-track__error-bar[data-v-42a3fd47]{fill:none}.waveform-track__point[data-v-42a3fd47],.waveform-track__error-bar[data-v-42a3fd47]{pointer-events:none}.waveform-track{isolation:isolate;pointer-events:none}.waveform-track__y-axis-label-bg{fill:#fff;opacity:.9;pointer-events:none}.waveform-track__y-axis-label{pointer-events:none;font-size:12px;font-weight:500}.waveform-track__grid{pointer-events:none}.waveform-track__grid--major line{stroke:#dfe5ef;stroke-width:1px}.waveform-track__grid--minor line{stroke:#f2f5fa;stroke-width:1px}.waveform-track__plot-frame,.waveform-track__plot-background{pointer-events:none}.waveform-track__watermark{fill:#1677ff1a;pointer-events:none;-webkit-user-select:none;user-select:none;font-family:Consolas,Monaco,Courier New,monospace}.waveform-track__overlay{fill:#0000;cursor:crosshair;pointer-events:all;touch-action:none}.waveform-track__no-visible-series{fill:#8c8c8c;pointer-events:none;font:13px sans-serif}.waveform-track__overlay.is-zoomable{cursor:grab}.waveform-track__overlay.is-zoomable:active{cursor:grabbing}.waveform-track__overlay.is-annotating{cursor:crosshair}.waveform-track__overlay.is-presentation{cursor:default;pointer-events:none}.waveform-track__zero-line{fill:none;pointer-events:none}.waveform-track__axis-endpoint{fill:#667085;pointer-events:none;font-size:11px}.waveform-track__axis-exponent{fill:#666;font-family:sans-serif;font-size:10px}.waveform-track__axis path,.waveform-track__axis line{stroke:#1f2937}.waveform-track__axis text{fill:#667085;font-size:11px}.waveform-legend[data-v-2ae12e71]{pointer-events:none;overflow:hidden}.waveform-legend__viewport[data-v-2ae12e71]{box-sizing:border-box;width:100%;height:100%;padding:8px;display:flex;overflow:hidden}.waveform-legend__viewport--top-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-start}.waveform-legend__viewport--top[data-v-2ae12e71]{justify-content:center;align-items:flex-start}.waveform-legend__viewport--top-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-start}.waveform-legend__viewport--right[data-v-2ae12e71]{justify-content:flex-end;align-items:center}.waveform-legend__viewport--bottom-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-end}.waveform-legend__viewport--bottom[data-v-2ae12e71]{justify-content:center;align-items:flex-end}.waveform-legend__viewport--bottom-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-end}.waveform-legend__viewport--left[data-v-2ae12e71]{justify-content:flex-start;align-items:center}.waveform-legend__panel[data-v-2ae12e71]{color:#344054;border:1px solid #d0d5dde6;border-radius:4px;flex:0 auto;gap:5px 12px;max-width:100%;max-height:100%;padding:5px 7px;font:12px/1.35 sans-serif;display:flex;overflow:hidden}.waveform-legend__panel--interactive[data-v-2ae12e71]{pointer-events:auto}.waveform-legend__panel--horizontal[data-v-2ae12e71]{flex-flow:wrap;align-items:center}.waveform-legend__panel--vertical[data-v-2ae12e71]{flex-flow:column;align-items:stretch}.waveform-legend__item[data-v-2ae12e71]{min-width:0;max-width:160px;color:inherit;font:inherit;text-align:left;white-space:nowrap;appearance:none;background:0 0;border:0;align-items:center;gap:6px;padding:0;display:flex}.waveform-legend__item[data-v-2ae12e71]:disabled{opacity:1}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]{cursor:pointer}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]:focus-visible{outline-offset:2px;outline:2px solid #1677ff}.waveform-legend__item.is-hidden[data-v-2ae12e71]{opacity:.45}.waveform-legend__item.is-hidden .waveform-legend__label[data-v-2ae12e71]{text-decoration:line-through}.waveform-legend__swatch[data-v-2ae12e71]{stroke-linecap:round;stroke-linejoin:round;flex:0 0 26px;width:26px;height:16px;overflow:visible}.waveform-legend__label[data-v-2ae12e71]{text-overflow:ellipsis;min-width:0;overflow:hidden}.waveform-track__crosshair[data-v-a6f82099]{pointer-events:none}.waveform-track__crosshair line[data-v-a6f82099]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1px solid #00000014;border-radius:6px;min-width:0;min-height:180px;position:relative;overflow:hidden}.waveform-chart--clean{border-color:#0000}.waveform-chart__pagination{z-index:2;position:absolute;bottom:6px;right:10px}.waveform-chart__title-area{background:#fff;align-items:center;width:100%;min-width:0;padding:0 24px;display:flex;position:relative;overflow:hidden}.waveform-chart__title-measure{visibility:hidden;pointer-events:none;display:block;position:absolute}.waveform-chart__title-visual{flex:none;min-width:0;position:relative;overflow:visible}.waveform-chart__title-text{transform-origin:50%;line-height:1.2;display:block;position:absolute;top:50%;left:50%;overflow:visible}.waveform-chart__pagination .ant-pagination-item,.waveform-chart__pagination .ant-pagination-prev .ant-pagination-item-link,.waveform-chart__pagination .ant-pagination-next .ant-pagination-item-link{background:#fff;border-color:#d9d9d9}.waveform-chart__pagination .ant-pagination-item-active{border-color:#1677ff}.waveform-chart__grid-slot-placeholder{fill:#fafbfc;stroke:#e4e7ec;stroke-dasharray:4 4;pointer-events:none}.waveform-chart__svg{max-width:100%;display:block}.waveform-chart__overlay{fill:#0000;cursor:crosshair;touch-action:none}.waveform-chart__overlay.is-zoomable{cursor:crosshair}.waveform-chart--panning .waveform-chart__overlay.is-zoomable{cursor:grabbing}.waveform-chart__zoom-selection{fill:#1677ff24;stroke:#1677ff;stroke-width:1px;pointer-events:none}.waveform-chart__overlay.is-annotating{cursor:crosshair}.waveform-chart--presentation .waveform-chart__overlay{cursor:default;pointer-events:none}.waveform-chart__label,.waveform-chart__empty{fill:#595959;font-size:12px}
1
+ .waveform-annotation-layer[data-v-7254383e]{pointer-events:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]{pointer-events:auto;cursor:grab;touch-action:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]:active{cursor:grabbing}.waveform-annotation__arrow[data-v-7254383e]{stroke-width:2px;stroke-linecap:round;pointer-events:none}.waveform-annotation__box[data-v-7254383e]{stroke-width:1px;rx:3}.waveform-annotation-layer--interactive .waveform-annotation__box[data-v-7254383e]{pointer-events:auto}.waveform-annotation__text[data-v-7254383e]{pointer-events:none;-webkit-user-select:none;user-select:none}.waveform-annotation-context-menu[data-v-c70c98a1]{z-index:30;background:#fff;border:1px solid #dfe5ef;border-radius:4px;min-width:112px;padding:4px;display:grid;position:absolute;box-shadow:0 4px 14px #10182829}.waveform-annotation-context-menu button[data-v-c70c98a1]{color:#344054;text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:3px;padding:7px 10px;font-size:12px}.waveform-annotation-context-menu button[data-v-c70c98a1]:hover{background:#f2f4f7}.waveform-annotation-editor-root,.waveform-annotation-editor-root *{box-sizing:border-box}.waveform-annotation-editor-root{color:#344054;text-align:left;text-transform:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif;line-height:1.5715}.waveform-annotation-editor-root button,.waveform-annotation-editor-root input,.waveform-annotation-editor-root select,.waveform-annotation-editor-root textarea{letter-spacing:normal;text-transform:none;font-family:inherit}.waveform-annotation-editor-root button{appearance:auto}.waveform-annotation-editor__content[data-v-42466cf9]{color:#344054;gap:20px;font-size:13px;display:grid}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-42466cf9],.waveform-annotation-editor__series[data-v-42466cf9]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-42466cf9]{color:#667085}.waveform-annotation-editor__coordinate-input[data-v-42466cf9]{color:#667085;background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinate-input[data-v-42466cf9] .ant-input-number{color:#344054;width:auto;min-width:96px;font-family:SFMono-Regular,Consolas,monospace}.waveform-annotation-editor__coordinate-value[data-v-42466cf9]{display:none}.waveform-annotation-editor__coordinate-error[data-v-42466cf9]{color:#d92d20;grid-column:1/-1;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinate-hint[data-v-42466cf9]{color:#98a2b3;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinates b[data-v-42466cf9]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-42466cf9]{gap:10px}.waveform-annotation-editor__series i[data-v-42466cf9]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-42466cf9]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-42466cf9]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-42466cf9]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-42466cf9]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-42466cf9],.waveform-annotation-editor__color-field[data-v-42466cf9]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-42466cf9]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-42466cf9],.waveform-annotation-editor__colors legend[data-v-42466cf9]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-42466cf9]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-42466cf9]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-42466cf9]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-42466cf9]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-42466cf9]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-42466cf9]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}@media (width<=420px){.waveform-annotation-editor__content[data-v-42466cf9]{gap:16px}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:1fr}}.waveform-tooltip[data-v-1f55d6c8]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-1f55d6c8]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-1f55d6c8]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-1f55d6c8]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-1f55d6c8]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-1f55d6c8]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-1f55d6c8]{font-weight:600}.waveform-tooltip__value[data-v-1f55d6c8]{color:#555}.waveform-tooltip__series small[data-v-1f55d6c8]{color:#667085}.waveform-track__line[data-v-42a3fd47]{fill:none;stroke-width:1.5px;stroke-linejoin:round;stroke-linecap:round}.waveform-track__error-bar[data-v-42a3fd47]{fill:none}.waveform-track__point[data-v-42a3fd47],.waveform-track__error-bar[data-v-42a3fd47]{pointer-events:none}.waveform-track{isolation:isolate;pointer-events:none}.waveform-track__y-axis-label-bg{fill:#fff;opacity:.9;pointer-events:none}.waveform-track__y-axis-label{pointer-events:none;font-size:12px;font-weight:500}.waveform-track__grid{pointer-events:none}.waveform-track__grid--major line{stroke:#dfe5ef;stroke-width:1px}.waveform-track__grid--minor line{stroke:#f2f5fa;stroke-width:1px}.waveform-track__plot-frame,.waveform-track__plot-background{pointer-events:none}.waveform-track__watermark{fill:#1677ff1a;pointer-events:none;-webkit-user-select:none;user-select:none;font-family:Consolas,Monaco,Courier New,monospace}.waveform-track__overlay{fill:#0000;cursor:crosshair;pointer-events:all;touch-action:none}.waveform-track__no-visible-series{fill:#8c8c8c;pointer-events:none;font:13px sans-serif}.waveform-track__overlay.is-zoomable{cursor:grab}.waveform-track__overlay.is-zoomable:active{cursor:grabbing}.waveform-track__overlay.is-annotating{cursor:crosshair}.waveform-track__overlay.is-presentation{cursor:default;pointer-events:none}.waveform-track__zero-line{fill:none;pointer-events:none}.waveform-track__axis-endpoint{fill:#667085;pointer-events:none;font-size:11px}.waveform-track__axis-exponent{fill:#666;font-family:sans-serif;font-size:10px}.waveform-track__axis path,.waveform-track__axis line{stroke:#1f2937}.waveform-track__axis text{fill:#667085;font-size:11px}.waveform-legend[data-v-2ae12e71]{pointer-events:none;overflow:hidden}.waveform-legend__viewport[data-v-2ae12e71]{box-sizing:border-box;width:100%;height:100%;padding:8px;display:flex;overflow:hidden}.waveform-legend__viewport--top-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-start}.waveform-legend__viewport--top[data-v-2ae12e71]{justify-content:center;align-items:flex-start}.waveform-legend__viewport--top-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-start}.waveform-legend__viewport--right[data-v-2ae12e71]{justify-content:flex-end;align-items:center}.waveform-legend__viewport--bottom-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-end}.waveform-legend__viewport--bottom[data-v-2ae12e71]{justify-content:center;align-items:flex-end}.waveform-legend__viewport--bottom-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-end}.waveform-legend__viewport--left[data-v-2ae12e71]{justify-content:flex-start;align-items:center}.waveform-legend__panel[data-v-2ae12e71]{color:#344054;border:1px solid #d0d5dde6;border-radius:4px;flex:0 auto;gap:5px 12px;max-width:100%;max-height:100%;padding:5px 7px;font:12px/1.35 sans-serif;display:flex;overflow:hidden}.waveform-legend__panel--interactive[data-v-2ae12e71]{pointer-events:auto}.waveform-legend__panel--horizontal[data-v-2ae12e71]{flex-flow:wrap;align-items:center}.waveform-legend__panel--vertical[data-v-2ae12e71]{flex-flow:column;align-items:stretch}.waveform-legend__item[data-v-2ae12e71]{min-width:0;max-width:160px;color:inherit;font:inherit;text-align:left;white-space:nowrap;appearance:none;background:0 0;border:0;align-items:center;gap:6px;padding:0;display:flex}.waveform-legend__item[data-v-2ae12e71]:disabled{opacity:1}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]{cursor:pointer}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]:focus-visible{outline-offset:2px;outline:2px solid #1677ff}.waveform-legend__item.is-hidden[data-v-2ae12e71]{opacity:.45}.waveform-legend__item.is-hidden .waveform-legend__label[data-v-2ae12e71]{text-decoration:line-through}.waveform-legend__swatch[data-v-2ae12e71]{stroke-linecap:round;stroke-linejoin:round;flex:0 0 26px;width:26px;height:16px;overflow:visible}.waveform-legend__label[data-v-2ae12e71]{text-overflow:ellipsis;min-width:0;overflow:hidden}.waveform-track__crosshair[data-v-a6f82099]{pointer-events:none}.waveform-track__crosshair line[data-v-a6f82099]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1px solid #00000014;border-radius:6px;min-width:0;min-height:180px;position:relative;overflow:hidden}.waveform-chart--clean{border-color:#0000}.waveform-chart__pagination{z-index:2;position:absolute;bottom:6px;right:10px}.waveform-chart__title-area{background:#fff;align-items:center;width:100%;min-width:0;padding:0 24px;display:flex;position:relative;overflow:hidden}.waveform-chart__title-measure{visibility:hidden;pointer-events:none;display:block;position:absolute}.waveform-chart__title-visual{flex:none;min-width:0;position:relative;overflow:visible}.waveform-chart__title-text{transform-origin:50%;line-height:1.2;display:block;position:absolute;top:50%;left:50%;overflow:visible}.waveform-chart__pagination .ant-pagination-item,.waveform-chart__pagination .ant-pagination-prev .ant-pagination-item-link,.waveform-chart__pagination .ant-pagination-next .ant-pagination-item-link{background:#fff;border-color:#d9d9d9}.waveform-chart__pagination .ant-pagination-item-active{border-color:#1677ff}.waveform-chart__grid-slot-placeholder{fill:#fafbfc;stroke:#e4e7ec;stroke-dasharray:4 4;pointer-events:none}.waveform-chart__svg{max-width:100%;display:block}.waveform-chart__overlay{fill:#0000;cursor:crosshair;touch-action:none}.waveform-chart__overlay.is-zoomable{cursor:crosshair}.waveform-chart--panning .waveform-chart__overlay.is-zoomable{cursor:grabbing}.waveform-chart__zoom-selection{fill:#1677ff24;stroke:#1677ff;stroke-width:1px;pointer-events:none}.waveform-chart__overlay.is-annotating{cursor:crosshair}.waveform-chart--presentation .waveform-chart__overlay{cursor:default;pointer-events:none}.waveform-chart__label,.waveform-chart__empty{fill:#595959;font-size:12px}
2
2
  /*$vite$:1*/
@@ -6,6 +6,7 @@ export interface WaveformGridOptions {
6
6
  rowCount?: number;
7
7
  columnCount?: number;
8
8
  showPagination?: boolean;
9
+ fillIncompleteLastRow?: boolean;
9
10
  trackLines?: WaveformGridTrackLines;
10
11
  }
11
12
  export interface WaveformGridLineOptions {
@@ -27,6 +28,7 @@ export interface NormalizedWaveformGridOptions {
27
28
  rowCount: number;
28
29
  columnCount: number;
29
30
  showPagination: boolean;
31
+ fillIncompleteLastRow: boolean;
30
32
  trackLines: Record<string, NormalizedWaveformGridLineOptions>;
31
33
  }
32
34
  export interface GridCellGeometry {
@@ -41,6 +43,7 @@ export interface GridCellGeometry {
41
43
  plotHeight: number;
42
44
  cellHeight: number;
43
45
  xAxisBand: number;
46
+ isLastRow?: boolean;
44
47
  }
45
48
  export declare function normalizeGridOptions(options?: WaveformGridOptions): NormalizedWaveformGridOptions;
46
49
  export declare function getPageSize(options: NormalizedWaveformGridOptions): number;
@@ -272,6 +272,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
272
272
  plotHeight: number;
273
273
  cellHeight: number;
274
274
  xAxisBand: number;
275
+ isLastRow?: boolean;
275
276
  }[];
276
277
  trackLayouts: import("./types").TrackLayout[];
277
278
  annotationLayoutsForTrack: (track: import("./types").TrackLayout) => import("../annotation").AnnotationTrackLayout[];
@@ -2,6 +2,7 @@ import { type ZoomTransform } from 'd3';
2
2
  import { type ComputedRef, type Ref, type ShallowRef } from 'vue';
3
3
  import type { AnnotationSeriesCandidate } from '../annotation';
4
4
  import type { useWaveformAnnotationInteraction } from '../annotation';
5
+ import type { NormalizedWaveformGridOptions } from './grid';
5
6
  import type { DisplaySeries, DisplayTrack, TrackLayout } from './types';
6
7
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes';
7
8
  interface LifecycleContext {
@@ -22,10 +23,7 @@ interface LifecycleContext {
22
23
  currentPage: Ref<number>;
23
24
  pageCount: ComputedRef<number>;
24
25
  pagedTracks: ComputedRef<Array<DisplayTrack | undefined>>;
25
- gridOptions: ComputedRef<{
26
- rowCount: number;
27
- columnCount: number;
28
- }>;
26
+ gridOptions: ComputedRef<NormalizedWaveformGridOptions>;
29
27
  chartSeries: ComputedRef<DisplaySeries[]>;
30
28
  chartTracks: ComputedRef<DisplayTrack[]>;
31
29
  trackLayouts: ComputedRef<TrackLayout[]>;
@@ -48,6 +48,7 @@ export declare function useWaveformLayout(context: LayoutContext): {
48
48
  plotHeight: number;
49
49
  cellHeight: number;
50
50
  xAxisBand: number;
51
+ isLastRow?: boolean;
51
52
  }[]>;
52
53
  trackLayouts: ComputedRef<TrackLayout[]>;
53
54
  annotationLayoutsForTrack: (track: TrackLayout) => AnnotationTrackLayout[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -1,3 +1,33 @@
1
+ /* The modal is teleported to body. Keep its entire subtree namespaced so host
2
+ application resets (for example `button { ... }`) cannot change the editor. */
3
+ :global(.waveform-annotation-editor-root),
4
+ :global(.waveform-annotation-editor-root *) {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ :global(.waveform-annotation-editor-root) {
9
+ color: #344054;
10
+ font-family:
11
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
12
+ 'Noto Sans', sans-serif;
13
+ line-height: 1.5715;
14
+ text-align: left;
15
+ text-transform: none;
16
+ }
17
+
18
+ :global(.waveform-annotation-editor-root button),
19
+ :global(.waveform-annotation-editor-root input),
20
+ :global(.waveform-annotation-editor-root select),
21
+ :global(.waveform-annotation-editor-root textarea) {
22
+ font-family: inherit;
23
+ letter-spacing: normal;
24
+ text-transform: none;
25
+ }
26
+
27
+ :global(.waveform-annotation-editor-root button) {
28
+ appearance: auto;
29
+ }
30
+
1
31
  .waveform-annotation-editor__content {
2
32
  display: grid;
3
33
  gap: 20px;
@@ -126,6 +126,7 @@ function handleSeriesChange(event: Event) {
126
126
  cancel-text="取消"
127
127
  ok-text="保存标注"
128
128
  :ok-button-props="{ disabled: !canConfirm }"
129
+ root-class-name="waveform-annotation-editor-root"
129
130
  wrap-class-name="waveform-annotation-editor"
130
131
  @cancel="emit('cancel')"
131
132
  @ok="confirm"
@@ -18,13 +18,14 @@ const modalStub = defineComponent({
18
18
  'cancelText',
19
19
  'okText',
20
20
  'okButtonProps',
21
+ 'rootClassName',
21
22
  ],
22
23
  emits: ['cancel', 'ok'],
23
24
  setup(props, { emit, slots }) {
24
25
  return () => {
25
26
  const title = slots.title?.() ?? []
26
27
  const titleId = (title[0]?.props as { id?: string } | undefined)?.id
27
- return h('div', { class: 'ant-modal-root' }, [
28
+ return h('div', { class: ['ant-modal-root', props.rootClassName] }, [
28
29
  h(
29
30
  'div',
30
31
  {
@@ -96,6 +97,18 @@ describe('waveform annotation controls', () => {
96
97
  expect(second.get('[role="dialog"]').attributes('aria-labelledby')).toBe(secondTitleId)
97
98
  })
98
99
 
100
+ it('uses a dedicated root class to isolate teleported modal styles', () => {
101
+ const wrapper = mountEditor({
102
+ props: {
103
+ annotation: { id: 'isolated', seriesId: 'a', x: 1, y: 2, text: '说明' },
104
+ mode: 'add',
105
+ },
106
+ })
107
+
108
+ expect(wrapper.get('.ant-modal-root').classes()).toContain('waveform-annotation-editor-root')
109
+ expect(wrapper.get('.ant-modal-wrap').classes()).toContain('waveform-annotation-editor')
110
+ })
111
+
99
112
  it.each(['add', 'edit'] as const)('shows the X-axis snapping hint in %s mode', (mode) => {
100
113
  const wrapper = mountEditor({
101
114
  props: {
@@ -53,7 +53,13 @@ describe('fixed Y-domain layout', () => {
53
53
  series: sourceTrack,
54
54
  },
55
55
  ],
56
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
56
+ grid: {
57
+ rowCount: 1,
58
+ columnCount: 1,
59
+ showPagination: false,
60
+ fillIncompleteLastRow: false,
61
+ trackLines: {},
62
+ },
57
63
  displayMode: 'independent',
58
64
  overlayMode: 'single-axis',
59
65
  independentTransforms: [zoomIdentity],
@@ -89,7 +95,13 @@ describe('fixed Y-domain layout', () => {
89
95
  series: track([series('a', 0, 100)]),
90
96
  },
91
97
  ],
92
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
98
+ grid: {
99
+ rowCount: 1,
100
+ columnCount: 1,
101
+ showPagination: false,
102
+ fillIncompleteLastRow: false,
103
+ trackLines: {},
104
+ },
93
105
  displayMode: 'independent',
94
106
  overlayMode: 'single-axis',
95
107
  independentTransforms: [zoomIdentity],
@@ -124,7 +136,13 @@ describe('fixed Y-domain layout', () => {
124
136
  series: track([series('a', 3, 97)]),
125
137
  },
126
138
  ],
127
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
139
+ grid: {
140
+ rowCount: 1,
141
+ columnCount: 1,
142
+ showPagination: false,
143
+ fillIncompleteLastRow: false,
144
+ trackLines: {},
145
+ },
128
146
  displayMode: 'independent',
129
147
  overlayMode: 'single-axis',
130
148
  independentTransforms: [zoomIdentity],
@@ -15,12 +15,14 @@ describe('waveform grid helpers', () => {
15
15
  rowCount: 2,
16
16
  columnCount: 1,
17
17
  showPagination: true,
18
+ fillIncompleteLastRow: false,
18
19
  trackLines: {},
19
20
  })
20
21
  expect(normalizeGridOptions({ rowCount: 0, columnCount: 99 })).toEqual({
21
22
  rowCount: 1,
22
23
  columnCount: 10,
23
24
  showPagination: true,
25
+ fillIncompleteLastRow: false,
24
26
  trackLines: {},
25
27
  })
26
28
  })
@@ -131,4 +133,50 @@ describe('waveform grid helpers', () => {
131
133
  expect(cells[1].left).toBe(232)
132
134
  expect(cells[2].top).toBe(cells[0].plotHeight + X_AXIS_BAND + 14)
133
135
  })
136
+
137
+ it('fills incomplete final rows without changing page capacity', () => {
138
+ const oneColumn = resolveGridCellGeometry(
139
+ 400,
140
+ 400,
141
+ normalizeGridOptions({ rowCount: 4, columnCount: 1, fillIncompleteLastRow: true }),
142
+ 'separated',
143
+ [true, true, true],
144
+ )
145
+ expect(oneColumn).toHaveLength(3)
146
+ expect(oneColumn.map((cell) => cell.width)).toEqual([400, 400, 400])
147
+ expect(oneColumn.at(-1)?.isLastRow).toBe(true)
148
+
149
+ const twoColumns = resolveGridCellGeometry(
150
+ 400,
151
+ 300,
152
+ normalizeGridOptions({ rowCount: 2, columnCount: 2, fillIncompleteLastRow: true }),
153
+ 'separated',
154
+ [true, true, true],
155
+ )
156
+ expect(twoColumns).toHaveLength(3)
157
+ expect(twoColumns[2]).toMatchObject({ row: 1, column: 0, left: 0, width: 400 })
158
+
159
+ const threeColumns = resolveGridCellGeometry(
160
+ 400,
161
+ 300,
162
+ normalizeGridOptions({ rowCount: 2, columnCount: 3, fillIncompleteLastRow: true }),
163
+ 'separated',
164
+ [true, true, true, true, true],
165
+ )
166
+ expect(threeColumns).toHaveLength(5)
167
+ expect(threeColumns.slice(3).map((cell) => cell.width)).toEqual([192, 192])
168
+ expect(threeColumns.slice(3).map((cell) => cell.left)).toEqual([0, 208])
169
+ })
170
+
171
+ it('keeps full pages and the disabled option byte-for-byte compatible', () => {
172
+ const base = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
173
+ const filled = normalizeGridOptions({ rowCount: 2, columnCount: 2, fillIncompleteLastRow: true })
174
+ const fullSlots = [true, true, true, true]
175
+ expect(resolveGridCellGeometry(400, 300, filled, 'independent', fullSlots)).toEqual(
176
+ resolveGridCellGeometry(400, 300, base, 'independent', fullSlots),
177
+ )
178
+ expect(resolveGridCellGeometry(400, 300, base, 'independent', [true, true, true])).toHaveLength(
179
+ 4,
180
+ )
181
+ })
134
182
  })
@@ -8,6 +8,7 @@ export interface WaveformGridOptions {
8
8
  rowCount?: number
9
9
  columnCount?: number
10
10
  showPagination?: boolean
11
+ fillIncompleteLastRow?: boolean
11
12
  trackLines?: WaveformGridTrackLines
12
13
  }
13
14
 
@@ -33,6 +34,7 @@ export interface NormalizedWaveformGridOptions {
33
34
  rowCount: number
34
35
  columnCount: number
35
36
  showPagination: boolean
37
+ fillIncompleteLastRow: boolean
36
38
  trackLines: Record<string, NormalizedWaveformGridLineOptions>
37
39
  }
38
40
 
@@ -48,6 +50,7 @@ export interface GridCellGeometry {
48
50
  plotHeight: number
49
51
  cellHeight: number
50
52
  xAxisBand: number
53
+ isLastRow?: boolean
51
54
  }
52
55
 
53
56
  const normalizeCount = (value: unknown, fallback: number) => {
@@ -78,6 +81,7 @@ export function normalizeGridOptions(options?: WaveformGridOptions): NormalizedW
78
81
  rowCount: normalizeCount(options?.rowCount, 2),
79
82
  columnCount: normalizeCount(options?.columnCount, 1),
80
83
  showPagination: options?.showPagination ?? true,
84
+ fillIncompleteLastRow: options?.fillIncompleteLastRow ?? false,
81
85
  trackLines,
82
86
  }
83
87
  }
@@ -114,20 +118,23 @@ export function resolveGridCellGeometry(
114
118
  horizontalGap?: number,
115
119
  showXAxis = true,
116
120
  ): GridCellGeometry[] {
121
+ const pageSize = getPageSize(options)
122
+ const seriesCount = slotHasSeries.filter(Boolean).length
123
+ const filledLastRow = options.fillIncompleteLastRow && seriesCount > 0 && seriesCount < pageSize
124
+ const rowCount = filledLastRow ? Math.ceil(seriesCount / options.columnCount) : options.rowCount
117
125
  const defaultGap = getGridGap(displayMode)
118
126
  const columnGap = Number.isFinite(horizontalGap)
119
127
  ? Math.max(0, horizontalGap as number)
120
128
  : defaultGap
121
- const totalHorizontalGap = Math.max(0, options.columnCount - 1) * columnGap
122
129
  const axisRows = new Set<number>()
123
130
  if (!showXAxis) {
124
131
  // Net view uses the full drawing area for waveform pixels.
125
132
  } else if (displayMode === 'independent') {
126
- for (let row = 0; row < options.rowCount; row += 1) axisRows.add(row)
133
+ for (let row = 0; row < rowCount; row += 1) axisRows.add(row)
127
134
  } else if (displayMode === 'compact') {
128
135
  // Compact tracks share one continuous plot stack. Reserve the X-axis band
129
136
  // only beneath the final grid row, including when that row is empty.
130
- axisRows.add(options.rowCount - 1)
137
+ axisRows.add(rowCount - 1)
131
138
  } else {
132
139
  for (let column = 0; column < options.columnCount; column += 1) {
133
140
  for (let slotIndex = getPageSize(options) - 1; slotIndex >= 0; slotIndex -= 1) {
@@ -139,17 +146,22 @@ export function resolveGridCellGeometry(
139
146
  }
140
147
  }
141
148
  }
142
- const totalVerticalGap = Math.max(0, options.rowCount - 1) * defaultGap
149
+ const totalVerticalGap = Math.max(0, rowCount - 1) * defaultGap
143
150
  const totalAxisBand = axisRows.size * X_AXIS_BAND
144
- const width = Math.max(1, (innerWidth - totalHorizontalGap) / options.columnCount)
145
151
  const plotHeight = Math.max(
146
152
  1,
147
- (innerHeight - totalVerticalGap - totalAxisBand) / options.rowCount,
153
+ (innerHeight - totalVerticalGap - totalAxisBand) / rowCount,
148
154
  )
149
155
 
150
- return Array.from({ length: getPageSize(options) }, (_, slotIndex) => {
156
+ return Array.from({ length: filledLastRow ? seriesCount : pageSize }, (_, slotIndex) => {
151
157
  const row = Math.floor(slotIndex / options.columnCount)
152
158
  const column = slotIndex % options.columnCount
159
+ const rowSeriesCount =
160
+ filledLastRow && row === rowCount - 1
161
+ ? seriesCount - row * options.columnCount
162
+ : options.columnCount
163
+ const rowGap = Math.max(0, rowSeriesCount - 1) * columnGap
164
+ const width = Math.max(1, (innerWidth - rowGap) / rowSeriesCount)
153
165
  const xAxisBand = axisRows.has(row) ? X_AXIS_BAND : 0
154
166
  const cellHeight = plotHeight + xAxisBand
155
167
  const top = Array.from({ length: row }, (_, previousRow) => {
@@ -167,6 +179,7 @@ export function resolveGridCellGeometry(
167
179
  plotHeight,
168
180
  cellHeight,
169
181
  xAxisBand,
182
+ isLastRow: row === rowCount - 1,
170
183
  }
171
184
  })
172
185
  }
@@ -65,7 +65,13 @@ function layoutForSeries(
65
65
  series: sourceTrack,
66
66
  },
67
67
  ],
68
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
68
+ grid: {
69
+ rowCount: 1,
70
+ columnCount: 1,
71
+ showPagination: false,
72
+ fillIncompleteLastRow: false,
73
+ trackLines: {},
74
+ },
69
75
  displayMode: 'independent',
70
76
  overlayMode: 'single-axis',
71
77
  independentTransforms: [transform],
@@ -107,7 +113,13 @@ describe('multi-value Y-axis grouping', () => {
107
113
  series: sourceTrack,
108
114
  },
109
115
  ],
110
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
116
+ grid: {
117
+ rowCount: 1,
118
+ columnCount: 1,
119
+ showPagination: false,
120
+ fillIncompleteLastRow: false,
121
+ trackLines: {},
122
+ },
111
123
  displayMode: 'independent',
112
124
  overlayMode: 'single-axis',
113
125
  independentTransforms: [zoomIdentity],
@@ -217,7 +229,13 @@ describe('multi-value Y-axis grouping', () => {
217
229
  series: track([series('left', 1000, 3000), series('right', 1000, 3000)]),
218
230
  },
219
231
  ],
220
- grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
232
+ grid: {
233
+ rowCount: 1,
234
+ columnCount: 1,
235
+ showPagination: false,
236
+ fillIncompleteLastRow: false,
237
+ trackLines: {},
238
+ },
221
239
  displayMode: 'independent',
222
240
  overlayMode: 'multi-axis',
223
241
  independentTransforms: [zoomIdentity],
@@ -266,7 +266,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
266
266
  (isEmpty || hasVisibleSeries) &&
267
267
  (options.displayMode === 'independent' ||
268
268
  (options.displayMode === 'compact'
269
- ? cell.row === options.grid.rowCount - 1
269
+ ? (cell.isLastRow ?? cell.row === options.grid.rowCount - 1)
270
270
  : bottomCells.has(cell.slotIndex))),
271
271
  }
272
272
  })
@@ -11,6 +11,7 @@ import {
11
11
 
12
12
  import type { AnnotationSeriesCandidate } from '../annotation'
13
13
  import type { useWaveformAnnotationInteraction } from '../annotation'
14
+ import type { NormalizedWaveformGridOptions } from './grid'
14
15
  import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
15
16
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
16
17
  import { constrainZoomDomain, transformForDomain } from '../interaction/zoomConstraints'
@@ -33,7 +34,7 @@ interface LifecycleContext {
33
34
  currentPage: Ref<number>
34
35
  pageCount: ComputedRef<number>
35
36
  pagedTracks: ComputedRef<Array<DisplayTrack | undefined>>
36
- gridOptions: ComputedRef<{ rowCount: number; columnCount: number }>
37
+ gridOptions: ComputedRef<NormalizedWaveformGridOptions>
37
38
  chartSeries: ComputedRef<DisplaySeries[]>
38
39
  chartTracks: ComputedRef<DisplayTrack[]>
39
40
  trackLayouts: ComputedRef<TrackLayout[]>
@@ -220,6 +221,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
220
221
  currentPage,
221
222
  () => gridOptions.value.rowCount,
222
223
  () => gridOptions.value.columnCount,
224
+ () => gridOptions.value.fillIncompleteLastRow,
223
225
  activeInteractionMode,
224
226
  ],
225
227
  async () => {
@@ -239,20 +241,28 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
239
241
  },
240
242
  )
241
243
 
242
- watch([pageCount, () => props.grid?.rowCount, () => props.grid?.columnCount], () => {
243
- const previousPage = currentPage.value
244
- currentPage.value =
245
- currentPage.value > pageCount.value
246
- ? pageCount.value
247
- : currentPage.value !== 1
248
- ? 1
249
- : currentPage.value
250
- if (previousPage !== currentPage.value) {
251
- emit('page-change', currentPage.value, pageCount.value)
252
- }
253
- clearHover()
254
- void nextTick(configureZoom)
255
- })
244
+ watch(
245
+ [
246
+ pageCount,
247
+ () => props.grid?.rowCount,
248
+ () => props.grid?.columnCount,
249
+ () => props.grid?.fillIncompleteLastRow,
250
+ ],
251
+ () => {
252
+ const previousPage = currentPage.value
253
+ currentPage.value =
254
+ currentPage.value > pageCount.value
255
+ ? pageCount.value
256
+ : currentPage.value !== 1
257
+ ? 1
258
+ : currentPage.value
259
+ if (previousPage !== currentPage.value) {
260
+ emit('page-change', currentPage.value, pageCount.value)
261
+ }
262
+ clearHover()
263
+ void nextTick(configureZoom)
264
+ },
265
+ )
256
266
 
257
267
  watch(activeInteractionMode, () => {
258
268
  editorSeriesOptions.value = []