<template>
<div>
<div class="mz-wrap">
<div class="mz-bar">
<button id="mz-clear">Clear</button>
<span class="readout" id="mz-readout"
>The ruler tool is pre-selected in the toolbar. Drag from A to B on
the chart.</span
>
</div>
<div id="chart">
<apexchart
type="area"
height="380"
:options="chartOptions"
:series="series"
></apexchart>
</div>
<div class="mz-note">
The ruler is a built-in <b>toolbar tool</b>, pre-selected here with
<code>toolbar.autoSelected: 'measure'</code>. Drag across the plot: a
shaded band spans the two points and a readout shows the <b>change</b>,
the <b>% change</b> and the <b>range</b> between them, with the
endpoints snapped to the price line (the finance-style default,
<code>measure.mode: 'span'</code>). Green means the move is up, red
means down. Release to pin it; a pinned ruler is stored in data
coordinates, so it stays glued when you zoom or resize. Click the ruler
icon again to deselect it and hand the plot back to zoom; the
<code>m</code> key still works too. Fires the
<code>measured</code> event. Set <code>measure.mode: 'free'</code> for a
diagonal ruler between any two points. Needs the
<code>measure</code> feature. <b>Clear</b> removes all rulers.
</div>
</div>
</div>
</template>
<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'
function series() {
var out = []
var v = 100
for (var i = 0; i < 60; i++) {
v += 2 + Math.sin(i / 5) * 3
out.push([i, Math.round(v)])
}
return out
}
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: [
{ name: 'Price', data: series() }
],
chartOptions: {
chart: {
id: 'price',
type: 'area',
height: 380,
fontFamily: 'Helvetica, Arial, sans-serif',
animations: { enabled: false },
measure: { enabled: true, pinOnRelease: true },
toolbar: { show: true, autoSelected: 'measure' },
zoom: { enabled: true, type: 'x' },
},
stroke: { width: 2, curve: 'straight' },
dataLabels: { enabled: false },
title: { text: 'Price (measure any move)', align: 'left' },
xaxis: { type: 'numeric', title: { text: 'Session' } },
yaxis: { title: { text: 'Price' }, decimalsInFloat: 0 },
},
pollTimer: null,
}
},
mounted: function () {
// The vue-apexcharts wrapper owns the render, so reach the live instance by
// its chart.id, then wire the same handlers the vanilla build does.
// (series() lives in the shared head script.)
var me = this
me.pollTimer = window.setInterval(function () {
var chart = ApexCharts.getChartByID('price')
if (!chart) return
window.clearInterval(me.pollTimer)
me.pollTimer = null
var readout = document.getElementById('mz-readout')
document.getElementById('mz-clear').addEventListener('click', function () {
chart.clearMeasures()
})
chart.addEventListener('measured', function (c, o) {
readout.textContent =
'dx=' + Number(o.dx).toFixed(2) +
' dy=' + Number(o.dy).toFixed(2) +
' (' + (o.percentChange >= 0 ? '+' : '') + Number(o.percentChange).toFixed(1) + '%)'
})
}, 50)
},
beforeDestroy: function () {
if (this.pollTimer) window.clearInterval(this.pollTimer)
},,
}
</script>
<style>
.mz-wrap {
max-width: 820px;
margin: 12px auto;
font-family: Helvetica, Arial, sans-serif;
}
.mz-bar {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
margin: 4px 6px 10px;
}
.mz-bar button {
padding: 6px 13px;
cursor: pointer;
border: 1px solid #0ea5e9;
color: #0369a1;
background: #fff;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
}
.mz-bar button.active {
background: #0ea5e9;
color: #fff;
}
.mz-bar .readout {
font-family: monospace;
font-size: 13px;
color: #445;
}
.mz-note {
background: #f0f9ff;
border-left: 3px solid #0ea5e9;
padding: 11px 15px;
font-size: 13px;
color: #234;
border-radius: 2px;
line-height: 1.6;
margin: 12px 6px;
}
.mz-note code {
background: #dceafb;
padding: 1px 5px;
border-radius: 3px;
}
</style>