Files
peardata/shared/chart-types.js
T
Raven Scott 700e621882
CI / test (push) Successful in 57s
Release rolling / release (push) Successful in 7m0s
Update
2026-07-18 21:02:15 -04:00

135 lines
3.7 KiB
JavaScript

/**
* Chart display modes — how a metrics card should paint its series.
*
* Catalog agents emit chartType: line | area | stacked.
* The desktop can also render bar / multibar / pie where they make sense.
*/
/** @typedef {'line'|'area'|'stacked'|'bar'|'multibar'|'pie'} ChartMode */
/** @type {ChartMode[]} */
export const CHART_MODES = ['line', 'area', 'stacked', 'bar', 'multibar', 'pie']
/** Short labels for the type toggle button */
export const CHART_MODE_LABEL = {
line: 'line',
area: 'area',
stacked: 'stack',
bar: 'bar',
multibar: 'bars',
pie: 'pie',
}
/**
* @param {unknown} raw
* @returns {ChartMode}
*/
export function normalizeChartMode(raw) {
const m = String(raw || 'line')
.trim()
.toLowerCase()
.replace(/[_-]/g, '')
if (m === 'multibar' || m === 'bargroup') return 'multibar'
if (m === 'stack') return 'stacked'
if (CHART_MODES.includes(/** @type {ChartMode} */ (m))) {
return /** @type {ChartMode} */ (m)
}
return 'line'
}
/**
* Default mode from catalog metadata.
* @param {object} [meta]
* @returns {ChartMode}
*/
export function defaultModeFromMeta(meta = {}) {
return normalizeChartMode(meta.chartType || meta.chart_type || 'line')
}
/**
* @param {object} [meta]
*/
function dimCount(meta = {}) {
const dims = meta.dimensions
if (Array.isArray(dims)) return dims.length
if (dims && typeof dims === 'object') return Object.keys(dims).length
return 0
}
/**
* Composition charts (parts of a whole) — pie / stacked are natural.
* @param {object} [meta]
*/
export function isCompositionChart(meta = {}) {
const mode = defaultModeFromMeta(meta)
if (mode === 'stacked') return true
const units = String(meta.units || '')
if (/%|percentage|percent/i.test(units)) return true
const id = String(meta.id || meta.name || '')
// Common composition contexts
if (/\.(cpu|ram|swap|slab|kernel|states|usage)$/i.test(id)) return true
if (/^(system\.cpu|system\.ram|mem\.(swap|kernel|slab))/i.test(id)) return true
return false
}
/**
* Modes that make sense for this chart (catalog default first).
* @param {object} [meta]
* @returns {ChartMode[]}
*/
export function allowedModesFor(meta = {}) {
const preferred = defaultModeFromMeta(meta)
const n = dimCount(meta)
const composition = isCompositionChart(meta)
/** @type {Set<ChartMode>} */
const set = new Set(['line', 'area'])
if (n !== 1) set.add('stacked')
set.add('bar')
if (n === 0 || (n >= 2 && n <= 16)) set.add('multibar')
if (composition || preferred === 'stacked') set.add('pie')
// Always allow the catalog default
set.add(preferred)
const order = /** @type {ChartMode[]} */ ([
preferred,
...CHART_MODES.filter((m) => m !== preferred && set.has(m)),
])
return order
}
/**
* Cycle to the next allowed mode.
* @param {ChartMode|string} current
* @param {object} [meta]
* @returns {ChartMode}
*/
export function nextChartMode(current, meta = {}) {
const allowed = allowedModesFor(meta)
const cur = normalizeChartMode(current)
const idx = allowed.indexOf(cur)
return allowed[(idx < 0 ? 0 : idx + 1) % allowed.length]
}
/**
* Human hint for tooltips / empty states.
* @param {ChartMode|string} mode
*/
export function chartModeHint(mode) {
switch (normalizeChartMode(mode)) {
case 'area':
return 'Filled time series — good for volume and rates'
case 'stacked':
return 'Stacked over time — parts of a whole'
case 'bar':
return 'Columns over time — compare magnitude per sample'
case 'multibar':
return 'Grouped bars — compare dimensions side by side'
case 'pie':
return 'Snapshot share — latest values as a whole'
default:
return 'Lines over time — independent series'
}
}