Koala logo Design

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.

New
24 +3
Accepted
18 +5
Expired
6 0
Cancelled
2 -1
<!-- 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) at opacity 0.04
  • Dark mode: #FFFFFF at opacity 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 -->