Phase 1 UX + JS transforms: tabs, windowing, percent/grouping, smoothing, stacked series, metadata pass-through, top_n
- Replace tabs with Recent/Trends/Distribution/Tables/Analysis and add sticky controls (interval, domain, window [default 7d], percent, group small, exclude '-' -> Uncached, smoothing toggle). - Client-side transforms: time-window slicing, percent mode, group others (3%), per-report exclusions; stackedBar multi-series; moving average for error_rate. - Generator: pass through optional UX metadata (windows_supported, window_default, group_others_threshold, exclude_values, top_n, stacked, palette) and enforce top_n LIMIT for table reports. - Reports: add status_classes_timeseries and cache_status_timeseries; apply top_n=50 to heavy tables. - Chart manager: add helpers (sliceWindow, excludeValues, toPercent, groupOthers, movingAverage). - URL state + localStorage for context; per-tab filtering for Trends/Distribution/Tables.
This commit is contained in:
parent
9c26ae3e90
commit
fab91d2e04
4 changed files with 442 additions and 59 deletions
|
@ -47,3 +47,63 @@ export function reset(container) {
|
|||
});
|
||||
container.innerHTML = '';
|
||||
}
|
||||
|
||||
// ---- Lightweight client-side data helpers ----
|
||||
|
||||
// Slice last N rows from a time-ordered array
|
||||
export function sliceWindow(data, n) {
|
||||
if (!Array.isArray(data) || n === undefined || n === null) return data;
|
||||
if (n === 'all') return data;
|
||||
const count = Number(n);
|
||||
if (!Number.isFinite(count) || count <= 0) return data;
|
||||
return data.slice(-count);
|
||||
}
|
||||
|
||||
// Exclude rows whose value in key is in excluded list
|
||||
export function excludeValues(data, key, excluded = []) {
|
||||
if (!excluded || excluded.length === 0) return data;
|
||||
const set = new Set(excluded);
|
||||
return data.filter(row => !set.has(row[key]));
|
||||
}
|
||||
|
||||
// Compute percentages for categorical distributions (valueKey default 'value')
|
||||
export function toPercent(data, valueKey = 'value') {
|
||||
const total = data.reduce((s, r) => s + (Number(r[valueKey]) || 0), 0);
|
||||
if (total <= 0) return data.map(r => ({ ...r }));
|
||||
return data.map(r => ({ ...r, [valueKey]: (Number(r[valueKey]) || 0) * 100 / total }));
|
||||
}
|
||||
|
||||
// Group categories with share < threshold into an 'Other' bucket.
|
||||
export function groupOthers(data, bucketKey, valueKey = 'value', threshold = 0.03, otherLabel = 'Other') {
|
||||
if (!Array.isArray(data) || data.length === 0) return data;
|
||||
const total = data.reduce((s, r) => s + (Number(r[valueKey]) || 0), 0);
|
||||
if (total <= 0) return data;
|
||||
const major = [];
|
||||
let other = 0;
|
||||
for (const r of data) {
|
||||
const v = Number(r[valueKey]) || 0;
|
||||
if (total && v / total < threshold) {
|
||||
other += v;
|
||||
} else {
|
||||
major.push({ ...r });
|
||||
}
|
||||
}
|
||||
if (other > 0) major.push({ [bucketKey]: otherLabel, [valueKey]: other });
|
||||
return major;
|
||||
}
|
||||
|
||||
// Simple moving average over numeric array
|
||||
export function movingAverage(series, span = 3) {
|
||||
const n = Math.max(1, Number(span) || 1);
|
||||
const out = [];
|
||||
for (let i = 0; i < series.length; i++) {
|
||||
const start = Math.max(0, i - n + 1);
|
||||
let sum = 0, cnt = 0;
|
||||
for (let j = start; j <= i; j++) {
|
||||
const v = Number(series[j]);
|
||||
if (Number.isFinite(v)) { sum += v; cnt++; }
|
||||
}
|
||||
out.push(cnt ? sum / cnt : null);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue