@@ -191,6 +191,33 @@ function analyticsPage() {
191191 return segments ;
192192 } ,
193193
194+ donutSegmentsSvg ( ) {
195+ var segments = this . donutSegments ( ) ;
196+ if ( ! segments . length ) return '' ;
197+
198+ function escapeXml ( value ) {
199+ return String ( value )
200+ . replace ( / & / g, '&' )
201+ . replace ( / < / g, '<' )
202+ . replace ( / > / g, '>' )
203+ . replace ( / " / g, '"' )
204+ . replace ( / ' / g, ''' ) ;
205+ }
206+
207+ var out = [ ] ;
208+ for ( var i = 0 ; i < segments . length ; i ++ ) {
209+ var seg = segments [ i ] ;
210+ var title = seg . provider + ': ' + seg . percent + '% (' + this . formatCost ( seg . cost ) + ')' ;
211+ out . push (
212+ '<circle cx="80" cy="80" r="60" fill="none" stroke="' + escapeXml ( seg . color ) + '" stroke-width="24" stroke-dasharray="' + escapeXml ( seg . dasharray ) + '" stroke-dashoffset="' + escapeXml ( seg . dashoffset ) + '" transform="rotate(-90 80 80)" class="donut-segment">' +
213+ '<title>' + escapeXml ( title ) + '</title>' +
214+ '</circle>'
215+ ) ;
216+ }
217+
218+ return out . join ( '' ) ;
219+ } ,
220+
194221 // ── Bar chart (last 7 days) ──
195222
196223 barChartData ( ) {
@@ -218,6 +245,42 @@ function analyticsPage() {
218245 return result ;
219246 } ,
220247
248+ barChartSvg ( ) {
249+ var bars = this . barChartData ( ) ;
250+ if ( ! bars . length ) return '' ;
251+
252+ function escapeXml ( value ) {
253+ return String ( value )
254+ . replace ( / & / g, '&' )
255+ . replace ( / < / g, '<' )
256+ . replace ( / > / g, '>' )
257+ . replace ( / " / g, '"' )
258+ . replace ( / ' / g, ''' ) ;
259+ }
260+
261+ var out = [ ] ;
262+ for ( var i = 0 ; i < bars . length ; i ++ ) {
263+ var bar = bars [ i ] ;
264+ var x = i * 50 + 18 ;
265+ var labelX = i * 50 + 30 ;
266+ var y = 150 - bar . barHeight ;
267+ var costLabelY = y - 4 ;
268+ var title = bar . date + ': ' + this . formatCost ( bar . cost ) + ' (' + bar . calls + ' calls)' ;
269+
270+ out . push (
271+ '<g>' +
272+ '<rect x="' + x + '" y="' + y + '" width="24" height="' + bar . barHeight + '" rx="3" fill="var(--accent)" class="cost-bar" style="opacity:0.85">' +
273+ '<title>' + escapeXml ( title ) + '</title>' +
274+ '</rect>' +
275+ '<text x="' + labelX + '" y="166" text-anchor="middle" fill="var(--text-muted)" style="font-size:9px;font-family:var(--font-mono)">' + escapeXml ( bar . dayName ) + '</text>' +
276+ '<text x="' + labelX + '" y="' + costLabelY + '" text-anchor="middle" fill="var(--text-dim)" style="font-size:8px;font-family:var(--font-mono)">' + escapeXml ( this . formatCost ( bar . cost ) ) + '</text>' +
277+ '</g>'
278+ ) ;
279+ }
280+
281+ return out . join ( '' ) ;
282+ } ,
283+
221284 // ── Cost by model table (sorted by cost descending) ──
222285
223286 costByModelSorted ( ) {
0 commit comments