web-mojo 2.4.11 → 2.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/admin.cjs.js +1 -1
- package/dist/admin.es.js +1 -1
- package/dist/auth.cjs.js +1 -1
- package/dist/auth.es.js +1 -1
- package/dist/charts.cjs.js +1 -1
- package/dist/charts.es.js +1 -1
- package/dist/chunks/exportChart-CiiOP0OV.js +2 -0
- package/dist/chunks/exportChart-CiiOP0OV.js.map +1 -0
- package/dist/chunks/exportChart-D_QM7QwD.js +2 -0
- package/dist/chunks/exportChart-D_QM7QwD.js.map +1 -0
- package/dist/chunks/{version-BxKPs9hq.js → version-CF8OZ-hE.js} +2 -2
- package/dist/chunks/{version-BxKPs9hq.js.map → version-CF8OZ-hE.js.map} +1 -1
- package/dist/chunks/{version-CfHtDs3p.js → version-D_sJdHZU.js} +2 -2
- package/dist/chunks/{version-CfHtDs3p.js.map → version-D_sJdHZU.js.map} +1 -1
- package/dist/docit.cjs.js +1 -1
- package/dist/docit.es.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/lightbox.cjs.js +1 -1
- package/dist/lightbox.es.js +1 -1
- package/package.json +1 -1
- package/dist/chunks/exportChart-DVnTgKP0.js +0 -2
- package/dist/chunks/exportChart-DVnTgKP0.js.map +0 -1
- package/dist/chunks/exportChart-DYWi1WFJ.js +0 -2
- package/dist/chunks/exportChart-DYWi1WFJ.js.map +0 -1
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{V as t}from"./View-DADtcBcb.js";import{d as e}from"./Collection-Bq_EMAqK.js";import{a as s}from"./Modal-Uwt-GBgJ.js";const i=["#36A2EB","#FF6384","#FFCE56","#4BC0C0","#9966FF","#FF9F40","#66BB6A","#EF5350","#AB47BC","#26C6DA"],a=t=>`hsl(${(137.508*t%360).toFixed(0)}, 65%, 52%)`;class SeriesChart extends t{constructor(t={}){super({className:"mini-series-chart",...t}),this.chartType=t.chartType||"line",!0===t.grouped?this.stacked=!1:void 0===t.stacked?this.stacked="auto":this.stacked=t.stacked,this.width=t.width||"100%",this.height=t.height||200,this.showXLabels=!1!==t.showXLabels,this.showYLabels=!1!==t.showYLabels,this.padTop=10,this.padRight=12,this.padBottom=this.showXLabels?24:8,this.padLeft=this.showYLabels?40:8,this._padBottomOverride=null,this.strokeWidth=t.strokeWidth||2,this.smoothing=t.smoothing??.3,this.showDots=!1!==t.showDots,this.dotRadius=t.dotRadius||3,this.fill=t.fill??"area"===this.chartType,this.barGap=t.barGap??2,this.groupGap=t.groupGap??6,this.showGrid=!1!==t.showGrid,this.gridLines=t.gridLines||5,this.showLegend=!1!==t.showLegend,this.legendPosition=t.legendPosition||"top",this.legendJustify=this._normalizeLegendJustify(t.legendJustify),this.showTooltip=!1!==t.showTooltip,this.highlightOnHover=!0===t.highlightOnHover,this.colors=Array.isArray(t.colors)&&t.colors.length?[...t.colors]:[...i],this.colorGenerator=t.colorGenerator||a,this.valueFormatter=t.valueFormatter||null,this.xLabelFormat=t.xLabelFormat||null,this.xLabelFormatter=t.xLabelFormatter||null,this.dataFormatter=e,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.crosshairTracking=!0===t.crosshairTracking,this.crosshairColor=t.crosshairColor||null,this.crosshairWidth=t.crosshairWidth||1,this._rawData=t.data||null,this._labels=[],this._datasets=[],this._hidden=/* @__PURE__ */new Set,this._tweenId=0,this._currentGeometry=null,this._crosshairLayer=null,this._crosshairLine=null,this._ghostDots=null,this._hitRect=null}getTemplate(){const t="number"==typeof this.width?`${this.width}px`:this.width,e="number"==typeof this.height?`${this.height}px`:this.height;return`\n <div class="mini-series-wrapper mini-series-legend-${this.legendPosition} mini-series-legend-justify-${this.legendJustify}">\n ${this.showLegend?'<div class="mini-series-legend"></div>':""}\n <div class="mini-series-svg-area" style="width:${t}; height:${e};">\n <svg class="mini-series-svg" width="100%" height="100%"\n preserveAspectRatio="none" style="display:block;">\n </svg>\n ${this.showTooltip?'<div class="mini-series-tooltip" style="display:none;"></div>':""}\n </div>\n </div>\n `}async onAfterRender(){this.svg=this.element.querySelector(".mini-series-svg"),this.tooltipEl=this.element.querySelector(".mini-series-tooltip"),this.legendEl=this.element.querySelector(".mini-series-legend"),this._updateDimensions(),this._setupResizeObserver(),this._rawData&&(this._parseData(this._rawData),this._renderChart({animate:!1}),this._renderLegend())}async onBeforeDestroy(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._tweenId++,await super.onBeforeDestroy()}setData(t,e={}){if(this._rawData=t,this._parseData(t),this.svg){const t=e.animate??this.animate;this._renderChart({animate:t}),this._renderLegend()}}setChartType(t){t!==this.chartType&&(this.chartType=t,void 0===this.fill&&(this.fill="area"===t),this.svg&&(this._renderChart({animate:!1}),this._renderLegend()))}toggleSeries(t){this._hidden.has(t)?this._hidden.delete(t):this._hidden.add(t),this.svg&&(this._renderChart({animate:this.animate}),this._renderLegend()),this.emit?.("chart:series-toggled",{chart:this,index:t,hidden:this._hidden.has(t)})}_updateDimensions(){if(!this.svg)return;const t=this.svg.getBoundingClientRect();this._w=t.width||300,this._h=t.height||("number"==typeof this.height?this.height:200),this.svg.setAttribute("viewBox",`0 0 ${this._w} ${this._h}`)}_setupResizeObserver(){"undefined"!=typeof ResizeObserver&&(this._resizeObserver=new ResizeObserver(()=>{this._updateDimensions(),this._datasets.length>0&&this._renderChart({animate:!1})}),this.svg&&this._resizeObserver.observe(this.svg))}_parseData(t){if(!t)return this._labels=[],void(this._datasets=[]);let e=[],s=[];Array.isArray(t)?(e=t.map((t,e)=>String(e+1)),s=[{label:"Series 1",data:[...t]}]):t.labels&&t.datasets?(e=t.labels,s=t.datasets.map((t,e)=>({label:t.label||`Series ${e+1}`,data:t.data||[],color:t.color||t.borderColor,fill:t.fill,smoothing:t.smoothing,fillColor:t.backgroundColor||t.fillColor}))):t.labels&&t.series&&(e=t.labels,s=t.series.map((t,e)=>({label:t.name||t.label||`Series ${e+1}`,data:t.data||[],color:t.color||t.borderColor,fill:t.fill,smoothing:t.smoothing,fillColor:t.backgroundColor||t.fillColor}))),this._labels=e,this._datasets=s.map((t,e)=>{const s=t.color||this.colors[e]||this.colorGenerator(e),i=t.fill??("area"===this.chartType||"bar"===this.chartType||this.fill),a=t.fillColor||this._toRgba(s,"bar"===this.chartType?.85:.18);return{label:t.label,data:t.data,color:s,fillColor:a,fill:i,smoothing:t.smoothing??this.smoothing}}),this._hidden.size&&(this._hidden=new Set([...this._hidden].filter(t=>t<this._datasets.length)))}_isStacked(){return"auto"===this.stacked?"bar"===this.chartType:!!this.stacked}_visibleDatasets(){return this._datasets.map((t,e)=>({ds:t,i:e})).filter(({i:t})=>!this._hidden.has(t))}get _plotLeft(){return this.padLeft}get _plotTop(){return this.padTop}get _plotRight(){return this._w-this.padRight}get _plotBottom(){return this._h-(this._padBottomOverride??this.padBottom)}get _plotW(){return this._plotRight-this._plotLeft}get _plotH(){return this._plotBottom-this._plotTop}_calcBounds(){let t=1/0,e=-1/0;const s=this._visibleDatasets();if(this._isStacked()&&"bar"===this.chartType){const i=this._labels.length||s[0]?.ds.data.length||0;for(let a=0;a<i;a++){let i=0,r=0;for(const{ds:t}of s){const e=+t.data[a]||0;e>=0?i+=e:r+=e}i>e&&(e=i),r<t&&(t=r)}isFinite(t)||(t=0),isFinite(e)||(e=1)}else for(const{ds:a}of s)for(const s of a.data)s<t&&(t=s),s>e&&(e=s);isFinite(t)||(t=0,e=1),t===e&&(t-=1,e+=1);const i=e-t;return e+=.05*i,t>0&&(t=Math.max(0,t-.05*i)),{min:t,max:e}}_yToPixel(t,e,s){return this._plotBottom-(t-e)/(s-e)*this._plotH}_xToPixel(t,e){return e<=1?this._plotLeft+this._plotW/2:this._plotLeft+t/(e-1)*this._plotW}_renderChart({animate:t=!1}={}){if(!this.svg)return;this._updateDimensions(),this._tweenId++;const e=this._tweenId;if(this.crosshairTracking&&this._clearCrosshair(),0===this._datasets.length)return this.svg.innerHTML="",void(this._currentGeometry=null);const{min:s,max:i}=this._calcBounds(),a=this._labels.length||this._datasets[0]?.data.length||0,r=this._buildGeometry(s,i,a),n=this._currentGeometry;if(!(t&&n&&n.chartType===r.chartType&&this.animationDuration>0&&"undefined"!=typeof requestAnimationFrame))return this._paintFrame(r),void(this._currentGeometry=r);const o=performance.now(),h=this.animationDuration,l=t=>{if(this._tweenId!==e)return;const s=Math.min(1,(t-o)/h),i=(t=>1-Math.pow(1-t,3))(s),a=this._interpolateGeometry(n,r,i);this._paintFrame(a),s<1?requestAnimationFrame(l):this._currentGeometry=r};requestAnimationFrame(l)}_buildGeometry(t,e,s){this._padBottomOverride=null;const i=this._niceTicks(t,e,this.gridLines);t=i.niceMin,e=i.niceMax;const a=i.step,r=i.count,n=[];let o=!1;if(this.showXLabels){const t=Math.max(2,Math.floor(this._plotW/60)),e=Math.max(1,Math.ceil(s/t));for(let a=0;a<s;a+=e){const t=void 0!==this._labels[a]?this._labels[a]:"";n.push({index:a,text:this._truncateLabel(this._formatXLabel(t))})}const i="bar"===this.chartType?this._plotW/Math.max(1,s):this._plotW/Math.max(1,s-1);o=n.reduce((t,e)=>Math.max(t,6.5*e.text.length),0)>.9*i,o&&(this._padBottomOverride=48)}const h=this._yToPixel(Math.max(t,Math.min(0,e)),t,e),l=this._visibleDatasets(),d={chartType:this.chartType,min:t,max:e,count:s,baseline:h,xLabelsRotated:o,grid:[],yLabels:[],xLabels:[],lines:[],bars:[]};for(let c=0;c<r;c++){const s=t+c*a,i=this._yToPixel(s,t,e);d.grid.push({x1:this._plotLeft,y1:i,x2:this._plotRight,y2:i}),this.showYLabels&&d.yLabels.push({x:this._plotLeft-4,y:i+3,text:this._formatAxisValue(s,a)})}for(const{index:c,text:u}of n){const t="bar"===this.chartType?this._barSlotCenter(c,s):this._xToPixel(c,s);d.xLabels.push({x:t,y:this._plotBottom+14,text:u})}return"bar"===this.chartType?this._buildBars(d,l,t,e,s):this._buildLines(d,l,t,e,s),d}_barSlotCenter(t,e){if(e<=0)return this._plotLeft;const s=this._plotW/e;return this._plotLeft+t*s+s/2}_buildBars(t,e,s,i,a){const r=this._isStacked(),n=this._plotW/Math.max(a,1),o=Math.max(2,n-this.groupGap);if(r){const r=o;for(let o=0;o<a;o++){let a=0,h=0;for(const{ds:l,i:d}of e){const e=+l.data[o]||0;if(0===e)continue;let c,u;e>=0?(c=this._yToPixel(a+e,s,i),u=this._yToPixel(a,s,i),a+=e):(c=this._yToPixel(h,s,i),u=this._yToPixel(h+e,s,i),h+=e);const p=this._plotLeft+o*n+(n-r)/2;t.bars.push({x:p,y:Math.min(c,u),w:r,h:Math.max(.5,Math.abs(u-c)),color:l.color,fillColor:l.fillColor,dsIdx:d,dataIdx:o,value:e,label:this._labels[o]})}}}else{const r=e.length||1,h=Math.max(1,(o-(r-1)*this.barGap)/r);for(let l=0;l<r;l++){const{ds:r,i:d}=e[l];for(let e=0;e<a;e++){const a=+r.data[e]||0,c=this._plotLeft+e*n+(n-o)/2+l*(h+this.barGap),u=this._yToPixel(a,s,i),p=Math.abs(t.baseline-u);t.bars.push({x:c,y:Math.min(u,t.baseline),w:h,h:Math.max(p,.5),color:r.color,fillColor:r.fillColor,dsIdx:d,dataIdx:e,value:a,label:this._labels[e]})}}}}_buildLines(t,e,s,i,a){for(const{ds:r,i:n}of e){const e=r.data.map((t,e)=>({x:this._xToPixel(e,a),y:this._yToPixel(+t||0,s,i),value:+t||0,index:e,label:this._labels[e]}));t.lines.push({dsIdx:n,color:r.color,fillColor:r.fillColor,fill:r.fill,smoothing:r.smoothing??this.smoothing,points:e})}}_paintFrame(t){if(this.svg){if(this.svg.innerHTML="",this.showGrid)for(const e of t.grid)this.svg.appendChild(this._svgEl("line",{x1:e.x1,y1:e.y1,x2:e.x2,y2:e.y2,stroke:"var(--bs-border-color, #dee2e6)","stroke-width":.5,"stroke-dasharray":"3,3"}));if(this.showYLabels)for(const e of t.yLabels){const t=this._svgEl("text",{x:e.x,y:e.y,"text-anchor":"end","font-size":"10",fill:"var(--bs-secondary-color, #6c757d)"});t.textContent=e.text,this.svg.appendChild(t)}if(this.showXLabels)for(const e of t.xLabels){const s={x:e.x,y:e.y,"font-size":"10",fill:"var(--bs-secondary-color, #6c757d)"};t.xLabelsRotated?(s["text-anchor"]="end",s.transform=`rotate(-45 ${e.x} ${e.y})`):s["text-anchor"]="middle";const i=this._svgEl("text",s);i.textContent=e.text,this.svg.appendChild(i)}"bar"===t.chartType?this._paintBars(t):this._paintLines(t),this._attachHoverListeners(),this.crosshairTracking&&"bar"!==t.chartType&&t.lines.length>0?this._setupCrosshairTracking(t):(this._crosshairLayer=null,this._crosshairLine=null,this._ghostDots=null,this._hitRect=null)}}_paintLines(t){for(const e of t.lines){if(e.fill&&e.points.length>1){const t=this._buildLinePath(e.points,e.smoothing)+` L ${e.points[e.points.length-1].x},${this._plotBottom} L ${e.points[0].x},${this._plotBottom} Z`;this.svg.appendChild(this._svgEl("path",{d:t,fill:e.fillColor,stroke:"none",class:`mini-series-area mini-series-ds-${e.dsIdx}`}))}if(e.points.length>1&&this.svg.appendChild(this._svgEl("path",{d:this._buildLinePath(e.points,e.smoothing),fill:"none",stroke:e.color,"stroke-width":this.strokeWidth,"stroke-linecap":"round","stroke-linejoin":"round",class:`mini-series-line mini-series-ds-${e.dsIdx}`})),this.showDots)for(const t of e.points)this.svg.appendChild(this._svgEl("circle",{cx:t.x,cy:t.y,r:this.dotRadius,fill:e.color,class:`mini-series-dot mini-series-ds-${e.dsIdx}`,"data-ds":e.dsIdx,"data-col":t.index}))}}_paintBars(t){for(const e of t.bars)this.svg.appendChild(this._svgEl("rect",{x:e.x,y:e.y,width:e.w,height:e.h,fill:e.fillColor||e.color,rx:1,class:`mini-series-bar mini-series-ds-${e.dsIdx}`,"data-ds":e.dsIdx,"data-col":e.dataIdx}))}_setupCrosshairTracking(t){const e=this._svgEl("g",{class:"mini-series-crosshair-layer"}),s={x1:0,y1:this._plotTop,x2:0,y2:this._plotBottom,"stroke-width":this.crosshairWidth,class:"mini-series-crosshair",style:"display:none;"};this.crosshairColor?s.stroke=this.crosshairColor:s.stroke="currentColor";const i=this._svgEl("line",s);e.appendChild(i);const a=t.lines.map(t=>{const s=this._svgEl("circle",{cx:0,cy:0,r:this.dotRadius+1,fill:t.color,class:`mini-series-ghost mini-series-ds-${t.dsIdx}`,"data-ds":t.dsIdx,style:"display:none;"});return e.appendChild(s),s}),r=this._svgEl("rect",{x:this._plotLeft,y:this._plotTop,width:this._plotW,height:this._plotH,class:"mini-series-hit"});e.appendChild(r),this.svg.appendChild(e),this._crosshairLayer=e,this._crosshairLine=i,this._ghostDots=a,this._hitRect=r,r.addEventListener("mousemove",t=>{const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,i=this._findColumn(s);if(i<0||!this._currentGeometry)return this._clearCrosshair(),void this._hideTooltip();this._paintCrosshair(i,this._currentGeometry),this.showTooltip&&this._showTooltip(i,t)}),r.addEventListener("mouseleave",()=>{this._clearCrosshair(),this._hideTooltip(),this._clearFade()}),r.addEventListener("click",t=>{const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,i=this._findColumn(s);if(i<0)return;const a=this._visibleDatasets()[0];a&&this.emit?.("chart:click",{chart:this,datasetIndex:a.i,index:i,value:a.ds.data[i],label:this._labels[i]})})}_findColumn(t){const e=this._labels.length||this._datasets[0]?.data.length||0;if(e<2)return-1;if(t<this._plotLeft||t>this._plotRight)return-1;const s=(t-this._plotLeft)/this._plotW,i=Math.round(s*(e-1));return i<0?0:i>e-1?e-1:i}_paintCrosshair(t,e){if(!this._crosshairLine||!e||!e.lines.length)return;const s=e.lines[0].points[t];if(s){this._crosshairLine.setAttribute("x1",s.x),this._crosshairLine.setAttribute("x2",s.x),this._crosshairLine.style.display="";for(let s=0;s<e.lines.length;s++){const i=this._ghostDots&&this._ghostDots[s];if(!i)continue;const a=e.lines[s].points[t];a?(i.setAttribute("cx",a.x),i.setAttribute("cy",a.y),i.style.display=""):i.style.display="none"}}}_clearCrosshair(){if(this._crosshairLine&&(this._crosshairLine.style.display="none"),this._ghostDots)for(const t of this._ghostDots)t.style.display="none"}_buildLinePath(t,e){if(!t||0===t.length)return"";if(e>0&&t.length>=2){let s=`M ${t[0].x},${t[0].y}`;for(let i=0;i<t.length-1;i++){const a=t[i],r=t[i+1],n=a.x+(r.x-a.x)*e,o=r.x-(r.x-a.x)*e;s+=` C ${n},${a.y} ${o},${r.y} ${r.x},${r.y}`}return s}return t.map((t,e)=>`${0===e?"M":"L"} ${t.x},${t.y}`).join(" ")}_interpolateGeometry(t,e,s){const i=(t,e)=>t+(e-t)*s,a={chartType:e.chartType,min:e.min,max:e.max,count:e.count,baseline:e.baseline,grid:e.grid,yLabels:e.yLabels,xLabels:e.xLabels,lines:[],bars:[]};return"bar"===e.chartType?a.bars=e.bars.map(e=>{const s=t.bars.find(t=>t.dsIdx===e.dsIdx&&t.dataIdx===e.dataIdx);return s?{...e,x:i(s.x,e.x),y:i(s.y,e.y),w:i(s.w,e.w),h:i(s.h,e.h)}:{...e,y:i(t.baseline,e.y),h:i(0,e.h)}}):a.lines=e.lines.map(e=>{const s=t.lines.find(t=>t.dsIdx===e.dsIdx),a=e.points.map((e,a)=>{const r=s&&s.points[a];return r?{...e,x:i(r.x,e.x),y:i(r.y,e.y)}:{...e,y:i(t.baseline,e.y)}});return{...e,points:a}}),a}_attachHoverListeners(){this.svg&&this.svg.querySelectorAll(".mini-series-bar, .mini-series-dot").forEach(t=>{const e=parseInt(t.getAttribute("data-col"),10),s=parseInt(t.getAttribute("data-ds"),10);isNaN(e)||isNaN(s)||(t.addEventListener("mouseenter",t=>{this.showTooltip&&this._showTooltip(e,t),this.highlightOnHover&&this._fadeOtherSeries(s)}),t.addEventListener("mousemove",t=>{this.showTooltip&&this._moveTooltip(t)}),t.addEventListener("mouseleave",()=>{this._hideTooltip(),this.highlightOnHover&&this._clearFade()}),t.addEventListener("click",()=>{const t=this._datasets[s]?.data[e];this.emit?.("chart:click",{chart:this,datasetIndex:s,index:e,value:t,label:this._labels[e]})}))})}_fadeOtherSeries(t){this.svg&&this.svg.querySelectorAll('[class*="mini-series-ds-"]').forEach(e=>{const s=e.getAttribute("class").match(/mini-series-ds-(\d+)/);s&&(parseInt(s[1],10)!==t?e.classList.add("mini-series-faded"):e.classList.remove("mini-series-faded"))})}_clearFade(){this.svg&&this.svg.querySelectorAll(".mini-series-faded").forEach(t=>t.classList.remove("mini-series-faded"))}_showTooltip(t,e){if(!this.tooltipEl)return;const s=void 0!==this._labels[t]?this._labels[t]:`#${t+1}`;this.tooltipEl.innerHTML="";const i=document.createElement("div");i.className="mini-series-tooltip-label",i.textContent=String(this._formatXLabel(s)),this.tooltipEl.appendChild(i);for(let a=0;a<this._datasets.length;a++){if(this._hidden.has(a))continue;const e=this._datasets[a],s=e.data[t],i=document.createElement("div");i.className="mini-series-tooltip-row";const r=document.createElement("span");r.className="mini-series-tooltip-swatch",r.style.background=e.color,i.appendChild(r);const n=document.createElement("span");n.textContent=`${e.label}:`,i.appendChild(n);const o=document.createElement("strong");o.textContent=this._formatValue(s),i.appendChild(o),this.tooltipEl.appendChild(i)}this.tooltipEl.style.display="block",this._moveTooltip(e)}_moveTooltip(t){if(!this.tooltipEl||"none"===this.tooltipEl.style.display)return;const e=this.element.querySelector(".mini-series-svg-area");if(!e)return;const s=e.getBoundingClientRect(),i=t.clientX-s.left,a=t.clientY-s.top;this.tooltipEl.style.left="0px",this.tooltipEl.style.top="0px",this.tooltipEl.style.transform="none";const r=this.tooltipEl.offsetWidth,n=this.tooltipEl.offsetHeight,o=s.width,h=s.height;let l=i-r/2,d=a-n-8;l<0&&(l=0),l+r>o&&(l=o-r),d<0&&(d=a+12),d+n>h&&(d=h-n),this.tooltipEl.style.left=`${l}px`,this.tooltipEl.style.top=`${d}px`}_hideTooltip(){this.tooltipEl&&(this.tooltipEl.style.display="none")}_normalizeLegendJustify(t){return void 0===t?"start":"start"===t||"center"===t||"end"===t?t:(console.warn(`SeriesChart: invalid legendJustify "${t}" — expected 'start' | 'center' | 'end'. Falling back to 'start'.`),"start")}_renderLegend(){this.showLegend&&this.legendEl&&(this.legendEl.innerHTML="",this._datasets.forEach((t,e)=>{const s=document.createElement("span");s.className="mini-series-legend-item",this._hidden.has(e)&&s.classList.add("mini-series-legend-hidden"),s.setAttribute("role","button"),s.setAttribute("data-ds",String(e));const i=document.createElement("span");i.className="mini-series-legend-swatch",i.style.background=t.color,s.appendChild(i);const a=document.createElement("span");a.className="mini-series-legend-label",a.textContent=t.label,s.appendChild(a),s.addEventListener("click",()=>this.toggleSeries(e)),this.legendEl.appendChild(s)}))}_formatValue(t){return null==t?"":this.valueFormatter?"function"==typeof this.valueFormatter?this.valueFormatter(t):this.dataFormatter.pipe(t,this.valueFormatter):"number"==typeof t?t.toLocaleString():String(t)}_formatXLabel(t){if(null==t)return"";if(this.xLabelFormatter)return this.xLabelFormatter(t);if(this.xLabelFormat){const e=this.dataFormatter.pipe(t,this.xLabelFormat);if(e)return e}return String(t)}_niceNumber(t,e){if(!isFinite(t)||t<=0)return 1;const s=Math.floor(Math.log10(t)),i=t/Math.pow(10,s);let a;return a=e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10,a*Math.pow(10,s)}_niceTicks(t,e,s){t===e&&(t-=1,e+=1);const i=this._niceNumber(e-t,!1),a=this._niceNumber(i/Math.max(1,s-1),!0),r=Math.floor(t/a)*a,n=Math.ceil(e/a)*a;return{niceMin:r,niceMax:n,step:a,count:Math.round((n-r)/a)+1}}_formatAxisValue(t,e){if(this.valueFormatter)return"function"==typeof this.valueFormatter?this.valueFormatter(t):this.dataFormatter.pipe(t,this.valueFormatter);if(Math.abs(t)>=1e9)return(t/1e9).toFixed(1)+"B";if(Math.abs(t)>=1e6)return(t/1e6).toFixed(1)+"M";if(Math.abs(t)>=1e3)return(t/1e3).toFixed(1)+"K";if(Number.isInteger(t))return String(t);const s=e&&e>0&&e<1?Math.max(0,-Math.floor(Math.log10(e))):1;return t.toFixed(s)}_truncateLabel(t){const e=String(t);return e.length>24?e.substring(0,23)+"…":e}_toRgba(t,e){if(!t)return t;const s=String(t).trim();if(s.startsWith("#")){let t=s.slice(1);return 3===t.length&&(t=t.split("").map(t=>t+t).join("")),6===t.length?`rgba(${parseInt(t.slice(0,2),16)},${parseInt(t.slice(2,4),16)},${parseInt(t.slice(4,6),16)},${e})`:s}let i=s.match(/^rgba?\(([^)]+)\)$/i);if(i){const t=i[1].split(",").map(t=>t.trim());return`rgba(${t[0]},${t[1]},${t[2]},${e})`}if(i=s.match(/^hsla?\(([^)]+)\)$/i),i){const t=i[1].split(",").map(t=>t.trim());return`hsla(${t[0]},${t[1]},${t[2]},${e})`}return s}_svgEl(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);for(const[i,a]of Object.entries(e))s.setAttribute(i,String(a));return s}_esc(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}}const r=["#36A2EB","#FF6384","#FFCE56","#4BC0C0","#9966FF","#FF9F40","#66BB6A","#EF5350","#AB47BC","#26C6DA"],n=t=>`hsl(${(137.508*t%360).toFixed(0)}, 65%, 52%)`;class PieChart extends t{constructor(t={}){super({className:"mini-pie-chart",...t}),this.width=t.width||200,this.height=t.height||200,this.cutout=t.cutout||0,this.centerLabel=t.centerLabel??null,this.centerSubLabel=t.centerSubLabel??null,this.colors=Array.isArray(t.colors)&&t.colors.length?[...t.colors]:[...r],this.colorGenerator=t.colorGenerator||n,this.showLegend=!1!==t.showLegend,this.legendPosition=t.legendPosition||"right","none"===this.legendPosition&&(this.showLegend=!1),this.showLabels=!0===t.showLabels,this.showPercentages=!1!==t.showPercentages,this.showTooltip=!1!==t.showTooltip,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.valueFormatter=t.valueFormatter||null,this.dataFormatter=e,this.endpoint=t.endpoint||null,this._rawData=t.data||null,this._segments=[],this._currentSegments=null,this._tweenId=0}getTemplate(){return`\n <div class="mini-pie-wrapper ${"bottom"===this.legendPosition?"mini-pie-layout-bottom":"mini-pie-layout-right"}">\n <div class="mini-pie-svg-area" style="width:${this.width}px; height:${this.height}px;">\n <svg class="mini-pie-svg" viewBox="0 0 ${this.width} ${this.height}"\n width="100%" height="100%" preserveAspectRatio="xMidYMid meet">\n </svg>\n ${this.showTooltip?'<div class="mini-pie-tooltip" style="display:none;"></div>':""}\n </div>\n ${this.showLegend?'<div class="mini-pie-legend"></div>':""}\n </div>\n `}async onInit(){if(this.endpoint)try{const t=this.getApp()?.rest;if(t){const e=await t.GET(this.endpoint);e?.success&&(this._rawData=e.data?.data??e.data??this._rawData)}}catch(t){console.error("PieChart endpoint fetch failed:",t)}}async onAfterRender(){this.svg=this.element.querySelector(".mini-pie-svg"),this.tooltipEl=this.element.querySelector(".mini-pie-tooltip"),this.legendEl=this.element.querySelector(".mini-pie-legend"),this._rawData&&(this._parseData(this._rawData),this._renderPie({animate:!1}),this._renderLegend())}async onBeforeDestroy(){this._tweenId++,await super.onBeforeDestroy()}setData(t,e={}){if(this._rawData=t,this._parseData(t),this.svg){const t=e.animate??this.animate;this._renderPie({animate:t}),this._renderLegend()}}async refresh(){if(!this.endpoint)return;const t=this.getApp()?.rest;if(t)try{const e=await t.GET(this.endpoint);if(e?.success){const t=e.data?.data??e.data;t&&this.setData(t)}}catch(e){console.error("PieChart refresh failed:",e)}}_parseData(t){let e=[],s=[],i=[];if(Array.isArray(t))for(const r of t)e.push(r.label),s.push(+r.value||0),i.push(r.color||null);else if(t&&t.labels&&t.datasets){e=t.labels,s=(t.datasets[0]?.data||[]).map(t=>+t||0);const a=t.datasets[0]?.backgroundColor;Array.isArray(a)&&(i=a)}else t&&"object"==typeof t&&(e=Object.keys(t),s=Object.values(t).map(t=>+t||0));const a=s.reduce((t,e)=>t+e,0);this._segments=e.map((t,e)=>({label:t,value:s[e]||0,pct:a>0?(s[e]||0)/a*100:0,color:i[e]||this.colors[e]||this.colorGenerator(e)}))}_renderPie({animate:t=!1}={}){if(!this.svg)return;this._tweenId++;const e=this._tweenId,s=this._buildSegmentGeometry(this._segments),i=this._currentSegments;if(!(t&&i&&this.animationDuration>0&&"undefined"!=typeof requestAnimationFrame))return this._paintPie(s),void(this._currentSegments=s);const a=performance.now(),r=this.animationDuration,n=t=>{if(this._tweenId!==e)return;const o=Math.min(1,(t-a)/r),h=(t=>1-Math.pow(1-t,3))(o),l=this._interpolateSegments(i,s,h);this._paintPie(l),o<1?requestAnimationFrame(n):this._currentSegments=s};requestAnimationFrame(n)}_buildSegmentGeometry(t){const e=[];let s=-Math.PI/2;for(const i of t){const t=i.pct/100*Math.PI*2;e.push({...i,startAngle:s,endAngle:s+t}),s+=t}return e}_interpolateSegments(t,e,s){const i=(t,e)=>t+(e-t)*s,a=/* @__PURE__ */new Map;for(const r of t)a.set(r.label,r);return e.map(t=>{const e=a.get(t.label);return e?{...t,startAngle:i(e.startAngle,t.startAngle),endAngle:i(e.endAngle,t.endAngle)}:{...t,startAngle:t.startAngle,endAngle:i(t.startAngle,t.endAngle)}})}_paintPie(t){if(!this.svg)return;if(this.svg.innerHTML="",!t||0===t.length)return;const e=this.width/2,s=this.height/2,i=Math.min(e,s)-4,a=this.cutout>0?i*this.cutout:0,r=t.filter(t=>Math.abs(t.endAngle-t.startAngle)>1e-6);if(1===r.length&&Math.abs(Math.abs(r[0].endAngle-r[0].startAngle)-2*Math.PI)<.001){const t=r[0];return a>0?this.svg.appendChild(this._svgEl("circle",{cx:e,cy:s,r:(i+a)/2,fill:"none",stroke:t.color,"stroke-width":i-a})):this.svg.appendChild(this._svgEl("circle",{cx:e,cy:s,r:i,fill:t.color})),void this._setupHover()}for(let n=0;n<t.length;n++){const r=t[n],o=r.endAngle-r.startAngle;if(o<=0)continue;const h=r.startAngle,l=r.endAngle,d=o>Math.PI?1:0,c=e+i*Math.cos(h),u=s+i*Math.sin(h),p=e+i*Math.cos(l),g=s+i*Math.sin(l);let m;if(m=a>0?[`M ${c} ${u}`,`A ${i} ${i} 0 ${d} 1 ${p} ${g}`,`L ${e+a*Math.cos(l)} ${s+a*Math.sin(l)}`,`A ${a} ${a} 0 ${d} 0 ${e+a*Math.cos(h)} ${s+a*Math.sin(h)}`,"Z"].join(" "):[`M ${e} ${s}`,`L ${c} ${u}`,`A ${i} ${i} 0 ${d} 1 ${p} ${g}`,"Z"].join(" "),this.svg.appendChild(this._svgEl("path",{d:m,fill:r.color,stroke:"var(--bs-body-bg, #fff)","stroke-width":1.5,"data-index":n,class:"mini-pie-segment"})),this.showLabels&&o>.05){const t=(h+l)/2,a=i+12,n=e+a*Math.cos(t),o=s+a*Math.sin(t),d=this._svgEl("text",{x:n,y:o,"text-anchor":n<e?"end":"start","font-size":"10",fill:"var(--bs-body-color, #212529)"});d.textContent=this.showPercentages?`${r.label} (${r.pct.toFixed(1)}%)`:r.label,this.svg.appendChild(d)}}if(a>0&&(null!=this.centerLabel||null!=this.centerSubLabel)){const t={total:this._segments.reduce((t,e)=>t+e.value,0),segments:this._segments},i="function"==typeof this.centerLabel?this.centerLabel(t):this.centerLabel,a="function"==typeof this.centerSubLabel?this.centerSubLabel(t):this.centerSubLabel;if(null!=i){const t=this._svgEl("text",{x:e,y:null!=a?s-2:s+6,"text-anchor":"middle","dominant-baseline":"middle","font-size":"20","font-weight":"500",fill:"var(--bs-body-color, #212529)",class:"mini-pie-center-label"});t.textContent=String(i),this.svg.appendChild(t)}if(null!=a){const t=this._svgEl("text",{x:e,y:s+16,"text-anchor":"middle","dominant-baseline":"middle","font-size":"9","letter-spacing":"0.12em",fill:"var(--bs-secondary-color, #6c757d)",class:"mini-pie-center-sub"});t.textContent=String(a).toUpperCase(),this.svg.appendChild(t)}}this._setupHover()}_setupHover(){this.svg&&this.svg.querySelectorAll(".mini-pie-segment, circle").forEach((t,e)=>{const s=t.hasAttribute("data-index")?parseInt(t.getAttribute("data-index"),10):e;t.addEventListener("mouseenter",t=>{this.showTooltip&&this._showTooltip(s,t)}),t.addEventListener("mousemove",t=>{this.showTooltip&&this._moveTooltip(t)}),t.addEventListener("mouseleave",()=>this._hideTooltip()),t.addEventListener("click",()=>{const t=this._segments[s];t&&this.emit?.("chart:click",{chart:this,slice:t,index:s,value:t.value,label:t.label})})})}_showTooltip(t,e){const s=this._segments[t];if(!s||!this.tooltipEl)return;let i=s.value;this.valueFormatter?i="function"==typeof this.valueFormatter?this.valueFormatter(s.value):this.dataFormatter.pipe(s.value,this.valueFormatter):"number"==typeof s.value&&(i=s.value.toLocaleString()),this.tooltipEl.innerHTML=`\n <strong>${this._esc(s.label)}</strong><br>\n ${this._esc(i)} (${s.pct.toFixed(1)}%)\n `,this.tooltipEl.style.display="block",this._moveTooltip(e)}_moveTooltip(t){if(!this.tooltipEl||"none"===this.tooltipEl.style.display)return;const e=this.element.querySelector(".mini-pie-svg-area");if(!e)return;const s=e.getBoundingClientRect(),i=t.clientX-s.left,a=t.clientY-s.top;this.tooltipEl.style.left="0px",this.tooltipEl.style.top="0px",this.tooltipEl.style.transform="none";const r=this.tooltipEl.offsetWidth,n=this.tooltipEl.offsetHeight,o=s.width,h=s.height;let l=i-r/2,d=a-n-8;l<0&&(l=0),l+r>o&&(l=o-r),d<0&&(d=a+12),d+n>h&&(d=h-n),this.tooltipEl.style.left=`${l}px`,this.tooltipEl.style.top=`${d}px`}_hideTooltip(){this.tooltipEl&&(this.tooltipEl.style.display="none")}_renderLegend(){if(this.showLegend&&this.legendEl){this.legendEl.innerHTML="";for(const t of this._segments){const e=document.createElement("div");e.className="mini-pie-legend-item";const s=document.createElement("span");s.className="mini-pie-legend-swatch",s.style.background=t.color,e.appendChild(s);const i=document.createElement("span");i.className="mini-pie-legend-label",i.textContent=t.label,e.appendChild(i);const a=document.createElement("span");a.className="mini-pie-legend-value",a.textContent=`${t.pct.toFixed(1)}%`,e.appendChild(a),this.legendEl.appendChild(e)}}}_svgEl(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);for(const[i,a]of Object.entries(e))s.setAttribute(i,String(a));return s}_esc(t){const e=document.createElement("div");return e.textContent=t,e.innerHTML}}class MetricsChart extends t{constructor(t={}){super({...t,className:`mojo-metrics-chart ${t.className||""}`.trim()}),this.title=t.title||"Metrics",this.chartTitle=t.chartTitle||"",this.chartType=t.chartType||"line",this.height=t.height||300,this.yAxis=t.yAxis||{label:"Count",beginAtZero:!0},this.tooltip=t.tooltip||{y:"number:0"},this.colors=t.colors,this.colorGenerator=t.colorGenerator,this.legendPosition=t.legendPosition||"top",this.legendJustify=t.legendJustify||"start",this.showLegend=!1!==t.showLegend,this.showXLabels=!1!==t.showXLabels,this.showYLabels=!1!==t.showYLabels,this.highlightOnHover=!0===t.highlightOnHover,this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||"24h",this.childKind=t.childKind||null,this.breakdown=!0===t.breakdown,this.showGranularity=!1!==t.showGranularity,this.showDateRange=!1!==t.showDateRange,this.showTypeSwitch=!1!==t.showTypeSwitch,this.showRefresh=!1!==t.showRefresh,this.showStats=!1!==t.showStats,this.showDataTable=!1!==t.showDataTable,this.inlineGranularity=!1!==t.inlineGranularity,this.compactHeader=!0===t.compactHeader,this.compactHeader&&(this.showGranularity=!1,this.showTypeSwitch=!0===t.showTypeSwitch,this.showRefresh=!0===t.showRefresh,this.inlineGranularity=!1,this.showStats=!0===t.showStats,this.showDataTable=!0===t.showDataTable),this.withDelta=!0===t.withDelta,this.withDelta&&void 0===t.endpoint&&(this.endpoint="/api/metrics/series"),this.granularityOptions=t.granularityOptions||[{value:"minutes",label:"Minutes",shortLabel:"MIN"},{value:"hours",label:"Hours",shortLabel:"HR"},{value:"days",label:"Days",shortLabel:"DAY"},{value:"weeks",label:"Weeks",shortLabel:"WK"},{value:"months",label:"Months",shortLabel:"MO"}],this.quickRanges=t.quickRanges||[{value:"1h",label:"1H"},{value:"24h",label:"24H"},{value:"7d",label:"7D"},{value:"30d",label:"30D"}],this.maxDatasets=Number.isFinite(t.maxDatasets)?t.maxDatasets:null,this.groupRemainingLabel=t.groupRemainingLabel||"Other",this.isLoading=!1,this.lastFetch=null,this.dateStart&&this.dateEnd||this.setQuickRange(this.defaultDateRange)}async onInit(){this.chart=new SeriesChart({containerId:"chart",chartType:this.chartType,height:this.height,valueFormatter:this.tooltip?.y||null,xLabelFormat:this._resolveXLabelFormat(),colors:this.colors,colorGenerator:this.colorGenerator,showLegend:this.showLegend,legendPosition:this.legendPosition,legendJustify:this.legendJustify,showXLabels:this.showXLabels,showYLabels:this.showYLabels,highlightOnHover:this.highlightOnHover}),this.addChild(this.chart)}_resolveXLabelFormat(){return this.tooltip&&void 0!==this.tooltip.x?this.tooltip.x:MetricsChart.X_LABEL_FORMAT_BY_GRANULARITY[this.granularity]||null}async onAfterRender(){await this.fetchData()}async getTemplate(){return`\n <div class="mojo-metrics-chart-container">\n ${this.compactHeader?"":`\n <div class="mb-2 mojo-metrics-chart-header">\n <h5 class="mb-0 mojo-metrics-chart-title">{{{title}}}</h5>\n <div class="btn-toolbar align-items-center mojo-metrics-chart-toolbar" role="toolbar">\n ${this._renderGranularityToggleHtml()}\n <div class="mc-secondary-actions">\n <div class="mc-secondary-extras">\n ${this._renderGearMenuHtml()}\n ${this._renderTypeSwitchHtml()}\n ${this._renderStatsButtonHtml()}\n ${this._renderDataTableButtonHtml()}\n ${this._renderRefreshButtonHtml()}\n </div>\n <button type="button" class="btn btn-link btn-sm mc-actions-trigger ms-1 px-2" tabindex="0" aria-label="More actions" title="More actions"><i class="bi bi-three-dots-vertical"></i></button>\n </div>\n </div>\n </div>`}\n <div class="position-relative" style="min-height:${"number"==typeof this.height?this.height+"px":this.height};">\n <div data-container="chart"></div>\n <div class="chart-overlay d-none" data-loading>\n <div class="d-flex flex-column align-items-center">\n <div class="spinner-border text-primary mb-2" role="status">\n <span class="visually-hidden">Loading...</span>\n </div>\n <small class="text-muted">Loading metrics…</small>\n </div>\n </div>\n <div class="chart-overlay d-none" data-error>\n <div class="alert alert-danger mb-0">\n <i class="bi bi-exclamation-triangle me-1"></i>\n <span class="error-message"></span>\n <button class="btn btn-sm btn-outline-danger ms-2" data-action="retry-fetch">Retry</button>\n </div>\n </div>\n </div>\n </div>\n `}_renderGearMenuHtml(){const t=[];if(this.showGranularity&&!this.inlineGranularity){t.push('<li><h6 class="dropdown-header">Granularity</h6></li>');for(const e of this.granularityOptions){const s=e.value===this.granularity?" mc-selected":"";t.push(`<li><a class="dropdown-item${s}" role="button" data-action="granularity-changed" data-value="${this._escAttr(e.value)}">${this._escHtml(e.label)}</a></li>`)}}if(this.showDateRange){t.length&&t.push('<li><hr class="dropdown-divider"></li>'),t.push('<li><h6 class="dropdown-header">Date Range</h6></li>');for(const e of this.quickRanges){const s=e.value===this.defaultDateRange?" mc-selected":"";t.push(`<li><a class="dropdown-item${s}" role="button" data-action="quick-range" data-range="${this._escAttr(e.value)}">${this._escHtml(e.label)}</a></li>`)}t.push('<li><a class="dropdown-item" role="button" data-action="show-date-range-dialog"><i class="bi bi-calendar-range me-1"></i>Custom Range…</a></li>')}return t.length?`\n <style>\n .mc-gear-menu .dropdown-item.mc-selected { background:#f0f0f0; color:inherit; }\n .mc-gear-menu .dropdown-item.mc-selected::before { content:'\\F633'; font-family:'bootstrap-icons'; margin-right:0.4rem; font-size:0.75em; }\n </style>\n <div class="btn-group btn-group-sm me-2">\n <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"\n data-bs-toggle="dropdown" aria-expanded="false" title="Chart Settings">\n <i class="bi bi-gear"></i>\n </button>\n <ul class="dropdown-menu dropdown-menu-end mc-gear-menu">${t.join("")}</ul>\n </div>`:""}_renderGranularityToggleHtml(){return this.inlineGranularity&&this.showGranularity&&this.granularityOptions?.length?`\n <style>\n .mojo-metrics-chart-container { container-type: inline-size; }\n .mojo-metrics-chart-container .mc-gran-toggle { display:inline-flex; align-items:center; gap:0.05rem; margin-right:0.5rem; }\n .mojo-metrics-chart-container .mc-gran-btn {\n background: transparent;\n border: 0;\n color: var(--bs-secondary-color);\n font-size: 0.7rem;\n font-weight: 500;\n letter-spacing: 0.03em;\n padding: 0.2rem 0.45rem;\n border-radius: 0.25rem;\n line-height: 1;\n cursor: pointer;\n transition: color 0.15s, background-color 0.15s;\n }\n .mojo-metrics-chart-container .mc-gran-btn:hover { color: var(--bs-body-color); background: var(--bs-secondary-bg); }\n .mojo-metrics-chart-container .mc-gran-btn.mc-selected { color: var(--bs-body-color); font-weight: 600; background: var(--bs-secondary-bg); }\n .mojo-metrics-chart-container .mc-gran-select { display: none; }\n @container (max-width: 560px) {\n .mojo-metrics-chart-container .mc-gran-toggle { display: none; }\n .mojo-metrics-chart-container .mc-gran-select {\n display: inline-block;\n width: auto;\n margin-right: 0.5rem;\n }\n }\n </style>\n <div class="mc-gran-toggle" role="group" aria-label="Granularity">${this.granularityOptions.map(t=>{const e=t.value===this.granularity?"mc-gran-btn mc-selected":"mc-gran-btn",s=t.shortLabel||t.label||t.value;return`<button type="button" class="${e}" data-action="granularity-changed" data-value="${this._escAttr(t.value)}" title="${this._escAttr(t.label||t.value)}">${this._escHtml(s)}</button>`}).join("")}</div>\n <select class="form-select form-select-sm mc-gran-select" data-action="granularity-changed" aria-label="Granularity">${this.granularityOptions.map(t=>{const e=t.value===this.granularity?" selected":"";return`<option value="${this._escAttr(t.value)}"${e}>${this._escHtml(t.label||t.value)}</option>`}).join("")}</select>`:""}_renderTypeSwitchHtml(){return this.showTypeSwitch?`\n <div class="btn-group btn-group-sm" role="group">\n <button type="button" class="btn ${"line"===this.chartType?"btn-primary":"btn-outline-primary"} btn-sm" data-action="set-chart-type" data-type="line" title="Line"><i class="bi bi-graph-up"></i></button>\n <button type="button" class="btn ${"bar"===this.chartType?"btn-primary":"btn-outline-primary"} btn-sm" data-action="set-chart-type" data-type="bar" title="Bar"><i class="bi bi-bar-chart"></i></button>\n </div>`:""}_renderRefreshButtonHtml(){return this.showRefresh?'\n <div class="btn-group btn-group-sm ms-2 refresh-btn" role="group">\n <button type="button" class="btn btn-outline-secondary btn-sm" data-action="refresh-chart" title="Refresh"><i class="bi bi-arrow-clockwise"></i></button>\n </div>':""}_renderStatsButtonHtml(){return this.showStats?'\n <div class="btn-group btn-group-sm ms-2" role="group">\n <button type="button" class="btn btn-outline-secondary btn-sm" data-action="show-stats" title="Stats"><i class="bi bi-info-circle"></i></button>\n </div>':""}_renderDataTableButtonHtml(){return this.showDataTable?'\n <div class="btn-group btn-group-sm ms-1" role="group">\n <button type="button" class="btn btn-outline-secondary btn-sm" data-action="show-data-table" title="View data"><i class="bi bi-table"></i></button>\n </div>':""}_escHtml(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}_escAttr(t){return String(t??"").replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}async onActionGranularityChanged(t,e){const s="SELECT"===e.tagName?e.value:e.dataset?.value;if(!s||s===this.granularity)return;this.granularity=s,this.setQuickRange(MetricsChart.GRANULARITY_DEFAULTS[s]||"24h"),this._updateDropdownActive("granularity-changed",s,"value");const i=this.element?.querySelector("select.mc-gran-select");i&&i.value!==s&&(i.value=s),!this.chart||this.tooltip&&void 0!==this.tooltip.x||(this.chart.xLabelFormat=this._resolveXLabelFormat()),await this.fetchData()}async onActionQuickRange(t,e){const s=e.dataset?.range;s&&(this.setQuickRange(s),this._updateDropdownActive("quick-range",s,"range"),await this.fetchData())}async onActionShowDateRangeDialog(){try{const t=await s.form({title:"Select Date Range",size:"sm",fields:[{name:"dt_start",type:"datetime-local",label:"Start",value:this.formatDateTimeLocal(this.dateStart),required:!0},{name:"dt_end",type:"datetime-local",label:"End",value:this.formatDateTimeLocal(this.dateEnd),required:!0}]});t?.dt_start&&t?.dt_end&&(this.dateStart=new Date(t.dt_start),this.dateEnd=new Date(t.dt_end),this._updateDropdownActive("quick-range","","range"),await this.fetchData())}catch(t){console.error("Date range dialog error:",t)}}async onActionSetChartType(t,e){t.stopPropagation();const s=e.getAttribute("data-type");if(!s||s===this.chartType)return;this.chartType=s,this.chart&&this.chart.setChartType(s);const i=this.element?.querySelectorAll('[data-action="set-chart-type"]');i?.forEach(t=>{const e=t.getAttribute("data-type")===s;t.classList.toggle("btn-primary",e),t.classList.toggle("btn-outline-primary",!e)})}async onActionRetryFetch(){return this.fetchData()}async onActionShowStats(){const t=this.chartTitle||("string"==typeof this.title?this.title.replace(/<[^>]*>/g,"").trim():"")||"Stats";return s.dialog({title:`${t} — Stats`,body:this._renderStatsHtml(),size:"md",buttons:[{text:"Close",class:"btn-secondary",dismiss:!0}]})}async onActionShowDataTable(){const t=Array.isArray(this._lastChartData?.datasets)?this._lastChartData.datasets:null,e=Array.isArray(this._lastChartData?.labels)?this._lastChartData.labels:null;if(!t||0===t.length||!e||0===e.length)return s.alert({title:this.chartTitle||this.title||"Data",message:"No data to display."});const i=e.length,a=["Label",...t.map(t=>t.label||"")],r=[];for(let s=0;s<i;s++){const i={label:e[s],values:t.map(t=>t.data?.[s]??null)};r.push(i)}const n=`\n <div class="table-responsive" style="max-height: 60vh;">\n <table class="table table-sm table-striped mb-0 mc-data-table">\n <thead class="sticky-top bg-body">\n <tr>${a.map((t,e)=>`<th${0===e?"":' class="text-end"'}>${this._escHtml(t)}</th>`).join("")}</tr>\n </thead>\n <tbody>\n ${r.map(t=>`\n <tr>\n <td>${this._escHtml(t.label)}</td>\n ${t.values.map(t=>`<td class="text-end">${this._formatStatNumber("number"==typeof t?t:parseFloat(t)||0)}</td>`).join("")}\n </tr>\n `).join("")}\n </tbody>\n </table>\n </div>`;"csv"===await s.dialog({title:this.chartTitle||("string"==typeof this.title?this.title.replace(/<[^>]*>/g,"").trim():"Chart data")||"Chart data",body:n,size:"lg",buttons:[{text:"Download CSV",class:"btn-outline-primary",value:"csv"},{text:"Close",class:"btn-secondary",dismiss:!0}]})&&this._downloadCsv(r,a)}_computeStats(){const t=Array.isArray(this._lastChartData?.datasets)?this._lastChartData.datasets:null;return t&&0!==t.length?t.map(t=>{const e=(t.data||[]).map(t=>"number"==typeof t?t:parseFloat(t)||0);if(0===e.length)return{label:t.label||"",count:0};const s=[...e].sort((t,e)=>t-e),i=e.reduce((t,e)=>t+e,0),a=Math.floor(s.length/2),r=s.length%2==0?(s[a-1]+s[a])/2:s[a];return{label:t.label||"",count:e.length,sum:i,avg:i/e.length,median:r,min:s[0],max:s[s.length-1],latest:e[e.length-1]}}):null}_formatStatNumber(t){if(null==t||Number.isNaN(t))return"–";const e=this.tooltip?.y;return e&&this.chart?.dataFormatter?this.chart.dataFormatter.pipe(t,e):Number.isInteger(t)?t.toLocaleString():Number(t).toLocaleString(void 0,{maximumFractionDigits:2})}_renderStatsHtml(){const t=this._computeStats(),e=this._renderStatsHeader(t?.[0]?.count);if(!t||0===t.length)return`${e}<div class="text-muted small">No data</div>`;const s=t=>this._formatStatNumber(t);return`\n ${e}\n <div class="table-responsive">\n <table class="table table-sm mb-0 mc-stats-table">\n <thead>\n <tr>\n <th class="fw-normal text-muted">Series</th>\n ${["Latest","Min","Max","Avg","Median","Sum"].map(t=>`<th class="text-end fw-normal text-muted">${t}</th>`).join("")}\n </tr>\n </thead>\n <tbody>\n ${t.map(t=>`\n <tr>\n <td class="pe-3">${this._escHtml(t.label)}</td>\n <td class="text-end">${s(t.latest)}</td>\n <td class="text-end">${s(t.min)}</td>\n <td class="text-end">${s(t.max)}</td>\n <td class="text-end">${s(t.avg)}</td>\n <td class="text-end">${s(t.median)}</td>\n <td class="text-end">${s(t.sum)}</td>\n </tr>\n `).join("")}\n </tbody>\n </table>\n </div>`}_renderStatsHeader(t){const e=this.granularity||"days",s={minutes:"Per minute",hours:"Hourly",days:"Daily",weeks:"Weekly",months:"Monthly",years:"Yearly"}[e]||e,i=t??(Array.isArray(this._lastChartData?.labels)?this._lastChartData.labels.length:0);return`<div class="text-muted small mb-2">${s} · ${i} ${1===i?"point":"points"}</div>`}_downloadCsv(t,e){const s=t=>{const e=String(t??"");return/[",\n]/.test(e)?`"${e.replace(/"/g,'""')}"`:e},i=[e.map(s).join(",")];for(const d of t)i.push([s(d.label),...d.values.map(t=>s("number"==typeof t?t:parseFloat(t)||0))].join(","));const a=new Blob([i.join("\n")],{type:"text/csv;charset=utf-8"}),r=URL.createObjectURL(a),n=document.createElement("a"),o=/* @__PURE__ */(new Date).toISOString().slice(0,10),h=this.chartTitle||("string"==typeof this.title?this.title.replace(/<[^>]*>/g,"").trim():"")||"chart-data",l=String(h).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"chart-data";n.href=r,n.download=`${l}-${o}.csv`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(r)}async onActionRefreshChart(t,e){const s=e?.querySelector("i");s?.classList.add("spin");try{await this.fetchData()}finally{s?.classList.remove("spin")}}_updateDropdownActive(t,e,s){const i=this.element?.querySelectorAll(`[data-action="${t}"]`);i?.forEach(t=>{const i=t.dataset?.[s];t.classList.toggle("mc-selected",i===e)})}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.withDelta&&(t.with_delta=!0),this.childKind&&(t.child_kind=this.childKind),this.breakdown&&(t.breakdown=!0),this.slugs&&this.slugs.length&&(t.slugs=this.slugs.join(",")),this.category&&(t.category=this.category),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0,this._showLoading();try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const i=s.data.data;this._lastGroups=i?.groups||null;const a=this.processMetricsData(i);await this.setData(a),this.lastFetch=/* @__PURE__ */new Date,this._hideError(),this.emit?.("metrics:data-loaded",{chart:this,data:i,params:e,groups:this._lastGroups})}catch(t){console.error("Failed to fetch metrics:",t),this._showError(`Failed to load metrics: ${t.message}`),this.emit?.("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1,this._hideLoading()}}}processMetricsData(t){const{data:e,labels:s}=t||{},i=Object.entries(e||{}).map(([t,e])=>{const s=(e||[]).map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0),i=s.reduce((t,e)=>t+e,0);return{metric:t,values:s,total:i}});i.sort((t,e)=>e.total-t.total);let a=i,r=null;if(this.maxDatasets&&this.maxDatasets>0&&i.length>this.maxDatasets){a=i.slice(0,this.maxDatasets);const t=i.slice(this.maxDatasets),e=(s||[]).map((e,s)=>t.reduce((t,e)=>t+(e.values[s]||0),0));r={metric:this.groupRemainingLabel,values:e,total:e.reduce((t,e)=>t+e,0)}}return{labels:s||[],datasets:(r?[...a,r]:a).map(t=>({label:this.breakdown?t.metric:this.formatMetricLabel(t.metric),data:t.values}))}}formatMetricLabel(t){return String(t).split(/[_:]/).filter(Boolean).map(t=>t.charAt(0).toUpperCase()+t.slice(1)).join(" ")}async setData(t){this._lastChartData=t,this.chart&&this.chart.setData(t)}refresh(){return this.fetchData()}_showLoading(){const t=this.element?.querySelector("[data-loading]");t?.classList.remove("d-none")}_hideLoading(){const t=this.element?.querySelector("[data-loading]");t?.classList.add("d-none")}_showError(t){const e=this.element?.querySelector("[data-error]"),s=e?.querySelector(".error-message");s&&(s.textContent=t),e?.classList.remove("d-none")}_hideError(){const t=this.element?.querySelector("[data-error]");t?.classList.add("d-none")}static GRANULARITY_DEFAULTS={minutes:"1h",hours:"24h",days:"30d",weeks:"30d",months:"30d"};static X_LABEL_FORMAT_BY_GRANULARITY={minutes:"time:'HH:mm'",hours:"time:'HH:mm'",days:"date:'MMM D'",weeks:"date:'MMM D'",months:"date:'MMM YYYY'"};setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}formatDateTimeLocal(t){return t?`${t.getFullYear()}-${String(t.getMonth()+1).padStart(2,"0")}-${String(t.getDate()).padStart(2,"0")}T${String(t.getHours()).padStart(2,"0")}:${String(t.getMinutes()).padStart(2,"0")}`:""}setGranularity(t){return this.granularity=t,!this.chart||this.tooltip&&void 0!==this.tooltip.x||(this.chart.xLabelFormat=this._resolveXLabelFormat()),this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=[...t],this.fetchData()}setChildKind(t){return this.childKind=t||null,this.fetchData()}setBreakdown(t){return this.breakdown=!0===t,this.fetchData()}getStats(){return{isLoading:this.isLoading,lastFetch:this.lastFetch,granularity:this.granularity,slugs:this.slugs?[...this.slugs]:[],dateRange:{start:this.dateStart,end:this.dateEnd},childKind:this.childKind,breakdown:this.breakdown}}}class MiniChart extends t{constructor(t={}){super({className:"mini-chart",...t}),this.chartType=t.chartType||"line",this.data=t.data||[],this.width=t.width||"100%",this.height=t.height||30,this.maintainAspectRatio=t.maintainAspectRatio||!1,this.color=t.color||"rgba(54, 162, 235, 1)",this.fillColor=t.fillColor||"rgba(54, 162, 235, 0.1)",this.strokeWidth=t.strokeWidth||2,this.barGap=t.barGap||2,this.fill=!1!==t.fill,this.smoothing=t.smoothing||.3,this.padding=t.padding||2,this.minValue=t.minValue,this.maxValue=t.maxValue,this.softMin=t.softMin,this.softMax=t.softMax,this.showDots=t.showDots||!1,this.dotRadius=t.dotRadius||2,this.animate=!1!==t.animate,this.animationDuration=t.animationDuration||300,this.showTooltip=!1!==t.showTooltip,this.tooltipFormatter=t.tooltipFormatter||null,this.tooltipTemplate=t.tooltipTemplate||null,this.valueFormat=t.valueFormat||null,this.labelFormat=t.labelFormat||null,this.showCrosshair=!1!==t.showCrosshair,this.crosshairColor=t.crosshairColor||"rgba(0, 0, 0, 0.2)",this.crosshairWidth=t.crosshairWidth||1,this.showXAxis=t.showXAxis||!1,this.xAxisColor=t.xAxisColor||this.color,this.xAxisWidth=t.xAxisWidth||1,this.xAxisDashed=!1!==t.xAxisDashed,this.tooltip=null,this.crosshair=null,this.hoveredIndex=-1,this.dataFormatter=e,this.labels=t.labels||null}getTemplate(){const t="number"==typeof this.width?`${this.width}px`:this.width,e="number"==typeof this.height?`${this.height}px`:this.height,s=this.maintainAspectRatio?"xMidYMid meet":"none";return`\n <div class="mini-chart-wrapper" style="position: relative; display: block; width: ${t}; height: ${e};">\n <svg\n class="mini-chart-svg"\n width="100%"\n height="100%"\n viewBox="0 0 100 ${this.height}"\n preserveAspectRatio="${s}"\n style="display: block;">\n </svg>\n ${this.showTooltip?'<div class="mini-chart-tooltip" style="display: none;"></div>':""}\n </div>\n `}async onAfterRender(){this.svg=this.element.querySelector(".mini-chart-svg"),this.tooltip=this.element.querySelector(".mini-chart-tooltip"),this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart(),this.showTooltip&&this.svg&&this.setupTooltip(),this.setupResizeObserver()}updateDimensions(){if(!this.svg)return;const t=this.svg.getBoundingClientRect();this.actualWidth=t.width||100,this.actualHeight=t.height||this.height,this.svg.setAttribute("viewBox",`0 0 ${this.actualWidth} ${this.actualHeight}`)}setupResizeObserver(){"undefined"!=typeof ResizeObserver&&(this.resizeObserver=new ResizeObserver(()=>{this.updateDimensions(),this.data&&this.data.length>0&&this.renderChart()}),this.svg&&this.resizeObserver.observe(this.svg))}renderChart(){if(!this.svg||!this.data||0===this.data.length)return;this.svg.innerHTML="";const{min:t,max:e}=this.calculateBounds();if(this.showXAxis&&this.renderXAxis(t,e),"line"===this.chartType?this.renderLine(t,e):"bar"===this.chartType&&this.renderBar(t,e),this.showCrosshair){const t=this.getActualHeight();this.crosshair=this.createSVGElement("line",{x1:0,y1:0,x2:0,y2:t,stroke:this.crosshairColor,"stroke-width":this.crosshairWidth,"stroke-dasharray":"3,3",style:"display: none; pointer-events: none;"}),this.svg.appendChild(this.crosshair)}this.showTooltip&&this.tooltip&&this.setupTooltip(),this.animate&&this.applyAnimation()}renderXAxis(t,e){const s=this.getActualWidth(),i=this.getActualHeight();let a;if(t<=0&&e>=0){const s=e-t,r=(i-2*this.padding)/s;a=i-this.padding-(0-t)*r}else a=i-this.padding;const r=this.createSVGElement("line",{x1:this.padding,y1:a,x2:s-this.padding,y2:a,stroke:this.xAxisColor,"stroke-width":this.xAxisWidth,"stroke-dasharray":this.xAxisDashed?"2,2":"none","stroke-opacity":"0.5"});this.svg.appendChild(r)}calculateBounds(){const t=this.data.map(t=>"object"==typeof t?t.value:t);let e=void 0!==this.minValue?this.minValue:Math.min(...t),s=void 0!==this.maxValue?this.maxValue:Math.max(...t);return 0===s-e&&("bar"===this.chartType&&0===e?(e=0,s=1):(e-=1,s+=1)),{min:e,max:s}}getActualWidth(){return this.actualWidth||this.width||100}getActualHeight(){return this.actualHeight||this.height||30}renderLine(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t),i=this.calculatePoints(s,t,e);if(this.fill){const t=this.createAreaPath(i),e=this.createSVGElement("path",{d:t,fill:this.fillColor,stroke:"none"});this.svg.appendChild(e)}const a=this.smoothing>0?this.createSmoothPath(i):this.createLinePath(i),r=this.createSVGElement("path",{d:a,fill:"none",stroke:this.color,"stroke-width":this.strokeWidth,"stroke-linecap":"round","stroke-linejoin":"round"});this.svg.appendChild(r),this.showDots&&i.forEach(t=>{const e=this.createSVGElement("circle",{cx:t.x,cy:t.y,r:this.dotRadius,fill:this.color});this.svg.appendChild(e)})}renderBar(t,e){const s=this.data.map(t=>"object"==typeof t?t.value:t);if(0===s.length)return;const i=Math.min(...s),a=Math.max(...s),r=void 0!==this.minValue?this.minValue:Math.min(0,this.softMin??0,i),n=void 0!==this.maxValue?this.maxValue:Math.max(0,this.softMax??0,a);let o=r,h=n;h===o&&(h=o+1);const l=this.getActualWidth(),d=this.getActualHeight(),c=this.padding,u=d-this.padding,p=(u-c)/(h-o),g=0===i&&0===a,m=void 0!==this.minValue||void 0!==this.maxValue||void 0!==this.softMin||void 0!==this.softMax;if(g&&!m){const t=this.createSVGElement("line",{x1:this.padding,y1:u,x2:l-this.padding,y2:u,stroke:this.color,"stroke-width":1,"stroke-opacity":.4,"stroke-dasharray":"2,2",class:"mini-chart-empty-baseline"});return void this.svg.appendChild(t)}const b=u-(0-o)*p,y=Math.max(c,Math.min(u,b)),f=this.calculatePoints(s,t,e),v=(l-2*this.padding-this.barGap*(s.length-1))/s.length;f.forEach((t,e)=>{const i=s[e],a=u-(i-o)*p,r=Math.max(c,Math.min(u,a)),n=Math.min(r,y),h=Math.max(r,y)-n,l=t.x-v/2,d=this.createSVGElement("rect",{x:l,y:n,width:v,height:h,fill:this.color,rx:1,"data-bar-index":e,class:"mini-chart-bar"});this.svg.appendChild(d)})}calculatePoints(t,e,s){const i=s-e,a=this.getActualWidth(),r=this.getActualHeight(),n=(a-2*this.padding)/(t.length-1||1),o=(r-2*this.padding)/i;return t.map((t,s)=>({x:this.padding+s*n,y:r-this.padding-(t-e)*o}))}createLinePath(t){if(0===t.length)return"";let e=`M ${t[0].x},${t[0].y}`;for(let s=1;s<t.length;s++)e+=` L ${t[s].x},${t[s].y}`;return e}createSmoothPath(t){if(t.length<2)return this.createLinePath(t);let e=`M ${t[0].x},${t[0].y}`;for(let s=0;s<t.length-1;s++){const i=t[s],a=t[s+1];e+=` C ${i.x+(a.x-i.x)*this.smoothing},${i.y} ${a.x-(a.x-i.x)*this.smoothing},${a.y} ${a.x},${a.y}`}return e}createAreaPath(t){if(0===t.length)return"";const e=this.smoothing>0?this.createSmoothPath(t):this.createLinePath(t),s=t[t.length-1],i=t[0],a=this.getActualHeight();return`${e} L ${s.x},${a-this.padding} L ${i.x},${a-this.padding} Z`}createSVGElement(t,e={}){const s=document.createElementNS("http://www.w3.org/2000/svg",t);return Object.entries(e).forEach(([t,e])=>{s.setAttribute(t,e)}),s}applyAnimation(){this.svg.querySelectorAll("path").forEach(t=>{const e=t.getTotalLength();t.style.strokeDasharray=e,t.style.strokeDashoffset=e,t.style.animation=`mini-chart-draw ${this.animationDuration}ms ease-out forwards`}),this.svg.querySelectorAll("rect").forEach((t,e)=>{t.style.transformOrigin="bottom",t.style.animation=`mini-chart-bar-grow ${this.animationDuration}ms ease-out ${20*e}ms forwards`,t.style.transform="scaleY(0)"})}setupTooltip(){if(!this.svg||!this.tooltip)return;const t=this.data.map(t=>"object"==typeof t?t.value:t),e=this.calculatePoints(t,...Object.values(this.calculateBounds())),s=this.getActualWidth(),i=this.getActualHeight(),a=s/t.length;e.forEach((t,e)=>{const s=this.createSVGElement("rect",{x:e*a,y:0,width:a,height:i,fill:"transparent",style:"cursor: pointer;"});s.addEventListener("mouseenter",t=>{this.showTooltipAtIndex(e,t)}),s.addEventListener("mousemove",t=>{this.updateTooltipPosition(t)}),s.addEventListener("mouseleave",()=>{this.hideTooltip()}),this.svg.appendChild(s)})}showTooltipAtIndex(t,e){if(!this.tooltip)return;this.hoveredIndex=t;const s="object"==typeof this.data[t]?this.data[t].value:this.data[t],i="object"==typeof this.data[t]?this.data[t].label:null,a=this.labels?this.labels[t]:i;let r;if(this.tooltipTemplate&&"function"==typeof this.tooltipTemplate)r=this.tooltipTemplate({value:s,label:a,index:t,data:this.data[t]});else{let e=s;e=this.valueFormat&&this.dataFormatter?this.dataFormatter.pipe(s,this.valueFormat):this.tooltipFormatter&&"function"==typeof this.tooltipFormatter?this.tooltipFormatter(s,t):"number"==typeof s?s.toLocaleString():s;let i=a;a&&this.labelFormat&&this.dataFormatter&&(i=this.dataFormatter.pipe(a,this.labelFormat)),r=`<strong>${e}</strong>`,i&&(r=`<div class="mini-chart-tooltip-label">${i}</div>${r}`)}if(this.tooltip.innerHTML=r,this.tooltip.style.display="block",this.updateTooltipPosition(e),"bar"===this.chartType&&this.highlightBar(t),this.crosshair&&this.showCrosshair){const e=this.getActualWidth()/this.data.length,s=t*e+e/2;this.crosshair.setAttribute("x1",s),this.crosshair.setAttribute("x2",s),this.crosshair.style.display="block"}}updateTooltipPosition(t){if(!this.tooltip||"none"===this.tooltip.style.display)return;const e=this.svg.getBoundingClientRect(),s=t.clientX-e.left,i=t.clientY-e.top;this.tooltip.style.left=`${s}px`,this.tooltip.style.top=i-10+"px",this.tooltip.style.transform="translate(-50%, -100%)"}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none",this.hoveredIndex=-1),"bar"===this.chartType&&this.unhighlightBars(),this.crosshair&&(this.crosshair.style.display="none")}highlightBar(t){if(!this.svg)return;this.unhighlightBars();const e=this.svg.querySelector(`rect.mini-chart-bar[data-bar-index="${t}"]`);e&&(e.style.opacity="0.7")}unhighlightBars(){this.svg&&this.svg.querySelectorAll("rect.mini-chart-bar").forEach(t=>{t.style.opacity="1"})}setData(t){this.data=t,this.svg&&this.renderChart()}setColor(t){this.color=t,this.svg&&this.renderChart()}setType(t){["line","bar"].includes(t)&&(this.chartType=t,this.svg&&this.renderChart())}resize(t,e){this.width=t,this.height=e,this.updateDimensions(),this.svg&&this.renderChart()}async onBeforeDestroy(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),await super.onBeforeDestroy()}}class MetricsMiniChart extends MiniChart{constructor(t={}){super(t),this.endpoint=t.endpoint||"/api/metrics/fetch",this.account=t.account||"global",this.granularity=t.granularity||"hours",this.slugs=t.slugs||null,this.category=t.category||null,this.dateStart=t.dateStart||null,this.dateEnd=t.dateEnd||null,this.defaultDateRange=t.defaultDateRange||null,this.childKind=t.childKind||null,this.isLoading=!1,this.lastFetch=null,this.refreshInterval=t.refreshInterval,!this.defaultDateRange||this.dateStart||this.dateEnd||this.setQuickRange(this.defaultDateRange),this.slugs&&!Array.isArray(this.slugs)&&(this.slugs=[this.slugs])}async onAfterRender(){await super.onAfterRender(),!this.endpoint||this.data&&0!==this.data.length||this.fetchData(),this.refreshInterval&&this.endpoint&&this.startAutoRefresh()}buildApiParams(){const t={granularity:this.granularity,account:this.account,with_labels:!0};return this.slugs&&this.slugs.length>0&&this.slugs.forEach(e=>{t["slugs[]"]||(t["slugs[]"]=[]),t["slugs[]"].push(e)}),this.category&&(t.category=this.category),this.childKind&&(t.child_kind=this.childKind),this.dateStart&&(t.dr_start=Math.floor(this.dateStart.getTime()/1e3)),this.dateEnd&&(t.dr_end=Math.floor(this.dateEnd.getTime()/1e3)),t._=Date.now(),t}async fetchData(){if(this.endpoint){this.isLoading=!0;try{const t=this.getApp()?.rest;if(!t)throw new Error("No REST client available");const e=this.buildApiParams(),s=await t.GET(this.endpoint,e);if(!s.success)throw new Error(s.message||"Network error");if(!s.data?.status)throw new Error(s.data?.error||"Server error");const i=s.data.data;this.processMetricsData(i),this.lastFetch=/* @__PURE__ */new Date,await this.render(),this.emit("metrics:loaded",{chart:this,data:i,params:e})}catch(t){console.error("Failed to fetch metrics:",t),this.emit("metrics:error",{chart:this,error:t})}finally{this.isLoading=!1}}}processMetricsData(t){const{data:e,labels:s}=t;if(!e)return;const i=Object.keys(e);if(0===i.length)return;const a=e[i[0]].map(t=>null==t||""===t?0:"number"==typeof t?t:parseFloat(t)||0);this.labels=s||null,this.setData(a)}setQuickRange(t){const e=/* @__PURE__ */new Date;let s;switch(t){case"1h":s=new Date(e.getTime()-36e5);break;case"24h":default:s=new Date(e.getTime()-864e5);break;case"7d":s=new Date(e.getTime()-6048e5);break;case"30d":s=new Date(e.getTime()-2592e6)}this.dateStart=s,this.dateEnd=e}startAutoRefresh(){this.refreshTimer&&clearInterval(this.refreshTimer),this.refreshTimer=setInterval(()=>{this.fetchData()},this.refreshInterval)}stopAutoRefresh(){this.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null)}setGranularity(t){return this.granularity=t,this.fetchData()}setDateRange(t,e){return this.dateStart=new Date(t),this.dateEnd=new Date(e),this.fetchData()}setMetrics(t){return this.slugs=Array.isArray(t)?t:[t],this.fetchData()}setAccount(t){return this.account=t,this.fetchData()}setChildKind(t){return this.childKind=t||null,this.fetchData()}refresh(){return this.fetchData()}async onBeforeDestroy(){this.stopAutoRefresh(),await super.onBeforeDestroy()}}function o(t){if(!t)return"";if("string"==typeof t&&/^\d{4}-\d{2}-\d{2}$/.test(t))return t;const e=t instanceof Date?t:new Date(t);return isNaN(e.getTime())?"":`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}class MetricsMiniChartWidget extends t{constructor(t={}){super({...t,tagName:"div",className:`metrics-mini-chart-widget ${t.className||""}`.trim()}),this.icon=t.icon||null,this.title=t.title||"",this.subtitle=t.subtitle||"",this.background=t.background||null,this.textColor=t.textColor||null,this.showSettings=t.showSettings||!1,this.settingsKey=t.settingsKey||null,this.showDateRange=t.showDateRange||!1,this.showRefresh=!1!==t.showRefresh,this.showStats=!1!==t.showStats,this.showDataTable=!1!==t.showDataTable,this.showTrending=!!t.showTrending,this.trendRange=t.trendRange??null,this.trendOffset=t.trendOffset??0,this.prevTrendOffset=t.prevTrendOffset??0,this.total=0,this.lastValue=0,this.prevValue=0,this.trendingPercent=0,this.trendingUp=null,this.hasTrending=!1,this.trendingClass="metrics-mini-chart-trending-text",this.trendingIcon="",this.trendingLabel="",this.chartOptions={endpoint:t.endpoint,account:t.account,granularity:t.granularity,slugs:t.slugs,category:t.category,childKind:t.childKind,dateStart:t.dateStart,dateEnd:t.dateEnd,defaultDateRange:t.defaultDateRange,refreshInterval:t.refreshInterval,chartType:t.chartType||"line",showTooltip:void 0===t.showTooltip||t.showTooltip,showXAxis:t.showXAxis||!1,height:t.height||80,width:t.chartWidth||t.width||"100%",color:t.color,fill:void 0===t.fill||t.fill,fillColor:t.fillColor,smoothing:t.smoothing??.3,strokeWidth:t.strokeWidth,barGap:t.barGap,valueFormat:t.valueFormat,labelFormat:t.labelFormat,tooltipFormatter:t.tooltipFormatter,tooltipTemplate:t.tooltipTemplate,showCrosshair:t.showCrosshair,crosshairColor:t.crosshairColor,crosshairWidth:t.crosshairWidth,xAxisColor:t.xAxisColor,xAxisWidth:t.xAxisWidth,xAxisDashed:t.xAxisDashed,padding:t.padding,minValue:t.minValue,maxValue:t.maxValue,softMin:t.softMin,softMax:t.softMax,showDots:t.showDots,dotRadius:t.dotRadius,animate:t.animate,animationDuration:t.animationDuration}}async onInit(){this.showSettings&&this.settingsKey&&this._loadSettings(),this.chart=new MetricsMiniChart({...this.chartOptions,containerId:"chart"}),this.addChild(this.chart),this.header=new t({containerId:"chart-header",title:this.title,icon:this.icon,template:`\n <div class="d-flex justify-content-between align-items-start mb-2">\n <div class="flex-grow-1">\n <h6 class="card-title mb-1" style="${this.textColor?`color: ${this.textColor}`:""}">${this.title}</h6>\n <div class="metrics-mini-chart-subtitle" style="${this.textColor?`color: ${this.textColor}`:""}">${this.subtitle}</div>\n {{#hasTrending}}\n <div class="{{trendingClass}}" style="${this.textColor?`color: ${this.textColor}`:""}">\n <i class="{{trendingIcon}} me-1"></i>{{trendingLabel}}\n </div>\n {{/hasTrending}}\n </div>\n ${this.icon?`<i class="${this.icon} fs-4 flex-shrink-0" aria-hidden="true" style="${this.textColor?`color: ${this.textColor}`:""}"></i>`:""}\n </div>`}),this.addChild(this.header),this.chart?.on&&this.chart.on("metrics:loaded",this.onChildMetricsLoaded,this),this.updateFromChartData({render:!1})}async onAfterRender(){await super.onAfterRender()}onChildMetricsLoaded(){this.updateFromChartData({render:!0})}updateFromChartData({render:t=!0}={}){const e=Array.isArray(this.chart?.data)?this.chart.data:null;if(!e||0===e.length)return this.total=0,this.hasTrending=!1,this.header.title=this.title,void(t&&this.render());const s=e.map(t=>{if("number"==typeof t)return t;if(t&&"number"==typeof t.value)return t.value;const e=parseFloat(t);return Number.isNaN(e)?0:e});this.header.title=this.title,this.header.total=s.reduce((t,e)=>t+e,0);const i=Math.max(0,parseInt(this.trendOffset||0,10)||0),a=Math.max(0,s.length-1-i);this.header.now_value=s[a],this._updateGranularityLabels();let r=!1,n=0,o=0;const h=this.trendRange&&this.trendRange>=2?Math.max(1,Math.floor(this.trendRange/2)):1;if(a>=0){const t=a,e=t-(h-1);let i,l;if(this.prevTrendOffset&&this.prevTrendOffset>0?(i=e-this.prevTrendOffset,l=t-this.prevTrendOffset):(l=e-1,i=l-(h-1)),e>=0&&i>=0){const a=(t,e,s)=>{let i=0;for(let a=e;a<=s;a++)i+=t[a]||0;return i};n=a(s,e,t),o=a(s,i,l),r=!0}}if(!r){const t=a-(this.prevTrendOffset&&this.prevTrendOffset>0?this.prevTrendOffset:1);t>=0&&(n=s[a],o=s[t],r=!0)}if(r){this.header.lastValue=n,this.header.prevValue=o;let t=0;t=0===o?n>0?100:0:(n-o)/Math.abs(o)*100,this.header.trendingPercent=t,this.header.trendingUp=t>=0,this.textColor?this.header.trendingClass="":this.header.trendingClass=this.header.trendingUp?"text-success":"text-danger",this.header.trendingIcon=this.header.trendingUp?"bi bi-arrow-up":"bi bi-arrow-down";const e=t>0?"+":"";this.header.trendingLabel=`${e}${t.toFixed(1)}%`,this.header.hasTrending=this.showTrending}else this.header.hasTrending=!1;t&&this.header.render()}_updateGranularityLabels(){const t=this.chartOptions.granularity||"days";this.header.now_label={hours:"This Hour",days:"Today",weeks:"This Week",months:"This Month",years:"This Year"}[t]||"Current",this.header.total_label={hours:"Total (24h)",days:"Total (Period)",weeks:"Total (Period)",months:"Total (Period)",years:"Total (Period)"}[t]||"Total"}get cardStyle(){const t=[];return this.background&&t.push(`background: ${this.background}`),this.textColor&&t.push(`color: ${this.textColor}`),t.push("border: 0"),t.join("; ")}async getTemplate(){const t=this.showRefresh||this.showSettings||this.showStats||this.showDataTable,e=this.textColor?`color: ${this.textColor} !important`:"";return`\n <div class="card h-100 shadow-sm" style="${this.cardStyle}; position: relative;">\n ${t?`\n <div class="metrics-chart-actions">\n ${this.showStats?`\n <button class="btn btn-link p-0 text-muted metrics-stats-btn" type="button" data-action="show-stats" title="Stats" style="${e}">\n <i class="bi bi-info-circle"></i>\n </button>\n `:""}\n ${this.showDataTable?`\n <button class="btn btn-link p-0 text-muted metrics-data-btn" type="button" data-action="show-data-table" title="View data" style="${e}">\n <i class="bi bi-table"></i>\n </button>\n `:""}\n ${this.showRefresh?`\n <button class="btn btn-link p-0 text-muted metrics-refresh-btn" type="button" data-action="refresh-chart" style="${e}">\n <i class="bi bi-arrow-clockwise"></i>\n </button>\n `:""}\n ${this.showSettings?`\n <button class="btn btn-link p-0 text-muted metrics-settings-btn" type="button" data-action="toggle-settings" style="${e}">\n <i class="bi bi-gear-fill"></i>\n </button>\n `:""}\n </div>\n `:""}\n <div class="card-body p-3">\n <div data-container="chart-header"></div>\n <div data-container="chart"></div>\n </div>\n </div>\n `}async onBeforeDestroy(){this.chart?.off&&this.chart.off("metrics:loaded",this.onChildMetricsLoaded,this),await super.onBeforeDestroy()}_computeStats(){const t=Array.isArray(this.chart?.data)?this.chart.data:null;if(!t||0===t.length)return null;const e=t.map(t=>"object"==typeof t&&null!==t?t.value:t).map(t=>"number"==typeof t?t:parseFloat(t)||0),s=[...e].sort((t,e)=>t-e),i=e.reduce((t,e)=>t+e,0),a=Math.floor(s.length/2),r=s.length%2==0?(s[a-1]+s[a])/2:s[a];return{count:e.length,sum:i,avg:i/e.length,median:r,min:s[0],max:s[s.length-1],latest:e[e.length-1]}}_formatStatNumber(t){return null==t||Number.isNaN(t)?"–":this.chartOptions.valueFormat&&this.chart?.dataFormatter?this.chart.dataFormatter.pipe(t,this.chartOptions.valueFormat):"function"==typeof this.chartOptions.tooltipFormatter?this.chartOptions.tooltipFormatter(t):Number.isInteger(t)?t.toLocaleString():Number(t).toLocaleString(void 0,{maximumFractionDigits:2})}_renderStatsHtml(){const t=this._computeStats(),e=this._renderStatsHeader(t?.count);return t?`\n ${e}\n <table class="table table-sm mb-0 mc-stats-table">\n <tbody>\n ${[["Latest",t.latest],["Min",t.min],["Max",t.max],["Avg",t.avg],["Median",t.median],["Sum",t.sum],["Count",t.count]].map(([t,e])=>`\n <tr>\n <th class="fw-normal text-muted pe-3 py-1">${t}</th>\n <td class="text-end py-1">${"Count"===t?e:this._formatStatNumber(e)}</td>\n </tr>\n `).join("")}\n </tbody>\n </table>`:`${e}<div class="text-muted small">No data</div>`}_renderStatsHeader(t){const e=this.chartOptions.granularity||"days",s={minutes:"Per minute",hours:"Hourly",days:"Daily",weeks:"Weekly",months:"Monthly",years:"Yearly"}[e]||e,i=t??(Array.isArray(this.chart?.data)?this.chart.data.length:0);return`<div class="text-muted small mb-2">${s} · ${i} ${1===i?"point":"points"}</div>`}async onActionShowStats(){return s.dialog({title:this.title?`${this.title.replace(/<[^>]*>/g,"").trim()} — Stats`:"Stats",body:this._renderStatsHtml(),size:"sm",buttons:[{text:"Close",class:"btn-secondary",dismiss:!0}]})}async onActionShowDataTable(){const t=Array.isArray(this.chart?.labels)?this.chart.labels:null,e=Array.isArray(this.chart?.data)?this.chart.data:null;if(!e||0===e.length)return s.alert({title:this.title||"Data",message:"No data to display."});const i=e.map((e,s)=>({label:t?.[s]??String(s+1),value:"object"==typeof e&&null!==e?e.value:e})),a=`\n <div class="table-responsive" style="max-height: 60vh;">\n <table class="table table-sm table-striped mb-0 mc-data-table">\n <thead class="sticky-top bg-body">\n <tr>\n <th>Label</th>\n <th class="text-end">Value</th>\n </tr>\n </thead>\n <tbody>\n ${i.map(t=>`\n <tr>\n <td>${this._escHtml(t.label)}</td>\n <td class="text-end">${this._formatStatNumber("number"==typeof t.value?t.value:parseFloat(t.value)||0)}</td>\n </tr>\n `).join("")}\n </tbody>\n </table>\n </div>`;"csv"===await s.dialog({title:this.title||"Chart data",body:a,size:"md",buttons:[{text:"Download CSV",class:"btn-outline-primary",value:"csv"},{text:"Close",class:"btn-secondary",dismiss:!0}]})&&this._downloadCsv(i)}_downloadCsv(t){const e=t=>{const e=String(t??"");return/[",\n]/.test(e)?`"${e.replace(/"/g,'""')}"`:e},s=["Label,Value"];for(const h of t){const t="number"==typeof h.value?h.value:parseFloat(h.value)||0;s.push(`${e(h.label)},${e(t)}`)}const i=new Blob([s.join("\n")],{type:"text/csv;charset=utf-8"}),a=URL.createObjectURL(i),r=document.createElement("a"),n=/* @__PURE__ */(new Date).toISOString().slice(0,10),o=String(this.title||"chart-data").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")||"chart-data";r.href=a,r.download=`${o}-${n}.csv`,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(a)}_escHtml(t){return String(t??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}async onActionToggleSettings(t,e){const i=[{name:"granularity",type:"select",label:"Granularity",value:this.chartOptions.granularity||"hours",options:[{value:"hours",label:"Hours"},{value:"days",label:"Days"},{value:"weeks",label:"Weeks"},{value:"months",label:"Months"},{value:"years",label:"Years"}]},{name:"chartType",type:"select",label:"Chart Type",value:this.chartOptions.chartType||"line",options:[{value:"line",label:"Line"},{value:"bar",label:"Bar"}]}];this.showDateRange&&i.push({name:"dateStart",type:"date",label:"Start date",value:o(this.chartOptions.dateStart)},{name:"dateEnd",type:"date",label:"End date",value:o(this.chartOptions.dateEnd)});const a=await s.form({title:this.title?`${this.title} — Settings`:"Chart Settings",size:"sm",submitText:"Apply",cancelText:"Cancel",fields:i});a&&await this._handleSettingsApply(a)}async _handleSettingsApply(t){let e=!1,s=!1,i=!1;if((t.dateStart&&t.dateStart!==this.chartOptions.dateStart||t.dateEnd&&t.dateEnd!==this.chartOptions.dateEnd)&&(i=!0),t.granularity&&t.granularity!==this.chartOptions.granularity&&(this.chartOptions.granularity=t.granularity,this.chart.granularity=t.granularity,s=!0,e=!0),t.chartType&&t.chartType!==this.chartOptions.chartType&&(this.chartOptions.chartType=t.chartType,this.chart.chartType=t.chartType,e=!0),i)t.dateStart&&(this.chartOptions.dateStart=new Date(t.dateStart),this.chart.dateStart=new Date(t.dateStart)),t.dateEnd&&(this.chartOptions.dateEnd=new Date(t.dateEnd),this.chart.dateEnd=new Date(t.dateEnd)),e=!0;else if(s&&(this.chartOptions.dateStart||this.chartOptions.dateEnd)){const e=/* @__PURE__ */new Date;let s;switch(t.granularity){case"hours":s=new Date(e.getTime()-864e5);break;case"days":default:s=new Date(e.getTime()-2592e6);break;case"weeks":s=new Date(e.getTime()-72576e5);break;case"months":s=new Date(e),s.setMonth(s.getMonth()-12);break;case"years":s=new Date(e),s.setFullYear(s.getFullYear()-5)}this.chartOptions.dateStart=s,this.chart.dateStart=s,this.chartOptions.dateEnd=e,this.chart.dateEnd=e}e&&(this._saveSettings(),await this.chart.refresh())}async onActionRefreshChart(t,e){const s=e.querySelector("i");s&&s.classList.add("spin"),this.chart&&(this.chartOptions.account&&(this.chart.account=this.chartOptions.account),await this.chart.refresh()),s&&s.classList.remove("spin")}refresh(){this.chart&&(this.chartOptions.account&&(this.chart.account=this.chartOptions.account),this.chart.refresh())}setAccount(t){return this.chartOptions.account=t,this.chart?this.chart.setAccount(t):Promise.resolve()}_loadSettings(){if(this.settingsKey)try{const t=localStorage.getItem(`metrics-chart-${this.settingsKey}`);if(t){const e=JSON.parse(t);e.granularity&&(this.chartOptions.granularity=e.granularity),e.chartType&&(this.chartOptions.chartType=e.chartType),void 0!==e.dateStart&&(this.chartOptions.dateStart=e.dateStart),void 0!==e.dateEnd&&(this.chartOptions.dateEnd=e.dateEnd)}}catch(t){console.error("Failed to load chart settings:",t)}}_saveSettings(){if(this.settingsKey)try{const t={granularity:this.chartOptions.granularity,chartType:this.chartOptions.chartType,dateStart:this.chartOptions.dateStart,dateEnd:this.chartOptions.dateEnd};localStorage.setItem(`metrics-chart-${this.settingsKey}`,JSON.stringify(t))}catch(t){console.error("Failed to save chart settings:",t)}}}function h(t,{filename:e}={}){const s=t?.element||t;if(!s)return void console.warn("exportChartPng: no element provided");const i=s.tagName&&"svg"===s.tagName.toLowerCase()?s:s.querySelector?.("svg");if(!i)return void console.warn("exportChartPng: no <svg> found in target");const a=i.getBoundingClientRect(),r=i.getAttribute("viewBox")?.split(/\s+/).map(Number),n=r&&4===r.length?r[2]:Math.round(a.width||600),o=r&&4===r.length?r[3]:Math.round(a.height||400),h=i.cloneNode(!0);h.setAttribute("xmlns","http://www.w3.org/2000/svg"),h.getAttribute("width")||h.setAttribute("width",n),h.getAttribute("height")||h.setAttribute("height",o);const l=(new XMLSerializer).serializeToString(h),d=`data:image/svg+xml;base64,${"function"==typeof btoa?btoa(unescape(encodeURIComponent(l))):Buffer.from(l,"utf-8").toString("base64")}`,c=new Image;c.crossOrigin="anonymous",c.onload=()=>{const t=document.createElement("canvas");t.width=n,t.height=o;const s=t.getContext("2d");s.fillStyle="#ffffff",s.fillRect(0,0,t.width,t.height),s.drawImage(c,0,0,n,o);const i=t.toDataURL("image/png"),a=document.createElement("a");a.href=i,a.download=e||`chart-${Date.now()}.png`,document.body.appendChild(a),a.click(),a.remove()},c.onerror=t=>{console.error("exportChartPng: image load failed",t)},c.src=d}export{MetricsChart as M,PieChart as P,SeriesChart as S,MetricsMiniChart as a,MetricsMiniChartWidget as b,MiniChart as c,h as e};
|
|
2
|
-
//# sourceMappingURL=exportChart-DYWi1WFJ.js.map
|