Charts
The Portal uses ApexCharts via CDN
for data visualisation. Charts are initialised with vanilla JavaScript using
data- attributes, not Alpine.js components.
Sparkline charts
Small inline trend lines rendered as smooth SVG curves inside stat cards on the dashboard.
Each sparkline reads its data from data-sparkline,
colour from data-sparkline-color,
and optional labels from data-sparkline-labels.
These are custom SVG sparklines, not ApexCharts.
<!-- Sparkline container in a stat card -->
<div class="w-14 sm:w-20 h-8 flex-shrink-0"
data-sparkline="[2,5,3,8,6,12,9,15,11,18,14,24]"
data-sparkline-color="#6B7280"
data-sparkline-title="New"
data-sparkline-labels='["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]'>
</div>
<!-- The Layout script reads data-sparkline and renders a smooth SVG curve -->
<!-- with hover tooltips showing the title + value -->
Sparkline data attributes
Each sparkline container uses data-
attributes to configure its appearance and tooltip content.
| Attribute | Type | Description |
|---|---|---|
data-sparkline |
JSON array | Array of numeric values (e.g. [2,5,3,8,6,12]) |
data-sparkline-color |
Hex color | Line/dot colour (e.g. #6B8E73) |
data-sparkline-title |
String | Label shown in tooltip (e.g. "New quotes") |
data-sparkline-labels |
JSON array | Date labels for tooltips (e.g. ["Jan","Feb","Mar"]) |
Minimum 2 data points required. The chart auto-scales to container dimensions.
Area charts (ApexCharts)
Fee breakdown charts on the dashboard and partner detail pages use ApexCharts with the
data-apex-chart attribute.
Chart data is passed via data-chart-data
as a JSON array of objects with label,
totalFees, and
referralFees properties.
Fees
<!-- Razor page -->
<div data-apex-chart
data-chart-data="@Html.Raw(System.Text.Encodings.Web.HtmlEncoder.Default.Encode(
Json.Serialize(Model.ChartData).ToString()!))">
</div>
<!-- ChartData is IReadOnlyList<ChartDataPoint> -->
<!-- Each point has: label (string), totalFees (decimal), referralFees (decimal) -->
<!-- The Layout script reads data-chart-data and creates an ApexCharts area chart -->
<!-- with custom tooltips, crosshair, dark mode support, and responsive tick amounts -->
Banked + predicted dual-series
The fees chart on the conveyancing dashboard renders two series in one chart: banked fees (solid line) and predicted fees (dashed line). Banked points come from completed transactions on their completion date; predicted points come from active transactions on their forecast completion. The two share an x-axis and a y-axis so the predicted line picks up exactly where the banked line ends.
Each ChartDataPoint
carries an isPredicted
flag. The Layout script computes a boundaryIndex
— the last historical (non-predicted) point — and uses it to render the dashed predicted line
starting at the boundary so it doesn't visually overlap the solid banked line. Series are
omitted entirely when their corresponding totals are zero (e.g. a quote-only chart with no banked
completions hides the banked series).
Forecast band
The region after the boundary (today onwards into the predicted horizon) gets a subtle background
tint via an ApexCharts annotations.xaxis
band. Drawn from the first predicted point's label to the last point's label. The band is omitted
when there's no forecast tail (boundaryIndex === chartData.length - 1)
or when every point is predicted (boundaryIndex === -1).
Colours are deliberately soft so the band reads as "paper" rather than a darkened region:
- Light mode:
#D1D5DB(gray-300) atopacity 0.04 - Dark mode:
#FFFFFFatopacity 0.025
The band logic lives in the chart-init script in _Layout.cshtml
next to the series construction; the chart end itself tracks the latest predicted completion
among visible active transactions in BuildFeesChart.
Chart configuration pattern
Charts are initialised in the Portal's _Layout.cshtml
script block, not in individual pages. The script queries all [data-apex-chart]
elements and creates ApexCharts instances.
<!-- _Layout.cshtml script block -->
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
document.querySelectorAll('[data-apex-chart]').forEach(function (el) {
var raw = el.getAttribute('data-chart-data');
var chartData = JSON.parse(raw);
var isDark = document.documentElement.classList.contains('dark');
var chart = new ApexCharts(el, {
chart: {
type: 'area',
height: 300,
fontFamily: 'Inter, sans-serif',
toolbar: { show: false },
zoom: { enabled: false },
background: 'transparent'
},
series: [
{ name: 'Gross fees', data: chartData.map(p => p.totalFees) },
{ name: 'Referral fees', data: chartData.map(p => p.referralFees) }
],
xaxis: {
categories: chartData.map(p => p.label),
labels: { style: { colors: isDark ? '#9CA3AF' : '#6B7280' } }
},
yaxis: {
labels: {
formatter: val => '\u00A3' + val.toLocaleString('en-GB'),
style: { colors: isDark ? '#9CA3AF' : '#6B7280' }
}
},
colors: ['#6B8E73', '#B8860B'],
fill: { type: 'gradient', gradient: { opacityFrom: 0.55, opacityTo: 0.0 } },
stroke: { curve: 'smooth', width: 3 },
theme: { mode: isDark ? 'dark' : 'light' }
});
chart.render();
});
Dark mode integration
Charts detect dark mode at render time via
document.documentElement.classList.contains('dark').
Key dark mode adjustments:
| Property | Light | Dark |
|---|---|---|
| Axis label colour | #6B7280 (gray-500) |
#9CA3AF (gray-400) |
| Theme mode | 'light' |
'dark' |
| Tooltip background | bg-white |
bg-gray-700 |
| Chart background | transparent (inherits card bg) |
|
Tooltip CSS override
The default ApexCharts tooltip is hidden via CSS in the layout. Custom floating tooltips are rendered as absolutely-positioned DOM elements managed by JavaScript event handlers.
<!-- _Layout.cshtml style block -->
.apexcharts-tooltip {
background: transparent !important;
border: none !important;
box-shadow: none !important;
padding: 0 !important;
}
<!-- Custom tooltips are created as fixed DOM elements -->
<!-- with classes: fixed z-50 px-2.5 py-1.5 text-sm rounded-lg -->
<!-- bg-white dark:bg-gray-700 border border-gray-200 -->
<!-- dark:border-gray-600 shadow-md pointer-events-none -->