ioloco-charts 0.1.0
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/dist/D3/AreaChart/index.d.ts +2 -0
- package/dist/D3/AreaChart/index.js +54 -0
- package/dist/D3/AreaChart/types.d.ts +20 -0
- package/dist/D3/BarChart/index.d.ts +3 -0
- package/dist/D3/BarChart/index.js +29 -0
- package/dist/D3/BarChart/types.d.ts +13 -0
- package/dist/D3/DonutChart/index.d.ts +2 -0
- package/dist/D3/DonutChart/index.js +35 -0
- package/dist/D3/DonutChart/types.d.ts +15 -0
- package/dist/D3/FunnelChart/index.d.ts +2 -0
- package/dist/D3/FunnelChart/index.js +27 -0
- package/dist/D3/FunnelChart/types.d.ts +12 -0
- package/dist/D3/GanttTimeline/index.d.ts +2 -0
- package/dist/D3/GanttTimeline/index.js +36 -0
- package/dist/D3/GanttTimeline/types.d.ts +15 -0
- package/dist/D3/GaugeChart/index.d.ts +2 -0
- package/dist/D3/GaugeChart/index.js +22 -0
- package/dist/D3/GaugeChart/types.d.ts +17 -0
- package/dist/D3/HeatmapChart/heatmapLayout.d.ts +28 -0
- package/dist/D3/HeatmapChart/heatmapLayout.js +11 -0
- package/dist/D3/HeatmapChart/index.d.ts +2 -0
- package/dist/D3/HeatmapChart/index.js +31 -0
- package/dist/D3/HeatmapChart/types.d.ts +17 -0
- package/dist/D3/KpiCard/index.d.ts +2 -0
- package/dist/D3/KpiCard/index.js +12 -0
- package/dist/D3/KpiCard/index.styles.d.ts +108 -0
- package/dist/D3/KpiCard/index.styles.js +3 -0
- package/dist/D3/KpiCard/types.d.ts +16 -0
- package/dist/D3/LineChart/index.d.ts +2 -0
- package/dist/D3/LineChart/index.js +40 -0
- package/dist/D3/LineChart/types.d.ts +20 -0
- package/dist/D3/RadialBarChart/index.d.ts +2 -0
- package/dist/D3/RadialBarChart/index.js +34 -0
- package/dist/D3/RadialBarChart/types.d.ts +18 -0
- package/dist/D3/RelationshipDiagram/index.d.ts +3 -0
- package/dist/D3/RelationshipDiagram/index.js +34 -0
- package/dist/D3/RelationshipDiagram/index.styles.d.ts +66 -0
- package/dist/D3/RelationshipDiagram/index.styles.js +3 -0
- package/dist/D3/RelationshipDiagram/types.d.ts +25 -0
- package/dist/D3/SankeyChart/index.d.ts +2 -0
- package/dist/D3/SankeyChart/index.js +32 -0
- package/dist/D3/SankeyChart/types.d.ts +22 -0
- package/dist/D3/ScatterPlot/index.d.ts +2 -0
- package/dist/D3/ScatterPlot/index.js +39 -0
- package/dist/D3/ScatterPlot/scatterPlotUtils.d.ts +6 -0
- package/dist/D3/ScatterPlot/scatterPlotUtils.js +8 -0
- package/dist/D3/ScatterPlot/types.d.ts +17 -0
- package/dist/D3/SparklineChart/index.d.ts +2 -0
- package/dist/D3/SparklineChart/index.js +9 -0
- package/dist/D3/SparklineChart/index.styles.d.ts +35 -0
- package/dist/D3/SparklineChart/index.styles.js +3 -0
- package/dist/D3/SparklineChart/types.d.ts +14 -0
- package/dist/D3/StackedBarChart/index.d.ts +2 -0
- package/dist/D3/StackedBarChart/index.js +39 -0
- package/dist/D3/StackedBarChart/types.d.ts +17 -0
- package/dist/D3/TreemapChart/index.d.ts +2 -0
- package/dist/D3/TreemapChart/index.js +31 -0
- package/dist/D3/TreemapChart/types.d.ts +13 -0
- package/dist/D3/index.d.ts +43 -0
- package/dist/D3/shared/ChartFrame/index.d.ts +16 -0
- package/dist/D3/shared/ChartFrame/index.js +8 -0
- package/dist/D3/shared/ChartLegend/index.d.ts +3 -0
- package/dist/D3/shared/ChartLegend/index.js +7 -0
- package/dist/D3/shared/ChartLegend/types.d.ts +14 -0
- package/dist/D3/shared/ChartTooltip/index.d.ts +5 -0
- package/dist/D3/shared/ChartTooltip/index.js +7 -0
- package/dist/D3/shared/ChartTooltip/index.styles.d.ts +42 -0
- package/dist/D3/shared/ChartTooltip/index.styles.js +3 -0
- package/dist/D3/shared/Sparkline/index.d.ts +8 -0
- package/dist/D3/shared/Sparkline/index.js +18 -0
- package/dist/D3/shared/chartAxis.d.ts +16 -0
- package/dist/D3/shared/chartAxis.js +2 -0
- package/dist/D3/shared/chartAxisLayout/areaChart.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/areaChart.js +2 -0
- package/dist/D3/shared/chartAxisLayout/barChart.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/barChart.js +2 -0
- package/dist/D3/shared/chartAxisLayout/ganttTimeline.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/ganttTimeline.js +2 -0
- package/dist/D3/shared/chartAxisLayout/heatmap.d.ts +8 -0
- package/dist/D3/shared/chartAxisLayout/heatmap.js +2 -0
- package/dist/D3/shared/chartAxisLayout/index.d.ts +9 -0
- package/dist/D3/shared/chartAxisLayout/lineChart.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/lineChart.js +2 -0
- package/dist/D3/shared/chartAxisLayout/render.d.ts +8 -0
- package/dist/D3/shared/chartAxisLayout/render.js +11 -0
- package/dist/D3/shared/chartAxisLayout/scatterPlot.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/scatterPlot.js +2 -0
- package/dist/D3/shared/chartAxisLayout/stackedBarChart.d.ts +2 -0
- package/dist/D3/shared/chartAxisLayout/stackedBarChart.js +2 -0
- package/dist/D3/shared/chartAxisLayout/types.d.ts +34 -0
- package/dist/D3/shared/chartBinding.d.ts +16 -0
- package/dist/D3/shared/chartBinding.js +1 -0
- package/dist/D3/shared/chartInteraction.d.ts +20 -0
- package/dist/D3/shared/chartInteraction.js +5 -0
- package/dist/D3/shared/chartTooltip.d.ts +20 -0
- package/dist/D3/shared/chartTooltip.js +2 -0
- package/dist/D3/shared/chartUtils.d.ts +11 -0
- package/dist/D3/shared/chartUtils.js +2 -0
- package/dist/D3/shared/chartVisualStyle.d.ts +24 -0
- package/dist/D3/shared/chartVisualStyle.js +10 -0
- package/dist/D3/shared/index.styles.d.ts +100 -0
- package/dist/D3/shared/index.styles.js +3 -0
- package/dist/D3/shared/useChartInteraction.d.ts +15 -0
- package/dist/D3/shared/useChartInteraction.js +17 -0
- package/dist/D3/shared/useChartLegendToggle.d.ts +6 -0
- package/dist/D3/shared/useChartLegendToggle.js +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -0
- package/dist/ioloco-charts.css +1 -0
- package/dist/tokens.stylex.d.ts +99 -0
- package/dist/tokens.stylex.js +3 -0
- package/package.json +135 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as r}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as t,useMemo as e,useEffect as o}from"react"
|
|
4
|
+
import{max as n}from"d3-array"
|
|
5
|
+
import{scalePoint as s,scaleLinear as i}from"d3-scale"
|
|
6
|
+
import{select as a,pointer as l}from"d3-selection"
|
|
7
|
+
import{area as c,line as h}from"d3-shape"
|
|
8
|
+
import{chartPalette as d}from"../../tokens.stylex.js"
|
|
9
|
+
import{ChartFrame as m}from"../shared/ChartFrame/index.js"
|
|
10
|
+
import{ChartLegend as u}from"../shared/ChartLegend/index.js"
|
|
11
|
+
import{areaChartAxisLayout as f}from"../shared/chartAxisLayout/areaChart.js"
|
|
12
|
+
import{renderXAxis as p,renderYAxis as $}from"../shared/chartAxisLayout/render.js"
|
|
13
|
+
import{chartFrameInteractionProps as g,chartPointerHandlers as v}from"../shared/chartBinding.js"
|
|
14
|
+
import{CHART_SERIES_CURVE as y,sanitizeGradientKey as j,appendAreaSeriesGradient as x,chartLineStrokeWidth as k,seriesDimOpacity as C}from"../shared/chartVisualStyle.js"
|
|
15
|
+
import{useChartInteraction as I}from"../shared/useChartInteraction.js"
|
|
16
|
+
import{useChartLegendToggle as w}from"../shared/useChartLegendToggle.js"
|
|
17
|
+
import{innerSize as b}from"../shared/chartUtils.js"
|
|
18
|
+
function L(r,t,e){if(0===r.length)return
|
|
19
|
+
let o=r[0],n=Infinity
|
|
20
|
+
for(const s of r){const r=e(s)??0,i=Math.abs(t-r)
|
|
21
|
+
i<n&&(n=i,o=s)}return o}function B(r,t,e){return`${r} · ${t}: ${e.toLocaleString()} CIs`}function M(r,t,e){const o=t.map(t=>{const o=t.points.find(t=>t.x===r)
|
|
22
|
+
if(!o)return null
|
|
23
|
+
const n=e.findIndex(r=>r.id===t.id)
|
|
24
|
+
return{color:t.color??d[n%d.length],label:t.id,value:`${o.y.toLocaleString()} CIs`}}).filter(r=>null!==r)
|
|
25
|
+
return{title:r,rows:o}}function q({series:q,title:A="Warranty expiries",description:P="Projected count of CIs reaching warranty end by quarter.",height:S=320,compact:T=!1,onPointClick:F}){const H=t(null),K=I("Hover the chart for the quarterly value."),R=t(F)
|
|
26
|
+
R.current=F
|
|
27
|
+
const U=e(()=>q.map(r=>r.id),[q]),{hiddenIds:V,visibleIds:W,toggle:z}=w(U),D=e(()=>q.filter(r=>W.includes(r.id)),[q,W])
|
|
28
|
+
o(()=>{const r=H.current
|
|
29
|
+
if(!r||0===D.length)return
|
|
30
|
+
const t=r.clientWidth||640,e=T?Math.min(S,280):S,o=f(T),m=o.margin,{width:u,height:g}=b(t,e,m),I=k(T),w=v(K),A=a(r)
|
|
31
|
+
A.selectAll("*").remove(),A.attr("viewBox",`0 0 ${t} ${e}`)
|
|
32
|
+
const P=A.append("g").attr("transform",`translate(${m.left},${m.top})`),F=A.append("defs"),U=D[0]?.points.map(r=>r.x)??[],V=s().domain(U).range([0,u]).padding(.5),W=i().domain([0,n(D,r=>n(r.points,r=>r.y)??0)??0]).nice().range([g,0])
|
|
33
|
+
p(P,V,g,o.x),$(P,W,o.y)
|
|
34
|
+
const z=c().x(r=>V(r.x)??0).y0(g).y1(r=>W(r.y)).curve(y),E=h().x(r=>V(r.x)??0).y(r=>W(r.y)).curve(y)
|
|
35
|
+
D.forEach((r,t)=>{const e=q.findIndex(t=>t.id===r.id),o=r.color??d[e%d.length],n=`area-gradient-${j(r.id)}`
|
|
36
|
+
x(F,n,o,T),P.append("path").datum(r.points).attr("class",`area-fill-${t}`).attr("fill",`url(#${n})`).attr("stroke","none").attr("d",z).attr("pointer-events","none"),P.append("path").datum(r.points).attr("class",`area-line-${t}`).attr("fill","none").attr("stroke",o).attr("stroke-width",I).attr("stroke-linecap","round").attr("stroke-linejoin","round").attr("d",E).attr("pointer-events","none")})
|
|
37
|
+
let G=null
|
|
38
|
+
const J=(r,t)=>{const e=L(U,r,V)
|
|
39
|
+
if(e)for(let r=D.length-1;r>=0;r-=1){const o=D[r],n=o.points.find(r=>r.x===e)
|
|
40
|
+
if(n&&(t>=W(n.y)&&t<=g))return{seriesKey:o.id,point:n}}},N=P.append("rect").attr("class","area-interaction-overlay").attr("x",0).attr("y",0).attr("width",u).attr("height",g).attr("fill","transparent").attr("pointer-events","all").attr("cursor","crosshair")
|
|
41
|
+
N.on("mousemove",r=>{const[t,e]=l(r,P.node()),o=J(t,e)
|
|
42
|
+
if(!o)return void(G&&(G=null,w.onHoverEnd()))
|
|
43
|
+
const n=`${o.seriesKey}:${o.point.x}`,s=B(o.seriesKey,o.point.x,o.point.y),i=M(o.point.x,D,q)
|
|
44
|
+
if(n!==G)return G=n,void w.onHover(n,s,r,i)
|
|
45
|
+
w.onPointerMove?.(r)}).on("mouseleave",()=>{G=null,w.onHoverEnd()}).on("click",r=>{const[t,e]=l(r,P.node()),o=J(t,e)
|
|
46
|
+
if(!o)return
|
|
47
|
+
const n=`${o.seriesKey}:${o.point.x}`,s=B(o.seriesKey,o.point.x,o.point.y)
|
|
48
|
+
w.onToggleSelect(n,s),R.current?.({seriesId:o.seriesKey,x:o.point.x,y:o.point.y})})
|
|
49
|
+
const O=K.registerFocusApply(()=>{const r=K.getHoverKey(),t=K.getSelectedKey()
|
|
50
|
+
D.forEach((e,o)=>{const n=e.id,s=!r&&!t||Boolean(r?.startsWith(`${n}:`))||Boolean(t?.startsWith(`${n}:`)),i=C(s)
|
|
51
|
+
P.select(`path.area-fill-${o}`).attr("opacity",i),P.select(`path.area-line-${o}`).attr("opacity",i)})})
|
|
52
|
+
return()=>{O(),N.on("mousemove mouseleave click",null)}},[T,S,K.getHoverKey,K.getSelectedKey,K.onHover,K.onHoverEnd,K.onPointerMove,K.onToggleSelect,K.registerFocusApply,q,D])
|
|
53
|
+
const E=e(()=>r(u,{ariaLabel:"Series",hiddenIds:V,onToggle:z,items:q.map((r,t)=>({id:r.id,label:r.id,color:r.color??d[t%d.length]}))}),[V,q,z])
|
|
54
|
+
return r(m,{title:A,description:P,height:T?Math.min(S,280):S,compact:T,...g(K),svgRef:H,legend:E})}export{q as AreaChart}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type AreaSeries = {
|
|
2
|
+
id: string;
|
|
3
|
+
color?: string;
|
|
4
|
+
points: {
|
|
5
|
+
x: string;
|
|
6
|
+
y: number;
|
|
7
|
+
}[];
|
|
8
|
+
};
|
|
9
|
+
export type AreaChartProps = {
|
|
10
|
+
series: AreaSeries[];
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
height?: number;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
onPointClick?: (point: {
|
|
16
|
+
seriesId: string;
|
|
17
|
+
x: string;
|
|
18
|
+
y: number;
|
|
19
|
+
}) => void;
|
|
20
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as r,useEffect as e}from"react"
|
|
4
|
+
import{max as o}from"d3-array"
|
|
5
|
+
import{scaleBand as i,scaleLinear as s}from"d3-scale"
|
|
6
|
+
import{select as a}from"d3-selection"
|
|
7
|
+
import"d3-transition"
|
|
8
|
+
import{chartPalette as n}from"../../tokens.stylex.js"
|
|
9
|
+
import{ChartFrame as m}from"../shared/ChartFrame/index.js"
|
|
10
|
+
import{barChartAxisLayout as c}from"../shared/chartAxisLayout/barChart.js"
|
|
11
|
+
import{renderXAxis as h,renderYAxis as d}from"../shared/chartAxisLayout/render.js"
|
|
12
|
+
import{chartFrameInteractionProps as l,chartPointerHandlers as p}from"../shared/chartBinding.js"
|
|
13
|
+
import{bindChartPointer as f,applyPointerOpacity as u}from"../shared/chartInteraction.js"
|
|
14
|
+
import{sanitizeGradientKey as g,appendBarSeriesGradient as y,SHAPE_STROKE_WIDTH as j}from"../shared/chartVisualStyle.js"
|
|
15
|
+
import{useChartInteraction as $}from"../shared/useChartInteraction.js"
|
|
16
|
+
import{innerSize as x}from"../shared/chartUtils.js"
|
|
17
|
+
function k({data:k,title:C="CIs by site",description:b="Count of registered configuration items per data centre site.",height:w=320,compact:v=!1,valueLabel:I="CIs",onDatumClick:F}){const L=r(null),A=$(`Vertical bars show ${I.toLowerCase()} per category.`),B=r(F)
|
|
18
|
+
return B.current=F,e(()=>{const t=L.current
|
|
19
|
+
if(!t)return
|
|
20
|
+
const r=t.clientWidth||640,e=v?Math.min(w,280):w,m=c(v),l=m.margin,{width:$,height:C}=x(r,e,l),b=n[0],F=`bar-gradient-${g(b)}`,M=a(t)
|
|
21
|
+
M.selectAll("*").remove(),M.attr("viewBox",`0 0 ${r} ${e}`)
|
|
22
|
+
const V=M.append("defs")
|
|
23
|
+
y(V,F,b,v)
|
|
24
|
+
const D=M.append("g").attr("transform",`translate(${l.left},${l.top})`),R=i().domain(k.map(t=>t.label)).range([0,$]).padding(.25),S=s().domain([0,o(k,t=>t.value)??0]).nice().range([C,0])
|
|
25
|
+
h(D,R,C,m.x),d(D,S,m.y)
|
|
26
|
+
const U=D.selectAll("rect").data(k).join("rect").attr("x",t=>R(t.label)??0).attr("width",R.bandwidth()).attr("y",C).attr("height",0).attr("fill",`url(#${F})`).attr("stroke",b).attr("stroke-width",j).attr("stroke-opacity",.35).attr("rx",4)
|
|
27
|
+
U.transition().duration(500).attr("y",t=>S(t.value)).attr("height",t=>C-S(t.value)),f(U,{keyFn:t=>t.label,hintFn:t=>`${t.label}: ${t.value.toLocaleString()} ${I}`,...p(A),onClick:t=>B.current?.(t)})
|
|
28
|
+
const q=A.registerFocusApply(()=>{u(U,t=>t.label,A.getFocusOpacity)})
|
|
29
|
+
return()=>{q(),U.on("mouseenter mouseleave click",null)}},[v,k,w,A.getFocusOpacity,A.onHover,A.onHoverEnd,A.onToggleSelect,A.registerFocusApply,I]),t(m,{title:C,description:b,height:v?Math.min(w,280):w,compact:v,...l(A),svgRef:L})}export{k as BarChart}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type BarDatum = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
};
|
|
5
|
+
export type BarChartProps = {
|
|
6
|
+
data: BarDatum[];
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
height?: number;
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
valueLabel?: string;
|
|
12
|
+
onDatumClick?: (datum: BarDatum) => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as e,useMemo as r,useEffect as o}from"react"
|
|
4
|
+
import{sum as n}from"d3-array"
|
|
5
|
+
import{scaleOrdinal as s}from"d3-scale"
|
|
6
|
+
import{select as i}from"d3-selection"
|
|
7
|
+
import{pie as a,arc as d}from"d3-shape"
|
|
8
|
+
import{chartPalette as m}from"../../tokens.stylex.js"
|
|
9
|
+
import{ChartFrame as l}from"../shared/ChartFrame/index.js"
|
|
10
|
+
import{ChartLegend as c}from"../shared/ChartLegend/index.js"
|
|
11
|
+
import{chartFrameInteractionProps as h,chartPointerHandlers as f}from"../shared/chartBinding.js"
|
|
12
|
+
import{bindChartPointerLite as p}from"../shared/chartInteraction.js"
|
|
13
|
+
import{appendBarSeriesGradient as u,sanitizeGradientKey as g,chartSeriesStrokeColor as j}from"../shared/chartVisualStyle.js"
|
|
14
|
+
import{useChartInteraction as k}from"../shared/useChartInteraction.js"
|
|
15
|
+
import{useChartLegendToggle as x}from"../shared/useChartLegendToggle.js"
|
|
16
|
+
const C=.4,y=.75
|
|
17
|
+
function I({data:I,title:$="Asset class distribution",description:v="Registered CIs grouped by hardware class in the CMDB.",height:b=320,compact:M=!1,innerRadiusRatio:w=.58,onDatumClick:R}){const B=e(null),F=k("Hover a segment for details."),L=e(R)
|
|
18
|
+
L.current=R
|
|
19
|
+
const T=r(()=>I.map(t=>t.label),[I]),{hiddenIds:z,visibleIds:D,toggle:S}=x(T),A=r(()=>I.filter(t=>D.includes(t.label)),[I,D])
|
|
20
|
+
o(()=>{const t=B.current
|
|
21
|
+
if(!t||0===A.length)return
|
|
22
|
+
const e=t.clientWidth||480,r=M?Math.min(b,280):b,o=Math.min(e,r)/2-16,l=o*Math.min(y,Math.max(C,w)),c=i(t)
|
|
23
|
+
c.selectAll("*").remove(),c.attr("viewBox",`0 0 ${e} ${r}`)
|
|
24
|
+
const h=c.append("defs")
|
|
25
|
+
I.forEach((t,e)=>{if(!D.includes(t.label))return
|
|
26
|
+
const r=t.color??m[e%m.length]
|
|
27
|
+
u(h,`donut-${g(t.label)}`,r,M)})
|
|
28
|
+
const k=c.append("g").attr("transform",`translate(${e/2},${r/2})`),x=s().domain(I.map(t=>t.label)).range(I.map((t,e)=>t.color??m[e%m.length])),$=a().value(t=>t.value).sort(null).padAngle(.015),v=d().innerRadius(l).outerRadius(o),R=k.selectAll("path").data($(A)).join("path").attr("fill",t=>`url(#donut-${g(t.data.label)})`).attr("stroke",t=>x(t.data.label)).attr("stroke-width",M?2.5:3).attr("stroke-linejoin","round").attr("d",v)
|
|
29
|
+
p(R,{keyFn:t=>t.data.label,hintFn:t=>`${t.data.label}: ${t.data.value.toLocaleString()} CIs`,...f(F),onClick:t=>L.current?.(t.data)})
|
|
30
|
+
const T=F.registerFocusApply(()=>{const t=F.getHoverKey()
|
|
31
|
+
R.attr("opacity",t=>F.getFocusOpacity(t.data.label)).attr("stroke",e=>{const r=x(e.data.label)
|
|
32
|
+
return e.data.label===t?j(r):r}).attr("stroke-width",e=>e.data.label===t?M?3:3.5:M?2.5:3)}),z=n(A,t=>t.value)
|
|
33
|
+
return k.append("text").attr("text-anchor","middle").attr("dy","-0.2em").attr("font-size",M?18:22).attr("font-weight",700).attr("pointer-events","none").text(z.toLocaleString()),k.append("text").attr("text-anchor","middle").attr("dy","1.2em").attr("font-size",M?10:12).attr("fill","rgba(0,0,0,0.5)").attr("pointer-events","none").text("Total CIs"),()=>{T(),R.on("mouseenter mouseleave click",null)}},[M,I,b,w,F.getFocusOpacity,F.onHover,F.onHoverEnd,F.onPointerMove,F.onToggleSelect,F.registerFocusApply,A,D])
|
|
34
|
+
const H=r(()=>t(c,{ariaLabel:"Segments",hiddenIds:z,onToggle:S,items:I.map((t,e)=>({id:t.label,label:t.label,color:t.color??m[e%m.length]}))}),[I,z,S])
|
|
35
|
+
return t(l,{title:$,description:v,height:M?Math.min(b,280):b,compact:M,...h(F),svgRef:B,legend:H})}export{I as DonutChart}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DonutDatum = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
color?: string;
|
|
5
|
+
};
|
|
6
|
+
export type DonutChartProps = {
|
|
7
|
+
data: DonutDatum[];
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
height?: number;
|
|
11
|
+
compact?: boolean;
|
|
12
|
+
/** Hole size as a fraction of the outer radius. Clamped to keep a visible donut ring. */
|
|
13
|
+
innerRadiusRatio?: number;
|
|
14
|
+
onDatumClick?: (datum: DonutDatum) => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as e,useEffect as r}from"react"
|
|
4
|
+
import{max as n}from"d3-array"
|
|
5
|
+
import{select as o}from"d3-selection"
|
|
6
|
+
import{chartPalette as s}from"../../tokens.stylex.js"
|
|
7
|
+
import{ChartFrame as i}from"../shared/ChartFrame/index.js"
|
|
8
|
+
import{chartFrameInteractionProps as a,chartPointerHandlers as c}from"../shared/chartBinding.js"
|
|
9
|
+
import{bindChartPointer as h,applyPointerOpacity as l}from"../shared/chartInteraction.js"
|
|
10
|
+
import{appendCenterWeightedGradient as m,sanitizeGradientKey as f,SHAPE_STROKE_WIDTH as d}from"../shared/chartVisualStyle.js"
|
|
11
|
+
import{useChartInteraction as u}from"../shared/useChartInteraction.js"
|
|
12
|
+
import{defaultMargin as p,innerSize as g}from"../shared/chartUtils.js"
|
|
13
|
+
function x({data:x,title:j="Change request funnel",description:k="Progression of CI change requests through approval stages this quarter.",height:y=340,compact:$=!1,onStageClick:v}){const C=e(null),w=u("Hover a stage for the count."),q=e(v)
|
|
14
|
+
return q.current=v,r(()=>{const t=C.current
|
|
15
|
+
if(!t)return
|
|
16
|
+
const e=t.clientWidth||520,r=$?Math.min(y,300):y,i={...p,left:120},{width:a,height:u}=g(e,r,i),j=o(t)
|
|
17
|
+
j.selectAll("*").remove(),j.attr("viewBox",`0 0 ${e} ${r}`)
|
|
18
|
+
const k=j.append("defs")
|
|
19
|
+
x.forEach((t,e)=>{const r=s[e%s.length]
|
|
20
|
+
m(k,`funnel-${f(t.label)}`,r,$)})
|
|
21
|
+
const v=j.append("g").attr("transform",`translate(${i.left},${i.top})`),F=n(x,t=>t.value)??0,I=u/x.length
|
|
22
|
+
x.forEach((t,e)=>{const r=t.value/F*a,n=(a-r)/2,o=e*I+4,i=s[e%s.length]
|
|
23
|
+
v.append("rect").attr("class","funnel-stage").attr("x",n).attr("y",o).attr("width",r).attr("height",I-8).attr("fill",`url(#funnel-${f(t.label)})`).attr("stroke",i).attr("stroke-width",d).attr("stroke-opacity",.35).attr("rx",6).datum(t),v.append("text").attr("x",-8).attr("y",o+(I-8)/2).attr("text-anchor","end").attr("dominant-baseline","middle").attr("font-size",11).attr("pointer-events","none").text(t.label),v.append("text").attr("x",n+r/2).attr("y",o+(I-8)/2).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-size",12).attr("font-weight",600).attr("fill","#fff").attr("pointer-events","none").text(t.value)})
|
|
24
|
+
const b=v.selectAll("rect.funnel-stage")
|
|
25
|
+
h(b,{keyFn:t=>t.label,hintFn:t=>`${t.label}: ${t.value.toLocaleString()} requests`,...c(w),onClick:t=>q.current?.(t)})
|
|
26
|
+
const z=w.registerFocusApply(()=>{l(b,t=>t.label,w.getFocusOpacity)})
|
|
27
|
+
return()=>{z(),b.on("mouseenter mouseleave click",null)}},[$,x,y,w.getFocusOpacity,w.onHover,w.onHoverEnd,w.onPointerMove,w.onToggleSelect,w.registerFocusApply]),t(i,{title:j,description:k,height:$?Math.min(y,300):y,compact:$,...a(w),svgRef:C})}export{x as FunnelChart}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type FunnelStage = {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
};
|
|
5
|
+
export type FunnelChartProps = {
|
|
6
|
+
data: FunnelStage[];
|
|
7
|
+
title?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
height?: number;
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
onStageClick?: (stage: FunnelStage) => void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as r,useMemo as e,useEffect as o}from"react"
|
|
4
|
+
import{extent as i}from"d3-array"
|
|
5
|
+
import{scaleTime as s,scaleBand as a,scaleOrdinal as n}from"d3-scale"
|
|
6
|
+
import{select as h}from"d3-selection"
|
|
7
|
+
import{timeParse as m,timeFormat as d}from"d3-time-format"
|
|
8
|
+
import{chartPalette as c}from"../../tokens.stylex.js"
|
|
9
|
+
import{ChartFrame as l}from"../shared/ChartFrame/index.js"
|
|
10
|
+
import{ChartLegend as f}from"../shared/ChartLegend/index.js"
|
|
11
|
+
import{ganttTimelineAxisLayout as p}from"../shared/chartAxisLayout/ganttTimeline.js"
|
|
12
|
+
import{renderXAxis as g,renderYAxis as u}from"../shared/chartAxisLayout/render.js"
|
|
13
|
+
import{chartFrameInteractionProps as j,chartPointerHandlers as k}from"../shared/chartBinding.js"
|
|
14
|
+
import{bindChartPointer as w,applyPointerOpacity as x}from"../shared/chartInteraction.js"
|
|
15
|
+
import{appendBarSeriesGradient as $,sanitizeGradientKey as y,SHAPE_STROKE_WIDTH as C}from"../shared/chartVisualStyle.js"
|
|
16
|
+
import{useChartInteraction as b}from"../shared/useChartInteraction.js"
|
|
17
|
+
import{useChartLegendToggle as v}from"../shared/useChartLegendToggle.js"
|
|
18
|
+
import{innerSize as I}from"../shared/chartUtils.js"
|
|
19
|
+
function L({tasks:L,title:O="Hardware change timeline",description:T="Scheduled maintenance and refresh windows for in-flight change records.",height:F=360,compact:M=!1,onTaskClick:S}){const A=r(null),B=b("Hover a bar for the change window."),D=r(S)
|
|
20
|
+
D.current=S
|
|
21
|
+
const H=e(()=>[...new Set(L.map(t=>t.group??"Other"))],[L]),{hiddenIds:G,visibleIds:R,toggle:U}=v(H),V=e(()=>H.filter(t=>R.includes(t)),[H,R]),Y=e(()=>L.filter(t=>V.includes(t.group??"Other")),[L,V])
|
|
22
|
+
o(()=>{const t=A.current
|
|
23
|
+
if(!t||0===Y.length)return
|
|
24
|
+
const r=t.clientWidth||720,e=M?Math.min(F,320):F,o=p(M),l=o.margin,{width:f,height:j}=I(r,e,l),b=m("%Y-%m-%d"),v=Y.map(t=>({...t,startDate:b(t.start),endDate:b(t.end)})).filter(t=>t.startDate&&t.endDate),L=h(t)
|
|
25
|
+
L.selectAll("*").remove(),L.attr("viewBox",`0 0 ${r} ${e}`)
|
|
26
|
+
const O=L.append("defs")
|
|
27
|
+
H.forEach((t,r)=>{const e=c[r%c.length]
|
|
28
|
+
$(O,`gantt-${y(t)}`,e,M)})
|
|
29
|
+
const T=L.append("g").attr("transform",`translate(${l.left},${l.top})`),S=s().domain(i(v.flatMap(t=>[t.startDate,t.endDate]))).range([0,f]),G=a().domain(v.map(t=>t.label)).range([0,j]).padding(.25),R=n().domain(H).range(H.map((t,r)=>c[r%c.length]))
|
|
30
|
+
g(T,S,j,o.x,t=>t.ticks(M?4:6).tickFormat(d("%d %b"))),u(T,G,o.y)
|
|
31
|
+
const U=T.selectAll("rect").data(v).join("rect").attr("x",t=>S(t.startDate)).attr("y",t=>G(t.label)??0).attr("width",t=>Math.max(4,S(t.endDate)-S(t.startDate))).attr("height",G.bandwidth()).attr("fill",t=>`url(#gantt-${y(t.group??"Other")})`).attr("stroke",t=>R(t.group??"Other")).attr("stroke-width",C).attr("stroke-opacity",.35).attr("rx",4)
|
|
32
|
+
w(U,{keyFn:t=>t.id,hintFn:t=>`${t.label}: ${t.start} → ${t.end}`,...k(B),onClick:t=>D.current?.(t)})
|
|
33
|
+
const V=B.registerFocusApply(()=>{x(U,t=>t.id,B.getFocusOpacity)})
|
|
34
|
+
return()=>{V(),U.on("mouseenter mouseleave click",null)}},[M,H,F,B.getFocusOpacity,B.onHover,B.onHoverEnd,B.onPointerMove,B.onToggleSelect,B.registerFocusApply,L,Y])
|
|
35
|
+
const q=e(()=>t(f,{ariaLabel:"Groups",hiddenIds:G,onToggle:U,items:H.map((t,r)=>({id:t,label:t,color:c[r%c.length]}))}),[H,G,U])
|
|
36
|
+
return t(l,{title:O,description:T,height:M?Math.min(F,320):F,compact:M,...j(B),svgRef:A,legend:q})}export{L as GanttTimeline}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type GanttTask = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
group?: string;
|
|
5
|
+
start: string;
|
|
6
|
+
end: string;
|
|
7
|
+
};
|
|
8
|
+
export type GanttTimelineProps = {
|
|
9
|
+
tasks: GanttTask[];
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
height?: number;
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
onTaskClick?: (task: GanttTask) => void;
|
|
15
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as e,useEffect as o}from"react"
|
|
4
|
+
import{scaleLinear as r}from"d3-scale"
|
|
5
|
+
import{select as n}from"d3-selection"
|
|
6
|
+
import{arc as i}from"d3-shape"
|
|
7
|
+
import{chartPalette as a}from"../../tokens.stylex.js"
|
|
8
|
+
import{ChartFrame as s}from"../shared/ChartFrame/index.js"
|
|
9
|
+
import{chartFrameInteractionProps as m}from"../shared/chartBinding.js"
|
|
10
|
+
import{sanitizeGradientKey as l,appendArcSeriesGradient as c,chartLineStrokeWidth as h}from"../shared/chartVisualStyle.js"
|
|
11
|
+
import{useChartInteraction as p}from"../shared/useChartInteraction.js"
|
|
12
|
+
function f({value:f,min:u=0,max:d=100,title:g="CMDB completeness",description:x="Percentage of in-scope assets with a complete CI record.",height:$=260,compact:v=!1,label:M="Complete records",unit:b="%",onClick:y}){const k=e(null),C=p(`Current completeness: ${f}${b}.`),j=e(y)
|
|
13
|
+
return j.current=y,o(()=>{const t=k.current
|
|
14
|
+
if(!t)return
|
|
15
|
+
const e=t.clientWidth||420,o=v?Math.min($,220):$,s=Math.min(e,2*o)/2-12,m=`${M}: ${f}${b} (${u}–${d})`,p=n(t)
|
|
16
|
+
p.selectAll("*").remove(),p.attr("viewBox",`0 0 ${e} ${o}`)
|
|
17
|
+
const g=a[0],x=`gauge-${l(g)}`,y=p.append("defs")
|
|
18
|
+
c(y,x,g,v)
|
|
19
|
+
const w=p.append("g").attr("transform",`translate(${e/2},${o-8})`),z=r().domain([u,d]).range([-Math.PI/2,Math.PI/2]),B=i().innerRadius(.62*s).outerRadius(s).startAngle(-Math.PI/2).endAngle(Math.PI/2)
|
|
20
|
+
w.append("path").attr("d",B).attr("fill","rgba(0,0,0,0.08)")
|
|
21
|
+
const S=i().innerRadius(.62*s).outerRadius(s).startAngle(-Math.PI/2).endAngle(z(f)),I=w.append("path").attr("d",S).attr("fill",`url(#${x})`).attr("stroke",g).attr("stroke-width",h(v)).attr("stroke-linecap","round").attr("cursor","pointer").attr("role","button").attr("tabindex",0)
|
|
22
|
+
return I.on("mouseenter",t=>{I.attr("opacity",.85),C.onHover("gauge",m,t)}).on("mousemove",t=>{C.onPointerMove(t)}).on("mouseleave",()=>{I.attr("opacity",1),C.onHoverEnd()}).on("click",()=>{C.onToggleSelect("gauge",m),j.current?.({value:f,min:u,max:d,label:M})}),w.append("text").attr("text-anchor","middle").attr("y",.15*-s).attr("font-size",v?24:32).attr("font-weight",700).attr("pointer-events","none").text(`${f}${b}`),w.append("text").attr("text-anchor","middle").attr("y",.08*s).attr("font-size",12).attr("fill","rgba(0,0,0,0.5)").attr("pointer-events","none").text(M),w.append("text").attr("x",-s).attr("y",18).attr("font-size",11).attr("fill","rgba(0,0,0,0.45)").text(String(u)),w.append("text").attr("x",s).attr("y",18).attr("text-anchor","end").attr("font-size",11).attr("fill","rgba(0,0,0,0.45)").text(String(d)),()=>{I.on("mouseenter mousemove mouseleave click",null)}},[v,$,C.onHover,C.onHoverEnd,C.onPointerMove,C.onToggleSelect,M,d,u,b,f]),t(s,{title:g,description:x,height:v?Math.min($,220):$,compact:v,...m(C),svgRef:k})}export{f as GaugeChart}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type GaugeChartProps = {
|
|
2
|
+
value: number;
|
|
3
|
+
min?: number;
|
|
4
|
+
max?: number;
|
|
5
|
+
title?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
height?: number;
|
|
8
|
+
compact?: boolean;
|
|
9
|
+
label?: string;
|
|
10
|
+
unit?: string;
|
|
11
|
+
onClick?: (payload: {
|
|
12
|
+
value: number;
|
|
13
|
+
min: number;
|
|
14
|
+
max: number;
|
|
15
|
+
label: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Selection } from 'd3-selection';
|
|
2
|
+
export declare const AXIS_LABEL_STROKE = "#cbd5e1";
|
|
3
|
+
export declare const AXIS_TITLE_FILL = "#f3f4f6";
|
|
4
|
+
export declare function appendLabelBox(root: Selection<SVGGElement, unknown, null, undefined>, x: number, y: number, width: number, height: number, label: string, fontSize: number): void;
|
|
5
|
+
export declare function computeHeatmapLayout(rows: string[], columns: string[], innerWidth: number, innerHeight: number, compact: boolean): {
|
|
6
|
+
axisTitleSize: number;
|
|
7
|
+
rowLabelWidth: number;
|
|
8
|
+
colLabelHeight: number;
|
|
9
|
+
colLabelWidth: number;
|
|
10
|
+
cellGap: number;
|
|
11
|
+
cellSize: number;
|
|
12
|
+
gridWidth: number;
|
|
13
|
+
gridHeight: number;
|
|
14
|
+
gridX: number;
|
|
15
|
+
gridY: number;
|
|
16
|
+
columnLabelY: number;
|
|
17
|
+
columnTitleY: number;
|
|
18
|
+
rowLabelX: number;
|
|
19
|
+
titleFontSize: number;
|
|
20
|
+
labelFontSize: number;
|
|
21
|
+
columnLabelFontSize: number;
|
|
22
|
+
cellPosition: (row: string, column: string) => {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
rowCenterY: (row: string) => number;
|
|
27
|
+
columnCenterX: (column: string) => number;
|
|
28
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import{styleChartText as t,chartAxisColors as e}from"../shared/chartAxis.js"
|
|
2
|
+
import{heatmapAxisSpacing as i}from"../shared/chartAxisLayout/heatmap.js"
|
|
3
|
+
const o="#eef2f7",l="#cbd5e1",n="#f3f4f6"
|
|
4
|
+
function a(t,e,i=16){const o=.58*e
|
|
5
|
+
return Math.ceil(Math.max(...t.map(t=>t.length*o))+i)}function r(i,n,a,r,h,s,c){i.append("rect").attr("x",n).attr("y",a).attr("width",r).attr("height",h).attr("fill",o).attr("stroke",l).attr("stroke-width",1).attr("rx",2)
|
|
6
|
+
const d=s.split(/\s+/),b=d.length>1&&s.length>12?[d.slice(0,Math.ceil(d.length/2)).join(" "),d.slice(Math.ceil(d.length/2)).join(" ")]:[s],u=c+3,m=a+h/2-(b.length-1)*u/2,L=i.append("text").attr("x",n+r/2).attr("y",m).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("pointer-events","none")
|
|
7
|
+
t(L,{size:c,weight:500,fill:e.title}),b.forEach((t,e)=>{L.append("tspan").attr("x",n+r/2).attr("dy",0===e?0:u).text(t)})}function h(t,e,o,l,n){const r=i(n,t.length,e.length),{axisTitleSize:h,rowLabelWidthMin:s,colLabelHeight:c,rowTitleToLabels:d,rowLabelsToGrid:b,gridToColumnLabels:u,columnLabelsToTitle:m,columnLabelInset:L,gridTop:M}=r,f=2,x=n?10:11,g=Math.max(s,a(t,n?11:12)),w=Math.max(a(e,x,14),c+6),p=Math.max(w,Math.floor(Math.min((o-h-d-g-b-(e.length-1)*f)/e.length,(l-M-(u+c+m+h)-(t.length-1)*f)/t.length,n?58:72))),T=e.length*p+(e.length-1)*f,z=t.length*p+(t.length-1)*f,S=h+d+g+b,y=M,W=Math.max(28,p-2*L),Y=new Map(e.map((t,e)=>[t,e])),C=new Map(t.map((t,e)=>[t,e]))
|
|
8
|
+
return{axisTitleSize:h,rowLabelWidth:g,colLabelHeight:c,colLabelWidth:W,cellGap:f,cellSize:p,gridWidth:T,gridHeight:z,gridX:S,gridY:y,columnLabelY:y+z+u,columnTitleY:y+z+u+c+m,rowLabelX:h+d,titleFontSize:n?11:12,labelFontSize:n?11:12,columnLabelFontSize:n?10:11,cellPosition(t,e){const i=Y.get(e)??0,o=C.get(t)??0
|
|
9
|
+
return{x:S+i*(p+f),y:y+o*(p+f)}},rowCenterY(t){const e=C.get(t)??0
|
|
10
|
+
return y+e*(p+f)+p/2},columnCenterX(t){const e=Y.get(t)??0
|
|
11
|
+
return S+e*(p+f)+p/2}}}export{l as AXIS_LABEL_STROKE,n as AXIS_TITLE_FILL,r as appendLabelBox,h as computeHeatmapLayout}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsx as t}from"react/jsx-runtime"
|
|
3
|
+
import{useRef as e,useEffect as i}from"react"
|
|
4
|
+
import{extent as r}from"d3-array"
|
|
5
|
+
import{scaleQuantize as o}from"d3-scale"
|
|
6
|
+
import{select as n}from"d3-selection"
|
|
7
|
+
import{heatmapScale as l}from"../../tokens.stylex.js"
|
|
8
|
+
import{ChartFrame as a}from"../shared/ChartFrame/index.js"
|
|
9
|
+
import{styleAxisTitle as s,chartFontFamily as c,chartAxisColors as m}from"../shared/chartAxis.js"
|
|
10
|
+
import{heatmapChartMargin as h}from"../shared/chartAxisLayout/heatmap.js"
|
|
11
|
+
import{chartFrameInteractionProps as d,chartPointerHandlers as f}from"../shared/chartBinding.js"
|
|
12
|
+
import{bindChartPointer as u,applyPointerOpacity as p}from"../shared/chartInteraction.js"
|
|
13
|
+
import{useChartInteraction as x}from"../shared/useChartInteraction.js"
|
|
14
|
+
import{innerSize as g}from"../shared/chartUtils.js"
|
|
15
|
+
import{computeHeatmapLayout as y,AXIS_TITLE_FILL as w,AXIS_LABEL_STROKE as $,appendLabelBox as b}from"./heatmapLayout.js"
|
|
16
|
+
function L({data:L,rows:k,columns:j,title:C="Lifecycle by site",description:v="Server CI counts by lifecycle status and data centre site.",height:S=420,compact:z=!1,rowAxisLabel:F="Site",columnAxisLabel:I="Lifecycle status",onCellClick:A}){const Y=e(null),H=x("Hover a cell for the CI count."),M=e(A)
|
|
17
|
+
return M.current=A,i(()=>{const t=Y.current
|
|
18
|
+
if(!t)return
|
|
19
|
+
const e=t.clientWidth||640,i=z?Math.min(S,320):S,a=h(z),{width:d,height:x}=g(e,i,a),C=n(t)
|
|
20
|
+
C.selectAll("*").remove(),C.attr("viewBox",`0 0 ${e} ${i}`)
|
|
21
|
+
const v=C.append("g").attr("transform",`translate(${a.left},${a.top})`),A=y(k,j,d,x,z),{axisTitleSize:W,rowLabelWidth:X,colLabelHeight:B,colLabelWidth:T,cellSize:P,gridX:R,gridY:U,gridWidth:q,gridHeight:D,columnLabelY:E,columnTitleY:G,rowLabelX:J,titleFontSize:K,labelFontSize:N,columnLabelFontSize:O,cellPosition:Q,rowCenterY:V,columnCenterX:Z}=A,_=r(L,t=>t.value),tt=o().domain(_).range([...l]),et=t=>{const e=tt.range().indexOf(tt(t))
|
|
22
|
+
return e<0?0:e/Math.max(1,tt.range().length-1)}
|
|
23
|
+
v.append("rect").attr("x",0).attr("y",U).attr("width",W).attr("height",D).attr("fill",w).attr("stroke",$).attr("stroke-width",1)
|
|
24
|
+
const it=v.append("text").attr("transform",`translate(${W/2}, ${U+D/2}) rotate(-90)`).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("letter-spacing","0.06em").text(F.toUpperCase())
|
|
25
|
+
s(it,K).attr("font-weight",700),k.forEach(t=>{b(v,J,V(t)-B/2,X,B,t,N)}),j.forEach(t=>{b(v,Z(t)-T/2,E,T,B,t,O)}),v.append("rect").attr("x",R).attr("y",G).attr("width",q).attr("height",W).attr("fill",w).attr("stroke",$).attr("stroke-width",1)
|
|
26
|
+
const rt=v.append("text").attr("x",R+q/2).attr("y",G+W/2).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("letter-spacing","0.06em").text(I.toUpperCase())
|
|
27
|
+
s(rt,K).attr("font-weight",700)
|
|
28
|
+
const ot=v.selectAll("rect.cell").data(L).join("rect").attr("class","cell").attr("x",t=>Q(t.row,t.column).x).attr("y",t=>Q(t.row,t.column).y).attr("width",P).attr("height",P).attr("fill",t=>tt(t.value)).attr("stroke","#fff").attr("stroke-width",2)
|
|
29
|
+
u(ot,{keyFn:t=>`${t.row}:${t.column}`,hintFn:t=>`${t.row} · ${t.column}: ${t.value.toLocaleString()} CIs`,...f(H),onClick:t=>M.current?.(t)})
|
|
30
|
+
const nt=v.selectAll("text.value").data(L).join("text").attr("class","value").attr("x",t=>Q(t.row,t.column).x+P/2).attr("y",t=>Q(t.row,t.column).y+P/2).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",c).attr("font-size",z?14:16).attr("font-weight",700).attr("fill",t=>et(t.value)>=.5?"#fff":m.title).style("user-select","none").attr("pointer-events","none").text(t=>t.value),lt=H.registerFocusApply(()=>{p(ot,t=>`${t.row}:${t.column}`,H.getFocusOpacity),nt.attr("opacity",t=>1===H.getFocusOpacity(`${t.row}:${t.column}`)?1:.55)})
|
|
31
|
+
return()=>{lt(),ot.on("mouseenter mouseleave click",null)}},[I,j,z,L,S,H.getFocusOpacity,H.onHover,H.onHoverEnd,H.onPointerMove,H.onToggleSelect,H.registerFocusApply,F,k]),t(a,{title:C,description:v,height:z?Math.min(S,320):S,compact:z,...d(H),svgRef:Y})}export{L as HeatmapChart}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type HeatmapCell = {
|
|
2
|
+
row: string;
|
|
3
|
+
column: string;
|
|
4
|
+
value: number;
|
|
5
|
+
};
|
|
6
|
+
export type HeatmapChartProps = {
|
|
7
|
+
data: HeatmapCell[];
|
|
8
|
+
rows: string[];
|
|
9
|
+
columns: string[];
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
height?: number;
|
|
13
|
+
compact?: boolean;
|
|
14
|
+
rowAxisLabel?: string;
|
|
15
|
+
columnAxisLabel?: string;
|
|
16
|
+
onCellClick?: (cell: HeatmapCell) => void;
|
|
17
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import{jsxs as e,jsx as n}from"react/jsx-runtime"
|
|
3
|
+
import*as o from"@stylexjs/stylex"
|
|
4
|
+
import{chartPalette as r}from"../../tokens.stylex.js"
|
|
5
|
+
import{ChartTooltip as i}from"../shared/ChartTooltip/index.js"
|
|
6
|
+
import{useChartInteraction as t}from"../shared/useChartInteraction.js"
|
|
7
|
+
import{Sparkline as l}from"../shared/Sparkline/index.js"
|
|
8
|
+
import{styles as c}from"./index.styles.js"
|
|
9
|
+
function d(e){const n=`${e.value.toLocaleString()}${e.unit?` ${e.unit}`:""}`
|
|
10
|
+
return void 0===e.delta?`${e.label}\n${n}`:`${e.label}\n${n}\n${e.delta>=0?"+":""}${e.delta}% ${e.deltaLabel??""}`}function s({metric:i,index:t,isHovered:d,isSelected:s,onHover:a,onPointerMove:m,onHoverEnd:u,onToggleSelect:v,onClick:h}){const p=i.delta,f=void 0===p?c.deltaNeutral:p>=0?c.deltaUp:c.deltaDown,$=void 0===p?"":p>=0?"+":""
|
|
11
|
+
return e("article",{...o.props(c.card,c.cardInteractive,d?c.cardHovered:null,s?c.cardSelected:null),"aria-label":i.label,role:"button",tabIndex:0,onMouseEnter:a,onMouseMove:m,onMouseLeave:u,onClick(){v(),h?.(i)},onKeyDown(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),v(),h?.(i))},children:[n("span",{...o.props(c.label),children:i.label}),e("div",{...o.props(c.valueRow),children:[n("span",{...o.props(c.value),children:i.value.toLocaleString()}),i.unit?n("span",{...o.props(c.unit),children:i.unit}):null]}),void 0!==p?e("span",{...o.props(f),children:[$,p,"% ",i.deltaLabel??""]}):null,i.sparkline?n(l,{values:i.sparkline,color:r[t%r.length]}):null]})}function a({metrics:r,title:l="CMDB KPIs",description:a="Headline configuration management metrics with recent trend.",compact:m=!1,onMetricClick:u}){const v=t("Hover a KPI card for details.")
|
|
12
|
+
return e("div",{...o.props(c.root),children:[e("div",{...o.props(c.header),children:[n("h4",{...o.props(c.headerTitle),children:l}),m?null:n("p",{...o.props(c.headerDescription),children:a})]}),e("div",{ref:v.setTooltipContainer,...o.props(c.grid,c.gridRelative,m?c.gridCompact:null),children:[r.map((e,o)=>n(s,{metric:e,index:o,isHovered:v.getHoverKey()===e.id,isSelected:v.getSelectedKey()===e.id,onHover:n=>v.onHover(e.id,d(e),n.nativeEvent),onPointerMove:e=>v.onPointerMove(e.nativeEvent),onHoverEnd:v.onHoverEnd,onToggleSelect:()=>v.onToggleSelect(e.id,`${e.label}: ${e.value.toLocaleString()}`),onClick:u},e.id)),n(i,{tooltip:v.tooltip})]}),n("span",{...o.props(c.footerHint,v.isPinned?c.footerHintPinned:null),children:v.hint})]})}export{a as KpiCard}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import * as stylex from '@stylexjs/stylex';
|
|
2
|
+
export declare const styles: Readonly<{
|
|
3
|
+
readonly root: Readonly<{
|
|
4
|
+
readonly display: stylex.StyleXClassNameFor<"display", "flex">;
|
|
5
|
+
readonly flexDirection: stylex.StyleXClassNameFor<"flexDirection", "column">;
|
|
6
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "1.2rem">;
|
|
7
|
+
readonly width: stylex.StyleXClassNameFor<"width", "100%">;
|
|
8
|
+
}>;
|
|
9
|
+
readonly header: Readonly<{
|
|
10
|
+
readonly display: stylex.StyleXClassNameFor<"display", "flex">;
|
|
11
|
+
readonly flexDirection: stylex.StyleXClassNameFor<"flexDirection", "column">;
|
|
12
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "0.4rem">;
|
|
13
|
+
}>;
|
|
14
|
+
readonly headerTitle: Readonly<{
|
|
15
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.8rem">;
|
|
16
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "600">;
|
|
17
|
+
readonly lineHeight: stylex.StyleXClassNameFor<"lineHeight", "1.2">;
|
|
18
|
+
readonly margin: stylex.StyleXClassNameFor<"margin", 0>;
|
|
19
|
+
}>;
|
|
20
|
+
readonly headerDescription: Readonly<{
|
|
21
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.3rem">;
|
|
22
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
23
|
+
readonly margin: stylex.StyleXClassNameFor<"margin", 0>;
|
|
24
|
+
}>;
|
|
25
|
+
readonly grid: Readonly<{
|
|
26
|
+
readonly display: stylex.StyleXClassNameFor<"display", "grid">;
|
|
27
|
+
readonly gridTemplateColumns: stylex.StyleXClassNameFor<"gridTemplateColumns", "repeat(auto-fit, minmax(18rem, 1fr))">;
|
|
28
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "1.2rem">;
|
|
29
|
+
}>;
|
|
30
|
+
readonly gridRelative: Readonly<{
|
|
31
|
+
readonly position: stylex.StyleXClassNameFor<"position", "relative">;
|
|
32
|
+
}>;
|
|
33
|
+
readonly gridCompact: Readonly<{
|
|
34
|
+
readonly gridTemplateColumns: stylex.StyleXClassNameFor<"gridTemplateColumns", "1fr">;
|
|
35
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "0.8rem">;
|
|
36
|
+
}>;
|
|
37
|
+
readonly card: Readonly<{
|
|
38
|
+
readonly display: stylex.StyleXClassNameFor<"display", "flex">;
|
|
39
|
+
readonly flexDirection: stylex.StyleXClassNameFor<"flexDirection", "column">;
|
|
40
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "0.8rem">;
|
|
41
|
+
readonly padding: stylex.StyleXClassNameFor<"padding", "1.6rem">;
|
|
42
|
+
readonly borderWidth: stylex.StyleXClassNameFor<"borderWidth", "1px">;
|
|
43
|
+
readonly borderStyle: stylex.StyleXClassNameFor<"borderStyle", "solid">;
|
|
44
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
45
|
+
readonly borderRadius: stylex.StyleXClassNameFor<"borderRadius", "0.8rem">;
|
|
46
|
+
readonly backgroundColor: stylex.StyleXClassNameFor<"backgroundColor", string>;
|
|
47
|
+
}>;
|
|
48
|
+
readonly cardInteractive: Readonly<{
|
|
49
|
+
readonly cursor: stylex.StyleXClassNameFor<"cursor", "pointer">;
|
|
50
|
+
readonly transitionProperty: stylex.StyleXClassNameFor<"transitionProperty", "box-shadow, transform">;
|
|
51
|
+
readonly transitionDuration: stylex.StyleXClassNameFor<"transitionDuration", "0.15s">;
|
|
52
|
+
readonly transitionTimingFunction: stylex.StyleXClassNameFor<"transitionTimingFunction", "ease">;
|
|
53
|
+
}>;
|
|
54
|
+
readonly cardHovered: Readonly<{
|
|
55
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
56
|
+
readonly boxShadow: stylex.StyleXClassNameFor<"boxShadow", "0 4px 14px rgba(0, 0, 0, 0.08)">;
|
|
57
|
+
}>;
|
|
58
|
+
readonly cardSelected: Readonly<{
|
|
59
|
+
readonly borderColor: stylex.StyleXClassNameFor<"borderColor", string>;
|
|
60
|
+
readonly boxShadow: stylex.StyleXClassNameFor<"boxShadow", "0 0 0 2px rgba(0, 0, 0, 0.08)">;
|
|
61
|
+
}>;
|
|
62
|
+
readonly label: Readonly<{
|
|
63
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.2rem">;
|
|
64
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
65
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "500">;
|
|
66
|
+
}>;
|
|
67
|
+
readonly valueRow: Readonly<{
|
|
68
|
+
readonly display: stylex.StyleXClassNameFor<"display", "flex">;
|
|
69
|
+
readonly alignItems: stylex.StyleXClassNameFor<"alignItems", "baseline">;
|
|
70
|
+
readonly gap: stylex.StyleXClassNameFor<"gap", "0.4rem">;
|
|
71
|
+
}>;
|
|
72
|
+
readonly value: Readonly<{
|
|
73
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "2.4rem">;
|
|
74
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "700">;
|
|
75
|
+
readonly lineHeight: stylex.StyleXClassNameFor<"lineHeight", "1.1">;
|
|
76
|
+
}>;
|
|
77
|
+
readonly unit: Readonly<{
|
|
78
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.3rem">;
|
|
79
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
80
|
+
}>;
|
|
81
|
+
readonly deltaUp: Readonly<{
|
|
82
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.2rem">;
|
|
83
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
84
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "600">;
|
|
85
|
+
}>;
|
|
86
|
+
readonly deltaDown: Readonly<{
|
|
87
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.2rem">;
|
|
88
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
89
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "600">;
|
|
90
|
+
}>;
|
|
91
|
+
readonly deltaNeutral: Readonly<{
|
|
92
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.2rem">;
|
|
93
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
94
|
+
}>;
|
|
95
|
+
readonly sparkline: Readonly<{
|
|
96
|
+
readonly width: stylex.StyleXClassNameFor<"width", "100%">;
|
|
97
|
+
readonly height: stylex.StyleXClassNameFor<"height", "3.2rem">;
|
|
98
|
+
readonly display: stylex.StyleXClassNameFor<"display", "block">;
|
|
99
|
+
}>;
|
|
100
|
+
readonly footerHint: Readonly<{
|
|
101
|
+
readonly fontSize: stylex.StyleXClassNameFor<"fontSize", "1.2rem">;
|
|
102
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
103
|
+
}>;
|
|
104
|
+
readonly footerHintPinned: Readonly<{
|
|
105
|
+
readonly color: stylex.StyleXClassNameFor<"color", string>;
|
|
106
|
+
readonly fontWeight: stylex.StyleXClassNameFor<"fontWeight", "500">;
|
|
107
|
+
}>;
|
|
108
|
+
}>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import"@stylexjs/stylex"
|
|
2
|
+
const k={root:{k1xSpc:"_78zum5",kXwgrk:"_dt5ytf",kOIVth:"_bhn5rf",kzqmXN:"_h8yej3",$$css:!0},header:{k1xSpc:"_78zum5",kXwgrk:"_dt5ytf",kOIVth:"_1neeqzj",$$css:!0},headerTitle:{kGuDYH:"_1elmx46",k63SB2:"_1s688f",kLWn49:"_1u7k74",kogj98:"_1ghz6dp",$$css:!0},headerDescription:{kGuDYH:"_1xebwwb",kMwMTN:"_qdq3cf",kogj98:"_1ghz6dp",$$css:!0},grid:{k1xSpc:"_rvj5dj",kumcoG:"_19vp1m2",kOIVth:"_bhn5rf",$$css:!0},gridRelative:{kVAEAm:"_1n2onr6",$$css:!0},gridCompact:{kumcoG:"_1y6fwsi",kOIVth:"_1rq4zhh",$$css:!0},card:{k1xSpc:"_78zum5",kXwgrk:"_dt5ytf",kOIVth:"_1rq4zhh",kmVPX3:"_qo3fwf",kMzoRj:"_mkeg23",ksu8eU:"_1y0btm7",kVAM5u:"_1yf9t2n",kaIpWk:"_1o5ta1t",kWkggS:"_1a1xyot",$$css:!0},cardInteractive:{kkrTdU:"_1ypdohk",k1ekBW:"_hd6ntr",kIyJzY:"_x6bhzk",kAMwcw:"_wji4o3",$$css:!0},cardHovered:{kVAM5u:"_45n503",kGVxlE:"_14nb6pt",$$css:!0},cardSelected:{kVAM5u:"_45n503",kGVxlE:"_1rvjved",$$css:!0},label:{kGuDYH:"_vewgow",kMwMTN:"_qdq3cf",k63SB2:"_k50ysn",$$css:!0},valueRow:{k1xSpc:"_78zum5",kGNEyG:"_1pha0wt",kOIVth:"_1neeqzj",$$css:!0},value:{kGuDYH:"_19orhxu",k63SB2:"_1xlr1w8",kLWn49:"_1159mfc",$$css:!0},unit:{kGuDYH:"_1xebwwb",kMwMTN:"_qdq3cf",$$css:!0},deltaUp:{kGuDYH:"_vewgow",kMwMTN:"_5zgiyw",k63SB2:"_1s688f",$$css:!0},deltaDown:{kGuDYH:"_vewgow",kMwMTN:"_67euey",k63SB2:"_1s688f",$$css:!0},deltaNeutral:{kGuDYH:"_vewgow",kMwMTN:"_qdq3cf",$$css:!0},footerHint:{kGuDYH:"_vewgow",kMwMTN:"_qdq3cf",$$css:!0},footerHintPinned:{kMwMTN:"_4iqcr8",k63SB2:"_k50ysn",$$css:!0}}
|
|
3
|
+
export{k as styles}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type KpiMetric = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
unit?: string;
|
|
6
|
+
delta?: number;
|
|
7
|
+
deltaLabel?: string;
|
|
8
|
+
sparkline?: number[];
|
|
9
|
+
};
|
|
10
|
+
export type KpiCardProps = {
|
|
11
|
+
metrics: KpiMetric[];
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
onMetricClick?: (metric: KpiMetric) => void;
|
|
16
|
+
};
|