waveform-analysis 0.1.29 → 0.1.31

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.
Files changed (65) hide show
  1. package/README.md +8 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +937 -714
  4. package/dist/style.css +1 -1
  5. package/dist/types/components/WaveformChart.vue.d.ts +1 -0
  6. package/dist/types/components/core/constants.d.ts +2 -0
  7. package/dist/types/components/core/useWaveformChartController.d.ts +8 -15
  8. package/dist/types/components/core/useWaveformChartLifecycle.d.ts +4 -1
  9. package/dist/types/components/core/useWaveformData.d.ts +1 -1
  10. package/dist/types/components/core/useWaveformLayout.d.ts +0 -1
  11. package/dist/types/components/core/useWaveformPresentation.d.ts +5 -0
  12. package/dist/types/components/core/waveformChartTypes.d.ts +9 -5
  13. package/dist/types/components/data/types.d.ts +1 -1
  14. package/dist/types/components/index.d.ts +1 -1
  15. package/dist/types/components/interaction/pointerCapture.d.ts +1 -0
  16. package/dist/types/components/interaction/viewportInteractionState.d.ts +39 -0
  17. package/dist/types/components/interaction/zoomConstraints.d.ts +14 -0
  18. package/dist/types/components/waveform.d.ts +1 -1
  19. package/dist/types/core/rendering.d.ts +1 -4
  20. package/dist/types/core/renderingStrategies.d.ts +22 -0
  21. package/dist/types/demo/types.d.ts +4 -2
  22. package/dist/types/index.d.ts +1 -1
  23. package/dist/types/types/chart.d.ts +7 -0
  24. package/dist/types/types/index.d.ts +1 -1
  25. package/package.json +5 -1
  26. package/src/App.vue +10 -4
  27. package/src/components/WaveformChart.vue +1 -0
  28. package/src/components/WaveformChartView.vue +15 -12
  29. package/src/components/core/constants.ts +3 -0
  30. package/src/components/core/useWaveformChartController.ts +7 -2
  31. package/src/components/core/useWaveformChartLifecycle.ts +47 -3
  32. package/src/components/core/useWaveformData.ts +6 -1
  33. package/src/components/core/useWaveformLayout.ts +0 -3
  34. package/src/components/core/useWaveformPresentation.ts +17 -2
  35. package/src/components/core/waveformChartTypes.ts +7 -3
  36. package/src/components/data/types.ts +1 -0
  37. package/src/components/index.ts +1 -0
  38. package/src/components/interaction/WaveformTooltip.test.ts +39 -0
  39. package/src/components/interaction/WaveformTooltip.vue +72 -22
  40. package/src/components/interaction/pointerCapture.ts +10 -0
  41. package/src/components/interaction/useWaveformHover.ts +1 -1
  42. package/src/components/interaction/useWaveformViewport.ts +86 -74
  43. package/src/components/interaction/useWaveformZoom.ts +46 -14
  44. package/src/components/interaction/viewportInteractionState.test.ts +101 -0
  45. package/src/components/interaction/viewportInteractionState.ts +89 -0
  46. package/src/components/interaction/zoomConstraints.ts +167 -0
  47. package/src/components/waveform.ts +1 -0
  48. package/src/components/waveformChartCases/sizingAndTitles.test.ts +59 -0
  49. package/src/components/waveformChartCases/viewportLifecycle.test.ts +132 -0
  50. package/src/components/waveformChartCases/zoomPointConstraints.test.ts +248 -0
  51. package/src/core/rendering.ts +11 -81
  52. package/src/core/renderingStrategies.test.ts +90 -0
  53. package/src/core/renderingStrategies.ts +131 -0
  54. package/src/data/simulatedWaveforms.ts +9 -10
  55. package/src/demo/DemoChartHost.vue +2 -2
  56. package/src/demo/DemoGridFrameControls.vue +29 -0
  57. package/src/demo/plotMarginControls.test.ts +29 -0
  58. package/src/demo/types.ts +4 -1
  59. package/src/index.ts +1 -0
  60. package/src/styles.css +1 -0
  61. package/src/types/chart.ts +8 -0
  62. package/src/types/index.ts +1 -0
  63. package/src/utils/formatters.test.ts +4 -3
  64. package/src/utils/formatters.ts +1 -2
  65. package/src/utils/sampling.ts +2 -1
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[data-v-52818b45]{z-index:40;background:#10182852;place-items:center;padding:20px;animation:.16s ease-out waveform-annotation-editor-fade-in-52818b45;display:grid;position:absolute;inset:0}.waveform-annotation-editor__panel[data-v-52818b45]{color:#344054;background:#fff;border:1px solid #e4e7ec;border-radius:12px;gap:20px;width:min(440px,100%);max-height:100%;padding:22px;font-size:13px;animation:.18s ease-out waveform-annotation-editor-rise-in-52818b45;display:grid;overflow-y:auto;box-shadow:0 20px 50px #10182838}.waveform-annotation-editor__header[data-v-52818b45]{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.waveform-annotation-editor__header h2[data-v-52818b45]{color:#101828;margin:0;font-size:18px;line-height:1.3}.waveform-annotation-editor__header p[data-v-52818b45]{color:#667085;margin:5px 0 0;font-size:12px}.waveform-annotation-editor__close[data-v-52818b45]{color:#667085;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:0 0 30px;place-items:center;width:30px;height:30px;padding:0;font-size:22px;line-height:1;display:inline-grid}.waveform-annotation-editor__close[data-v-52818b45]:hover{color:#101828;background:#f2f4f7}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-52818b45],.waveform-annotation-editor__series[data-v-52818b45]{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-52818b45]{color:#667085}.waveform-annotation-editor__coordinates b[data-v-52818b45]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-52818b45]{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-52818b45]{gap:10px}.waveform-annotation-editor__series i[data-v-52818b45]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-52818b45]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-52818b45]{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-52818b45]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-52818b45]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-52818b45]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-52818b45],.waveform-annotation-editor__color-field[data-v-52818b45]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-52818b45]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-52818b45],.waveform-annotation-editor__colors legend[data-v-52818b45]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-52818b45]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-52818b45]{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-52818b45]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-52818b45]{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-52818b45]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-52818b45]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-52818b45] .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-52818b45] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}.waveform-annotation-editor__actions[data-v-52818b45]{border-top:1px solid #eaecf0;justify-content:flex-end;gap:8px;padding-top:8px;display:flex}.waveform-annotation-editor__actions button[data-v-52818b45]{color:#475467;cursor:pointer;background:#fff;border:1px solid #d0d5dd;border-radius:6px;height:34px;padding:0 12px}.waveform-annotation-editor__actions button.is-primary[data-v-52818b45]{color:#fff;background:#1677ff;border-color:#1677ff}.waveform-annotation-editor__actions button[data-v-52818b45]:disabled{opacity:.45;cursor:not-allowed}@keyframes waveform-annotation-editor-fade-in-52818b45{0%{opacity:0}to{opacity:1}}@keyframes waveform-annotation-editor-rise-in-52818b45{0%{opacity:0;transform:translateY(8px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}@media (width<=420px){.waveform-annotation-editor[data-v-52818b45]{padding:12px}.waveform-annotation-editor__panel[data-v-52818b45]{gap:16px;padding:18px}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:1fr}}.waveform-tooltip[data-v-b8b5592a]{box-sizing:border-box;z-index:2;color:#333;pointer-events:none;background:#fff;border:1px solid #d9d9d9;border-radius:2px;gap:3px;min-width:180px;max-width:238px;padding:8px 10px;font:12px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;display:grid;position:absolute;box-shadow:0 4px 12px #1018281f}.waveform-tooltip__time[data-v-b8b5592a]{border-bottom:1px solid #f0f0f0;padding-bottom:4px}.waveform-tooltip__series[data-v-b8b5592a]{grid-template-columns:8px minmax(0,1fr) auto;align-items:center;gap:6px;display:grid}.waveform-tooltip__series i[data-v-b8b5592a]{border-radius:50%;width:8px;height:8px}.waveform-tooltip__series strong[data-v-b8b5592a]{text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.waveform-tooltip__value[data-v-b8b5592a]{white-space:nowrap}.waveform-tooltip__series small[data-v-b8b5592a]{color:#667085;white-space:nowrap}.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[data-v-52818b45]{z-index:40;background:#10182852;place-items:center;padding:20px;animation:.16s ease-out waveform-annotation-editor-fade-in-52818b45;display:grid;position:absolute;inset:0}.waveform-annotation-editor__panel[data-v-52818b45]{color:#344054;background:#fff;border:1px solid #e4e7ec;border-radius:12px;gap:20px;width:min(440px,100%);max-height:100%;padding:22px;font-size:13px;animation:.18s ease-out waveform-annotation-editor-rise-in-52818b45;display:grid;overflow-y:auto;box-shadow:0 20px 50px #10182838}.waveform-annotation-editor__header[data-v-52818b45]{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.waveform-annotation-editor__header h2[data-v-52818b45]{color:#101828;margin:0;font-size:18px;line-height:1.3}.waveform-annotation-editor__header p[data-v-52818b45]{color:#667085;margin:5px 0 0;font-size:12px}.waveform-annotation-editor__close[data-v-52818b45]{color:#667085;cursor:pointer;background:0 0;border:0;border-radius:6px;flex:0 0 30px;place-items:center;width:30px;height:30px;padding:0;font-size:22px;line-height:1;display:inline-grid}.waveform-annotation-editor__close[data-v-52818b45]:hover{color:#101828;background:#f2f4f7}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-52818b45],.waveform-annotation-editor__series[data-v-52818b45]{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-52818b45]{color:#667085}.waveform-annotation-editor__coordinates b[data-v-52818b45]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-52818b45]{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-52818b45]{gap:10px}.waveform-annotation-editor__series i[data-v-52818b45]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-52818b45]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-52818b45]{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-52818b45]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-52818b45]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-52818b45]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-52818b45],.waveform-annotation-editor__color-field[data-v-52818b45]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-52818b45]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-52818b45],.waveform-annotation-editor__colors legend[data-v-52818b45]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-52818b45]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-52818b45]{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-52818b45]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-52818b45]{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-52818b45]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-52818b45]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-52818b45] .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-52818b45] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}.waveform-annotation-editor__actions[data-v-52818b45]{border-top:1px solid #eaecf0;justify-content:flex-end;gap:8px;padding-top:8px;display:flex}.waveform-annotation-editor__actions button[data-v-52818b45]{color:#475467;cursor:pointer;background:#fff;border:1px solid #d0d5dd;border-radius:6px;height:34px;padding:0 12px}.waveform-annotation-editor__actions button.is-primary[data-v-52818b45]{color:#fff;background:#1677ff;border-color:#1677ff}.waveform-annotation-editor__actions button[data-v-52818b45]:disabled{opacity:.45;cursor:not-allowed}@keyframes waveform-annotation-editor-fade-in-52818b45{0%{opacity:0}to{opacity:1}}@keyframes waveform-annotation-editor-rise-in-52818b45{0%{opacity:0;transform:translateY(8px)scale(.98)}to{opacity:1;transform:translateY(0)scale(1)}}@media (width<=420px){.waveform-annotation-editor[data-v-52818b45]{padding:12px}.waveform-annotation-editor__panel[data-v-52818b45]{gap:16px;padding:18px}.waveform-annotation-editor__coordinates[data-v-52818b45]{grid-template-columns:1fr}}.waveform-tooltip[data-v-93daa641]{box-sizing:border-box;z-index:2;color:#333;pointer-events:none;background:#fff;border:1px solid #d9d9d9;border-radius:2px;gap:3px;width:max-content;max-width:min(320px,100% - 16px);padding:8px 10px;font:12px/1.45 ui-monospace,SFMono-Regular,Consolas,monospace;display:grid;position:absolute;box-shadow:0 4px 12px #1018281f}.waveform-tooltip__time[data-v-93daa641]{border-bottom:1px solid #f0f0f0;padding-bottom:4px}.waveform-tooltip__series[data-v-93daa641]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-93daa641]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-93daa641]{overflow-wrap:anywhere;white-space:normal;min-width:0}.waveform-tooltip__series strong[data-v-93daa641]{font-weight:600}.waveform-tooltip__value[data-v-93daa641]{overflow-wrap:anywhere}.waveform-tooltip__series small[data-v-93daa641]{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*/
@@ -48,6 +48,7 @@ declare const __VLS_export: import("vue").DefineComponent<WaveformChartProps, {
48
48
  annotationsVisible: boolean;
49
49
  grid: import("..").WaveformGridOptions;
50
50
  rendering: import("..").WaveformRenderingOptions;
51
+ plotMargin: import("..").WaveformPlotMargin;
51
52
  defaultHiddenSeriesIds: string[];
52
53
  cleanView: boolean;
53
54
  presentationMode: boolean;
@@ -8,6 +8,8 @@ export declare const margin: {
8
8
  bottom: number;
9
9
  left: number;
10
10
  };
11
+ /** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
12
+ export declare const X_AXIS_TITLE_BOTTOM_OFFSET = 12;
11
13
  /**
12
14
  * 图表最小高度(像素)
13
15
  */
@@ -1,7 +1,7 @@
1
1
  import { type ComponentPublicInstance } from 'vue';
2
2
  import { getPageSize } from './grid';
3
3
  import type { WaveformHoverState } from './types';
4
- import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes';
4
+ import type { ResolvedWaveformChartProps, ViewportSelectionState, WaveformChartEmit } from './waveformChartTypes';
5
5
  export declare function useWaveformChartController(props: ResolvedWaveformChartProps, emit: WaveformChartEmit): {
6
6
  margin: {
7
7
  top: number;
@@ -11,19 +11,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
11
11
  };
12
12
  getPageSize: typeof getPageSize;
13
13
  currentPage: number;
14
- selection: {
15
- trackIndex: number;
16
- independent: boolean;
17
- overlay: SVGRectElement;
18
- startX: number;
19
- startY: number;
20
- currentX: number;
21
- currentY: number;
22
- pointerId: number;
23
- mode: "box" | "pan";
24
- xDomain: [number, number];
25
- yDomains: Record<string, [number, number]>;
26
- } | null;
14
+ selection: ViewportSelectionState | null;
27
15
  pointerInsideChart: boolean;
28
16
  hoverState: import("vue").ShallowReactive<WaveformHoverState>;
29
17
  annotationInteraction: {
@@ -287,7 +275,6 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
287
275
  resolveSeriesYScale: (track: import("./types").TrackLayout, seriesId: string) => import("d3").ScaleLinear<number, number, never>;
288
276
  annotationTrackLayouts: import("../annotation").AnnotationTrackLayout[];
289
277
  renderedAnnotations: import("../annotation").RenderedAnnotation[];
290
- xAxisTitleY: number;
291
278
  editorSeries: import("../annotation").AnnotationSeriesInfo | undefined;
292
279
  resolveFrameNumber: (trackIndex: number) => string | number | undefined;
293
280
  fixedWidth: number | undefined;
@@ -318,8 +305,13 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
318
305
  titleMeasureStyle: import("vue").CSSProperties;
319
306
  titleLayout: import("./title").RotatedTitleLayout;
320
307
  titleAreaHeight: number;
308
+ resolvedPlotMargin: {
309
+ top: number;
310
+ bottom: number;
311
+ };
321
312
  chartTopMargin: number;
322
313
  drawingHeight: number;
314
+ xAxisTitleY: number;
323
315
  innerHeight: number;
324
316
  titleAreaStyle: import("vue").CSSProperties;
325
317
  titleVisualStyle: import("vue").CSSProperties;
@@ -349,6 +341,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
349
341
  interactionMode?: import("..").WaveformInteractionMode | undefined;
350
342
  grid: import("./grid").WaveformGridOptions;
351
343
  rendering: import("..").WaveformRenderingOptions;
344
+ plotMargin: import("..").WaveformPlotMargin;
352
345
  title?: import("..").WaveformTitleOptions | undefined;
353
346
  legend: import("..").WaveformLegendOptions;
354
347
  hiddenSeriesIds?: string[] | undefined;
@@ -2,7 +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 { DisplaySeries, DisplayTrack } from './types';
5
+ import type { DisplaySeries, DisplayTrack, TrackLayout } from './types';
6
6
  import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes';
7
7
  interface LifecycleContext {
8
8
  props: ResolvedWaveformChartProps;
@@ -28,6 +28,7 @@ interface LifecycleContext {
28
28
  }>;
29
29
  chartSeries: ComputedRef<DisplaySeries[]>;
30
30
  chartTracks: ComputedRef<DisplayTrack[]>;
31
+ trackLayouts: ComputedRef<TrackLayout[]>;
31
32
  innerWidth: ComputedRef<number>;
32
33
  innerHeight: ComputedRef<number>;
33
34
  activeInteractionMode: ComputedRef<string | undefined>;
@@ -35,6 +36,7 @@ interface LifecycleContext {
35
36
  internalHiddenSeriesIds: Ref<Set<string>>;
36
37
  independentTransforms: ShallowRef<ZoomTransform[]>;
37
38
  independentYDomains: Ref<Record<number, [number, number]>>;
39
+ resolveInitialTrackDomain: (track: TrackLayout) => [number, number];
38
40
  annotationInteraction: ReturnType<typeof useWaveformAnnotationInteraction>;
39
41
  editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>;
40
42
  isPresentationMode: ComputedRef<boolean>;
@@ -48,6 +50,7 @@ interface LifecycleContext {
48
50
  }
49
51
  export declare function useWaveformChartLifecycle(context: LifecycleContext): {
50
52
  goToPage: (page: number) => void;
53
+ handleBeforeDataReferenceChange: () => void;
51
54
  handleDataReferenceChange: () => void;
52
55
  };
53
56
  export {};
@@ -15,4 +15,4 @@ export interface PreparedWaveformSeries {
15
15
  hasErrorPoints: boolean;
16
16
  }
17
17
  export declare function prepareWaveformSeries(data: WaveformData): PreparedWaveformSeries[];
18
- export declare function usePreparedWaveformSeries(data: () => WaveformData, onDataChange: () => void): import("vue").ShallowRef<PreparedWaveformSeries[], PreparedWaveformSeries[]>;
18
+ export declare function usePreparedWaveformSeries(data: () => WaveformData, onBeforeDataChange: () => void, onDataChange: () => void): import("vue").ShallowRef<PreparedWaveformSeries[], PreparedWaveformSeries[]>;
@@ -54,7 +54,6 @@ export declare function useWaveformLayout(context: LayoutContext): {
54
54
  resolveSeriesYScale: (track: TrackLayout, seriesId: string) => import("d3").ScaleLinear<number, number, never>;
55
55
  annotationTrackLayouts: ComputedRef<AnnotationTrackLayout[]>;
56
56
  renderedAnnotations: ComputedRef<import("../annotation").RenderedAnnotation[]>;
57
- xAxisTitleY: ComputedRef<number>;
58
57
  editorSeries: ComputedRef<AnnotationSeriesInfo | undefined>;
59
58
  resolveFrameNumber: (trackIndex: number) => string | number | undefined;
60
59
  };
@@ -39,8 +39,13 @@ export declare function useWaveformPresentation(context: PresentationContext): {
39
39
  titleMeasureStyle: import("vue").ComputedRef<CSSProperties>;
40
40
  titleLayout: import("vue").ComputedRef<import("./title").RotatedTitleLayout>;
41
41
  titleAreaHeight: import("vue").ComputedRef<number>;
42
+ resolvedPlotMargin: import("vue").ComputedRef<{
43
+ top: number;
44
+ bottom: number;
45
+ }>;
42
46
  chartTopMargin: import("vue").ComputedRef<number>;
43
47
  drawingHeight: import("vue").ComputedRef<number>;
48
+ xAxisTitleY: import("vue").ComputedRef<number>;
44
49
  innerHeight: import("vue").ComputedRef<number>;
45
50
  titleAreaStyle: import("vue").ComputedRef<CSSProperties>;
46
51
  titleVisualStyle: import("vue").ComputedRef<CSSProperties>;
@@ -1,4 +1,4 @@
1
- import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../data/types';
1
+ import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformInteractionMode, WaveformLegendOptions, WaveformOverlayMode, WaveformPlotMargin, WaveformPoint, WaveformRenderingOptions, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../data/types';
2
2
  import type { WaveformGridOptions } from './grid';
3
3
  export interface WaveformChartProps {
4
4
  data: WaveformData;
@@ -27,6 +27,7 @@ export interface WaveformChartProps {
27
27
  interactionMode?: WaveformInteractionMode;
28
28
  grid?: WaveformGridOptions;
29
29
  rendering?: WaveformRenderingOptions;
30
+ plotMargin?: WaveformPlotMargin;
30
31
  title?: WaveformTitleOptions;
31
32
  legend?: WaveformLegendOptions;
32
33
  hiddenSeriesIds?: string[];
@@ -35,7 +36,7 @@ export interface WaveformChartProps {
35
36
  presentationMode?: boolean;
36
37
  zeroLine?: WaveformZeroLineOptions;
37
38
  }
38
- type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
39
+ type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
39
40
  export type ResolvedWaveformChartProps = Readonly<WaveformChartProps & Required<Pick<WaveformChartProps, DefaultedProp>>>;
40
41
  export interface WaveformChartEmit {
41
42
  (event: 'point-hover', point: WaveformPoint | null): void;
@@ -54,17 +55,20 @@ export interface WaveformChartEmit {
54
55
  (event: 'annotation-delete', annotation: WaveformAnnotation): void;
55
56
  (event: 'page-change', page: number, pageCount: number): void;
56
57
  }
57
- export interface ViewportSelectionState {
58
+ interface ViewportSelectionBase {
58
59
  trackIndex: number;
59
60
  independent: boolean;
60
- overlay: SVGRectElement;
61
61
  startX: number;
62
62
  startY: number;
63
63
  currentX: number;
64
64
  currentY: number;
65
65
  pointerId: number;
66
- mode: 'box' | 'pan';
67
66
  xDomain: [number, number];
68
67
  yDomains: Record<string, [number, number]>;
69
68
  }
69
+ export type ViewportSelectionState = (ViewportSelectionBase & {
70
+ kind: 'box';
71
+ }) | (ViewportSelectionBase & {
72
+ kind: 'pan';
73
+ });
70
74
  export {};
@@ -2,6 +2,6 @@
2
2
  * 数据系统类型定义
3
3
  * 重新导出 src/types 中的类型,方便组件内部使用
4
4
  */
5
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from '../../types';
5
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from '../../types';
6
6
  export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from '../core/grid';
7
7
  export { normalizeWaveformData, normalizeWaveformSeries } from '../../core';
@@ -1,5 +1,5 @@
1
1
  export { default as WaveformChart } from './WaveformChart.vue';
2
- export type { SingleWaveformData, WaveformData, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, WaveformPoint, WaveformSeries, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './data/types';
2
+ export type { SingleWaveformData, WaveformData, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformAxesOptions, WaveformZeroLineOptions, WaveformPoint, WaveformSeries, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './data/types';
3
3
  export type { WaveformGridOptions as WaveformGridConfig } from './core/grid';
4
4
  export { WaveformTooltip } from './interaction';
5
5
  export { WaveformTrack } from './rendering';
@@ -0,0 +1 @@
1
+ export declare function tryReleasePointerCapture(target: SVGRectElement | null | undefined, pointerId: number): void;
@@ -0,0 +1,39 @@
1
+ import type { ViewportSelectionState } from '../core/waveformChartTypes';
2
+ export interface ViewportGestureStart {
3
+ trackIndex: number;
4
+ independent: boolean;
5
+ startX: number;
6
+ startY: number;
7
+ pointerId: number;
8
+ kind: 'box' | 'pan';
9
+ xDomain: [number, number];
10
+ yDomains: Record<string, [number, number]>;
11
+ }
12
+ export interface ViewportGesturePosition {
13
+ currentX: number;
14
+ currentY: number;
15
+ }
16
+ export type ViewportInteractionEvent = {
17
+ type: 'begin';
18
+ gesture: ViewportGestureStart;
19
+ } | {
20
+ type: 'move';
21
+ pointerId: number;
22
+ position: ViewportGesturePosition;
23
+ } | {
24
+ type: 'finish';
25
+ pointerId: number;
26
+ position: ViewportGesturePosition;
27
+ } | {
28
+ type: 'cancel';
29
+ pointerId?: number;
30
+ } | {
31
+ type: 'reset';
32
+ };
33
+ export interface ViewportInteractionTransition {
34
+ state: ViewportSelectionState | null;
35
+ accepted: boolean;
36
+ completed: ViewportSelectionState | null;
37
+ }
38
+ export declare function transitionViewportInteraction(state: ViewportSelectionState | null, event: ViewportInteractionEvent): ViewportInteractionTransition;
39
+ export declare function reduceViewportInteraction(state: ViewportSelectionState | null, event: ViewportInteractionEvent): ViewportSelectionState | null;
@@ -0,0 +1,14 @@
1
+ import { type ZoomTransform } from 'd3';
2
+ import type { WaveformPoint } from '../../types';
3
+ interface PointSeriesSource {
4
+ points: WaveformPoint[];
5
+ }
6
+ export type ZoomSeriesGroup = readonly PointSeriesSource[];
7
+ interface ZoomConstraintOptions {
8
+ minZoomSpan?: number;
9
+ minVisiblePoints?: number;
10
+ }
11
+ export declare function resolveMinimumZoomSpan(boundary: [number, number], groups: readonly ZoomSeriesGroup[], options: ZoomConstraintOptions): number;
12
+ export declare function constrainZoomDomain(domain: [number, number], boundary: [number, number], groups: readonly ZoomSeriesGroup[], options: ZoomConstraintOptions): [number, number];
13
+ export declare function transformForDomain(domain: [number, number], baseDomain: [number, number], width: number): ZoomTransform;
14
+ export {};
@@ -2,5 +2,5 @@
2
2
  * 向后兼容的导出文件
3
3
  * 保留原有导入路径,内部从新的模块结构导出
4
4
  */
5
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
5
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformFrameStyle, SingleWaveformData, WaveformSeries, WaveformLineStyle, WaveformData, NormalizedWaveformSeries, } from '../types';
6
6
  export { normalizeWaveformData, normalizeWaveformSeries } from '../core';
@@ -1,10 +1,7 @@
1
1
  import type { WaveformPoint } from '@/types';
2
2
  import type { ResolvedWaveformRenderingOptions } from './renderingOptions';
3
+ import { type VisiblePointRange } from './renderingStrategies';
3
4
  export { DEFAULT_WAVEFORM_RENDERING_OPTIONS, resolveWaveformRenderingOptions, type ResolvedWaveformRenderingOptions, } from './renderingOptions';
4
- interface VisiblePointRange {
5
- start: number;
6
- end: number;
7
- }
8
5
  interface PointSeriesSource {
9
6
  points: WaveformPoint[];
10
7
  }
@@ -0,0 +1,22 @@
1
+ import type { WaveformPoint } from '../types';
2
+ import type { ResolvedWaveformRenderingOptions } from './renderingOptions';
3
+ export interface VisiblePointRange {
4
+ start: number;
5
+ end: number;
6
+ }
7
+ export interface RenderablePointSelectionContext {
8
+ points: WaveformPoint[];
9
+ range: VisiblePointRange;
10
+ domain: [number, number];
11
+ width: number;
12
+ options: ResolvedWaveformRenderingOptions;
13
+ }
14
+ export type RenderablePointSelectionStrategy = (context: RenderablePointSelectionContext) => WaveformPoint[];
15
+ export declare const completePointSelectionStrategy: RenderablePointSelectionStrategy;
16
+ export declare const peakPreservingPointSelectionStrategy: RenderablePointSelectionStrategy;
17
+ export interface RenderablePointSelectionStrategyRequest {
18
+ visibleCount: number;
19
+ width: number;
20
+ options: ResolvedWaveformRenderingOptions;
21
+ }
22
+ export declare function resolveRenderablePointSelectionStrategy(request: RenderablePointSelectionStrategyRequest): RenderablePointSelectionStrategy;
@@ -1,4 +1,4 @@
1
- import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
1
+ import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformPlotMargin, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
2
2
  import type { DemoXAxisLabelFormat, DemoXAxisLabelTimeZone } from './useDemoXAxisLabelControls';
3
3
  interface SelectOption<T> {
4
4
  label: string;
@@ -9,6 +9,8 @@ export interface DemoControlPanelModel {
9
9
  displayMode: WaveformDisplayMode;
10
10
  overlayMode: WaveformOverlayMode;
11
11
  showTooltip: boolean;
12
+ plotMarginTop: number;
13
+ plotMarginBottom: number;
12
14
  cleanView: boolean;
13
15
  presentationMode: boolean;
14
16
  selectedSeriesId: string;
@@ -65,7 +67,6 @@ export interface DemoControlPanelModel {
65
67
  }
66
68
  export interface DemoChartModel {
67
69
  data: WaveformData;
68
- minZoomSpan?: number;
69
70
  initialXDomain?: [number, number];
70
71
  displayMode: WaveformDisplayMode;
71
72
  overlayMode: WaveformOverlayMode;
@@ -81,6 +82,7 @@ export interface DemoChartModel {
81
82
  cleanView: boolean;
82
83
  presentationMode: boolean;
83
84
  showTooltip: boolean;
85
+ plotMargin: WaveformPlotMargin;
84
86
  zeroLine: WaveformZeroLineOptions;
85
87
  frameWatermarkVisible: boolean;
86
88
  annotations: WaveformAnnotation[];
@@ -3,7 +3,7 @@
3
3
  * @packageDocumentation
4
4
  */
5
5
  export { default as WaveformChart } from './components/WaveformChart.vue';
6
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
6
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
7
7
  export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './components/core/grid';
8
8
  export { normalizeWaveformData, normalizeWaveformSeries } from './core';
9
9
  export { paddedDomain, buildMinorTicks, displayTime, formatEndpointTime, formatAxisTime, formatTooltipTime, resolveTrackGeometry, clamp, type TimeUnit, type TrackGeometry, } from './utils';
@@ -66,6 +66,13 @@ export interface WaveformRenderingOptions {
66
66
  /** Minimum horizontal CSS-pixel spacing between rendered error bars. Use 0 to disable. */
67
67
  errorBarMinSpacing?: number;
68
68
  }
69
+ /** Pixel margins reserved above and below the waveform plotting area. */
70
+ export interface WaveformPlotMargin {
71
+ /** Space between the top of the drawing SVG and the plotting area. Defaults to 18. */
72
+ top?: number;
73
+ /** Space between the plotting area and the bottom of the drawing SVG. Defaults to 52. */
74
+ bottom?: number;
75
+ }
69
76
  /** Text styling for the chart-level title. */
70
77
  export interface WaveformTitleTextStyle {
71
78
  color?: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * 类型定义统一导出
3
3
  */
4
- export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
4
+ export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformZoomEndPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
5
5
  export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -43,6 +43,7 @@
43
43
  "eslint-plugin-vue": "10.9.2",
44
44
  "globals": "17.7.0",
45
45
  "jsdom": "29.1.1",
46
+ "oxlint": "1.77.0",
46
47
  "prettier": "3.9.5",
47
48
  "typescript": "~6.0.0",
48
49
  "typescript-eslint": "8.64.0",
@@ -61,7 +62,10 @@
61
62
  "typecheck": "vue-tsc -b",
62
63
  "check:file-length": "node scripts/check-file-length.mjs",
63
64
  "lint": "eslint . --max-warnings=0",
65
+ "lint:oxlint": "oxlint . --deny-warnings",
66
+ "lint:all": "pnpm lint:oxlint && pnpm lint",
64
67
  "format": "prettier --write .",
68
+ "format:check": "prettier --check .",
65
69
  "test": "vitest run",
66
70
  "test:coverage": "vitest run --coverage"
67
71
  }
package/src/App.vue CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  type WaveformLegendOrientation,
15
15
  type WaveformLegendPosition,
16
16
  type WaveformOverlayMode,
17
+ type WaveformPlotMargin,
17
18
  type WaveformTitleOptions,
18
19
  type WaveformZoomEndPayload,
19
20
  type WaveformZeroLineOptions,
@@ -47,6 +48,8 @@ const annotationsVisible = ref(true)
47
48
  const cleanView = ref(false)
48
49
  const presentationMode = ref(false)
49
50
  const showTooltip = ref(true)
51
+ const plotMarginTop = ref(18)
52
+ const plotMarginBottom = ref(52)
50
53
  const zeroLineVisible = ref(false)
51
54
  const zeroLineColor = ref('#98a2b3')
52
55
  const zeroLineWidth = ref(1)
@@ -126,6 +129,10 @@ const zeroLine = computed<WaveformZeroLineOptions>(() => ({
126
129
  width: zeroLineWidth.value,
127
130
  dash: zeroLineDash.value,
128
131
  }))
132
+ const plotMargin = computed<WaveformPlotMargin>(() => ({
133
+ top: plotMarginTop.value,
134
+ bottom: plotMarginBottom.value,
135
+ }))
129
136
 
130
137
  const initialXValues = normalizeWaveformSeries(fullChartData).flatMap((series) =>
131
138
  series.points.map((point) => point.x),
@@ -134,9 +141,6 @@ const [initialXMinimum, initialXMaximum] = initialXValues.reduce<[number, number
134
141
  ([minimum, maximum], value) => [Math.min(minimum, value), Math.max(maximum, value)],
135
142
  [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
136
143
  )
137
- const initialXSpan = initialXMaximum - initialXMinimum
138
- const minZoomSpan =
139
- Number.isFinite(initialXSpan) && initialXSpan > 0 ? initialXSpan / 40 : undefined
140
144
  const initialXDomainValue: [number, number] | undefined =
141
145
  Number.isFinite(initialXMinimum) && Number.isFinite(initialXMaximum)
142
146
  ? [initialXMinimum, initialXMaximum]
@@ -294,6 +298,8 @@ const controlPanelModel = reactive({
294
298
  displayMode,
295
299
  overlayMode,
296
300
  showTooltip,
301
+ plotMarginTop,
302
+ plotMarginBottom,
297
303
  cleanView,
298
304
  presentationMode,
299
305
  selectedSeriesId,
@@ -344,7 +350,6 @@ const controlPanelModel = reactive({
344
350
 
345
351
  const chartModel = reactive({
346
352
  data: displayChartData,
347
- minZoomSpan,
348
353
  initialXDomain,
349
354
  displayMode,
350
355
  overlayMode,
@@ -360,6 +365,7 @@ const chartModel = reactive({
360
365
  cleanView,
361
366
  presentationMode,
362
367
  showTooltip,
368
+ plotMargin,
363
369
  zeroLine,
364
370
  frameWatermarkVisible,
365
371
  annotations,
@@ -23,6 +23,7 @@ const props = withDefaults(defineProps<WaveformChartProps>(), {
23
23
  interactionMode: undefined,
24
24
  grid: () => ({ rowCount: 2, columnCount: 1, showPagination: true }),
25
25
  rendering: () => ({}),
26
+ plotMargin: () => ({}),
26
27
  legend: () => ({ position: 'top-right', orientation: 'auto' }),
27
28
  defaultHiddenSeriesIds: () => [],
28
29
  cleanView: false,
@@ -19,6 +19,7 @@ const {
19
19
  overlayMode,
20
20
  resolvedChartLeftMargin,
21
21
  titleAreaHeight,
22
+ resolvedPlotMargin,
22
23
  pointerInsideChart,
23
24
  handleNativeContextMenu,
24
25
  titleAreaReserved,
@@ -109,7 +110,7 @@ const {
109
110
  {
110
111
  'waveform-chart--clean': isCleanView,
111
112
  'waveform-chart--presentation': isPresentationMode,
112
- 'waveform-chart--panning': selection?.mode === 'pan',
113
+ 'waveform-chart--panning': selection?.kind === 'pan',
113
114
  },
114
115
  ]"
115
116
  :style="containerStyle"
@@ -118,6 +119,8 @@ const {
118
119
  :data-presentation-mode="isPresentationMode"
119
120
  :data-overlay-mode="overlayMode"
120
121
  :data-chart-left-margin="resolvedChartLeftMargin"
122
+ :data-plot-margin-top="resolvedPlotMargin.top"
123
+ :data-plot-margin-bottom="resolvedPlotMargin.bottom"
121
124
  :data-title-area-height="titleAreaHeight"
122
125
  @pointerenter="pointerInsideChart = true"
123
126
  @pointerleave="pointerInsideChart = false"
@@ -244,7 +247,7 @@ const {
244
247
  />
245
248
 
246
249
  <rect
247
- v-if="selectionBox && selection?.mode === 'box'"
250
+ v-if="selectionBox && selection?.kind === 'box'"
248
251
  class="waveform-chart__zoom-selection"
249
252
  :x="selectionBox.x"
250
253
  :y="selectionBox.y"
@@ -287,18 +290,18 @@ const {
287
290
  />
288
291
  </g>
289
292
  </g>
290
-
291
- <text
292
- v-if="resolvedXLabel && !isCleanView"
293
- class="waveform-chart__label waveform-chart__x-label"
294
- :x="innerWidth / 2"
295
- :y="xAxisTitleY"
296
- text-anchor="middle"
297
- >
298
- {{ resolvedXLabel }}
299
- </text>
300
293
  </g>
301
294
 
295
+ <text
296
+ v-if="resolvedXLabel && !isCleanView"
297
+ class="waveform-chart__label waveform-chart__x-label"
298
+ :x="resolvedChartLeftMargin + innerWidth / 2"
299
+ :y="xAxisTitleY"
300
+ text-anchor="middle"
301
+ >
302
+ {{ resolvedXLabel }}
303
+ </text>
304
+
302
305
  <text
303
306
  v-if="hasChartArea && !hasWaveformData"
304
307
  class="waveform-chart__empty"
@@ -7,6 +7,9 @@
7
7
  /** 图表边距 */
8
8
  export const margin = { top: 18, right: 24, bottom: 52, left: 64 }
9
9
 
10
+ /** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
11
+ export const X_AXIS_TITLE_BOTTOM_OFFSET = 12
12
+
10
13
  /**
11
14
  * 图表最小高度(像素)
12
15
  */