waveform-analysis 0.1.30 → 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.
- package/README.md +8 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +815 -601
- package/dist/style.css +1 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +2 -14
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +4 -1
- package/dist/types/components/core/useWaveformData.d.ts +1 -1
- package/dist/types/components/core/waveformChartTypes.d.ts +6 -3
- package/dist/types/components/interaction/pointerCapture.d.ts +1 -0
- package/dist/types/components/interaction/viewportInteractionState.d.ts +39 -0
- package/dist/types/components/interaction/zoomConstraints.d.ts +14 -0
- package/dist/types/core/rendering.d.ts +1 -4
- package/dist/types/core/renderingStrategies.d.ts +22 -0
- package/dist/types/demo/types.d.ts +0 -1
- package/package.json +5 -1
- package/src/App.vue +0 -4
- package/src/components/WaveformChartView.vue +2 -2
- package/src/components/core/useWaveformChartController.ts +7 -2
- package/src/components/core/useWaveformChartLifecycle.ts +47 -3
- package/src/components/core/useWaveformData.ts +6 -1
- package/src/components/core/waveformChartTypes.ts +4 -3
- package/src/components/interaction/WaveformTooltip.test.ts +39 -0
- package/src/components/interaction/WaveformTooltip.vue +72 -22
- package/src/components/interaction/pointerCapture.ts +10 -0
- package/src/components/interaction/useWaveformHover.ts +1 -1
- package/src/components/interaction/useWaveformViewport.ts +86 -74
- package/src/components/interaction/useWaveformZoom.ts +46 -14
- package/src/components/interaction/viewportInteractionState.test.ts +101 -0
- package/src/components/interaction/viewportInteractionState.ts +89 -0
- package/src/components/interaction/zoomConstraints.ts +167 -0
- package/src/components/waveformChartCases/viewportLifecycle.test.ts +132 -0
- package/src/components/waveformChartCases/zoomPointConstraints.test.ts +248 -0
- package/src/core/rendering.ts +11 -81
- package/src/core/renderingStrategies.test.ts +90 -0
- package/src/core/renderingStrategies.ts +131 -0
- package/src/data/simulatedWaveforms.ts +9 -10
- package/src/demo/DemoChartHost.vue +1 -2
- package/src/demo/types.ts +0 -1
- package/src/utils/formatters.test.ts +4 -3
- package/src/utils/formatters.ts +1 -2
- 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*/
|
|
@@ -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: {
|
|
@@ -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[]>;
|
|
@@ -55,17 +55,20 @@ export interface WaveformChartEmit {
|
|
|
55
55
|
(event: 'annotation-delete', annotation: WaveformAnnotation): void;
|
|
56
56
|
(event: 'page-change', page: number, pageCount: number): void;
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
interface ViewportSelectionBase {
|
|
59
59
|
trackIndex: number;
|
|
60
60
|
independent: boolean;
|
|
61
|
-
overlay: SVGRectElement;
|
|
62
61
|
startX: number;
|
|
63
62
|
startY: number;
|
|
64
63
|
currentX: number;
|
|
65
64
|
currentY: number;
|
|
66
65
|
pointerId: number;
|
|
67
|
-
mode: 'box' | 'pan';
|
|
68
66
|
xDomain: [number, number];
|
|
69
67
|
yDomains: Record<string, [number, number]>;
|
|
70
68
|
}
|
|
69
|
+
export type ViewportSelectionState = (ViewportSelectionBase & {
|
|
70
|
+
kind: 'box';
|
|
71
|
+
}) | (ViewportSelectionBase & {
|
|
72
|
+
kind: 'pan';
|
|
73
|
+
});
|
|
71
74
|
export {};
|
|
@@ -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 {};
|
|
@@ -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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waveform-analysis",
|
|
3
|
-
"version": "0.1.
|
|
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
|
@@ -141,9 +141,6 @@ const [initialXMinimum, initialXMaximum] = initialXValues.reduce<[number, number
|
|
|
141
141
|
([minimum, maximum], value) => [Math.min(minimum, value), Math.max(maximum, value)],
|
|
142
142
|
[Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY],
|
|
143
143
|
)
|
|
144
|
-
const initialXSpan = initialXMaximum - initialXMinimum
|
|
145
|
-
const minZoomSpan =
|
|
146
|
-
Number.isFinite(initialXSpan) && initialXSpan > 0 ? initialXSpan / 40 : undefined
|
|
147
144
|
const initialXDomainValue: [number, number] | undefined =
|
|
148
145
|
Number.isFinite(initialXMinimum) && Number.isFinite(initialXMaximum)
|
|
149
146
|
? [initialXMinimum, initialXMaximum]
|
|
@@ -353,7 +350,6 @@ const controlPanelModel = reactive({
|
|
|
353
350
|
|
|
354
351
|
const chartModel = reactive({
|
|
355
352
|
data: displayChartData,
|
|
356
|
-
minZoomSpan,
|
|
357
353
|
initialXDomain,
|
|
358
354
|
displayMode,
|
|
359
355
|
overlayMode,
|
|
@@ -110,7 +110,7 @@ const {
|
|
|
110
110
|
{
|
|
111
111
|
'waveform-chart--clean': isCleanView,
|
|
112
112
|
'waveform-chart--presentation': isPresentationMode,
|
|
113
|
-
'waveform-chart--panning': selection?.
|
|
113
|
+
'waveform-chart--panning': selection?.kind === 'pan',
|
|
114
114
|
},
|
|
115
115
|
]"
|
|
116
116
|
:style="containerStyle"
|
|
@@ -247,7 +247,7 @@ const {
|
|
|
247
247
|
/>
|
|
248
248
|
|
|
249
249
|
<rect
|
|
250
|
-
v-if="selectionBox && selection?.
|
|
250
|
+
v-if="selectionBox && selection?.kind === 'box'"
|
|
251
251
|
class="waveform-chart__zoom-selection"
|
|
252
252
|
:x="selectionBox.x"
|
|
253
253
|
:y="selectionBox.y"
|
|
@@ -78,12 +78,14 @@ export function useWaveformChartController(
|
|
|
78
78
|
},
|
|
79
79
|
{ deep: true },
|
|
80
80
|
)
|
|
81
|
-
const selection =
|
|
81
|
+
const selection = shallowRef<ViewportSelectionState | null>(null)
|
|
82
82
|
const spacePressed = ref(false)
|
|
83
83
|
const pointerInsideChart = ref(false)
|
|
84
|
+
let handleBeforeDataReferenceChange: () => void = () => undefined
|
|
84
85
|
let handleDataReferenceChange: () => void = () => undefined
|
|
85
86
|
const preparedSeries = usePreparedWaveformSeries(
|
|
86
87
|
() => props.data,
|
|
88
|
+
() => handleBeforeDataReferenceChange(),
|
|
87
89
|
() => handleDataReferenceChange(),
|
|
88
90
|
)
|
|
89
91
|
|
|
@@ -127,6 +129,7 @@ export function useWaveformChartController(
|
|
|
127
129
|
const {
|
|
128
130
|
chartSeries,
|
|
129
131
|
chartTracks,
|
|
132
|
+
trackLayouts,
|
|
130
133
|
gridOptions,
|
|
131
134
|
pageCount,
|
|
132
135
|
pagedTracks,
|
|
@@ -138,7 +141,6 @@ export function useWaveformChartController(
|
|
|
138
141
|
initialXDomain,
|
|
139
142
|
sharedZoomDomain,
|
|
140
143
|
resolveInitialTrackDomain,
|
|
141
|
-
trackLayouts,
|
|
142
144
|
annotationLayoutsForTrack,
|
|
143
145
|
resolveSeriesYScale,
|
|
144
146
|
} = layout
|
|
@@ -254,6 +256,7 @@ export function useWaveformChartController(
|
|
|
254
256
|
gridOptions,
|
|
255
257
|
chartSeries,
|
|
256
258
|
chartTracks,
|
|
259
|
+
trackLayouts,
|
|
257
260
|
innerWidth,
|
|
258
261
|
innerHeight,
|
|
259
262
|
activeInteractionMode,
|
|
@@ -261,6 +264,7 @@ export function useWaveformChartController(
|
|
|
261
264
|
internalHiddenSeriesIds,
|
|
262
265
|
independentTransforms,
|
|
263
266
|
independentYDomains,
|
|
267
|
+
resolveInitialTrackDomain,
|
|
264
268
|
annotationInteraction,
|
|
265
269
|
editorSeriesOptions,
|
|
266
270
|
isPresentationMode,
|
|
@@ -272,6 +276,7 @@ export function useWaveformChartController(
|
|
|
272
276
|
cancelPendingHover: hover.cancelPendingHover,
|
|
273
277
|
clearZoomBindings: zoom.clearZoomBindings,
|
|
274
278
|
})
|
|
279
|
+
handleBeforeDataReferenceChange = lifecycle.handleBeforeDataReferenceChange
|
|
275
280
|
handleDataReferenceChange = lifecycle.handleDataReferenceChange
|
|
276
281
|
|
|
277
282
|
return reactive({
|
|
@@ -11,8 +11,9 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import type { AnnotationSeriesCandidate } from '../annotation'
|
|
13
13
|
import type { useWaveformAnnotationInteraction } from '../annotation'
|
|
14
|
-
import type { DisplaySeries, DisplayTrack } from './types'
|
|
14
|
+
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
15
15
|
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
|
16
|
+
import { constrainZoomDomain, transformForDomain } from '../interaction/zoomConstraints'
|
|
16
17
|
|
|
17
18
|
interface LifecycleContext {
|
|
18
19
|
props: ResolvedWaveformChartProps
|
|
@@ -35,6 +36,7 @@ interface LifecycleContext {
|
|
|
35
36
|
gridOptions: ComputedRef<{ rowCount: number; columnCount: number }>
|
|
36
37
|
chartSeries: ComputedRef<DisplaySeries[]>
|
|
37
38
|
chartTracks: ComputedRef<DisplayTrack[]>
|
|
39
|
+
trackLayouts: ComputedRef<TrackLayout[]>
|
|
38
40
|
innerWidth: ComputedRef<number>
|
|
39
41
|
innerHeight: ComputedRef<number>
|
|
40
42
|
activeInteractionMode: ComputedRef<string | undefined>
|
|
@@ -42,6 +44,7 @@ interface LifecycleContext {
|
|
|
42
44
|
internalHiddenSeriesIds: Ref<Set<string>>
|
|
43
45
|
independentTransforms: ShallowRef<ZoomTransform[]>
|
|
44
46
|
independentYDomains: Ref<Record<number, [number, number]>>
|
|
47
|
+
resolveInitialTrackDomain: (track: TrackLayout) => [number, number]
|
|
45
48
|
annotationInteraction: ReturnType<typeof useWaveformAnnotationInteraction>
|
|
46
49
|
editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>
|
|
47
50
|
isPresentationMode: ComputedRef<boolean>
|
|
@@ -87,6 +90,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
87
90
|
gridOptions,
|
|
88
91
|
chartSeries,
|
|
89
92
|
chartTracks,
|
|
93
|
+
trackLayouts,
|
|
90
94
|
innerWidth,
|
|
91
95
|
innerHeight,
|
|
92
96
|
activeInteractionMode,
|
|
@@ -94,6 +98,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
94
98
|
internalHiddenSeriesIds,
|
|
95
99
|
independentTransforms,
|
|
96
100
|
independentYDomains,
|
|
101
|
+
resolveInitialTrackDomain,
|
|
97
102
|
annotationInteraction,
|
|
98
103
|
editorSeriesOptions,
|
|
99
104
|
isPresentationMode,
|
|
@@ -139,6 +144,27 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
139
144
|
emit('page-change', nextPage, pageCount.value)
|
|
140
145
|
}
|
|
141
146
|
|
|
147
|
+
let pendingIndependentXDomains: Array<[number, number] | undefined> | undefined
|
|
148
|
+
|
|
149
|
+
function handleBeforeDataReferenceChange() {
|
|
150
|
+
if (props.displayMode !== 'independent') return
|
|
151
|
+
pendingIndependentXDomains = trackLayouts.value.map((track) => {
|
|
152
|
+
const configuredDomain =
|
|
153
|
+
props.initialXDomains?.[track.series.trackId ?? track.series.id] ??
|
|
154
|
+
props.initialXDomains?.[track.series.id] ??
|
|
155
|
+
props.initialXDomain
|
|
156
|
+
if (
|
|
157
|
+
!configuredDomain ||
|
|
158
|
+
!Number.isFinite(configuredDomain[0]) ||
|
|
159
|
+
!Number.isFinite(configuredDomain[1]) ||
|
|
160
|
+
configuredDomain[0] === configuredDomain[1]
|
|
161
|
+
) {
|
|
162
|
+
return undefined
|
|
163
|
+
}
|
|
164
|
+
return track.xScale.domain() as [number, number]
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
function handleDataReferenceChange() {
|
|
143
169
|
if (props.displayMode === 'independent') {
|
|
144
170
|
const currentTransforms = independentTransforms.value
|
|
@@ -148,7 +174,24 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
148
174
|
}
|
|
149
175
|
clearHover()
|
|
150
176
|
editorSeriesOptions.value = []
|
|
151
|
-
void nextTick(
|
|
177
|
+
void nextTick(() => {
|
|
178
|
+
if (props.displayMode === 'independent' && pendingIndependentXDomains) {
|
|
179
|
+
const nextTransforms = chartTracks.value.map(() => zoomIdentity)
|
|
180
|
+
trackLayouts.value.forEach((track) => {
|
|
181
|
+
const previousDomain = pendingIndependentXDomains?.[track.index]
|
|
182
|
+
if (!previousDomain) return
|
|
183
|
+
const boundary = resolveInitialTrackDomain(track)
|
|
184
|
+
const domain = constrainZoomDomain(previousDomain, boundary, [track.seriesList], props)
|
|
185
|
+
nextTransforms[track.index] = transformForDomain(domain, boundary, track.width)
|
|
186
|
+
})
|
|
187
|
+
independentTransforms.value = nextTransforms
|
|
188
|
+
pendingIndependentXDomains = undefined
|
|
189
|
+
void nextTick(configureZoom)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
pendingIndependentXDomains = undefined
|
|
193
|
+
configureZoom()
|
|
194
|
+
})
|
|
152
195
|
}
|
|
153
196
|
|
|
154
197
|
watch(
|
|
@@ -158,6 +201,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
158
201
|
() => props.zoomable,
|
|
159
202
|
isPresentationMode,
|
|
160
203
|
() => props.minZoomSpan,
|
|
204
|
+
() => props.minVisiblePoints,
|
|
161
205
|
() => props.initialXDomain,
|
|
162
206
|
() => props.initialXDomains,
|
|
163
207
|
() => props.displayMode,
|
|
@@ -324,5 +368,5 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
324
368
|
editorSeriesOptions.value = []
|
|
325
369
|
})
|
|
326
370
|
|
|
327
|
-
return { goToPage, handleDataReferenceChange }
|
|
371
|
+
return { goToPage, handleBeforeDataReferenceChange, handleDataReferenceChange }
|
|
328
372
|
}
|
|
@@ -62,9 +62,14 @@ export function prepareWaveformSeries(data: WaveformData): PreparedWaveformSerie
|
|
|
62
62
|
})
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
export function usePreparedWaveformSeries(
|
|
65
|
+
export function usePreparedWaveformSeries(
|
|
66
|
+
data: () => WaveformData,
|
|
67
|
+
onBeforeDataChange: () => void,
|
|
68
|
+
onDataChange: () => void,
|
|
69
|
+
) {
|
|
66
70
|
const preparedSeries = shallowRef<PreparedWaveformSeries[]>(prepareWaveformSeries(data()))
|
|
67
71
|
watch(data, (nextData) => {
|
|
72
|
+
onBeforeDataChange()
|
|
68
73
|
preparedSeries.value = prepareWaveformSeries(nextData)
|
|
69
74
|
onDataChange()
|
|
70
75
|
})
|
|
@@ -95,16 +95,17 @@ export interface WaveformChartEmit {
|
|
|
95
95
|
(event: 'page-change', page: number, pageCount: number): void
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
interface ViewportSelectionBase {
|
|
99
99
|
trackIndex: number
|
|
100
100
|
independent: boolean
|
|
101
|
-
overlay: SVGRectElement
|
|
102
101
|
startX: number
|
|
103
102
|
startY: number
|
|
104
103
|
currentX: number
|
|
105
104
|
currentY: number
|
|
106
105
|
pointerId: number
|
|
107
|
-
mode: 'box' | 'pan'
|
|
108
106
|
xDomain: [number, number]
|
|
109
107
|
yDomains: Record<string, [number, number]>
|
|
110
108
|
}
|
|
109
|
+
|
|
110
|
+
export type ViewportSelectionState =
|
|
111
|
+
(ViewportSelectionBase & { kind: 'box' }) | (ViewportSelectionBase & { kind: 'pan' })
|
|
@@ -38,9 +38,48 @@ describe('WaveformTooltip', () => {
|
|
|
38
38
|
const tooltip = mountTooltip(100, 200).get('.waveform-tooltip')
|
|
39
39
|
|
|
40
40
|
expect(tooltip.attributes('style')).toContain('left: 8px')
|
|
41
|
+
expect(tooltip.attributes('style')).toContain('max-width: 184px')
|
|
41
42
|
expect(tooltip.attributes('style')).not.toContain('right:')
|
|
42
43
|
})
|
|
43
44
|
|
|
45
|
+
it('keeps short content content-sized while exposing the available width cap', () => {
|
|
46
|
+
const tooltip = mountTooltip(100).get('.waveform-tooltip')
|
|
47
|
+
|
|
48
|
+
expect(tooltip.attributes('style')).toContain('left: 112px')
|
|
49
|
+
expect(tooltip.attributes('style')).toContain('max-width: 280px')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('keeps long series content in a wrapping content container', () => {
|
|
53
|
+
const longName = 'ENG8KJXAc-very-long-series-name-10001'
|
|
54
|
+
const pointWithErrors = { x: 1, y: 12, error: 1, upperError: 2 }
|
|
55
|
+
const wrapper = mount(WaveformTooltip, {
|
|
56
|
+
props: {
|
|
57
|
+
visible: true,
|
|
58
|
+
position: { x: 100, y: 100 },
|
|
59
|
+
timeUnit: 'ms',
|
|
60
|
+
hoveredPoint: pointWithErrors,
|
|
61
|
+
seriesPoints: [
|
|
62
|
+
{
|
|
63
|
+
trackIndex: 0,
|
|
64
|
+
name: longName,
|
|
65
|
+
color: '#f00',
|
|
66
|
+
unit: 'very-long-unit',
|
|
67
|
+
point: pointWithErrors,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
containerWidth: 400,
|
|
71
|
+
containerHeight: 300,
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const tooltip = wrapper.get('.waveform-tooltip')
|
|
76
|
+
expect(tooltip.get('.waveform-tooltip__series-content').text()).toContain(longName)
|
|
77
|
+
expect(tooltip.get('.waveform-tooltip__series-content').classes()).toContain(
|
|
78
|
+
'waveform-tooltip__series-content',
|
|
79
|
+
)
|
|
80
|
+
expect(tooltip.attributes('style')).toContain('max-width: 280px')
|
|
81
|
+
})
|
|
82
|
+
|
|
44
83
|
it('shows resolved asymmetric errors beside the hovered value', () => {
|
|
45
84
|
const pointWithErrors = { x: 1, y: 12, error: 1, upperError: 2 }
|
|
46
85
|
const wrapper = mount(WaveformTooltip, {
|