waveform-analysis 0.1.43 → 0.1.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.js +163 -161
- package/dist/style.css +1 -1
- package/dist/types/components/core/constants.d.ts +2 -2
- package/dist/types/components/core/types.d.ts +2 -1
- package/dist/types/components/core/yAxisConstants.d.ts +2 -2
- package/dist/types/components/interaction/WaveformTooltip.vue.d.ts +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +2 -7
- package/src/components/WaveformChart.css +1 -1
- package/src/components/core/constants.ts +3 -3
- package/src/components/core/layout.test.ts +3 -3
- package/src/components/core/layout.ts +2 -2
- package/src/components/core/trackLayoutBuilder.ts +2 -2
- package/src/components/core/types.ts +2 -1
- package/src/components/core/useWaveformChartController.ts +1 -0
- package/src/components/core/yAxisConstants.ts +2 -2
- package/src/components/interaction/WaveformHoverHost.vue +3 -1
- package/src/components/interaction/WaveformTooltip.test.ts +23 -0
- package/src/components/interaction/WaveformTooltip.vue +9 -6
- package/src/components/interaction/useWaveformHover.ts +19 -14
- package/src/components/rendering/WaveformHoverLayer.vue +5 -11
- package/src/components/rendering/WaveformTrackAxes.vue +5 -4
- package/src/components/waveformChartCases/dataLifecycle.test.ts +1 -1
- package/src/components/waveformChartCases/frameGridAndFormatting.test.ts +10 -0
- package/src/components/waveformChartCases/incompleteDataHover.test.ts +67 -0
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +5 -5
- package/src/components/waveformChartCases/legendVisibility.test.ts +1 -1
- package/src/components/waveformChartCases/multiAxisAndPagination.test.ts +1 -1
- package/src/data/simulatedWaveforms.test.ts +7 -2
- package/src/data/simulatedWaveforms.ts +12 -7
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-root,.waveform-annotation-editor-root *{box-sizing:border-box}.waveform-annotation-editor-root{color:#344054;text-align:left;text-transform:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif;line-height:1.5715}.waveform-annotation-editor-root button,.waveform-annotation-editor-root input,.waveform-annotation-editor-root select,.waveform-annotation-editor-root textarea{letter-spacing:normal;text-transform:none;font-family:inherit}.waveform-annotation-editor-root button{appearance:auto}.waveform-annotation-editor__content[data-v-42466cf9]{color:#344054;gap:20px;font-size:13px;display:grid}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-42466cf9],.waveform-annotation-editor__series[data-v-42466cf9]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-42466cf9]{color:#667085}.waveform-annotation-editor__coordinate-input[data-v-42466cf9]{color:#667085;background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinate-input[data-v-42466cf9] .ant-input-number{color:#344054;width:auto;min-width:96px;font-family:SFMono-Regular,Consolas,monospace}.waveform-annotation-editor__coordinate-value[data-v-42466cf9]{display:none}.waveform-annotation-editor__coordinate-error[data-v-42466cf9]{color:#d92d20;grid-column:1/-1;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinate-hint[data-v-42466cf9]{color:#98a2b3;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinates b[data-v-42466cf9]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-42466cf9]{gap:10px}.waveform-annotation-editor__series i[data-v-42466cf9]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-42466cf9]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-42466cf9]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-42466cf9]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-42466cf9]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-42466cf9],.waveform-annotation-editor__color-field[data-v-42466cf9]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-42466cf9]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-42466cf9],.waveform-annotation-editor__colors legend[data-v-42466cf9]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-42466cf9]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-42466cf9]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-42466cf9]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-42466cf9]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-42466cf9]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-42466cf9]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}@media (width<=420px){.waveform-annotation-editor__content[data-v-42466cf9]{gap:16px}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:1fr}}.waveform-tooltip[data-v-1f55d6c8]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-1f55d6c8]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-1f55d6c8]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-1f55d6c8]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-1f55d6c8]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-1f55d6c8]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-1f55d6c8]{font-weight:600}.waveform-tooltip__value[data-v-1f55d6c8]{color:#555}.waveform-tooltip__series small[data-v-1f55d6c8]{color:#667085}.waveform-track__line[data-v-42a3fd47]{fill:none;stroke-width:1.5px;stroke-linejoin:round;stroke-linecap:round}.waveform-track__error-bar[data-v-42a3fd47]{fill:none}.waveform-track__point[data-v-42a3fd47],.waveform-track__error-bar[data-v-42a3fd47]{pointer-events:none}.waveform-track{isolation:isolate;pointer-events:none}.waveform-track__y-axis-label-bg{fill:#fff;opacity:.9;pointer-events:none}.waveform-track__y-axis-label{pointer-events:none;font-size:12px;font-weight:500}.waveform-track__grid{pointer-events:none}.waveform-track__grid--major line{stroke:#dfe5ef;stroke-width:1px}.waveform-track__grid--minor line{stroke:#f2f5fa;stroke-width:1px}.waveform-track__plot-frame,.waveform-track__plot-background{pointer-events:none}.waveform-track__watermark{fill:#1677ff1a;pointer-events:none;-webkit-user-select:none;user-select:none;font-family:Consolas,Monaco,Courier New,monospace}.waveform-track__overlay{fill:#0000;cursor:crosshair;pointer-events:all;touch-action:none}.waveform-track__no-visible-series{fill:#8c8c8c;pointer-events:none;font:13px sans-serif}.waveform-track__overlay.is-zoomable{cursor:grab}.waveform-track__overlay.is-zoomable:active{cursor:grabbing}.waveform-track__overlay.is-annotating{cursor:crosshair}.waveform-track__overlay.is-presentation{cursor:default;pointer-events:none}.waveform-track__zero-line{fill:none;pointer-events:none}.waveform-track__axis-endpoint{fill:#667085;pointer-events:none;font-size:11px}.waveform-track__axis-exponent{fill:#666;font-family:sans-serif;font-size:10px}.waveform-track__axis path,.waveform-track__axis line{stroke:#1f2937}.waveform-track__axis text{fill:#667085;font-size:11px}.waveform-legend[data-v-2ae12e71]{pointer-events:none;overflow:hidden}.waveform-legend__viewport[data-v-2ae12e71]{box-sizing:border-box;width:100%;height:100%;padding:8px;display:flex;overflow:hidden}.waveform-legend__viewport--top-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-start}.waveform-legend__viewport--top[data-v-2ae12e71]{justify-content:center;align-items:flex-start}.waveform-legend__viewport--top-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-start}.waveform-legend__viewport--right[data-v-2ae12e71]{justify-content:flex-end;align-items:center}.waveform-legend__viewport--bottom-right[data-v-2ae12e71]{justify-content:flex-end;align-items:flex-end}.waveform-legend__viewport--bottom[data-v-2ae12e71]{justify-content:center;align-items:flex-end}.waveform-legend__viewport--bottom-left[data-v-2ae12e71]{justify-content:flex-start;align-items:flex-end}.waveform-legend__viewport--left[data-v-2ae12e71]{justify-content:flex-start;align-items:center}.waveform-legend__panel[data-v-2ae12e71]{color:#344054;border:1px solid #d0d5dde6;border-radius:4px;flex:0 auto;gap:5px 12px;max-width:100%;max-height:100%;padding:5px 7px;font:12px/1.35 sans-serif;display:flex;overflow:hidden}.waveform-legend__panel--interactive[data-v-2ae12e71]{pointer-events:auto}.waveform-legend__panel--horizontal[data-v-2ae12e71]{flex-flow:wrap;align-items:center}.waveform-legend__panel--vertical[data-v-2ae12e71]{flex-flow:column;align-items:stretch}.waveform-legend__item[data-v-2ae12e71]{min-width:0;max-width:160px;color:inherit;font:inherit;text-align:left;white-space:nowrap;appearance:none;background:0 0;border:0;align-items:center;gap:6px;padding:0;display:flex}.waveform-legend__item[data-v-2ae12e71]:disabled{opacity:1}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]{cursor:pointer}.waveform-legend__panel--interactive .waveform-legend__item[data-v-2ae12e71]:focus-visible{outline-offset:2px;outline:2px solid #1677ff}.waveform-legend__item.is-hidden[data-v-2ae12e71]{opacity:.45}.waveform-legend__item.is-hidden .waveform-legend__label[data-v-2ae12e71]{text-decoration:line-through}.waveform-legend__swatch[data-v-2ae12e71]{stroke-linecap:round;stroke-linejoin:round;flex:0 0 26px;width:26px;height:16px;overflow:visible}.waveform-legend__label[data-v-2ae12e71]{text-overflow:ellipsis;min-width:0;overflow:hidden}.waveform-track__crosshair[data-v-a6f82099]{pointer-events:none}.waveform-track__crosshair line[data-v-a6f82099]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1px solid #00000014;border-radius:6px;min-width:0;min-height:180px;position:relative;overflow:hidden}.waveform-chart--clean{border-color:#0000}.waveform-chart__pagination{z-index:2;position:absolute;bottom:6px;right:10px}.waveform-chart__title-area{background:#fff;align-items:center;width:100%;min-width:0;padding:0 24px;display:flex;position:relative;overflow:hidden}.waveform-chart__title-measure{visibility:hidden;pointer-events:none;display:block;position:absolute}.waveform-chart__title-visual{flex:none;min-width:0;position:relative;overflow:visible}.waveform-chart__title-text{transform-origin:50%;line-height:1.2;display:block;position:absolute;top:50%;left:50%;overflow:visible}.waveform-chart__pagination .ant-pagination-item,.waveform-chart__pagination .ant-pagination-prev .ant-pagination-item-link,.waveform-chart__pagination .ant-pagination-next .ant-pagination-item-link{background:#fff;border-color:#d9d9d9}.waveform-chart__pagination .ant-pagination-item-active{border-color:#1677ff}.waveform-chart__grid-slot-placeholder{fill:#fafbfc;stroke:#e4e7ec;stroke-dasharray:4 4;pointer-events:none}.waveform-chart__svg{max-width:100%;display:block}.waveform-chart__overlay{fill:#0000;cursor:crosshair;touch-action:none}.waveform-chart__overlay.is-zoomable{cursor:crosshair}.waveform-chart--panning .waveform-chart__overlay.is-zoomable{cursor:grabbing}.waveform-chart__zoom-selection{fill:#1677ff24;stroke:#1677ff;stroke-width:1px;pointer-events:none}.waveform-chart__overlay.is-annotating{cursor:crosshair}.waveform-chart--presentation .waveform-chart__overlay{cursor:default;pointer-events:none}.waveform-chart__label,.waveform-chart__empty{fill:#595959;font-size:12px}
|
|
1
|
+
.waveform-annotation-layer[data-v-7254383e]{pointer-events:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]{pointer-events:auto;cursor:grab;touch-action:none}.waveform-annotation-layer--interactive .waveform-annotation[data-v-7254383e]:active{cursor:grabbing}.waveform-annotation__arrow[data-v-7254383e]{stroke-width:2px;stroke-linecap:round;pointer-events:none}.waveform-annotation__box[data-v-7254383e]{stroke-width:1px;rx:3}.waveform-annotation-layer--interactive .waveform-annotation__box[data-v-7254383e]{pointer-events:auto}.waveform-annotation__text[data-v-7254383e]{pointer-events:none;-webkit-user-select:none;user-select:none}.waveform-annotation-context-menu[data-v-c70c98a1]{z-index:30;background:#fff;border:1px solid #dfe5ef;border-radius:4px;min-width:112px;padding:4px;display:grid;position:absolute;box-shadow:0 4px 14px #10182829}.waveform-annotation-context-menu button[data-v-c70c98a1]{color:#344054;text-align:left;white-space:nowrap;cursor:pointer;background:0 0;border:0;border-radius:3px;padding:7px 10px;font-size:12px}.waveform-annotation-context-menu button[data-v-c70c98a1]:hover{background:#f2f4f7}.waveform-annotation-editor-root,.waveform-annotation-editor-root *{box-sizing:border-box}.waveform-annotation-editor-root{color:#344054;text-align:left;text-transform:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif;line-height:1.5715}.waveform-annotation-editor-root button,.waveform-annotation-editor-root input,.waveform-annotation-editor-root select,.waveform-annotation-editor-root textarea{letter-spacing:normal;text-transform:none;font-family:inherit}.waveform-annotation-editor-root button{appearance:auto}.waveform-annotation-editor__content[data-v-42466cf9]{color:#344054;gap:20px;font-size:13px;display:grid}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;display:grid}.waveform-annotation-editor__coordinates span[data-v-42466cf9],.waveform-annotation-editor__series[data-v-42466cf9]{background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinates span[data-v-42466cf9]{color:#667085}.waveform-annotation-editor__coordinate-input[data-v-42466cf9]{color:#667085;background:#f8fafc;border:1px solid #eaecf0;border-radius:7px;align-items:center;gap:8px;min-width:0;padding:9px 10px;display:flex}.waveform-annotation-editor__coordinate-input[data-v-42466cf9] .ant-input-number{color:#344054;width:auto;min-width:96px;font-family:SFMono-Regular,Consolas,monospace}.waveform-annotation-editor__coordinate-value[data-v-42466cf9]{display:none}.waveform-annotation-editor__coordinate-error[data-v-42466cf9]{color:#d92d20;grid-column:1/-1;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinate-hint[data-v-42466cf9]{color:#98a2b3;margin:-12px 0 0;font-size:11px;line-height:1.4}.waveform-annotation-editor__coordinates b[data-v-42466cf9]{color:#1677ff;font-size:11px}.waveform-annotation-editor__coordinates code[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:SFMono-Regular,Consolas,monospace;font-size:11px;overflow:hidden}.waveform-annotation-editor__series[data-v-42466cf9]{gap:10px}.waveform-annotation-editor__series i[data-v-42466cf9]{border:1px solid #0000001f;border-radius:2px;flex:0 0 12px;width:12px;height:12px}.waveform-annotation-editor__series-label[data-v-42466cf9]{gap:1px;min-width:0;display:grid}.waveform-annotation-editor__series select[data-v-42466cf9]{color:#344054;min-width:0;max-width:190px;font:inherit;background:#fff;border:1px solid #d0d5dd;border-radius:5px;margin-left:auto;padding:5px 7px}.waveform-annotation-editor__series small[data-v-42466cf9]{color:#667085;font-size:10px}.waveform-annotation-editor__series b[data-v-42466cf9]{color:#344054;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.waveform-annotation-editor__series code[data-v-42466cf9]{color:#667085;margin-left:auto;font-size:11px}.waveform-annotation-editor__field[data-v-42466cf9],.waveform-annotation-editor__color-field[data-v-42466cf9]{gap:4px;display:grid}.waveform-annotation-editor__label-row[data-v-42466cf9]{justify-content:space-between;align-items:center;gap:8px;display:flex}.waveform-annotation-editor__label-row b[data-v-42466cf9],.waveform-annotation-editor__colors legend[data-v-42466cf9]{color:#344054;font-size:12px;font-weight:600}.waveform-annotation-editor__label-row small[data-v-42466cf9]{color:#98a2b3;font-size:11px}.waveform-annotation-editor textarea[data-v-42466cf9]{box-sizing:border-box;color:#1d2939;width:100%;min-height:92px;font:inherit;resize:vertical;background:#fff;border:1px solid #d0d5dd;border-radius:7px;padding:10px 11px;line-height:1.5}.waveform-annotation-editor textarea[data-v-42466cf9]:focus{border-color:#1677ff;outline:3px solid #1677ff24}.waveform-annotation-editor__colors[data-v-42466cf9]{border:0;border-top:1px solid #eaecf0;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;min-width:0;margin:0;padding:14px 0 0;display:grid}.waveform-annotation-editor__colors legend[data-v-42466cf9]{grid-column:1/-1;padding:0}.waveform-annotation-editor__color-field[data-v-42466cf9]{color:#667085;text-align:left;place-items:stretch stretch;min-width:0;font-size:11px}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap{border:1px solid #cfd5df;border-radius:6px;width:100%;height:34px;margin-right:0;box-shadow:inset 0 0 0 1px #ffffff61}.waveform-annotation-editor__color-field[data-v-42466cf9] .vc-color-wrap:hover{border-color:#98a2b3;box-shadow:0 0 0 3px #1677ff1f}@media (width<=420px){.waveform-annotation-editor__content[data-v-42466cf9]{gap:16px}.waveform-annotation-editor__coordinates[data-v-42466cf9]{grid-template-columns:1fr}}.waveform-tooltip[data-v-b9ed144d]{box-sizing:border-box;z-index:2;color:#505050;pointer-events:none;background:#fff;border:1px solid #e3e7eb;border-radius:4px;gap:2px;width:max-content;max-width:min(560px,100% - 16px);padding:9px 12px;font:14px/1.35 Arial,sans-serif;display:grid;position:absolute;box-shadow:0 3px 10px #10182826}.waveform-tooltip__series[data-v-b9ed144d]{grid-template-columns:8px minmax(0,1fr);align-items:start;gap:6px;display:grid}.waveform-tooltip__series i[data-v-b9ed144d]{border-radius:50%;flex:none;width:8px;height:8px;margin-top:4px}.waveform-tooltip__series-content[data-v-b9ed144d]{flex-wrap:nowrap;align-items:baseline;gap:12px;min-width:0;display:flex}.waveform-tooltip__series-label[data-v-b9ed144d]{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.waveform-tooltip__value[data-v-b9ed144d]{white-space:nowrap;flex:none}.waveform-tooltip__series strong[data-v-b9ed144d]{font-weight:600}.waveform-tooltip__value[data-v-b9ed144d]{color:#555}.waveform-tooltip__series small[data-v-b9ed144d]{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-044318c2]{pointer-events:none}.waveform-track__crosshair line[data-v-044318c2]{stroke:#57617b;stroke-width:1px;stroke-dasharray:4 3}.waveform-chart{box-sizing:border-box;color:#475467;background:#fff;border:1.2px 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*/
|
|
@@ -37,11 +37,11 @@ export declare const Y_AXIS_OUTER_PADDING = 4;
|
|
|
37
37
|
/**
|
|
38
38
|
* Y轴标签间距(像素)
|
|
39
39
|
*/
|
|
40
|
-
export declare const Y_AXIS_LABEL_GAP =
|
|
40
|
+
export declare const Y_AXIS_LABEL_GAP = 0;
|
|
41
41
|
/**
|
|
42
42
|
* Y轴标签带宽度(像素)
|
|
43
43
|
*/
|
|
44
|
-
export declare const Y_AXIS_LABEL_BAND_WIDTH =
|
|
44
|
+
export declare const Y_AXIS_LABEL_BAND_WIDTH = 12;
|
|
45
45
|
/**
|
|
46
46
|
* Y轴指数标签间距(像素)
|
|
47
47
|
*/
|
|
@@ -58,11 +58,12 @@ export interface HoveredSeriesPoint {
|
|
|
58
58
|
unit?: string;
|
|
59
59
|
color: string;
|
|
60
60
|
trackIndex: number;
|
|
61
|
-
point: WaveformPoint;
|
|
61
|
+
point: WaveformPoint | null;
|
|
62
62
|
}
|
|
63
63
|
export interface WaveformHoverState {
|
|
64
64
|
points: HoveredSeriesPoint[];
|
|
65
65
|
trackIndex: number | null;
|
|
66
|
+
queryX: number | null;
|
|
66
67
|
position: {
|
|
67
68
|
x: number;
|
|
68
69
|
y: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const Y_AXIS_CHARACTER_WIDTH = 7;
|
|
2
2
|
export declare const Y_AXIS_TICK_PADDING = 7;
|
|
3
3
|
export declare const Y_AXIS_OUTER_PADDING = 4;
|
|
4
|
-
export declare const Y_AXIS_LABEL_GAP =
|
|
5
|
-
export declare const Y_AXIS_LABEL_BAND_WIDTH =
|
|
4
|
+
export declare const Y_AXIS_LABEL_GAP = 0;
|
|
5
|
+
export declare const Y_AXIS_LABEL_BAND_WIDTH = 12;
|
|
6
6
|
export declare function resolveYAxisSides(axisCount: number): Array<'left' | 'right'>;
|
package/package.json
CHANGED
package/src/App.test.ts
CHANGED
|
@@ -18,23 +18,19 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
18
18
|
? data.series[0].data.points.length
|
|
19
19
|
: 0
|
|
20
20
|
const initialPointCount = pointCount(chart.props('data') as WaveformData)
|
|
21
|
-
|
|
22
21
|
chart.vm.$emit('zoom-end', { start: 0, end: 0.001 })
|
|
23
22
|
await wrapper.get('[aria-label="重置波形视图"]').trigger('click')
|
|
24
23
|
await vi.advanceTimersByTimeAsync(100)
|
|
25
24
|
await flushPromises()
|
|
26
|
-
|
|
27
25
|
expect(pointCount(chart.props('data') as WaveformData)).toBe(initialPointCount)
|
|
28
26
|
} finally {
|
|
29
27
|
wrapper.unmount()
|
|
30
28
|
vi.useRealTimers()
|
|
31
29
|
}
|
|
32
30
|
})
|
|
33
|
-
|
|
34
31
|
it('places controls in the sidebar beside the chart', async () => {
|
|
35
32
|
const wrapper = mount(App)
|
|
36
33
|
await flushPromises()
|
|
37
|
-
|
|
38
34
|
const panel = wrapper.get('#waveform-control-panel')
|
|
39
35
|
const frameControls = panel.get('.frame-style-controls')
|
|
40
36
|
expect(panel.find('h1').exists()).toBe(false)
|
|
@@ -88,7 +84,6 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
88
84
|
expect(legendColorPicker.props('disableAlpha')).toBe(false)
|
|
89
85
|
expect(panel.text()).not.toContain('数据摘要')
|
|
90
86
|
expect(wrapper.get('.chart-panel').find('.waveform-chart').exists()).toBe(true)
|
|
91
|
-
|
|
92
87
|
wrapper.unmount()
|
|
93
88
|
})
|
|
94
89
|
|
|
@@ -262,8 +257,8 @@ describe('App workspace layout', { timeout: 20_000 }, () => {
|
|
|
262
257
|
simulatedSeries.forEach((item) => {
|
|
263
258
|
expect(item.data.kind).toBe('points')
|
|
264
259
|
if (item.data.kind === 'points') {
|
|
265
|
-
expect(item.data.points).toHaveLength(1000)
|
|
266
|
-
expect(item.data.points[0]?.x).
|
|
260
|
+
expect(item.data.points).toHaveLength(item.name === '阶跃响应' ? 500 : 1000)
|
|
261
|
+
expect(item.data.points[0]?.x).toBeCloseTo(item.name === '阶跃响应' ? 5 / 999 : -5, 10)
|
|
267
262
|
}
|
|
268
263
|
})
|
|
269
264
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// ==================== 布局常量 ====================
|
|
6
6
|
|
|
7
7
|
/** 图表边距 */
|
|
8
|
-
export const margin = { top: 18, right: 24, bottom: 52, left:
|
|
8
|
+
export const margin = { top: 18, right: 24, bottom: 52, left: 80 }
|
|
9
9
|
|
|
10
10
|
/** Distance from the drawing SVG bottom edge to the X-axis title baseline. */
|
|
11
11
|
export const X_AXIS_TITLE_BOTTOM_OFFSET = 12
|
|
@@ -44,12 +44,12 @@ export const Y_AXIS_OUTER_PADDING = 4
|
|
|
44
44
|
/**
|
|
45
45
|
* Y轴标签间距(像素)
|
|
46
46
|
*/
|
|
47
|
-
export const Y_AXIS_LABEL_GAP =
|
|
47
|
+
export const Y_AXIS_LABEL_GAP = 0
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* Y轴标签带宽度(像素)
|
|
51
51
|
*/
|
|
52
|
-
export const Y_AXIS_LABEL_BAND_WIDTH =
|
|
52
|
+
export const Y_AXIS_LABEL_BAND_WIDTH = 12
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Y轴指数标签间距(像素)
|
|
@@ -253,8 +253,8 @@ describe('multi-value Y-axis grouping', () => {
|
|
|
253
253
|
labelOffset: Math.abs(labelX - x),
|
|
254
254
|
})),
|
|
255
255
|
).toEqual([
|
|
256
|
-
{ side: 'left', labelOffset:
|
|
257
|
-
{ side: 'right', labelOffset:
|
|
256
|
+
{ side: 'left', labelOffset: 55 },
|
|
257
|
+
{ side: 'right', labelOffset: 55 },
|
|
258
258
|
])
|
|
259
259
|
})
|
|
260
260
|
|
|
@@ -262,7 +262,7 @@ describe('multi-value Y-axis grouping', () => {
|
|
|
262
262
|
const [group] = buildYAxisSeriesGroups(track([series('long', -1e120, 1e120)]), 'multi-axis')
|
|
263
263
|
|
|
264
264
|
expect(group).toBeDefined()
|
|
265
|
-
expect(measureYAxisGroupClearance(group!)).toBe(
|
|
265
|
+
expect(measureYAxisGroupClearance(group!)).toBe(72)
|
|
266
266
|
})
|
|
267
267
|
})
|
|
268
268
|
|
|
@@ -17,8 +17,8 @@ export { MAX_MULTI_Y_AXIS_COUNT } from './constants'
|
|
|
17
17
|
const Y_AXIS_CHARACTER_WIDTH = 7
|
|
18
18
|
const Y_AXIS_TICK_PADDING = 7
|
|
19
19
|
const Y_AXIS_OUTER_PADDING = 4
|
|
20
|
-
const Y_AXIS_LABEL_GAP =
|
|
21
|
-
const Y_AXIS_LABEL_BAND_WIDTH =
|
|
20
|
+
const Y_AXIS_LABEL_GAP = 0
|
|
21
|
+
const Y_AXIS_LABEL_BAND_WIDTH = 12
|
|
22
22
|
|
|
23
23
|
export function resolveYAxisTickCount(_plotHeight: number, splitNumber?: number): number {
|
|
24
24
|
if (typeof splitNumber === 'number' && Number.isFinite(splitNumber)) {
|
|
@@ -162,7 +162,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
162
162
|
labelX,
|
|
163
163
|
scale,
|
|
164
164
|
majorTicks,
|
|
165
|
-
minorTicks: buildMinorTicks(majorTicks),
|
|
165
|
+
minorTicks: buildMinorTicks(majorTicks, 2),
|
|
166
166
|
tickValues,
|
|
167
167
|
seriesList: group.seriesList,
|
|
168
168
|
}
|
|
@@ -248,7 +248,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
248
248
|
yScale,
|
|
249
249
|
yAxes,
|
|
250
250
|
xMajorTicks,
|
|
251
|
-
xMinorTicks: buildMinorTicks(xMajorTicks,
|
|
251
|
+
xMinorTicks: buildMinorTicks(xMajorTicks, 3, domain),
|
|
252
252
|
yMajorTicks,
|
|
253
253
|
yMinorTicks: yAxes[0]?.minorTicks ?? [],
|
|
254
254
|
yAxisTickValues,
|
|
@@ -69,12 +69,13 @@ export interface HoveredSeriesPoint {
|
|
|
69
69
|
unit?: string
|
|
70
70
|
color: string
|
|
71
71
|
trackIndex: number
|
|
72
|
-
point: WaveformPoint
|
|
72
|
+
point: WaveformPoint | null
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export interface WaveformHoverState {
|
|
76
76
|
points: HoveredSeriesPoint[]
|
|
77
77
|
trackIndex: number | null
|
|
78
|
+
queryX: number | null
|
|
78
79
|
position: { x: number; y: number }
|
|
79
80
|
}
|
|
80
81
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const Y_AXIS_CHARACTER_WIDTH = 7
|
|
2
2
|
export const Y_AXIS_TICK_PADDING = 7
|
|
3
3
|
export const Y_AXIS_OUTER_PADDING = 4
|
|
4
|
-
export const Y_AXIS_LABEL_GAP =
|
|
5
|
-
export const Y_AXIS_LABEL_BAND_WIDTH =
|
|
4
|
+
export const Y_AXIS_LABEL_GAP = 0
|
|
5
|
+
export const Y_AXIS_LABEL_BAND_WIDTH = 12
|
|
6
6
|
|
|
7
7
|
export function resolveYAxisSides(axisCount: number): Array<'left' | 'right'> {
|
|
8
8
|
if (axisCount >= 4) return ['left', 'left', 'right', 'right']
|
|
@@ -12,7 +12,9 @@ const props = defineProps<{
|
|
|
12
12
|
containerHeight: number
|
|
13
13
|
}>()
|
|
14
14
|
|
|
15
|
-
const hoveredPoint = computed(
|
|
15
|
+
const hoveredPoint = computed(
|
|
16
|
+
() => props.state.points.find((seriesPoint) => seriesPoint.point)?.point ?? null,
|
|
17
|
+
)
|
|
16
18
|
</script>
|
|
17
19
|
|
|
18
20
|
<template>
|
|
@@ -170,4 +170,27 @@ describe('WaveformTooltip', () => {
|
|
|
170
170
|
|
|
171
171
|
expect(wrapper.get('.waveform-tooltip__series-label').text()).toBe('未配置炮号: 温度')
|
|
172
172
|
})
|
|
173
|
+
|
|
174
|
+
it('uses each series sample time and marks unavailable series without synthesizing values', () => {
|
|
175
|
+
const wrapper = mount(WaveformTooltip, {
|
|
176
|
+
props: {
|
|
177
|
+
visible: true,
|
|
178
|
+
position: { x: 10, y: 10 },
|
|
179
|
+
timeUnit: 's',
|
|
180
|
+
hoveredPoint: { x: 1, y: 12 },
|
|
181
|
+
seriesPoints: [
|
|
182
|
+
{ trackIndex: 0, name: '完整数据', color: '#f00', point: { x: 1, y: 12 } },
|
|
183
|
+
{ trackIndex: 1, name: '异步采样', color: '#0a0', point: { x: 1.25, y: 8 } },
|
|
184
|
+
{ trackIndex: 2, name: '缺失数据', color: '#00f', point: null },
|
|
185
|
+
],
|
|
186
|
+
containerWidth: 400,
|
|
187
|
+
containerHeight: 300,
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const values = wrapper
|
|
192
|
+
.findAll('.waveform-tooltip__value')
|
|
193
|
+
.map((value) => value.text())
|
|
194
|
+
expect(values).toEqual(['(x:1 y:12)', '(x:1.25 y:8)', '无数据'])
|
|
195
|
+
})
|
|
173
196
|
})
|
|
@@ -9,7 +9,7 @@ interface SeriesPoint {
|
|
|
9
9
|
shotNo?: string
|
|
10
10
|
color: string
|
|
11
11
|
unit?: string
|
|
12
|
-
point: WaveformPoint
|
|
12
|
+
point: WaveformPoint | null
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
@@ -45,10 +45,12 @@ function estimateLineCount(text: string, width: number): number {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
function formatSeriesText(seriesPoint: SeriesPoint): string {
|
|
48
|
-
const
|
|
48
|
+
const valueText = seriesPoint.point
|
|
49
|
+
? `(x:${formatTooltipTime(seriesPoint.point.x, props.timeUnit)} y:${formatTooltipNumber(seriesPoint.point.y)})`
|
|
50
|
+
: '无数据'
|
|
49
51
|
return `${seriesPoint.shotNo?.trim() || '未配置炮号'}: ${seriesPoint.name}${
|
|
50
52
|
seriesPoint.unit ? `(${seriesPoint.unit})` : ''
|
|
51
|
-
}
|
|
53
|
+
} ${valueText}`
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function estimateTooltipHeight(width: number): number {
|
|
@@ -114,9 +116,10 @@ const tooltipStyle = computed(() => {
|
|
|
114
116
|
<span class="waveform-tooltip__series-label">{{ seriesPoint.shotNo?.trim() || '未配置炮号' }}: {{
|
|
115
117
|
seriesPoint.name
|
|
116
118
|
}}<template v-if="seriesPoint.unit">({{ seriesPoint.unit }})</template></span>
|
|
117
|
-
<span class="waveform-tooltip__value">(x:{{
|
|
118
|
-
|
|
119
|
-
}})</span>
|
|
119
|
+
<span v-if="seriesPoint.point" class="waveform-tooltip__value">(x:{{
|
|
120
|
+
formatTooltipTime(seriesPoint.point.x, timeUnit)
|
|
121
|
+
}} y:{{ formatTooltipNumber(seriesPoint.point.y) }})</span>
|
|
122
|
+
<span v-else class="waveform-tooltip__value waveform-tooltip__value--missing">无数据</span>
|
|
120
123
|
</span>
|
|
121
124
|
</span>
|
|
122
125
|
</div>
|
|
@@ -62,18 +62,21 @@ export function useWaveformHover(context: HoverContext) {
|
|
|
62
62
|
const commitHover = (
|
|
63
63
|
nextPoints: HoveredSeriesPoint[],
|
|
64
64
|
trackIndex: number | null,
|
|
65
|
+
queryX: number,
|
|
65
66
|
position: { x: number; y: number },
|
|
66
67
|
) => {
|
|
67
68
|
if (isPresentationMode.value) return
|
|
68
69
|
if (!hoveredPointsMatch(nextPoints)) hoverState.points = nextPoints
|
|
69
70
|
hoverState.trackIndex = trackIndex
|
|
71
|
+
hoverState.queryX = queryX
|
|
70
72
|
hoverState.position = position
|
|
71
|
-
emit('point-hover', hoverState.points
|
|
73
|
+
emit('point-hover', hoverState.points.find((point) => point.point)?.point ?? null)
|
|
72
74
|
}
|
|
73
75
|
const clearHover = () => {
|
|
74
76
|
cancelPendingHover()
|
|
75
77
|
hoverState.points = []
|
|
76
78
|
hoverState.trackIndex = null
|
|
79
|
+
hoverState.queryX = null
|
|
77
80
|
emit('point-hover', null)
|
|
78
81
|
}
|
|
79
82
|
const handlePointerLeave = () => {
|
|
@@ -82,7 +85,7 @@ export function useWaveformHover(context: HoverContext) {
|
|
|
82
85
|
const createHoveredSeriesPoint = (
|
|
83
86
|
series: DisplaySeries,
|
|
84
87
|
trackIndex: number,
|
|
85
|
-
point: WaveformPoint,
|
|
88
|
+
point: WaveformPoint | null,
|
|
86
89
|
): HoveredSeriesPoint => ({
|
|
87
90
|
id: series.id,
|
|
88
91
|
shotNo: series.shotNo,
|
|
@@ -106,8 +109,12 @@ export function useWaveformHover(context: HoverContext) {
|
|
|
106
109
|
clearHover()
|
|
107
110
|
return true
|
|
108
111
|
}
|
|
109
|
-
const nearestPoint = (series: DisplaySeries, xValue: number): WaveformPoint | undefined =>
|
|
110
|
-
series.points[
|
|
112
|
+
const nearestPoint = (series: DisplaySeries, xValue: number): WaveformPoint | undefined => {
|
|
113
|
+
const firstPoint = series.points[0]
|
|
114
|
+
const lastPoint = series.points.at(-1)
|
|
115
|
+
if (!firstPoint || !lastPoint || xValue < firstPoint.x || xValue > lastPoint.x) return undefined
|
|
116
|
+
return series.points[pointBisector.center(series.points, xValue)]
|
|
117
|
+
}
|
|
111
118
|
|
|
112
119
|
const handleIndependentPointerMove = (event: PointerEvent, trackIndex: number) => {
|
|
113
120
|
if (isPresentationMode.value) return
|
|
@@ -125,11 +132,10 @@ export function useWaveformHover(context: HoverContext) {
|
|
|
125
132
|
const currentTrack = trackLayouts.value[trackIndex]
|
|
126
133
|
if (!currentTrack || !currentTrack.hasVisibleSeries || currentTrack !== track) return
|
|
127
134
|
const xValue = track.xScale.invert(Math.max(0, Math.min(innerWidth.value, pointerX)))
|
|
128
|
-
const nextPoints = track.seriesList.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
commitHover(nextPoints, trackIndex, {
|
|
135
|
+
const nextPoints = track.seriesList.map((series) =>
|
|
136
|
+
createHoveredSeriesPoint(series, trackIndex, nearestPoint(series, xValue) ?? null),
|
|
137
|
+
)
|
|
138
|
+
commitHover(nextPoints, trackIndex, xValue, {
|
|
133
139
|
x: resolvedChartLeftMargin.value + track.left + pointerX,
|
|
134
140
|
y: titleAreaHeight.value + chartTopMargin.value + track.top + pointerY,
|
|
135
141
|
})
|
|
@@ -155,12 +161,11 @@ export function useWaveformHover(context: HoverContext) {
|
|
|
155
161
|
)
|
|
156
162
|
const xValue = referenceTrack.xScale.invert(localPointerX)
|
|
157
163
|
const nextPoints = trackLayouts.value.flatMap((track) =>
|
|
158
|
-
track.seriesList.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}),
|
|
164
|
+
track.seriesList.map((series) =>
|
|
165
|
+
createHoveredSeriesPoint(series, track.index, nearestPoint(series, xValue) ?? null),
|
|
166
|
+
),
|
|
162
167
|
)
|
|
163
|
-
commitHover(nextPoints, null, {
|
|
168
|
+
commitHover(nextPoints, null, xValue, {
|
|
164
169
|
x: resolvedChartLeftMargin.value + pointerX,
|
|
165
170
|
y: titleAreaHeight.value + chartTopMargin.value + pointerY,
|
|
166
171
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue'
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { TrackLayout, WaveformHoverState } from '../core/types'
|
|
5
5
|
|
|
6
6
|
const props = defineProps<{
|
|
7
7
|
state: WaveformHoverState
|
|
@@ -11,23 +11,17 @@ const props = defineProps<{
|
|
|
11
11
|
}>()
|
|
12
12
|
|
|
13
13
|
interface Crosshair {
|
|
14
|
-
point: HoveredSeriesPoint
|
|
15
14
|
track: TrackLayout
|
|
16
15
|
x: number
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const crosshairs = computed<Crosshair[]>(() => {
|
|
20
|
-
if (!props.visible) return []
|
|
21
|
-
|
|
22
|
-
const pointByTrack = new Map<number, HoveredSeriesPoint>()
|
|
23
|
-
props.state.points.forEach((point) => {
|
|
24
|
-
if (!pointByTrack.has(point.trackIndex)) pointByTrack.set(point.trackIndex, point)
|
|
25
|
-
})
|
|
19
|
+
if (!props.visible || props.state.queryX === null) return []
|
|
26
20
|
|
|
27
21
|
return props.tracks.flatMap((track) => {
|
|
28
|
-
const
|
|
29
|
-
return
|
|
30
|
-
? [{
|
|
22
|
+
const trackIsActive = props.state.trackIndex === null || props.state.trackIndex === track.index
|
|
23
|
+
return trackIsActive && !track.isEmpty && track.hasVisibleSeries
|
|
24
|
+
? [{ track, x: track.xScale(props.state.queryX!) }]
|
|
31
25
|
: []
|
|
32
26
|
})
|
|
33
27
|
})
|
|
@@ -5,6 +5,7 @@ import { nextTick, onMounted, ref, watch } from 'vue'
|
|
|
5
5
|
import type { WaveformAxesOptions } from '../../types'
|
|
6
6
|
import { formatScientificAxisLabel, formatXAxisLabel } from '../../utils'
|
|
7
7
|
import type { DisplaySeries, TrackLayout, WaveformYAxisLayout } from '../core/types'
|
|
8
|
+
import { Y_AXIS_LABEL_BAND_WIDTH } from '../core/yAxisConstants'
|
|
8
9
|
|
|
9
10
|
interface Props {
|
|
10
11
|
track: TrackLayout
|
|
@@ -169,9 +170,9 @@ watch(
|
|
|
169
170
|
>
|
|
170
171
|
<rect
|
|
171
172
|
class="waveform-track__y-axis-label-bg waveform-chart__y-axis-label-bg"
|
|
172
|
-
:x="track.yAxisLabelX -
|
|
173
|
+
:x="track.yAxisLabelX - Y_AXIS_LABEL_BAND_WIDTH / 2"
|
|
173
174
|
:y="track.height / 2 - 40"
|
|
174
|
-
width="
|
|
175
|
+
:width="Y_AXIS_LABEL_BAND_WIDTH"
|
|
175
176
|
height="80"
|
|
176
177
|
rx="2"
|
|
177
178
|
/>
|
|
@@ -194,9 +195,9 @@ watch(
|
|
|
194
195
|
>
|
|
195
196
|
<rect
|
|
196
197
|
class="waveform-track__y-axis-label-bg waveform-chart__y-axis-label-bg"
|
|
197
|
-
:x="axis.labelX -
|
|
198
|
+
:x="axis.labelX - Y_AXIS_LABEL_BAND_WIDTH / 2"
|
|
198
199
|
:y="track.height / 2 - 40"
|
|
199
|
-
width="
|
|
200
|
+
:width="Y_AXIS_LABEL_BAND_WIDTH"
|
|
200
201
|
height="80"
|
|
201
202
|
rx="2"
|
|
202
203
|
/>
|
|
@@ -170,7 +170,7 @@ describe('WaveformChart', () => {
|
|
|
170
170
|
)
|
|
171
171
|
expect(labelX).toBeLessThan(-46)
|
|
172
172
|
expect(Number(wrapper.get('.waveform-chart__y-axis-label-bg').attributes('x'))).toBe(
|
|
173
|
-
labelX -
|
|
173
|
+
labelX - 6,
|
|
174
174
|
)
|
|
175
175
|
expect(yAxisLabels.every((label) => !label.text().includes('(T)'))).toBe(true)
|
|
176
176
|
expect(wrapper.findAll('.waveform-chart__track-label')).toHaveLength(0)
|
|
@@ -22,6 +22,16 @@ describe('WaveformChart', () => {
|
|
|
22
22
|
|
|
23
23
|
expect(wrapper.findAll('.waveform-chart__grid--major line').length).toBeGreaterThan(0)
|
|
24
24
|
expect(wrapper.findAll('.waveform-chart__grid--minor line').length).toBeGreaterThan(0)
|
|
25
|
+
expect(
|
|
26
|
+
wrapper.findAll('.waveform-chart__grid--major [data-grid-direction="horizontal"]'),
|
|
27
|
+
).toHaveLength(5)
|
|
28
|
+
expect(
|
|
29
|
+
wrapper.findAll('.waveform-chart__grid--minor [data-grid-direction="horizontal"]'),
|
|
30
|
+
).toHaveLength(4)
|
|
31
|
+
expect(
|
|
32
|
+
wrapper.findAll('.waveform-chart__grid--minor [data-grid-direction="vertical"]'),
|
|
33
|
+
).not.toHaveLength(0)
|
|
34
|
+
expect(getComputedStyle(wrapper.element).borderTopWidth).toBe('1.2px')
|
|
25
35
|
expect(wrapper.find('.waveform-chart__plot-frame').exists()).toBe(true)
|
|
26
36
|
expect(wrapper.get('.waveform-chart__plot-frame').attributes()).toMatchObject({
|
|
27
37
|
fill: 'none',
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { flushPromises } from '@vue/test-utils'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { flushAnimationFrames } from '../../test/setup'
|
|
5
|
+
import { mountSizedChart } from '../../test/waveformChart'
|
|
6
|
+
|
|
7
|
+
describe('WaveformChart incomplete data hover', () => {
|
|
8
|
+
it('keeps the shared crosshair at the query time and marks out-of-range series unavailable', async () => {
|
|
9
|
+
const wrapper = await mountSizedChart(
|
|
10
|
+
{
|
|
11
|
+
kind: 'series',
|
|
12
|
+
series: [
|
|
13
|
+
{
|
|
14
|
+
name: '完整数据',
|
|
15
|
+
data: {
|
|
16
|
+
kind: 'points',
|
|
17
|
+
points: [
|
|
18
|
+
{ x: -1, y: 1 },
|
|
19
|
+
{ x: -0.5, y: 2 },
|
|
20
|
+
{ x: 1, y: 3 },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: '从零开始',
|
|
26
|
+
data: {
|
|
27
|
+
kind: 'points',
|
|
28
|
+
points: [
|
|
29
|
+
{ x: 0, y: 10 },
|
|
30
|
+
{ x: 1, y: 20 },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{ displayMode: 'separated' },
|
|
37
|
+
)
|
|
38
|
+
const overlay = wrapper.get('.waveform-chart__overlay--shared')
|
|
39
|
+
const overlayWidth = Number(overlay.attributes('width'))
|
|
40
|
+
Object.defineProperty(overlay.element, 'getBoundingClientRect', {
|
|
41
|
+
value: () => ({ left: 0, top: 0, width: overlayWidth, height: 290 }),
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
overlay.element.dispatchEvent(
|
|
45
|
+
new MouseEvent('pointermove', {
|
|
46
|
+
clientX: overlayWidth / 4,
|
|
47
|
+
clientY: 120,
|
|
48
|
+
bubbles: true,
|
|
49
|
+
}),
|
|
50
|
+
)
|
|
51
|
+
flushAnimationFrames()
|
|
52
|
+
await flushPromises()
|
|
53
|
+
|
|
54
|
+
const crosshairPositions = wrapper
|
|
55
|
+
.findAll('.waveform-chart__crosshair line')
|
|
56
|
+
.map((line) => Number(line.attributes('x1')))
|
|
57
|
+
expect(crosshairPositions).toHaveLength(2)
|
|
58
|
+
expect(crosshairPositions[0]).toBeCloseTo(overlayWidth / 4)
|
|
59
|
+
expect(crosshairPositions[1]).toBeCloseTo(overlayWidth / 4)
|
|
60
|
+
const tooltipRows = wrapper.findAll('.waveform-chart__tooltip-series')
|
|
61
|
+
expect(tooltipRows).toHaveLength(2)
|
|
62
|
+
expect(tooltipRows[0].text()).toContain('(x:-500 y:2)')
|
|
63
|
+
expect(tooltipRows[1].text()).toContain('无数据')
|
|
64
|
+
expect(tooltipRows[1].text()).not.toContain('x:')
|
|
65
|
+
expect(wrapper.emitted('point-hover')?.at(-1)).toEqual([{ x: -0.5, y: 2 }])
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -53,10 +53,10 @@ describe('WaveformChart', () => {
|
|
|
53
53
|
tracks[0].get('.waveform-chart__y-axis-label-bg').attributes('x'),
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
-
expect(labelX).toBe(-
|
|
57
|
-
expect(labelBackgroundX).toBe(labelX -
|
|
58
|
-
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(
|
|
59
|
-
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(
|
|
56
|
+
expect(labelX).toBe(-62)
|
|
57
|
+
expect(labelBackgroundX).toBe(labelX - 6)
|
|
58
|
+
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(80)
|
|
59
|
+
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(72)
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
it('keeps a tick-only gutter when channel labels are empty', async () => {
|
|
@@ -82,7 +82,7 @@ describe('WaveformChart', () => {
|
|
|
82
82
|
const secondLeft = Number(tracks[1].attributes('data-track-left'))
|
|
83
83
|
|
|
84
84
|
expect(wrapper.findAll('.waveform-chart__y-axis-label')).toHaveLength(0)
|
|
85
|
-
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(
|
|
85
|
+
expect(Number(wrapper.attributes('data-chart-left-margin'))).toBe(80)
|
|
86
86
|
expect(secondLeft - firstWidth).toBeGreaterThanOrEqual(60)
|
|
87
87
|
})
|
|
88
88
|
|
|
@@ -343,7 +343,7 @@ describe('WaveformChart', () => {
|
|
|
343
343
|
const secondLeft = Number(tracks[1].attributes('data-track-left'))
|
|
344
344
|
const labelX = Number(tracks[1].attributes('data-y-axis-label-x'))
|
|
345
345
|
|
|
346
|
-
expect(secondLeft - (firstLeft + firstWidth)).toBeGreaterThanOrEqual(Math.abs(labelX) +
|
|
346
|
+
expect(secondLeft - (firstLeft + firstWidth)).toBeGreaterThanOrEqual(Math.abs(labelX) + 6)
|
|
347
347
|
expect(tracks[1].find('.waveform-chart__y-axis-label-bg').exists()).toBe(true)
|
|
348
348
|
}
|
|
349
349
|
})
|