@@ -440,6 +440,11 @@ function dashboardPage(user: UserRow, uploads: UploadRow[], isAdmin: boolean, al
440440 <div class="card-header">
441441 <h3>Token Usage Trend</h3>
442442 <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
443+ <div class="tabs" id="chart-period-tabs">
444+ <button class="tab" data-chart-period="7">Last 7 days</button>
445+ <button class="tab active" data-chart-period="30">Last 30 days</button>
446+ <button class="tab" data-chart-period="0">All</button>
447+ </div>
443448 <div class="tabs" id="group-tabs">
444449 <button class="tab active" data-group="model">By Model</button>
445450 <button class="tab" data-group="editor">By Editor</button>
@@ -449,6 +454,10 @@ function dashboardPage(user: UserRow, uploads: UploadRow[], isAdmin: boolean, al
449454 <button class="tab" data-view="week">Week</button>
450455 <button class="tab" data-view="month">Month</button>
451456 </div>
457+ <div class="tabs" id="scale-tabs">
458+ <button class="tab active" data-scale="linear">Linear</button>
459+ <button class="tab" data-scale="log">Log</button>
460+ </div>
452461 </div>
453462 </div>
454463 <div class="chart-wrap"><canvas id="trend-chart"></canvas></div>
@@ -601,7 +610,37 @@ function dashboardPage(user: UserRow, uploads: UploadRow[], isAdmin: boolean, al
601610 .filter(function(ds) { return ds.data.some(function(v) { return v > 0; }); });
602611 }
603612
604- var currentGroup = 'model', currentView = 'day';
613+ var currentGroup = 'model', currentView = 'day', currentChartDays = 30, currentScale = 'linear';
614+
615+ function getChartData() {
616+ if (currentChartDays === 0) { return CHART_DATA; }
617+ var cutoff = new Date();
618+ cutoff.setUTCDate(cutoff.getUTCDate() - (currentChartDays - 1));
619+ var cutoffStr = cutoff.toISOString().slice(0, 10);
620+ return CHART_DATA.filter(function(r) { return r.day >= cutoffStr; });
621+ }
622+
623+ function makeYAxisConfig() {
624+ var isLog = currentScale === 'log';
625+ return {
626+ stacked: !isLog,
627+ type: isLog ? 'logarithmic' : 'linear',
628+ grid: { color: '#21262d' },
629+ ticks: {
630+ color: '#8b949e', font: { size: 11 },
631+ callback: function(v) {
632+ // Show only "round" log-scale ticks
633+ if (isLog) {
634+ var log = Math.log10(v);
635+ if (Math.abs(log - Math.round(log)) > 0.01) { return null; }
636+ }
637+ return v >= 1000 ? (v/1000).toFixed(1)+'M' : v+'K';
638+ },
639+ },
640+ title: { display: true, text: 'Tokens (K)', color: '#8b949e', font: { size: 11 } },
641+ };
642+ }
643+
605644 var grouped = aggregate(function(d) { return d; }, currentGroup);
606645 var labels = [...new Set(CHART_DATA.map(function(r) { return r.day; }))].sort();
607646
@@ -614,11 +653,7 @@ function dashboardPage(user: UserRow, uploads: UploadRow[], isAdmin: boolean, al
614653 interaction: { mode: 'index', intersect: false },
615654 scales: {
616655 x: { stacked: true, grid: { color: '#21262d' }, ticks: { color: '#8b949e', maxTicksLimit: 16, font: { size: 11 } } },
617- y: { stacked: true, grid: { color: '#21262d' },
618- ticks: { color: '#8b949e', font: { size: 11 },
619- callback: function(v) { return v >= 1000 ? (v/1000).toFixed(1)+'M' : v+'K'; } },
620- title: { display: true, text: 'Tokens (K)', color: '#8b949e', font: { size: 11 } },
621- },
656+ y: makeYAxisConfig(),
622657 },
623658 plugins: {
624659 legend: { position: 'bottom', labels: { color: '#c9d1d9', boxWidth: 11, padding: 14, font: { size: 11 } } },
@@ -640,16 +675,45 @@ function dashboardPage(user: UserRow, uploads: UploadRow[], isAdmin: boolean, al
640675 });
641676
642677 function rebuildChart() {
678+ var filteredData = getChartData();
643679 var keyFn = currentView === 'week' ? toWeekStart : currentView === 'month' ? toMonth : function(d) { return d; };
644- grouped = aggregate(keyFn, currentGroup);
645- labels = [...new Set(CHART_DATA.map(function(r) { return keyFn(r.day); }))].sort();
680+ // Re-aggregate using filtered data
681+ var filteredMap = {};
682+ filteredData.forEach(function(r) {
683+ var label = keyFn(r.day);
684+ var dim = currentGroup === 'editor' ? r.editor : r.model;
685+ if (!filteredMap[label]) filteredMap[label] = {};
686+ filteredMap[label][dim] = (filteredMap[label][dim] || 0) + r.inputTokens + r.outputTokens;
687+ });
688+ grouped = filteredMap;
689+ labels = [...new Set(filteredData.map(function(r) { return keyFn(r.day); }))].sort();
646690 var dims = currentGroup === 'editor' ? allEditors : allModels;
647691 var colorFn = currentGroup === 'editor' ? getEditorColor : getModelColor;
648692 chart.data.labels = labels;
649693 chart.data.datasets = buildDatasets(grouped, labels, dims, colorFn);
694+ chart.options.scales.x.stacked = currentScale !== 'log';
695+ chart.options.scales.y = makeYAxisConfig();
650696 chart.update();
651697 }
652698
699+ document.querySelectorAll('#chart-period-tabs .tab').forEach(function(btn) {
700+ btn.addEventListener('click', function() {
701+ document.querySelectorAll('#chart-period-tabs .tab').forEach(function(b) { b.classList.remove('active'); });
702+ btn.classList.add('active');
703+ currentChartDays = parseInt(btn.getAttribute('data-chart-period'), 10);
704+ rebuildChart();
705+ });
706+ });
707+
708+ document.querySelectorAll('#scale-tabs .tab').forEach(function(btn) {
709+ btn.addEventListener('click', function() {
710+ document.querySelectorAll('#scale-tabs .tab').forEach(function(b) { b.classList.remove('active'); });
711+ btn.classList.add('active');
712+ currentScale = btn.getAttribute('data-scale');
713+ rebuildChart();
714+ });
715+ });
716+
653717 document.querySelectorAll('#group-tabs .tab').forEach(function(btn) {
654718 btn.addEventListener('click', function() {
655719 document.querySelectorAll('#group-tabs .tab').forEach(function(b) { b.classList.remove('active'); });
0 commit comments