diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..fe1a61f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,57 @@
+# Repository Guidelines
+
+## Project Structure & Module Organization
+
+This repository is a Vue 3 + TypeScript waveform component library with a Vite demo.
+Production exports are defined in `src/index.ts`; demo entry points are `src/main.ts` and
+`src/App.vue`. The main chart is `src/components/WaveformChart.vue`, with focused modules under
+`src/components/{core,data,rendering,interaction,annotation}`. Shared types live in `src/types`,
+data normalization and chart logic in `src/core` and `src/utils`, styles in `src/styles.css`, and
+sample data in `src/data`. Tests are colocated with implementation files (`*.test.ts`), with shared
+setup in `src/test/setup.ts`. `dist/` and `dist-demo/` are generated; do not edit them.
+
+## Build, Test, and Development Commands
+
+Use pnpm (the lockfile is `pnpm-lock.yaml`) and Node.js 22 as CI does.
+
+```bash
+pnpm install # Install locked dependencies
+pnpm dev # Start the Vite demo server
+pnpm typecheck # Run vue-tsc checks
+pnpm lint # Run ESLint with zero warnings allowed
+pnpm test # Run Vitest once
+pnpm test:coverage # Run tests and enforce coverage thresholds
+pnpm build # Type-check and build library plus demo bundles
+pnpm preview # Preview the production demo build
+```
+
+Run `pnpm format` to apply the repository Prettier configuration.
+
+## Coding Style & Naming Conventions
+
+Use TypeScript and Vue 3 Composition API with two-space indentation, single quotes, no semicolons,
+and a 100-column print width. Prettier and ESLint are authoritative.
+Use PascalCase for Vue components, component filenames, and types; use camelCase for functions,
+variables, and composables (for example, `useWaveformData`). Keep public exports deliberate and
+preserve stable series IDs for multi-channel data.
+
+## Testing Guidelines
+
+Vitest with `@vue/test-utils` and jsdom is used. Name tests `*.test.ts` beside the
+code they cover. Exercise normalization, rendering/layout helpers, formatting, and component
+interactions, including empty, non-finite, and multi-series inputs. Coverage thresholds are 80%
+for lines/statements/functions and 75% for branches; run `pnpm test:coverage` before submitting.
+
+## Commit & Pull Request Guidelines
+
+The current history contains only `first commit`, so no established convention exists yet. Use short,
+imperative messages, preferably scoped (for example, `feat(chart): ...`, `fix(annotation): ...`, or
+`test: ...`). Pull requests should explain API or user-visible changes, list verification commands,
+link an issue or plan, and include screenshots or a short recording for visual changes. Keep generated
+files and unrelated refactors out of the change.
+
+## CI and Configuration
+
+GitHub Actions runs install, typecheck, lint, coverage, build, and `pnpm pack --dry-run` on pushes and
+pull requests. Do not commit secrets or local environment files; review the staged file list before
+opening a pull request.
diff --git a/README.md b/README.md
index 7916952..bdaa85a 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,26 @@ import { WaveformChart } from './index'
多通道数据应为每个 `WaveformSeries` 提供稳定的 `id`。内部时间坐标始终使用秒,
`timeUnit` 只控制坐标轴和 tooltip 的显示单位。
+### 绘图区域尺寸
+
+`width` 和 `height` 接收像素数值,并且可以独立设置。指定的维度使用固定尺寸,未指定的
+维度自适应填满父容器:
+
+```vue
+
+
+
+
+
+```
+
+自适应高度要求父容器具有明确高度;父容器未定高时,组件使用最低 `180px` 高度。
+显式高度同样保留 `180px` 下限。非有限尺寸按未指定处理,负宽度归零。
+
## 大数据渲染
组件按不可变数据处理:替换 `data` 引用会重新过滤、排序和缓存坐标域,并重置视口;
diff --git a/src/App.vue b/src/App.vue
index b333283..9adbf10 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,6 +1,6 @@
@@ -149,7 +133,6 @@ onBeforeUnmount(() => {
:data="chartData"
:display-mode="displayMode"
:grid="{ rowCount, columnCount, showPagination: true }"
- :height="chartHeight"
:frame-number="1"
v-model:annotations="annotations"
v-model:annotations-visible="annotationsVisible"
diff --git a/src/components/WaveformChart.test.ts b/src/components/WaveformChart.test.ts
index d05504c..541dd23 100644
--- a/src/components/WaveformChart.test.ts
+++ b/src/components/WaveformChart.test.ts
@@ -297,6 +297,64 @@ describe('WaveformChart', () => {
}
})
+ it('keeps unused compact cells visually empty while preserving bottom X axes', async () => {
+ const data = gridSeries(4)
+ if (data.kind === 'series') {
+ data.series.forEach((series, index) => {
+ series.data = {
+ kind: 'points',
+ points: [
+ { x: 100, y: index },
+ { x: 200, y: index + 1 },
+ ],
+ }
+ })
+ }
+ const wrapper = await mountSizedChart(data, {
+ displayMode: 'compact',
+ grid: { rowCount: 2, columnCount: 3 },
+ })
+
+ const tracks = wrapper.findAll('.waveform-chart__track')
+ const emptyTracks = wrapper.findAll('.waveform-chart__track--empty')
+ const firstTrackTop = Number(tracks[0].attributes('data-track-top'))
+ const firstTrackHeight = Number(tracks[0].attributes('data-track-height'))
+ const secondRowFirstTrackTop = Number(tracks[3].attributes('data-track-top'))
+
+ expect(tracks).toHaveLength(6)
+ expect(emptyTracks).toHaveLength(2)
+ expect(wrapper.find('.waveform-chart__grid-slot-placeholder').exists()).toBe(false)
+ expect(secondRowFirstTrackTop).toBe(firstTrackTop + firstTrackHeight)
+ expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(3)
+ expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(4)
+ expect(emptyTracks[0].findAll('.waveform-chart__grid')).toHaveLength(0)
+ expect(emptyTracks[0].find('.waveform-chart__plot-frame').exists()).toBe(false)
+ expect(emptyTracks[0].find('.waveform-chart__axis--y').exists()).toBe(false)
+ expect(emptyTracks[0].find('.waveform-chart__line').exists()).toBe(false)
+ expect(emptyTracks[0].find('.waveform-chart__y-axis-label').exists()).toBe(false)
+ expect(emptyTracks[0].find('.waveform-chart__watermark').exists()).toBe(false)
+
+ const populatedBottomTrack = tracks[3]
+ expect(emptyTracks[0].get('.waveform-chart__axis-endpoint--start').text()).toBe(
+ populatedBottomTrack.get('.waveform-chart__axis-endpoint--start').text(),
+ )
+ expect(emptyTracks[0].get('.waveform-chart__axis-endpoint--end').text()).toBe(
+ populatedBottomTrack.get('.waveform-chart__axis-endpoint--end').text(),
+ )
+ })
+
+ it('shows the global empty state when compact data has no valid channels', async () => {
+ const wrapper = await mountSizedChart(
+ { kind: 'samples', values: [1], sampleRate: -1 },
+ { displayMode: 'compact', grid: { rowCount: 2, columnCount: 3 } },
+ )
+
+ expect(wrapper.get('.waveform-chart__empty').text()).toContain('暂无有效波形数据')
+ expect(wrapper.findAll('.waveform-chart__track')).toHaveLength(0)
+ expect(wrapper.findAll('.waveform-chart__axis--x')).toHaveLength(0)
+ expect(wrapper.findAll('.waveform-chart__line')).toHaveLength(0)
+ })
+
it('resets the page when the grid configuration changes', async () => {
const wrapper = await mountSizedChart(gridSeries(5), {
grid: { rowCount: 1, columnCount: 1 },
@@ -352,6 +410,108 @@ describe('WaveformChart', () => {
expect(wrapper.get('.waveform-chart__line').attributes('d')).not.toBe(initialPath)
})
+ it('uses fixed dimensions when both width and height are specified', async () => {
+ const wrapper = mount(WaveformChart, {
+ props: {
+ data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
+ width: 640,
+ height: 420,
+ },
+ })
+
+ expect(wrapper.attributes('style')).toContain('width: 640px')
+ expect(wrapper.attributes('style')).toContain('height: 420px')
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
+
+ resizeObservers.at(-1)?.resize(640, 420)
+ await flushPromises()
+
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
+ })
+
+ it('allows fixed width and adaptive height to work independently', async () => {
+ const wrapper = mount(WaveformChart, {
+ props: {
+ data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
+ width: 640,
+ },
+ })
+
+ expect(wrapper.attributes('style')).toContain('width: 640px')
+ expect(wrapper.attributes('style')).toContain('height: 100%')
+
+ resizeObservers.at(-1)?.resize(640, 500)
+ await flushPromises()
+
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('640')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('500')
+ })
+
+ it('allows adaptive width and fixed height to work independently', async () => {
+ const wrapper = mount(WaveformChart, {
+ props: {
+ data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
+ height: 420,
+ },
+ })
+
+ expect(wrapper.attributes('style')).toContain('width: 100%')
+ expect(wrapper.attributes('style')).toContain('height: 420px')
+
+ resizeObservers.at(-1)?.resize(900, 420)
+ await flushPromises()
+
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('900')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('420')
+ })
+
+ it('fills both dimensions and responds to container size changes by default', async () => {
+ const wrapper = mount(WaveformChart, {
+ props: { data: { kind: 'samples', values: [0, 1], sampleRate: 1 } },
+ })
+
+ expect(wrapper.attributes('style')).toContain('width: 100%')
+ expect(wrapper.attributes('style')).toContain('height: 100%')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('180')
+
+ resizeObservers.at(-1)?.resize(800, 520)
+ await flushPromises()
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('800')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('520')
+
+ resizeObservers.at(-1)?.resize(500, 300)
+ await flushPromises()
+ expect(wrapper.get('.waveform-chart__svg').attributes('width')).toBe('500')
+ expect(wrapper.get('.waveform-chart__svg').attributes('height')).toBe('300')
+ })
+
+ it('applies size fallbacks for minimum, negative, and non-finite values', async () => {
+ const minimumWrapper = mount(WaveformChart, {
+ props: {
+ data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
+ width: -20,
+ height: -20,
+ },
+ })
+
+ expect(minimumWrapper.attributes('style')).toContain('width: 0px')
+ expect(minimumWrapper.attributes('style')).toContain('height: 180px')
+ expect(minimumWrapper.get('.waveform-chart__svg').attributes('height')).toBe('180')
+
+ const adaptiveWrapper = mount(WaveformChart, {
+ props: {
+ data: { kind: 'samples', values: [0, 1], sampleRate: 1 },
+ width: Number.POSITIVE_INFINITY,
+ height: Number.NaN,
+ },
+ })
+
+ expect(adaptiveWrapper.attributes('style')).toContain('width: 100%')
+ expect(adaptiveWrapper.attributes('style')).toContain('height: 100%')
+ })
+
it('keeps a 100k-point SVG path bounded by the plot width', async () => {
const sourcePoints = Array.from({ length: 100_000 }, (_, index) => ({
x: index / 1_000,
@@ -432,6 +592,32 @@ describe('WaveformChart', () => {
expect(wrapper.get('.waveform-chart__line').attributes('stroke')).toBe('#0960bd')
})
+ it('continues minor x-grid lines beyond the final major tick to the exact endpoint', async () => {
+ const wrapper = await mountSizedChart({
+ kind: 'points',
+ points: [
+ { x: -8, y: 0 },
+ { x: 4.9903, y: 1 },
+ ],
+ })
+ const majorGridPositions = wrapper
+ .findAll('.waveform-chart__grid--major line')
+ .map((line) => Number(line.attributes('x1')))
+ .filter(Number.isFinite)
+ const minorGridPositions = wrapper
+ .findAll('.waveform-chart__grid--minor line')
+ .map((line) => Number(line.attributes('x1')))
+ .filter(Number.isFinite)
+ const trackWidth = Number(wrapper.get('.waveform-chart__track').attributes('data-track-width'))
+
+ expect(Math.max(...minorGridPositions)).toBeGreaterThan(Math.max(...majorGridPositions))
+ expect(Math.max(...minorGridPositions)).toBeLessThan(trackWidth)
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').attributes('x')).toBe(
+ String(trackWidth),
+ )
+ expect(wrapper.get('.waveform-chart__axis-endpoint--end').text()).toBe('4,990')
+ })
+
it('uses one shared scientific exponent only for large and tiny Y-axis domains', async () => {
const cases = [
{ values: [0, 50], exponent: null },
@@ -672,7 +858,7 @@ describe('WaveformChart', () => {
).toEqual(['1,000', '2,000'])
})
- it('does not duplicate Y-axis boundary labels in compact mode', async () => {
+ it('keeps the zero Y-axis label on upper compact tracks', async () => {
const wrapper = await mountSizedChart(
{
kind: 'series',
@@ -703,14 +889,170 @@ describe('WaveformChart', () => {
)
const tracks = wrapper.findAll('.waveform-chart__track')
- const firstTrackHeight = Number(tracks[0].attributes('data-track-height'))
const firstAxisTicks = tracks[0].findAll('.waveform-chart__axis--y .tick')
- const hasBottomBoundaryTick = firstAxisTicks.some((tick) => {
- const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
- return match ? Math.abs(Number(match[1]) - firstTrackHeight) < 0.1 : false
- })
+ const zeroTicks = firstAxisTicks.filter((tick) => Number(tick.text()) === 0)
+ const firstTrackHeight = Number(tracks[0].attributes('data-track-height'))
+ const zeroTickY = Number(
+ zeroTicks[0].attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)?.[1],
+ )
- expect(hasBottomBoundaryTick).toBe(false)
+ expect(zeroTicks).toHaveLength(1)
+ expect(Math.abs(zeroTickY - firstTrackHeight)).toBeLessThanOrEqual(1)
+ })
+
+ it.each(['independent', 'separated', 'compact'] as const)(
+ 'shows a non-zero Y-axis start value once in %s mode',
+ async (displayMode) => {
+ const wrapper = await mountSizedChart(
+ {
+ kind: 'series',
+ series: [
+ {
+ name: 'first',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 0.11 },
+ { x: 1, y: 0.89 },
+ ],
+ },
+ },
+ {
+ name: 'second',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 4 },
+ { x: 1, y: 5 },
+ ],
+ },
+ },
+ ],
+ },
+ { displayMode },
+ )
+
+ const firstTrack = wrapper.findAll('.waveform-chart__track')[0]
+ const firstTrackHeight = Number(firstTrack.attributes('data-track-height'))
+ const startTicks = firstTrack.findAll('.waveform-chart__axis--y .tick').filter((tick) => {
+ const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
+ return match ? Math.abs(Number(match[1]) - firstTrackHeight) <= 1 : false
+ })
+
+ expect(startTicks).toHaveLength(1)
+ expect(Number(startTicks[0].text())).toBe(0.1)
+ },
+ )
+
+ it.each(['independent', 'separated'] as const)(
+ 'shows the Y-axis end value once on every track in %s mode',
+ async (displayMode) => {
+ const wrapper = await mountSizedChart(
+ {
+ kind: 'series',
+ series: [
+ {
+ name: 'first',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 0.11 },
+ { x: 1, y: 0.89 },
+ ],
+ },
+ },
+ {
+ name: 'second',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 4.11 },
+ { x: 1, y: 4.89 },
+ ],
+ },
+ },
+ ],
+ },
+ { displayMode },
+ )
+
+ const expectedEndValues = [0.9, 4.9]
+ wrapper.findAll('.waveform-chart__track').forEach((track, index) => {
+ const endTicks = track.findAll('.waveform-chart__axis--y .tick').filter((tick) => {
+ const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
+ return match ? Math.abs(Number(match[1])) <= 1 : false
+ })
+
+ expect(endTicks).toHaveLength(1)
+ expect(Number(endTicks[0].text())).toBe(expectedEndValues[index])
+ })
+ },
+ )
+
+ it('shows Y-axis end values only on the top row in compact mode', async () => {
+ const wrapper = await mountSizedChart(
+ {
+ kind: 'series',
+ series: [
+ {
+ name: 'top-left',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 0.11 },
+ { x: 1, y: 0.89 },
+ ],
+ },
+ },
+ {
+ name: 'top-right',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 1.11 },
+ { x: 1, y: 1.89 },
+ ],
+ },
+ },
+ {
+ name: 'bottom-left',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 2 },
+ { x: 1, y: 3 },
+ ],
+ },
+ },
+ {
+ name: 'bottom-right',
+ data: {
+ kind: 'points',
+ points: [
+ { x: 0, y: 4 },
+ { x: 1, y: 5 },
+ ],
+ },
+ },
+ ],
+ },
+ { displayMode: 'compact', grid: { rowCount: 2, columnCount: 2 } },
+ )
+
+ const tracks = wrapper.findAll('.waveform-chart__track')
+ const endTicks = tracks.map((track) =>
+ track.findAll('.waveform-chart__axis--y .tick').filter((tick) => {
+ const match = tick.attributes('transform')?.match(/translate\(0,\s*([\d.]+)\)/)
+ return match ? Math.abs(Number(match[1])) <= 1 : false
+ }),
+ )
+
+ expect(endTicks[0]).toHaveLength(1)
+ expect(Number(endTicks[0][0].text())).toBe(0.9)
+ expect(endTicks[1]).toHaveLength(1)
+ expect(Number(endTicks[1][0].text())).toBe(1.9)
+ expect(endTicks[2]).toHaveLength(0)
+ expect(endTicks[3]).toHaveLength(0)
})
it('keeps the shared exponent on the top visible tick in compact mode', async () => {
diff --git a/src/components/WaveformChart.vue b/src/components/WaveformChart.vue
index 0aebba5..c19e85f 100644
--- a/src/components/WaveformChart.vue
+++ b/src/components/WaveformChart.vue
@@ -65,6 +65,7 @@ const props = withDefaults(
defineProps<{
data: WaveformData
displayMode?: WaveformDisplayMode
+ width?: number
height?: number
xLabel?: string
yLabel?: string
@@ -82,7 +83,6 @@ const props = withDefaults(
}>(),
{
displayMode: 'independent',
- height: 360,
yLabel: '幅值',
lineColor: '#0960bd',
showTooltip: true,
@@ -115,7 +115,8 @@ const minimumHeight = chartMinimumHeight
const container = ref()
const svgElement = ref()
const sharedOverlayElement = ref()
-const width = ref(0)
+const observedWidth = ref(0)
+const observedHeight = ref(0)
const sharedTransform = shallowRef(zoomIdentity)
const independentTransforms = shallowRef([])
const hoveredSeriesPoints = ref([])
@@ -141,9 +142,22 @@ interface TooltipSeriesPoint {
point: WaveformPoint
}
-const chartHeight = computed(() =>
- Number.isFinite(props.height) ? Math.max(minimumHeight, props.height) : 360,
+const fixedWidth = computed(() =>
+ Number.isFinite(props.width) ? Math.max(0, props.width ?? 0) : undefined,
)
+const fixedHeight = computed(() =>
+ Number.isFinite(props.height) ? Math.max(minimumHeight, props.height ?? 0) : undefined,
+)
+const chartWidth = computed(() =>
+ observedWidth.value > 0 ? observedWidth.value : (fixedWidth.value ?? 0),
+)
+const chartHeight = computed(() =>
+ observedHeight.value > 0 ? observedHeight.value : (fixedHeight.value ?? minimumHeight),
+)
+const containerStyle = computed(() => ({
+ width: fixedWidth.value === undefined ? '100%' : `${fixedWidth.value}px`,
+ height: fixedHeight.value === undefined ? '100%' : `${fixedHeight.value}px`,
+}))
const innerHeight = computed(() => Math.max(0, chartHeight.value - margin.top - margin.bottom))
const chartSeries = computed(() =>
preparedSeries.value.map((series, index: number): DisplaySeries => ({
@@ -203,7 +217,9 @@ const chartLeftMargin = computed(() =>
: 0,
),
)
-const innerWidth = computed(() => Math.max(0, width.value - chartLeftMargin.value - margin.right))
+const innerWidth = computed(() =>
+ Math.max(0, chartWidth.value - chartLeftMargin.value - margin.right),
+)
const yAxisLayout = computed(() => {
const baseGap = getGridGap(props.displayMode)
const columnCount = gridOptions.value.columnCount
@@ -277,6 +293,7 @@ const trackLayouts = computed(() =>
rendering: renderingOptions.value,
hideSecondaryLabels: yAxisLayout.value.hideSecondaryLabels,
yAxisLabelX: yAxisMetrics.value.labelCenterX,
+ showCompactEmptyTracks: props.displayMode === 'compact' && hasWaveformData.value,
}),
)
@@ -445,7 +462,7 @@ function resolvePointerEditorAnchor(
trackIndex?: number,
): AnnotationEditorAnchor {
const overlay = event.currentTarget as SVGRectElement | null
- if (!overlay) return { x: width.value / 2, y: chartHeight.value / 2 }
+ if (!overlay) return { x: chartWidth.value / 2, y: chartHeight.value / 2 }
const [pointerX, pointerY] = pointer(event, overlay)
const track = trackIndex === undefined ? undefined : trackLayouts.value[trackIndex]
return {
@@ -457,7 +474,9 @@ function resolvePointerEditorAnchor(
function resolveAnnotationEditorAnchor(annotation: WaveformAnnotation): AnnotationEditorAnchor {
const track = trackLayouts.value.find((item) => item.series.id === annotation.seriesId)
return {
- x: track ? chartLeftMargin.value + track.left + track.xScale(annotation.x) : width.value / 2,
+ x: track
+ ? chartLeftMargin.value + track.left + track.xScale(annotation.x)
+ : chartWidth.value / 2,
y: track ? margin.top + track.top + track.yScale(annotation.y) : chartHeight.value / 2,
}
}
@@ -600,12 +619,12 @@ function handleExistingAnnotationContextMenu(annotationId: string, event: MouseE
if (!annotation) return
const bounds = container.value.getBoundingClientRect()
const editorAnchor = {
- x: Math.max(0, Math.min(event.clientX - bounds.left, width.value)),
+ x: Math.max(0, Math.min(event.clientX - bounds.left, chartWidth.value)),
y: Math.max(0, Math.min(event.clientY - bounds.top, chartHeight.value)),
}
annotationInteraction.openContextMenu({
annotationId,
- x: Math.max(4, Math.min(event.clientX - bounds.left, width.value - 120)),
+ x: Math.max(4, Math.min(event.clientX - bounds.left, chartWidth.value - 120)),
y: Math.max(4, Math.min(event.clientY - bounds.top, chartHeight.value - 110)),
editorAnchor,
})
@@ -799,7 +818,8 @@ watch(
onMounted(() => {
if (!container.value) return
resizeObserver.value = new ResizeObserver(([entry]) => {
- width.value = Math.max(0, entry?.contentRect.width ?? 0)
+ observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
+ observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
})
resizeObserver.value.observe(container.value)
})
@@ -819,7 +839,7 @@ onBeforeUnmount(() => {
`waveform-chart--${displayMode}`,
`waveform-chart--interaction-${activeInteractionMode}`,
]"
- :style="{ height: `${chartHeight}px` }"
+ :style="containerStyle"
:data-display-mode="displayMode"
:data-interaction-mode="activeInteractionMode"
:data-chart-left-margin="chartLeftMargin"
@@ -827,7 +847,7 @@ onBeforeUnmount(() => {
{
-
+
{
@@ -972,7 +992,7 @@ onBeforeUnmount(() => {
:time-unit="timeUnit"
:hovered-point="hoveredPoint"
:series-points="tooltipSeriesPoints"
- :container-width="width"
+ :container-width="chartWidth"
:container-height="chartHeight"
/>
@@ -980,9 +1000,10 @@ onBeforeUnmount(() => {