<template>
<div>
<div class="wrap">
<h1>Q3 target attainment, store by store</h1>
<p class="lead">
Eighteen gauges against one target. Green is done, amber is close, red
is a conversation: the laggards jump out in a way no 18-bar chart
manages.
</p>
<div class="chart-wrap">
<div id="chart">
<apexchart
type="radialBar"
height="560"
:options="chartOptions"
:series="series"
></apexchart>
</div>
</div>
<div class="note">
The radial family needs no scale sharing beyond consistent options,
which makes it one of the cheapest trellis types: every panel is a real
radialBar on the same 0-100 track. The traffic-light fill comes from
<code>trellis.panel</code>, the per-panel override hook: it reads each
panel's own value and hands back a color. Trellis is a premium feature;
without a license it renders with a trial watermark.
</div>
</div>
</div>
</template>
<script>
import VueApexCharts from 'vue-apexcharts'
import 'apexcharts/features/trellis'
// Eighteen stores, one gauge each, every gauge against the SAME target. A
// gauge grid is scannable for "who is under target" in a way an 18-bar chart
// is not, and a radialBar still reads at 120px. The trellis owns the grid,
// the headers and the responsive columns; each panel is a real radialBar.
//
// Deterministic values so the e2e snapshot is stable.
function mulberry32(seed) {
return function () {
seed |= 0
seed = (seed + 0x6d2b79f5) | 0
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed)
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
}
}
var STORES = [
'Aurora',
'Brookfield',
'Cedar Park',
'Dunwoody',
'Eastvale',
'Fairfield',
'Glenview',
'Harborside',
'Ironwood',
'Junction',
'Kingsford',
'Lakeshore',
'Maplewood',
'Northgate',
'Oakhurst',
'Pinecrest',
'Quarry Bay',
'Riverton',
]
var rand = mulberry32(20260821)
var gaugeSeries = STORES.map(function (store) {
// Attainment 48..104%, a few clear laggards in the mix.
var v = Math.round(48 + rand() * 56)
return { name: 'Attainment', store: store, data: [Math.min(v, 100)] }
})
export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: gaugeSeries,
chartOptions: {
chart: {
id: 'gaugeTrellis',
type: 'radialBar',
height: 560,
animations: {
enabled: false,
},
},
trellis: {
by: 'store',
columns: 6,
minPanelWidth: 150,
gap: 8,
panel: function (key, meta) {
var s = gaugeSeries[meta.index]
var v = s && s.data[0] ? s.data[0] : 0
var color = v >= 85 ? '#15803D' : v >= 65 ? '#D97706' : '#DC2626'
return { colors: [color] }
},
},
plotOptions: {
radialBar: {
hollow: {
size: '54%',
},
track: {
background: '#E5E7EB',
},
dataLabels: {
name: {
show: false,
},
value: {
show: true,
offsetY: 6,
fontSize: '15px',
fontWeight: 700,
formatter: function (val) {
return Math.round(Number(val)) + '%'
},
},
},
},
},
stroke: {
lineCap: 'round',
},
labels: ['Attainment'],
},
}
},
}
</script>
<style>
body {
font-family:
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fafbfc;
color: #2c3e50;
margin: 0;
padding: 32px 16px;
}
.wrap {
max-width: 1120px;
margin: 0 auto;
}
h1 {
font-size: 22px;
margin: 0 0 8px;
}
.lead {
color: #5b6b78;
line-height: 1.5;
margin: 0 0 20px;
}
.chart-wrap {
background: #fff;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.note {
background: #fefce8;
border-left: 3px solid #a16207;
padding: 12px 16px;
font-size: 13px;
color: #333;
border-radius: 2px;
line-height: 1.6;
margin: 26px auto 0;
}
.note code {
background: #fef9c3;
padding: 1px 5px;
border-radius: 3px;
}
</style>