<template>
  <div>
    <div class="panel">
      <div class="controls">
        <button id="undo" class="primary" disabled>Undo</button>
        <button id="redo" class="primary" disabled>Redo</button>
        <button id="randomize">Randomize data</button>
        <button id="reset">Clear history</button>
        <span class="readout" id="readout">history: empty</span>
      </div>
    </div>

    <div id="chart">
      <apexchart
        type="line"
        height="400"
        :options="chartOptions"
        :series="series"
      ></apexchart>
    </div>

    <div class="panel">
      <div class="note">
        History is enabled with
        <code>chart: &#123; history: &#123; enabled: true &#125; &#125;</code>.
        Every data update, zoom, pan, annotation and measurement commits a
        checkpoint. <b>Right-click</b> the plot to add a note, drop a dashed
        marker line, or start a measurement (context menu);
        <b>drag-select</b> or use the toolbar to zoom and pan; hold
        <code>m</code> and drag to measure; press Randomize to replace the data.
        Then step back with Undo / Redo (or press <code>Cmd/Ctrl + Z</code> and
        <code>Shift + Cmd/Ctrl + Z</code>). The buttons enable from the
        <code>historyChange</code> event via <code>chart.history.state()</code>.
      </div>
    </div>
  </div>
</template>

<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'

// A wandering line (numeric x, ~45 points) so there is room to zoom, pan,
// annotate and measure. Randomize regenerates it into a fresh checkpoint.
function randomSeries() {
  var d = []
  var v = 45 + Math.random() * 25
  for (var i = 0; i < 45; i++) {
    v += (Math.random() - 0.5) * 22
    if (v < 8) v = 8
    if (v > 100) v = 100
    d.push([i + 1, Math.round(v)])
  }
  return d
}

export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: [
  { name: 'Value', data: randomSeries() }
],
chartOptions: {
chart: {
  height: 400,
  type: 'line',
  id: 'rewind-demo',
  fontFamily: 'Helvetica, Arial, sans-serif',
  animations: { enabled: true },
  // Toolbar on so zoom / pan / reset (and the measure tool) are one click away.
  toolbar: { show: true, autoSelected: 'zoom' },
  zoom: { enabled: true, type: 'x' },
  history: { enabled: true, maxDepth: 50, coalesceMs: 250, keyboard: true },
  // Right-click actions: drop a note, a dashed marker line, or seed the ruler.
  contextMenu: {
    enabled: true,
    line: { text: 'Marker', strokeDashArray: 5, color: '#0EA5E9' },
    items: ['annotate', 'xline', 'yline', 'measure'],
  },
  // Measure ruler + editable / draggable annotations. Both commit checkpoints,
  // so Undo / Redo steps through them alongside data and zoom changes.
  measure: { enabled: true },
  ink: { enabled: true, snap: true },
},
colors: ['#0EA5E9'],
stroke: { width: 2.5, curve: 'smooth' },
markers: { size: 0, hover: { size: 5 } },
dataLabels: { enabled: false },
title: { text: 'Undo / redo over data, zoom, annotations, and measurements', align: 'left' },
xaxis: { type: 'numeric', title: { text: 'Session' }, tickAmount: 10 },
yaxis: { decimalsInFloat: 0 },
grid: { borderColor: '#eceff5' },
},
rewindTimer: null,
}
},
mounted: function () {
  // The vue-apexcharts wrapper owns the render, so reach the live instance by
  // its chart.id, then wire the same buttons the vanilla build does.
  // (randomSeries lives in the shared head script.)
  var me = this
  me.rewindTimer = window.setInterval(function () {
    var chart = ApexCharts.getChartByID('rewind-demo')
    if (!chart) return
    window.clearInterval(me.rewindTimer)

    var undoBtn = document.getElementById('undo')
    var redoBtn = document.getElementById('redo')
    var readout = document.getElementById('readout')

    function syncButtons(state) {
      if (!state) state = chart.history.state()
      undoBtn.disabled = !state.canUndo
      redoBtn.disabled = !state.canRedo
      readout.innerHTML =
        'history: <b>' + (state.index + 1) + ' / ' + state.length + '</b> checkpoints'
    }

    chart.addEventListener('historyChange', function (c, state) {
      syncButtons(state && state.length != null ? state : undefined)
    })

    undoBtn.addEventListener('click', function () { chart.history.undo() })
    redoBtn.addEventListener('click', function () { chart.history.redo() })
    document.getElementById('randomize').addEventListener('click', function () {
      chart.updateSeries([{ name: 'Value', data: randomSeries() }])
    })
    document.getElementById('reset').addEventListener('click', function () {
      chart.history.clear()
    })

    // initial state once the baseline checkpoint lands
    window.setTimeout(syncButtons, 300)
  }, 50)
},
beforeDestroy: function () {
  if (this.rewindTimer) window.clearInterval(this.rewindTimer)
},,
}
</script>

<style>
#chart {
  max-width: 760px;
  margin: 16px auto;
}
.panel {
  max-width: 760px;
  margin: 16px auto;
  font-family: sans-serif;
}
.controls {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.controls button {
  padding: 7px 15px;
  cursor: pointer;
  border: 1px solid #cdd3de;
  background: #fff;
  border-radius: 6px;
  font-size: 14px;
}
.controls button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.controls .primary {
  border-color: #0ea5e9;
  color: #0ea5e9;
  font-weight: 600;
}
.readout {
  font-family: monospace;
  font-size: 13px;
  color: #444;
  margin-left: auto;
}
.readout b {
  color: #111;
}
.note {
  background: #f6f4fe;
  border-left: 3px solid #0ea5e9;
  padding: 10px 14px;
  font-size: 13px;
  color: #234;
  border-radius: 2px;
  margin-top: 12px;
  line-height: 1.55;
}
.note code {
  background: #e9e4fd;
  padding: 1px 5px;
  border-radius: 3px;
}
</style>
Undo / Redo (Rewind) - Vue Narrative & State | ApexCharts.js | ApexCharts.js