Guide

Reactive Data

Bind reactive Vue refs and computed properties to Vue Plot marks so charts update automatically when data changes, filters, or swaps datasets.

Vue Plot automatically re-renders when your data changes. Bind reactive data to your marks — the plot re-renders on change.

Basic reactivity

App.vue
<script setup lang="ts">
import { ref } from 'vue'
import { VPlot } from '@memotux/vue-plot'

const data = ref([
  { x: 1, y: 2 },
  { x: 2, y: 4 },
  { x: 3, y: 1 },
])
</script>

<template>
  <VPlot :width="680">
    <PlotDot :data="data" x="x" y="y" />
  </VPlot>
</template>

When data.value changes, the plot updates automatically.

Adding data points

App.vue
<script setup lang="ts">
import { ref } from 'vue'
import { VPlot } from '@memotux/vue-plot'

const data = ref([
  { x: 1, y: 2 },
  { x: 2, y: 4 },
  { x: 3, y: 1 },
])

function addPoint() {
  data.value = [
    ...data.value,
    { x: data.value.length + 1, y: Math.random() * 10 },
  ]
}
</script>

<template>
  <VPlot :width="680">
    <PlotDot :data="data" x="x" y="y" r="4" />
  </VPlot>
  <button @click="addPoint">Add Point</button>
</template>
Always replace the array (immutable update) rather than mutating it with push(). Vue's reactivity system tracks array references, not mutations.

Filtering data

Use computed properties to derive filtered views:

App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { VPlot } from '@memotux/vue-plot'

const data = ref([
  { name: 'A', value: 10, category: 'odd' },
  { name: 'B', value: 20, category: 'even' },
  { name: 'C', value: 15, category: 'odd' },
  { name: 'D', value: 25, category: 'even' },
])

const filter = ref('all')

const filteredData = computed(() => {
  if (filter.value === 'all') return data.value
  return data.value.filter(d => d.category === filter.value)
})
</script>

<template>
  <VPlot :width="680">
    <PlotBarY :data="filteredData" x="name" y="value" />
  </VPlot>
  <button @click="filter = 'all'">All</button>
  <button @click="filter = 'odd'">Odd</button>
  <button @click="filter = 'even'">Even</button>
</template>

Updating marks reactively

When using the marks prop, wrap marks in a computed:

App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { VPlot } from '@memotux/vue-plot'
import { barY, ruleY } from '@observablehq/plot'

const data = ref([
  { name: 'A', value: 10 },
  { name: 'B', value: 20 },
])

const showAverage = ref(true)

const marks = computed(() => {
  const m = [barY(data.value, { x: 'name', y: 'value' })]
  if (showAverage.value) {
    const avg = data.value.reduce((s, d) => s + d.value, 0) / data.value.length
    m.push(ruleY([avg], { stroke: 'red', strokeDasharray: '4' }))
  }
  return m
})
</script>

<template>
  <VPlot :width="680" :marks="marks" />
  <label>
    <input type="checkbox" v-model="showAverage" />
    Show average
  </label>
</template>

Swapping datasets

Replace the entire dataset to switch views:

App.vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import { VPlot } from '@memotux/vue-plot'

const datasets = {
  sales: [
    { month: 'Jan', revenue: 4000 },
    { month: 'Feb', revenue: 3000 },
    { month: 'Mar', revenue: 5000 },
  ],
  users: [
    { month: 'Jan', revenue: 1200 },
    { month: 'Feb', revenue: 1500 },
    { month: 'Mar', revenue: 1800 },
  ],
}

const activeDataset = ref('sales')
const data = computed(() => datasets[activeDataset.value])
</script>

<template>
  <VPlot :width="680">
    <PlotBarY :data="data" x="month" y="revenue" />
  </VPlot>
  <button @click="activeDataset = 'sales'">Sales</button>
  <button @click="activeDataset = 'users'">Users</button>
</template>

Performance considerations

For large datasets (10k+ points):

  • Use PlotDot with small radius instead of PlotBarY
  • Consider downsampling data before rendering
  • Use PlotHexbin or PlotDensity for aggregated views
App.vue
<!-- Efficient rendering for large datasets -->
<VPlot :width="680" :height="400">
  <PlotHexbin
    :data="largeDataset"
    x="longitude"
    y="latitude"
    fill="count"
  />
</VPlot>

What's next?

Copyright © 2026