ioloco-charts 0.1.0 → 0.2.1

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.
Files changed (50) hide show
  1. package/dist/D3/AreaChart/index.d.ts +1 -1
  2. package/dist/D3/AreaChart/index.js +44 -43
  3. package/dist/D3/AreaChart/types.d.ts +2 -0
  4. package/dist/D3/BarChart/index.d.ts +1 -1
  5. package/dist/D3/BarChart/index.js +28 -27
  6. package/dist/D3/BarChart/types.d.ts +3 -0
  7. package/dist/D3/DonutChart/index.d.ts +1 -1
  8. package/dist/D3/DonutChart/index.js +26 -25
  9. package/dist/D3/DonutChart/types.d.ts +5 -0
  10. package/dist/D3/FunnelChart/index.d.ts +1 -1
  11. package/dist/D3/FunnelChart/index.js +20 -19
  12. package/dist/D3/FunnelChart/types.d.ts +2 -0
  13. package/dist/D3/GanttTimeline/index.d.ts +1 -1
  14. package/dist/D3/GanttTimeline/index.js +21 -21
  15. package/dist/D3/GanttTimeline/types.d.ts +1 -0
  16. package/dist/D3/GaugeChart/index.d.ts +1 -1
  17. package/dist/D3/GaugeChart/index.js +15 -15
  18. package/dist/D3/GaugeChart/types.d.ts +1 -0
  19. package/dist/D3/HeatmapChart/index.d.ts +1 -1
  20. package/dist/D3/HeatmapChart/index.js +26 -25
  21. package/dist/D3/HeatmapChart/types.d.ts +2 -0
  22. package/dist/D3/LineChart/index.js +30 -30
  23. package/dist/D3/RadialBarChart/index.d.ts +1 -1
  24. package/dist/D3/RadialBarChart/index.js +27 -27
  25. package/dist/D3/RadialBarChart/types.d.ts +1 -0
  26. package/dist/D3/RelationshipDiagram/index.js +20 -20
  27. package/dist/D3/SankeyChart/index.d.ts +1 -1
  28. package/dist/D3/SankeyChart/index.js +24 -23
  29. package/dist/D3/SankeyChart/types.d.ts +2 -0
  30. package/dist/D3/ScatterPlot/index.d.ts +1 -1
  31. package/dist/D3/ScatterPlot/index.js +25 -25
  32. package/dist/D3/ScatterPlot/types.d.ts +1 -0
  33. package/dist/D3/SparklineChart/index.js +5 -5
  34. package/dist/D3/StackedBarChart/index.d.ts +1 -1
  35. package/dist/D3/StackedBarChart/index.js +30 -29
  36. package/dist/D3/StackedBarChart/types.d.ts +2 -0
  37. package/dist/D3/TreemapChart/index.d.ts +1 -1
  38. package/dist/D3/TreemapChart/index.js +23 -22
  39. package/dist/D3/TreemapChart/types.d.ts +2 -0
  40. package/dist/D3/index.d.ts +0 -2
  41. package/dist/D3/shared/chartLabels.d.ts +2 -0
  42. package/dist/D3/shared/chartLabels.js +2 -0
  43. package/dist/index.js +0 -1
  44. package/dist/ioloco-charts.css +1 -1
  45. package/package.json +1 -1
  46. package/dist/D3/KpiCard/index.d.ts +0 -2
  47. package/dist/D3/KpiCard/index.js +0 -12
  48. package/dist/D3/KpiCard/index.styles.d.ts +0 -108
  49. package/dist/D3/KpiCard/index.styles.js +0 -3
  50. package/dist/D3/KpiCard/types.d.ts +0 -16
@@ -1,31 +1,32 @@
1
1
  "use client";
2
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"
3
+ import{useRef as e,useEffect as r}from"react"
4
+ import{extent as o}from"d3-array"
5
+ import{scaleQuantize as i}from"d3-scale"
6
+ import{select as a}from"d3-selection"
7
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"
8
+ import{ChartFrame as n}from"../shared/ChartFrame/index.js"
9
+ import{styleAxisTitle as s,chartFontFamily as m,chartAxisColors as c}from"../shared/chartAxis.js"
10
10
  import{heatmapChartMargin as h}from"../shared/chartAxisLayout/heatmap.js"
11
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
12
+ import{bindChartPointer as p,applyPointerOpacity as u}from"../shared/chartInteraction.js"
13
+ import{formatChartHint as x}from"../shared/chartLabels.js"
14
+ import{useChartInteraction as g}from"../shared/useChartInteraction.js"
15
+ import{innerSize as w}from"../shared/chartUtils.js"
16
+ import{computeHeatmapLayout as b,AXIS_TITLE_FILL as $,AXIS_LABEL_STROKE as y,appendLabelBox as L}from"./heatmapLayout.js"
17
+ function j({data:j,rows:k,columns:v,title:C="Heatmap",description:z,height:F=420,compact:H=!1,rowAxisLabel:S="Row",columnAxisLabel:A="Column",valueLabel:Y,hoverHint:M="Hover a cell for details.",onCellClick:W}){const X=e(null),B=g(M),I=e(W)
18
+ return I.current=W,r(()=>{const t=X.current
18
19
  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}
20
+ const e=t.clientWidth||640,r=H?Math.min(F,320):F,n=h(H),{width:d,height:g}=w(e,r,n),C=a(t)
21
+ C.selectAll("*").remove(),C.attr("viewBox",`0 0 ${e} ${r}`)
22
+ const z=C.append("g").attr("transform",`translate(${n.left},${n.top})`),M=b(k,v,d,g,H),{axisTitleSize:W,rowLabelWidth:R,colLabelHeight:T,colLabelWidth:P,cellSize:U,gridX:q,gridY:D,gridWidth:E,gridHeight:G,columnLabelY:J,columnTitleY:K,rowLabelX:N,titleFontSize:O,labelFontSize:Q,columnLabelFontSize:V,cellPosition:Z,rowCenterY:_,columnCenterX:tt}=M,et=o(j,t=>t.value),rt=i().domain(et).range([...l]),ot=t=>{const e=rt.range().indexOf(rt(t))
23
+ return e<0?0:e/Math.max(1,rt.range().length-1)}
24
+ z.append("rect").attr("x",0).attr("y",D).attr("width",W).attr("height",G).attr("fill",$).attr("stroke",y).attr("stroke-width",1)
25
+ const it=z.append("text").attr("transform",`translate(${W/2}, ${D+G/2}) rotate(-90)`).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("letter-spacing","0.06em").text(S.toUpperCase())
26
+ s(it,O).attr("font-weight",700),k.forEach(t=>{L(z,N,_(t)-T/2,R,T,t,Q)}),v.forEach(t=>{L(z,tt(t)-P/2,J,P,T,t,V)}),z.append("rect").attr("x",q).attr("y",K).attr("width",E).attr("height",W).attr("fill",$).attr("stroke",y).attr("stroke-width",1)
27
+ const at=z.append("text").attr("x",q+E/2).attr("y",K+W/2).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("letter-spacing","0.06em").text(A.toUpperCase())
28
+ s(at,O).attr("font-weight",700)
29
+ const lt=z.selectAll("rect.cell").data(j).join("rect").attr("class","cell").attr("x",t=>Z(t.row,t.column).x).attr("y",t=>Z(t.row,t.column).y).attr("width",U).attr("height",U).attr("fill",t=>rt(t.value)).attr("stroke","#fff").attr("stroke-width",2)
30
+ p(lt,{keyFn:t=>`${t.row}:${t.column}`,hintFn:t=>x(`${t.row} · ${t.column}`,t.value,Y),...f(B),onClick:t=>I.current?.(t)})
31
+ const nt=z.selectAll("text.value").data(j).join("text").attr("class","value").attr("x",t=>Z(t.row,t.column).x+U/2).attr("y",t=>Z(t.row,t.column).y+U/2).attr("text-anchor","middle").attr("dominant-baseline","middle").attr("font-family",m).attr("font-size",H?14:16).attr("font-weight",700).attr("fill",t=>ot(t.value)>=.5?"#fff":c.title).style("user-select","none").attr("pointer-events","none").text(t=>t.value),st=B.registerFocusApply(()=>{u(lt,t=>`${t.row}:${t.column}`,B.getFocusOpacity),nt.attr("opacity",t=>1===B.getFocusOpacity(`${t.row}:${t.column}`)?1:.55)})
32
+ return()=>{st(),lt.on("mouseenter mouseleave click",null)}},[A,v,H,j,F,B.getFocusOpacity,B.onHover,B.onHoverEnd,B.onPointerMove,B.onToggleSelect,B.registerFocusApply,M,S,k,Y]),t(n,{title:C,description:z,height:H?Math.min(F,320):F,compact:H,...d(B),svgRef:X})}export{j as HeatmapChart}
@@ -13,5 +13,7 @@ export type HeatmapChartProps = {
13
13
  compact?: boolean;
14
14
  rowAxisLabel?: string;
15
15
  columnAxisLabel?: string;
16
+ valueLabel?: string;
17
+ hoverHint?: string;
16
18
  onCellClick?: (cell: HeatmapCell) => void;
17
19
  };
@@ -1,40 +1,40 @@
1
1
  "use client";
2
- import{jsx as e}from"react/jsx-runtime"
3
- import{useRef as r,useMemo as t,useEffect as o}from"react"
2
+ import{jsx as r}from"react/jsx-runtime"
3
+ import{useRef as e,useMemo as t,useEffect as o}from"react"
4
4
  import{max as s}from"d3-array"
5
5
  import{scalePoint as i,scaleLinear as n}from"d3-scale"
6
6
  import{select as a}from"d3-selection"
7
7
  import{line as c}from"d3-shape"
8
8
  import{chartPalette as l}from"../../tokens.stylex.js"
9
- import{ChartFrame as d}from"../shared/ChartFrame/index.js"
10
- import{ChartLegend as m}from"../shared/ChartLegend/index.js"
9
+ import{ChartFrame as m}from"../shared/ChartFrame/index.js"
10
+ import{ChartLegend as d}from"../shared/ChartLegend/index.js"
11
11
  import{lineChartAxisLayout as h}from"../shared/chartAxisLayout/lineChart.js"
12
12
  import{renderXAxis as p,renderYAxis as f}from"../shared/chartAxisLayout/render.js"
13
- import{chartFrameInteractionProps as u,chartPointerHandlers as $}from"../shared/chartBinding.js"
13
+ import{chartFrameInteractionProps as $,chartPointerHandlers as u}from"../shared/chartBinding.js"
14
14
  import{bindChartPointer as g}from"../shared/chartInteraction.js"
15
- import{CHART_SERIES_CURVE as j,seriesMarkerRadius as y,SERIES_MARKER_FILL as k,SERIES_MARKER_STROKE_WIDTH as x,seriesDimOpacity as C,chartLineStrokeWidth as I}from"../shared/chartVisualStyle.js"
15
+ import{CHART_SERIES_CURVE as j,seriesMarkerRadius as k,SERIES_MARKER_FILL as y,SERIES_MARKER_STROKE_WIDTH as x,seriesDimOpacity as C,chartLineStrokeWidth as I}from"../shared/chartVisualStyle.js"
16
16
  import{useChartInteraction as v}from"../shared/useChartInteraction.js"
17
- import{useChartLegendToggle as B}from"../shared/useChartLegendToggle.js"
18
- import{innerSize as L}from"../shared/chartUtils.js"
19
- function w({series:w,title:M="CI change volume",description:b="Monthly count of CI records created and updated in the CMDB.",height:F=320,compact:A=!1,onPointClick:S}){const T=r(null),D=v("Hover a point for the monthly value."),H=r(S)
20
- H.current=S
21
- const P=t(()=>w.map(e=>e.id),[w]),{hiddenIds:R,visibleIds:U,toggle:V}=B(P),q=t(()=>w.filter(e=>U.includes(e.id)),[w,U])
22
- o(()=>{const e=T.current
23
- if(!e||0===q.length)return
24
- const r=e.clientWidth||640,t=A?Math.min(F,280):F,o=h(A),d=o.margin,{width:m,height:u}=L(r,t,d),v=I(A),B=a(e)
25
- B.selectAll("*").remove(),B.attr("viewBox",`0 0 ${r} ${t}`)
26
- const M=B.append("g").attr("transform",`translate(${d.left},${d.top})`),b=q[0]?.points.map(e=>e.x)??[],S=i().domain(b).range([0,m]).padding(.5),P=n().domain([0,s(q,e=>s(e.points,e=>e.y)??0)??0]).nice().range([u,0])
27
- p(M,S,u,o.x),f(M,P,o.y)
28
- const R=c().x(e=>S(e.x)??0).y(e=>P(e.y)).curve(j),U=[]
29
- q.forEach((e,r)=>{const t=w.findIndex(r=>r.id===e.id),o=e.color??l[t%l.length],s=e.id
30
- M.append("path").datum(e.points).attr("class",`line-series-${r}`).attr("fill","none").attr("stroke",o).attr("stroke-width",v).attr("stroke-linecap","round").attr("stroke-linejoin","round").attr("d",R).attr("pointer-events","none")
31
- const i=M.selectAll(`circle.series-${r}`).data(e.points.map(e=>({...e,seriesId:s}))).join("circle").attr("class",`series-${r}`).attr("cx",e=>S(e.x)??0).attr("cy",e=>P(e.y)).attr("r",y(A)).attr("fill",k).attr("stroke",o).attr("stroke-width",x).attr("opacity",0)
32
- g(i,{keyFn:e=>`${e.seriesId}:${e.x}`,hintFn:e=>`${e.seriesId} · ${e.x}: ${e.y.toLocaleString()}`,...$(D),onClick:e=>H.current?.({seriesId:e.seriesId,x:e.x,y:e.y})}),U.push(()=>i.on("mouseenter mouseleave click",null))})
33
- const V=D.registerFocusApply(()=>{const e=D.getHoverKey(),r=D.getSelectedKey()
34
- q.forEach((t,o)=>{const s=t.id,i=!e&&!r||Boolean(e?.startsWith(`${s}:`))||Boolean(r?.startsWith(`${s}:`))
35
- M.select(`path.line-series-${o}`).attr("opacity",C(i)),M.selectAll(`circle.series-${o}`).attr("opacity",t=>{const o=`${t.seriesId}:${t.x}`
36
- return o===e||o===r?1:0}).attr("r",t=>{const o=`${t.seriesId}:${t.x}`
37
- return y(A,o===e||o===r)})})})
38
- return()=>{V(),U.forEach(e=>e())}},[A,F,D.getFocusOpacity,D.getHoverKey,D.getSelectedKey,D.onHover,D.onHoverEnd,D.onPointerMove,D.onToggleSelect,D.registerFocusApply,w,q])
39
- const z=t(()=>e(m,{ariaLabel:"Series",hiddenIds:R,onToggle:V,items:w.map((e,r)=>({id:e.id,label:e.id,color:e.color??l[r%l.length]}))}),[R,w,V])
40
- return e(d,{title:M,description:b,height:A?Math.min(F,280):F,compact:A,...u(D),svgRef:T,legend:z})}export{w as LineChart}
17
+ import{useChartLegendToggle as L}from"../shared/useChartLegendToggle.js"
18
+ import{innerSize as w}from"../shared/chartUtils.js"
19
+ function B({series:B,title:b="Line chart",description:F,height:A=320,compact:M=!1,onPointClick:S}){const T=e(null),H=v("Hover a point for details."),P=e(S)
20
+ P.current=S
21
+ const R=t(()=>B.map(r=>r.id),[B]),{hiddenIds:U,visibleIds:V,toggle:q}=L(R),z=t(()=>B.filter(r=>V.includes(r.id)),[B,V])
22
+ o(()=>{const r=T.current
23
+ if(!r||0===z.length)return
24
+ const e=r.clientWidth||640,t=M?Math.min(A,280):A,o=h(M),m=o.margin,{width:d,height:$}=w(e,t,m),v=I(M),L=a(r)
25
+ L.selectAll("*").remove(),L.attr("viewBox",`0 0 ${e} ${t}`)
26
+ const b=L.append("g").attr("transform",`translate(${m.left},${m.top})`),F=z[0]?.points.map(r=>r.x)??[],S=i().domain(F).range([0,d]).padding(.5),R=n().domain([0,s(z,r=>s(r.points,r=>r.y)??0)??0]).nice().range([$,0])
27
+ p(b,S,$,o.x),f(b,R,o.y)
28
+ const U=c().x(r=>S(r.x)??0).y(r=>R(r.y)).curve(j),V=[]
29
+ z.forEach((r,e)=>{const t=B.findIndex(e=>e.id===r.id),o=r.color??l[t%l.length],s=r.id
30
+ b.append("path").datum(r.points).attr("class",`line-series-${e}`).attr("fill","none").attr("stroke",o).attr("stroke-width",v).attr("stroke-linecap","round").attr("stroke-linejoin","round").attr("d",U).attr("pointer-events","none")
31
+ const i=b.selectAll(`circle.series-${e}`).data(r.points.map(r=>({...r,seriesId:s}))).join("circle").attr("class",`series-${e}`).attr("cx",r=>S(r.x)??0).attr("cy",r=>R(r.y)).attr("r",k(M)).attr("fill",y).attr("stroke",o).attr("stroke-width",x).attr("opacity",0)
32
+ g(i,{keyFn:r=>`${r.seriesId}:${r.x}`,hintFn:r=>`${r.seriesId} · ${r.x}: ${r.y.toLocaleString()}`,...u(H),onClick:r=>P.current?.({seriesId:r.seriesId,x:r.x,y:r.y})}),V.push(()=>i.on("mouseenter mouseleave click",null))})
33
+ const q=H.registerFocusApply(()=>{const r=H.getHoverKey(),e=H.getSelectedKey()
34
+ z.forEach((t,o)=>{const s=t.id,i=!r&&!e||Boolean(r?.startsWith(`${s}:`))||Boolean(e?.startsWith(`${s}:`))
35
+ b.select(`path.line-series-${o}`).attr("opacity",C(i)),b.selectAll(`circle.series-${o}`).attr("opacity",t=>{const o=`${t.seriesId}:${t.x}`
36
+ return o===r||o===e?1:0}).attr("r",t=>{const o=`${t.seriesId}:${t.x}`
37
+ return k(M,o===r||o===e)})})})
38
+ return()=>{q(),V.forEach(r=>r())}},[M,A,H.getFocusOpacity,H.getHoverKey,H.getSelectedKey,H.onHover,H.onHoverEnd,H.onPointerMove,H.onToggleSelect,H.registerFocusApply,B,z])
39
+ const D=t(()=>r(d,{ariaLabel:"Series",hiddenIds:U,onToggle:q,items:B.map((r,e)=>({id:r.id,label:r.id,color:r.color??l[e%l.length]}))}),[U,B,q])
40
+ return r(m,{title:b,description:F,height:M?Math.min(A,280):A,compact:M,...$(H),svgRef:T,legend:D})}export{B as LineChart}
@@ -1,2 +1,2 @@
1
1
  import type { RadialBarChartProps } from './types';
2
- export declare function RadialBarChart({ data, max, title, description, height, compact, unit, innerRadiusRatio, onDatumClick }: Readonly<RadialBarChartProps>): import("react").JSX.Element;
2
+ export declare function RadialBarChart({ data, max, title, description, height, compact, unit, innerRadiusRatio, hoverHint, onDatumClick }: Readonly<RadialBarChartProps>): import("react").JSX.Element;
@@ -1,34 +1,34 @@
1
1
  "use client";
2
2
  import{jsx as t}from"react/jsx-runtime"
3
- import{useRef as e,useMemo as r,useEffect as o}from"react"
4
- import{mean as n}from"d3-array"
5
- import{select as a}from"d3-selection"
6
- import{arc as s}from"d3-shape"
7
- import{chartPalette as i}from"../../tokens.stylex.js"
8
- import{ChartFrame as c}from"../shared/ChartFrame/index.js"
9
- import{ChartLegend as h}from"../shared/ChartLegend/index.js"
3
+ import{useRef as r,useMemo as e,useEffect as o}from"react"
4
+ import{mean as a}from"d3-array"
5
+ import{select as n}from"d3-selection"
6
+ import{arc as i}from"d3-shape"
7
+ import{chartPalette as s}from"../../tokens.stylex.js"
8
+ import{ChartFrame as h}from"../shared/ChartFrame/index.js"
9
+ import{ChartLegend as d}from"../shared/ChartLegend/index.js"
10
10
  import{chartFontFamily as l}from"../shared/chartAxis.js"
11
- import{chartFrameInteractionProps as m,chartPointerHandlers as d}from"../shared/chartBinding.js"
11
+ import{chartFrameInteractionProps as m,chartPointerHandlers as c}from"../shared/chartBinding.js"
12
12
  import{bindChartPointer as f,applyPointerOpacity as p}from"../shared/chartInteraction.js"
13
13
  import{appendArcSeriesGradient as u,sanitizeGradientKey as g,chartLineStrokeWidth as M}from"../shared/chartVisualStyle.js"
14
14
  import{useChartInteraction as $}from"../shared/useChartInteraction.js"
15
15
  import{useChartLegendToggle as x}from"../shared/useChartLegendToggle.js"
16
- const j=-Math.PI/2,k=1.5*Math.PI
17
- function v(t,e){return t.max??e}function y(t,e,r,o,n){return s().innerRadius(t).outerRadius(e).startAngle(r).endAngle(o).cornerRadius(n)(null)??""}function b({data:s,max:b=100,title:C="Record completeness by class",description:I="Percentage of in-scope CIs with a complete record, grouped by asset class.",height:w=360,compact:R=!1,unit:F="%",innerRadiusRatio:L=.22,onDatumClick:z}){const A=e(null),B=$("Hover a ring for the completeness score."),T=e(z)
18
- T.current=z
19
- const D=r(()=>s.map(t=>t.label),[s]),{hiddenIds:H,visibleIds:P,toggle:S}=x(D),V=r(()=>s.filter(t=>P.includes(t.label)),[s,P])
20
- o(()=>{const t=A.current
21
- if(!t||0===V.length)return
22
- const e=t.clientWidth||480,r=R?Math.min(w,300):w,o=Math.min(e,r)/2-(R?12:20),c=o*Math.min(.6,Math.max(.12,L)),h=R?3:5,m=Math.max(4,(o-c-h*Math.max(V.length-1,0))/V.length),$=Math.min(m/2,6),x=a(t)
23
- x.selectAll("*").remove(),x.attr("viewBox",`0 0 ${e} ${r}`)
24
- const C=x.append("defs"),I=x.append("g").attr("transform",`translate(${e/2},${r/2})`),z=V.map((t,e)=>{const r=s.findIndex(e=>e.label===t.label),o=t.color??i[r%i.length]
25
- return u(C,`radial-${g(t.label)}`,o,R),{datum:t,inner:c+e*(m+h),outer:c+e*(m+h)+m,color:o,effectiveMax:v(t,b)}}),D=j+k
26
- I.selectAll("path.track").data(z).join("path").attr("class","track").attr("fill","rgba(0,0,0,0.08)").attr("pointer-events","none").attr("d",t=>y(t.inner,t.outer,j,D,$))
27
- const H=I.selectAll("path.bar").data(z).join("path").attr("class","bar").attr("fill",t=>`url(#radial-${g(t.datum.label)})`).attr("stroke",t=>t.color).attr("stroke-width",M(R)).attr("stroke-linejoin","round").attr("d",t=>{const e=Math.min(1,Math.max(0,t.datum.value/t.effectiveMax))
28
- return y(t.inner,t.outer,j,j+k*e,$)})
29
- f(H,{keyFn:t=>t.datum.label,hintFn(t){const e=Math.round(t.datum.value/t.effectiveMax*100)
30
- return`${t.datum.label}: ${t.datum.value.toLocaleString()}${F} (${e}% of target)`},...d(B),onClick:t=>T.current?.(t.datum)})
31
- const P=B.registerFocusApply(()=>{p(H,t=>t.datum.label,B.getFocusOpacity)}),S=n(V,t=>t.value/v(t,b)*100)??0
32
- return I.append("text").attr("text-anchor","middle").attr("dy","-0.15em").attr("font-family",l).attr("font-size",R?20:26).attr("font-weight",700).attr("pointer-events","none").text(`${Math.round(S)}${F}`),I.append("text").attr("text-anchor","middle").attr("dy","1.15em").attr("font-family",l).attr("font-size",R?10:12).attr("fill","rgba(0,0,0,0.5)").attr("pointer-events","none").text("Average"),()=>{P(),H.on("mouseenter mouseleave click",null)}},[R,s,w,L,B.getFocusOpacity,B.onHover,B.onHoverEnd,B.onPointerMove,B.onToggleSelect,B.registerFocusApply,s,b,F,V])
33
- const q=r(()=>t(h,{ariaLabel:"Categories",hiddenIds:H,onToggle:S,items:s.map((t,e)=>({id:t.label,label:`${t.label} · ${t.value.toLocaleString()}${F}`,color:t.color??i[e%i.length]}))}),[s,H,S,F])
34
- return t(c,{title:C,description:I,height:R?Math.min(w,300):w,compact:R,...m(B),svgRef:A,legend:q})}export{b as RadialBarChart}
16
+ const j=-Math.PI/2,v=1.5*Math.PI
17
+ function k(t,r){return t.max??r}function b(t,r,e,o,a){return i().innerRadius(t).outerRadius(r).startAngle(e).endAngle(o).cornerRadius(a)(null)??""}function y({data:i,max:y=100,title:C="Radial bar chart",description:I,height:R=360,compact:w=!1,unit:F="%",innerRadiusRatio:L=.22,hoverHint:z="Hover a ring for details.",onDatumClick:A}){const B=r(null),H=$(z),T=r(A)
18
+ T.current=A
19
+ const D=e(()=>i.map(t=>t.label),[i]),{hiddenIds:S,visibleIds:V,toggle:q}=x(D),E=e(()=>i.filter(t=>V.includes(t.label)),[i,V])
20
+ o(()=>{const t=B.current
21
+ if(!t||0===E.length)return
22
+ const r=t.clientWidth||480,e=w?Math.min(R,300):R,o=Math.min(r,e)/2-(w?12:20),h=o*Math.min(.6,Math.max(.12,L)),d=w?3:5,m=Math.max(4,(o-h-d*Math.max(E.length-1,0))/E.length),$=Math.min(m/2,6),x=n(t)
23
+ x.selectAll("*").remove(),x.attr("viewBox",`0 0 ${r} ${e}`)
24
+ const C=x.append("defs"),I=x.append("g").attr("transform",`translate(${r/2},${e/2})`),z=E.map((t,r)=>{const e=i.findIndex(r=>r.label===t.label),o=t.color??s[e%s.length]
25
+ return u(C,`radial-${g(t.label)}`,o,w),{datum:t,inner:h+r*(m+d),outer:h+r*(m+d)+m,color:o,effectiveMax:k(t,y)}}),A=j+v
26
+ I.selectAll("path.track").data(z).join("path").attr("class","track").attr("fill","rgba(0,0,0,0.08)").attr("pointer-events","none").attr("d",t=>b(t.inner,t.outer,j,A,$))
27
+ const D=I.selectAll("path.bar").data(z).join("path").attr("class","bar").attr("fill",t=>`url(#radial-${g(t.datum.label)})`).attr("stroke",t=>t.color).attr("stroke-width",M(w)).attr("stroke-linejoin","round").attr("d",t=>{const r=Math.min(1,Math.max(0,t.datum.value/t.effectiveMax))
28
+ return b(t.inner,t.outer,j,j+v*r,$)})
29
+ f(D,{keyFn:t=>t.datum.label,hintFn(t){const r=Math.round(t.datum.value/t.effectiveMax*100)
30
+ return`${t.datum.label}: ${t.datum.value.toLocaleString()}${F} (${r}% of target)`},...c(H),onClick:t=>T.current?.(t.datum)})
31
+ const S=H.registerFocusApply(()=>{p(D,t=>t.datum.label,H.getFocusOpacity)}),V=a(E,t=>t.value/k(t,y)*100)??0
32
+ return I.append("text").attr("text-anchor","middle").attr("dy","-0.15em").attr("font-family",l).attr("font-size",w?20:26).attr("font-weight",700).attr("pointer-events","none").text(`${Math.round(V)}${F}`),I.append("text").attr("text-anchor","middle").attr("dy","1.15em").attr("font-family",l).attr("font-size",w?10:12).attr("fill","rgba(0,0,0,0.5)").attr("pointer-events","none").text("Average"),()=>{S(),D.on("mouseenter mouseleave click",null)}},[w,i,R,L,H.getFocusOpacity,H.onHover,H.onHoverEnd,H.onPointerMove,H.onToggleSelect,H.registerFocusApply,i,y,F,E])
33
+ const G=e(()=>t(d,{ariaLabel:"Categories",hiddenIds:S,onToggle:q,items:i.map((t,r)=>({id:t.label,label:`${t.label} · ${t.value.toLocaleString()}${F}`,color:t.color??s[r%s.length]}))}),[i,S,q,F])
34
+ return t(h,{title:C,description:I,height:w?Math.min(R,300):R,compact:w,...m(H),svgRef:B,legend:G})}export{y as RadialBarChart}
@@ -14,5 +14,6 @@ export type RadialBarChartProps = {
14
14
  unit?: string;
15
15
  /** Inner radius as a fraction of the outer radius (0–1). */
16
16
  innerRadiusRatio?: number;
17
+ hoverHint?: string;
17
18
  onDatumClick?: (datum: RadialBarDatum) => void;
18
19
  };
@@ -4,31 +4,31 @@ import{useRef as r,useMemo as t,useEffect as n}from"react"
4
4
  import{drag as i}from"d3-drag"
5
5
  import{forceSimulation as s,forceLink as l,forceManyBody as d,forceCenter as c,forceCollide as a}from"d3-force"
6
6
  import{select as m}from"d3-selection"
7
- import{zoom as f}from"d3-zoom"
8
- import*as u from"@stylexjs/stylex"
7
+ import{zoom as u}from"d3-zoom"
8
+ import*as f from"@stylexjs/stylex"
9
9
  import{nodeColorByType as h}from"../../tokens.stylex.js"
10
10
  import{chartHintWithAction as p,chartFocusOpacity as g}from"../shared/chartInteraction.js"
11
11
  import{ChartLegend as k}from"../shared/ChartLegend/index.js"
12
12
  import{ChartTooltip as v}from"../shared/ChartTooltip/index.js"
13
13
  import{useChartInteraction as x}from"../shared/useChartInteraction.js"
14
14
  import{useChartLegendToggle as y}from"../shared/useChartLegendToggle.js"
15
- import{styles as $,relationshipLabels as w}from"./index.styles.js"
16
- const I=22
17
- function L(e){return w[e]}function b(e){return`${e.source.id}->${e.target.id}`}function j(e,o){return o.source.id===e||o.target.id===e}function C(e,o){const r=e??o
18
- return r?r.includes("->")?{focusNodeId:null,focusLinkKey:r}:{focusNodeId:r,focusLinkKey:null}:{focusNodeId:null,focusLinkKey:null}}function N({data:w,title:N="Server composition",description:z="Hardware components recorded as part of each server CI in the CMDB.",height:H=420,compact:K=!1,onNodeClick:M}){const S=r(null),T=x("Hover nodes or links for details."),B=r(M)
19
- B.current=M
20
- const D=K?Math.min(H,360):H,R=K?18:I,W=t(()=>[...new Set(w.nodes.map(e=>e.type))],[w.nodes]),{hiddenIds:X,visibleIds:Y,toggle:_}=y(W),q=t(()=>W.filter(e=>Y.includes(e)),[W,Y]),A=t(()=>e(k,{ariaLabel:"Node types",compact:K,hiddenIds:X,onToggle:_,items:W.map(e=>({id:e,label:e,color:h[e],swatchStyle:{borderRadius:"999px"}}))}),[K,X,W,_])
21
- return n(()=>{const e=S.current
22
- if(!e||0===q.length)return
15
+ import{styles as $,relationshipLabels as L}from"./index.styles.js"
16
+ const b=22
17
+ function j(e){return L[e]}function w(e){return`${e.source.id}->${e.target.id}`}function I(e,o){return o.source.id===e||o.target.id===e}function N(e,o){const r=e??o
18
+ return r?r.includes("->")?{focusNodeId:null,focusLinkKey:r}:{focusNodeId:r,focusLinkKey:null}:{focusNodeId:null,focusLinkKey:null}}function z({data:L,title:z="Relationship diagram",description:C,height:K=420,compact:H=!1,onNodeClick:M}){const T=r(null),R=x("Hover nodes or links for details."),S=r(M)
19
+ S.current=M
20
+ const B=H?Math.min(K,360):K,W=H?18:b,X=t(()=>[...new Set(L.nodes.map(e=>e.type))],[L.nodes]),{hiddenIds:Y,visibleIds:_,toggle:q}=y(X),A=t(()=>X.filter(e=>_.includes(e)),[X,_]),D=t(()=>e(k,{ariaLabel:"Node types",compact:H,hiddenIds:Y,onToggle:q,items:X.map(e=>({id:e,label:e,color:h[e],swatchStyle:{borderRadius:"999px"}}))}),[H,Y,X,q])
21
+ return n(()=>{const e=T.current
22
+ if(!e||0===A.length)return
23
23
  const o=e.clientWidth||960,r=m(e)
24
24
  r.selectAll("*").remove()
25
- const t=w.nodes.filter(e=>q.includes(e.type)).map(e=>({...e})),n=new Map(t.map(e=>[e.id,e])),u=w.links.map(e=>({source:n.get(e.source)??e.source,target:n.get(e.target)??e.target,relationship:e.relationship})).filter(e=>e.source&&e.target),p=r.append("g"),k=f().scaleExtent([.35,2.5]).on("zoom",e=>{p.attr("transform",e.transform)})
26
- r.call(k),r.append("defs").append("marker").attr("id","dependency-arrow").attr("viewBox","0 -4 8 8").attr("refX",R+2).attr("refY",0).attr("markerWidth",7).attr("markerHeight",7).attr("orient","auto").append("path").attr("d","M0,-4L8,0L0,4").attr("fill","rgba(0, 0, 0, 0.35)")
27
- const v=s(t).force("link",l(u).id(e=>e.id).distance(e=>"contains"===e.relationship||"installed_on"===e.relationship?90:130).strength(.7)).force("charge",d().strength(K?-320:-420)).force("center",c(o/2,D/2)).force("collide",a(R+16)),x=e=>{const{focusNodeId:o,focusLinkKey:r}=C(T.getHoverKey(),T.getSelectedKey())
28
- return o||r?r===b(e)||o&&j(o,e)?1:.2:.85},y=p.append("g").attr("stroke","rgba(0, 0, 0, 0.22)").attr("stroke-width",1.5).selectAll("line").data(u).join("line").attr("marker-end","url(#dependency-arrow)").attr("cursor","pointer").on("mouseenter",(e,o)=>{T.onHover(b(o),`Link · ${L(o.relationship)}: ${o.source.label} → ${o.target.label}`,e)}).on("mousemove",e=>{T.onPointerMove(e)}).on("mouseleave",()=>T.onHoverEnd()).on("click",(e,o)=>{T.onToggleSelect(b(o),`${L(o.relationship)}: ${o.source.label} → ${o.target.label}`)}),$=p.append("g").selectAll("text").data(u).join("text").attr("font-size",K?9:10).attr("fill","rgba(0, 0, 0, 0.45)").attr("text-anchor","middle").attr("pointer-events","none").text(e=>L(e.relationship)),I=p.append("g").selectAll("g").data(t).join("g").attr("cursor","pointer"),N=i().on("start",(e,o)=>{e.active||v.alphaTarget(.3).restart(),o.fx=o.x,o.fy=o.y}).on("drag",(e,o)=>{o.fx=e.x,o.fy=e.y}).on("end",(e,o)=>{e.active||v.alphaTarget(0),o.fx=null,o.fy=null})
29
- I.call(N)
30
- const z=I.append("circle").attr("r",R).attr("fill",e=>h[e.type]).on("mouseenter",(e,o)=>{T.onHover(o.id,`${o.label} · ${o.subtitle??o.type}`,e)}).on("mousemove",e=>{T.onPointerMove(e)}).on("mouseleave",()=>T.onHoverEnd()).on("click",(e,o)=>{T.onToggleSelect(o.id,`${o.label} · ${o.subtitle??o.type}`),B.current?.(o)})
31
- I.append("text").attr("text-anchor","middle").attr("dy","-0.2em").attr("font-size",K?10:11).attr("font-weight",600).attr("fill","#fff").attr("pointer-events","none").text(e=>e.label.length>16?`${e.label.slice(0,14)}…`:e.label),I.append("text").attr("text-anchor","middle").attr("dy","1.1em").attr("font-size",K?8:9).attr("fill","rgba(255, 255, 255, 0.85)").attr("pointer-events","none").text(e=>e.type)
32
- const H=()=>{const e=T.getHoverKey(),o=T.getSelectedKey()
33
- y.attr("stroke-opacity",x),z.attr("opacity",r=>g(r.id,e,o)).attr("stroke",e=>e.id===o?"#1e40af":"#fff").attr("stroke-width",e=>e.id===o?3:2)},M=T.registerFocusApply(H)
34
- return v.on("tick",()=>{y.attr("x1",e=>e.source.x??0).attr("y1",e=>e.source.y??0).attr("x2",e=>e.target.x??0).attr("y2",e=>e.target.y??0),$.attr("x",e=>((e.source.x??0)+(e.target.x??0))/2).attr("y",e=>((e.source.y??0)+(e.target.y??0))/2-4),I.attr("transform",e=>`translate(${e.x??0},${e.y??0})`)}),()=>{M(),y.on("mouseenter mousemove mouseleave click",null),z.on("mouseenter mousemove mouseleave click",null),v.stop()}},[K,w,D,T.getHoverKey,T.getSelectedKey,T.onHover,T.onHoverEnd,T.onPointerMove,T.onToggleSelect,T.registerFocusApply,R,q]),o("div",{...u.props($.root),children:[o("div",{...u.props($.header,K?$.headerCompact:null),children:[o("div",{children:[e("h4",{...u.props($.headerTitle),children:N}),K?null:e("p",{...u.props($.headerDescription),children:z})]}),A]}),o("div",{ref:T.setTooltipContainer,...u.props($.canvasWrap,K?$.canvasWrapCompact:null),children:[e("svg",{ref:S,...u.props($.svg),style:{height:D},role:"img","aria-label":N}),e(v,{tooltip:T.tooltip})]}),e("span",{...u.props($.hint,T.isPinned?$.hintPinned:null),children:T.hint||p("Hover nodes or links for details.")})]})}export{N as RelationshipDiagram}
25
+ const t=L.nodes.filter(e=>A.includes(e.type)).map(e=>({...e})),n=new Map(t.map(e=>[e.id,e])),f=L.links.map(e=>({source:n.get(e.source)??e.source,target:n.get(e.target)??e.target,relationship:e.relationship})).filter(e=>e.source&&e.target),p=r.append("g"),k=u().scaleExtent([.35,2.5]).on("zoom",e=>{p.attr("transform",e.transform)})
26
+ r.call(k),r.append("defs").append("marker").attr("id","dependency-arrow").attr("viewBox","0 -4 8 8").attr("refX",W+2).attr("refY",0).attr("markerWidth",7).attr("markerHeight",7).attr("orient","auto").append("path").attr("d","M0,-4L8,0L0,4").attr("fill","rgba(0, 0, 0, 0.35)")
27
+ const v=s(t).force("link",l(f).id(e=>e.id).distance(e=>"contains"===e.relationship||"installed_on"===e.relationship?90:130).strength(.7)).force("charge",d().strength(H?-320:-420)).force("center",c(o/2,B/2)).force("collide",a(W+16)),x=e=>{const{focusNodeId:o,focusLinkKey:r}=N(R.getHoverKey(),R.getSelectedKey())
28
+ return o||r?r===w(e)||o&&I(o,e)?1:.2:.85},y=p.append("g").attr("stroke","rgba(0, 0, 0, 0.22)").attr("stroke-width",1.5).selectAll("line").data(f).join("line").attr("marker-end","url(#dependency-arrow)").attr("cursor","pointer").on("mouseenter",(e,o)=>{R.onHover(w(o),`Link · ${j(o.relationship)}: ${o.source.label} → ${o.target.label}`,e)}).on("mousemove",e=>{R.onPointerMove(e)}).on("mouseleave",()=>R.onHoverEnd()).on("click",(e,o)=>{R.onToggleSelect(w(o),`${j(o.relationship)}: ${o.source.label} → ${o.target.label}`)}),$=p.append("g").selectAll("text").data(f).join("text").attr("font-size",H?9:10).attr("fill","rgba(0, 0, 0, 0.45)").attr("text-anchor","middle").attr("pointer-events","none").text(e=>j(e.relationship)),b=p.append("g").selectAll("g").data(t).join("g").attr("cursor","pointer"),z=i().on("start",(e,o)=>{e.active||v.alphaTarget(.3).restart(),o.fx=o.x,o.fy=o.y}).on("drag",(e,o)=>{o.fx=e.x,o.fy=e.y}).on("end",(e,o)=>{e.active||v.alphaTarget(0),o.fx=null,o.fy=null})
29
+ b.call(z)
30
+ const C=b.append("circle").attr("r",W).attr("fill",e=>h[e.type]).on("mouseenter",(e,o)=>{R.onHover(o.id,`${o.label} · ${o.subtitle??o.type}`,e)}).on("mousemove",e=>{R.onPointerMove(e)}).on("mouseleave",()=>R.onHoverEnd()).on("click",(e,o)=>{R.onToggleSelect(o.id,`${o.label} · ${o.subtitle??o.type}`),S.current?.(o)})
31
+ b.append("text").attr("text-anchor","middle").attr("dy","-0.2em").attr("font-size",H?10:11).attr("font-weight",600).attr("fill","#fff").attr("pointer-events","none").text(e=>e.label.length>16?`${e.label.slice(0,14)}…`:e.label),b.append("text").attr("text-anchor","middle").attr("dy","1.1em").attr("font-size",H?8:9).attr("fill","rgba(255, 255, 255, 0.85)").attr("pointer-events","none").text(e=>e.type)
32
+ const K=()=>{const e=R.getHoverKey(),o=R.getSelectedKey()
33
+ y.attr("stroke-opacity",x),C.attr("opacity",r=>g(r.id,e,o)).attr("stroke",e=>e.id===o?"#1e40af":"#fff").attr("stroke-width",e=>e.id===o?3:2)},M=R.registerFocusApply(K)
34
+ return v.on("tick",()=>{y.attr("x1",e=>e.source.x??0).attr("y1",e=>e.source.y??0).attr("x2",e=>e.target.x??0).attr("y2",e=>e.target.y??0),$.attr("x",e=>((e.source.x??0)+(e.target.x??0))/2).attr("y",e=>((e.source.y??0)+(e.target.y??0))/2-4),b.attr("transform",e=>`translate(${e.x??0},${e.y??0})`)}),()=>{M(),y.on("mouseenter mousemove mouseleave click",null),C.on("mouseenter mousemove mouseleave click",null),v.stop()}},[H,L,B,R.getHoverKey,R.getSelectedKey,R.onHover,R.onHoverEnd,R.onPointerMove,R.onToggleSelect,R.registerFocusApply,W,A]),o("div",{...f.props($.root),children:[o("div",{...f.props($.header,H?$.headerCompact:null),children:[o("div",{children:[e("h4",{...f.props($.headerTitle),children:z}),H?null:e("p",{...f.props($.headerDescription),children:C})]}),D]}),o("div",{ref:R.setTooltipContainer,...f.props($.canvasWrap,H?$.canvasWrapCompact:null),children:[e("svg",{ref:T,...f.props($.svg),style:{height:B},role:"img","aria-label":z}),e(v,{tooltip:R.tooltip})]}),e("span",{...f.props($.hint,R.isPinned?$.hintPinned:null),children:R.hint||p("Hover nodes or links for details.")})]})}export{z as RelationshipDiagram}
@@ -1,2 +1,2 @@
1
1
  import type { SankeyChartProps } from './types';
2
- export declare function SankeyChart({ data, title, description, height, compact, onLinkClick, onNodeClick }: Readonly<SankeyChartProps>): import("react").JSX.Element;
2
+ export declare function SankeyChart({ data, title, description, height, compact, valueLabel, hoverHint, onLinkClick, onNodeClick }: Readonly<SankeyChartProps>): import("react").JSX.Element;
@@ -1,32 +1,33 @@
1
1
  "use client";
2
2
  import{jsx as t}from"react/jsx-runtime"
3
- import{useRef as o,useEffect as n}from"react"
4
- import{scaleOrdinal as e}from"d3-scale"
3
+ import{useRef as o,useEffect as e}from"react"
4
+ import{scaleOrdinal as n}from"d3-scale"
5
5
  import{select as r}from"d3-selection"
6
6
  import{sankey as i,sankeyJustify as s,sankeyLinkHorizontal as a}from"d3-sankey"
7
7
  import{chartPalette as c}from"../../tokens.stylex.js"
8
8
  import{ChartFrame as l}from"../shared/ChartFrame/index.js"
9
9
  import{chartFrameInteractionProps as m,chartPointerHandlers as h}from"../shared/chartBinding.js"
10
10
  import{bindChartPointerLite as f,bindChartPointer as p,applyPointerOpacity as d}from"../shared/chartInteraction.js"
11
- import{sanitizeGradientKey as u}from"../shared/chartVisualStyle.js"
12
- import{useChartInteraction as g}from"../shared/useChartInteraction.js"
13
- import{innerSize as k}from"../shared/chartUtils.js"
14
- function $(t){return Math.max(1,(t.y1??0)-(t.y0??0))}function y(t){return((t.y0??0)+(t.y1??0))/2}function v(t,o,n,e,r,i){const s=t.append("linearGradient").attr("id",o).attr("gradientUnits","userSpaceOnUse").attr("x1",r).attr("x2",i).attr("y1",0).attr("y2",0)
15
- s.append("stop").attr("offset","0%").attr("stop-color",n).attr("stop-opacity",.5),s.append("stop").attr("offset","100%").attr("stop-color",e).attr("stop-opacity",.5)}function x({data:x,title:C="CI onboarding flow",description:M="Volume of configuration items moving from intake channels through validation into CMDB states.",height:j=480,compact:S=!1,onLinkClick:w,onNodeClick:F}){const I=o(null),b=g("Hover a flow or stage for counts."),B=o(w),U=o(F)
16
- B.current=w,U.current=F,n(()=>{const t=I.current
11
+ import{formatChartValue as u,formatChartHint as g}from"../shared/chartLabels.js"
12
+ import{sanitizeGradientKey as k}from"../shared/chartVisualStyle.js"
13
+ import{useChartInteraction as $}from"../shared/useChartInteraction.js"
14
+ import{innerSize as y}from"../shared/chartUtils.js"
15
+ function v(t){return Math.max(1,(t.y1??0)-(t.y0??0))}function x(t){return((t.y0??0)+(t.y1??0))/2}function j(t,o,e,n,r,i){const s=t.append("linearGradient").attr("id",o).attr("gradientUnits","userSpaceOnUse").attr("x1",r).attr("x2",i).attr("y1",0).attr("y2",0)
16
+ s.append("stop").attr("offset","0%").attr("stop-color",e).attr("stop-opacity",.5),s.append("stop").attr("offset","100%").attr("stop-color",n).attr("stop-opacity",.5)}function S({data:S,title:M="Sankey chart",description:C,height:F=480,compact:b=!1,valueLabel:w,hoverHint:L="Hover a flow or stage for details.",onLinkClick:U,onNodeClick:B}){const H=o(null),I=$(L),z=o(U),G=o(B)
17
+ z.current=U,G.current=B,e(()=>{const t=H.current
17
18
  if(!t)return
18
- const o=t.clientWidth||720,n=S?Math.max(Math.min(j,420),400):Math.max(j,480),l=S?104:128,m={left:l,right:l,bottom:16,top:16},{width:g,height:C}=k(o,n,m),M=S?12:14,w=S?14:18,F=r(t)
19
- F.selectAll("*").remove(),F.attr("viewBox",`0 0 ${o} ${n}`)
20
- const V=F.append("g").attr("transform",`translate(${m.left},${m.top})`),z=x.nodes.map(t=>({...t})),D=x.links.map(t=>({...t})),G=i().nodeId(t=>t.id).nodeAlign(s).nodeWidth(M).nodePadding(w).iterations(64).extent([[0,0],[g,C]])({nodes:z,links:D}),H=F.append("defs"),L=e().domain(G.nodes.map(t=>t.id)).range(G.nodes.map((t,o)=>c[o%c.length]))
21
- G.links.forEach(t=>{const o=`sankey-link-${u(String(t.source.id))}-${u(String(t.target.id))}`
22
- v(H,o,L(String(t.source.id)),L(String(t.target.id)),t.source.x1??0,t.target.x0??0)})
23
- const N=a(),O=V.append("g").attr("fill","none").selectAll("path").data(G.links).join("path").attr("d",N).attr("fill",t=>`url(#sankey-link-${u(String(t.source.id))}-${u(String(t.target.id))})`).attr("stroke","none")
24
- f(O,{keyFn:t=>`link:${t.source.id}:${t.target.id}`,hintFn:t=>`${t.source.name??t.source.id} → ${t.target.name??t.target.id}: ${t.value.toLocaleString()} CIs`,...h(b),onClick:t=>B.current?.({source:t.source.id,target:t.target.id,value:t.value})})
25
- const R=V.append("g").selectAll("g").data(G.nodes).join("g")
26
- R.each(function(t){r(this).append("rect").datum(t).attr("x",t.x0??0).attr("y",t.y0??0).attr("width",Math.max(1,(t.x1??0)-(t.x0??0))).attr("height",$(t)).attr("fill",L(t.id))})
27
- const q=R.select("rect")
28
- p(q,{keyFn:t=>`node:${t.id}`,hintFn:t=>`${t.name??t.id}: ${(t.value??0).toLocaleString()} CIs`,...h(b),onClick:t=>U.current?.({id:t.id,name:t.name})}),R.append("text").attr("x",t=>0===(t.depth??0)?(t.x1??0)+6:(t.x0??0)-6).attr("y",t=>y(t)).attr("dy","0.35em").attr("text-anchor",t=>0===(t.depth??0)?"start":"end").attr("font-size",S?10:11).attr("fill","rgba(0, 0, 0, 0.78)").attr("pointer-events","none").text(t=>t.name??t.id)
29
- const A=b.registerFocusApply(()=>{d(O,t=>`link:${t.source.id}:${t.target.id}`,b.getFocusOpacity),O.attr("opacity",t=>1===b.getFocusOpacity(`link:${t.source.id}:${t.target.id}`)?1:.22),d(q,t=>`node:${t.id}`,b.getFocusOpacity)})
30
- return()=>{A(),O.on("mouseenter mousemove mouseleave click",null),q.on("mouseenter mousemove mouseleave click",null)}},[S,x,j,b.getFocusOpacity,b.onHover,b.onHoverEnd,b.onPointerMove,b.onToggleSelect,b.registerFocusApply])
31
- const V=S?Math.max(Math.min(j,420),400):Math.max(j,480)
32
- return t(l,{title:C,description:M,height:V,compact:S,...m(b),svgRef:I})}export{x as SankeyChart}
19
+ const o=t.clientWidth||720,e=b?Math.max(Math.min(F,420),400):Math.max(F,480),l=b?104:128,m={left:l,right:l,bottom:16,top:16},{width:$,height:M}=y(o,e,m),C=b?12:14,L=b?14:18,U=r(t)
20
+ U.selectAll("*").remove(),U.attr("viewBox",`0 0 ${o} ${e}`)
21
+ const B=U.append("g").attr("transform",`translate(${m.left},${m.top})`),N=S.nodes.map(t=>({...t})),O=S.links.map(t=>({...t})),R=i().nodeId(t=>t.id).nodeAlign(s).nodeWidth(C).nodePadding(L).iterations(64).extent([[0,0],[$,M]])({nodes:N,links:O}),V=U.append("defs"),q=n().domain(R.nodes.map(t=>t.id)).range(R.nodes.map((t,o)=>c[o%c.length]))
22
+ R.links.forEach(t=>{const o=`sankey-link-${k(String(t.source.id))}-${k(String(t.target.id))}`
23
+ j(V,o,q(String(t.source.id)),q(String(t.target.id)),t.source.x1??0,t.target.x0??0)})
24
+ const A=a(),D=B.append("g").attr("fill","none").selectAll("path").data(R.links).join("path").attr("d",A).attr("fill",t=>`url(#sankey-link-${k(String(t.source.id))}-${k(String(t.target.id))})`).attr("stroke","none")
25
+ f(D,{keyFn:t=>`link:${t.source.id}:${t.target.id}`,hintFn:t=>`${t.source.name??t.source.id} → ${t.target.name??t.target.id}: ${u(t.value,w)}`,...h(I),onClick:t=>z.current?.({source:t.source.id,target:t.target.id,value:t.value})})
26
+ const E=B.append("g").selectAll("g").data(R.nodes).join("g")
27
+ E.each(function(t){r(this).append("rect").datum(t).attr("x",t.x0??0).attr("y",t.y0??0).attr("width",Math.max(1,(t.x1??0)-(t.x0??0))).attr("height",v(t)).attr("fill",q(t.id))})
28
+ const J=E.select("rect")
29
+ p(J,{keyFn:t=>`node:${t.id}`,hintFn:t=>g(t.name??t.id,t.value??0,w),...h(I),onClick:t=>G.current?.({id:t.id,name:t.name})}),E.append("text").attr("x",t=>0===(t.depth??0)?(t.x1??0)+6:(t.x0??0)-6).attr("y",t=>x(t)).attr("dy","0.35em").attr("text-anchor",t=>0===(t.depth??0)?"start":"end").attr("font-size",b?10:11).attr("fill","rgba(0, 0, 0, 0.78)").attr("pointer-events","none").text(t=>t.name??t.id)
30
+ const K=I.registerFocusApply(()=>{d(D,t=>`link:${t.source.id}:${t.target.id}`,I.getFocusOpacity),D.attr("opacity",t=>1===I.getFocusOpacity(`link:${t.source.id}:${t.target.id}`)?1:.22),d(J,t=>`node:${t.id}`,I.getFocusOpacity)})
31
+ return()=>{K(),D.on("mouseenter mousemove mouseleave click",null),J.on("mouseenter mousemove mouseleave click",null)}},[b,S,F,I.getFocusOpacity,I.onHover,I.onHoverEnd,I.onPointerMove,I.onToggleSelect,I.registerFocusApply,L,w])
32
+ const N=b?Math.max(Math.min(F,420),400):Math.max(F,480)
33
+ return t(l,{title:M,description:C,height:N,compact:b,...m(I),svgRef:H})}export{S as SankeyChart}
@@ -17,6 +17,8 @@ export type SankeyChartProps = {
17
17
  description?: string;
18
18
  height?: number;
19
19
  compact?: boolean;
20
+ valueLabel?: string;
21
+ hoverHint?: string;
20
22
  onLinkClick?: (link: SankeyLink) => void;
21
23
  onNodeClick?: (node: SankeyNode) => void;
22
24
  };
@@ -1,2 +1,2 @@
1
1
  import type { ScatterPlotProps } from './types';
2
- export declare function ScatterPlot({ data, title, description, height, compact, xLabel, yLabel, showTrendLine, onPointClick }: Readonly<ScatterPlotProps>): import("react").JSX.Element;
2
+ export declare function ScatterPlot({ data, title, description, height, compact, xLabel, yLabel, showTrendLine, hoverHint, onPointClick }: Readonly<ScatterPlotProps>): import("react").JSX.Element;
@@ -10,30 +10,30 @@ import{ChartFrame as m}from"../shared/ChartFrame/index.js"
10
10
  import{ChartLegend as h}from"../shared/ChartLegend/index.js"
11
11
  import{renderXAxis as p,renderYAxis as f,renderAxisTitles as u}from"../shared/chartAxisLayout/render.js"
12
12
  import{scatterPlotAxisLayout as g}from"../shared/chartAxisLayout/scatterPlot.js"
13
- import{chartFrameInteractionProps as y,chartPointerHandlers as k}from"../shared/chartBinding.js"
14
- import{bindChartPointer as x,applyPointerOpacity as j}from"../shared/chartInteraction.js"
15
- import{CHART_SERIES_CURVE as b,chartLineStrokeWidth as C,seriesMarkerRadius as v,SERIES_MARKER_FILL as $,SERIES_MARKER_STROKE_WIDTH as w}from"../shared/chartVisualStyle.js"
16
- import{useChartInteraction as L}from"../shared/useChartInteraction.js"
13
+ import{chartFrameInteractionProps as y,chartPointerHandlers as x}from"../shared/chartBinding.js"
14
+ import{bindChartPointer as j,applyPointerOpacity as k}from"../shared/chartInteraction.js"
15
+ import{CHART_SERIES_CURVE as b,chartLineStrokeWidth as w,seriesMarkerRadius as L,SERIES_MARKER_FILL as $,SERIES_MARKER_STROKE_WIDTH as v}from"../shared/chartVisualStyle.js"
16
+ import{useChartInteraction as C}from"../shared/useChartInteraction.js"
17
17
  import{useChartLegendToggle as I}from"../shared/useChartLegendToggle.js"
18
18
  import{innerSize as S}from"../shared/chartUtils.js"
19
- import{computeLinearRegression as A}from"./scatterPlotUtils.js"
20
- function T({data:T,title:B="Age vs acquisition cost",description:D="Server CIs plotted by install age (years) and recorded acquisition cost.",height:F=320,compact:P=!1,xLabel:R="Age (years)",yLabel:q="Cost (£)",showTrendLine:M=!0,onPointClick:U}){const G=r(null),H=L("Hover a point for the server CI."),V=r(U)
21
- V.current=U
22
- const z=e(()=>[...new Set(T.map(t=>t.group??"Default"))],[T]),E=e(()=>{const t=[...z]
23
- return M&&t.push("trend"),t},[z,M]),{hiddenIds:J,visibleIds:K,toggle:N}=I(E),O=e(()=>z.filter(t=>K.includes(t)),[z,K]),Q=M&&K.includes("trend"),W=e(()=>T.filter(t=>O.includes(t.group??"Default")),[T,O])
24
- o(()=>{const t=G.current
25
- if(!t||0===W.length&&!Q)return
26
- const r=t.clientWidth||640,e=P?Math.min(F,280):F,o=g(P),m=o.margin,{width:h,height:y}=S(r,e,m),L=n(t)
27
- L.selectAll("*").remove(),L.attr("viewBox",`0 0 ${r} ${e}`)
28
- const I=L.append("g").attr("transform",`translate(${m.left},${m.top})`),B=i().domain(s(W.length>0?W:T,t=>t.x)).nice().range([0,h]),D=i().domain(s(W.length>0?W:T,t=>t.y)).nice().range([y,0]),M=a().domain(z).range(z.map((t,r)=>d[r%d.length]))
29
- if(p(I,B,y,o.x),f(I,D,o.y,t=>t.tickFormat(t=>`£${t/1e3}k`)),o.title&&u(I,h,y,o.title,R,q,o.x.fontSize),Q&&W.length>0){const t=A(W)
30
- if(t){const[r,e]=B.domain(),o=[{x:r,y:t.slope*r+t.intercept},{x:e,y:t.slope*e+t.intercept}],s=c().x(t=>B(t.x)).y(t=>D(t.y)).curve(b)
31
- I.append("path").attr("class","trend-line").datum(o).attr("fill","none").attr("stroke",l.primary).attr("stroke-opacity",.45).attr("stroke-width",C(P)).attr("stroke-linecap","round").attr("stroke-dasharray","8 5").attr("d",s).attr("pointer-events","none")}}const U=I.selectAll("circle").data(W).join("circle").attr("cx",t=>B(t.x)).attr("cy",t=>D(t.y)).attr("r",v(P)).attr("fill",$).attr("stroke",t=>M(t.group??"Default")).attr("stroke-width",w)
32
- x(U,{keyFn:t=>t.id,hintFn:t=>`${t.id}: ${t.x}y · £${t.y.toLocaleString()}`,...k(H),onClick:t=>V.current?.(t)})
33
- const E=H.registerFocusApply(()=>{j(U,t=>t.id,H.getFocusOpacity)
34
- const t=H.getHoverKey(),r=H.getSelectedKey()
35
- U.attr("r",e=>v(P,e.id===t||e.id===r))})
36
- return()=>{E(),U.on("mouseenter mouseleave click",null)}},[P,T,z,F,H.getFocusOpacity,H.getHoverKey,H.getSelectedKey,H.onHover,H.onHoverEnd,H.onPointerMove,H.onToggleSelect,H.registerFocusApply,Q,W,R,q])
37
- const X=e(()=>{const r=z.map((t,r)=>({id:t,label:t,color:d[r%d.length],swatchStyle:{borderRadius:"999px"}}))
38
- return M&&r.push({id:"trend",label:"Trend",color:"transparent",swatchStyle:{backgroundColor:"transparent",borderBottom:`2px dashed ${l.primary}`,borderRadius:0,height:"0.2rem",opacity:.55}}),t(h,{ariaLabel:"Groups",hiddenIds:J,onToggle:N,items:r})},[z,J,M,N])
39
- return t(m,{title:B,description:D,height:P?Math.min(F,280):F,compact:P,...y(H),svgRef:G,legend:X})}export{T as ScatterPlot}
19
+ import{computeLinearRegression as T}from"./scatterPlotUtils.js"
20
+ function B({data:B,title:D="Scatter plot",description:F,height:P=320,compact:R=!1,xLabel:A="X",yLabel:H="Y",showTrendLine:M=!0,hoverHint:U="Hover a point for details.",onPointClick:G}){const V=r(null),X=C(U),Y=r(G)
21
+ Y.current=G
22
+ const q=e(()=>[...new Set(B.map(t=>t.group??"Default"))],[B]),z=e(()=>{const t=[...q]
23
+ return M&&t.push("trend"),t},[q,M]),{hiddenIds:E,visibleIds:J,toggle:K}=I(z),N=e(()=>q.filter(t=>J.includes(t)),[q,J]),O=M&&J.includes("trend"),Q=e(()=>B.filter(t=>N.includes(t.group??"Default")),[B,N])
24
+ o(()=>{const t=V.current
25
+ if(!t||0===Q.length&&!O)return
26
+ const r=t.clientWidth||640,e=R?Math.min(P,280):P,o=g(R),m=o.margin,{width:h,height:y}=S(r,e,m),C=n(t)
27
+ C.selectAll("*").remove(),C.attr("viewBox",`0 0 ${r} ${e}`)
28
+ const I=C.append("g").attr("transform",`translate(${m.left},${m.top})`),D=i().domain(s(Q.length>0?Q:B,t=>t.x)).nice().range([0,h]),F=i().domain(s(Q.length>0?Q:B,t=>t.y)).nice().range([y,0]),M=a().domain(q).range(q.map((t,r)=>d[r%d.length]))
29
+ if(p(I,D,y,o.x),f(I,F,o.y,t=>t.tickFormat(t=>t.toLocaleString())),o.title&&u(I,h,y,o.title,A,H,o.x.fontSize),O&&Q.length>0){const t=T(Q)
30
+ if(t){const[r,e]=D.domain(),o=[{x:r,y:t.slope*r+t.intercept},{x:e,y:t.slope*e+t.intercept}],s=c().x(t=>D(t.x)).y(t=>F(t.y)).curve(b)
31
+ I.append("path").attr("class","trend-line").datum(o).attr("fill","none").attr("stroke",l.primary).attr("stroke-opacity",.45).attr("stroke-width",w(R)).attr("stroke-linecap","round").attr("stroke-dasharray","8 5").attr("d",s).attr("pointer-events","none")}}const U=I.selectAll("circle").data(Q).join("circle").attr("cx",t=>D(t.x)).attr("cy",t=>F(t.y)).attr("r",L(R)).attr("fill",$).attr("stroke",t=>M(t.group??"Default")).attr("stroke-width",v)
32
+ j(U,{keyFn:t=>t.id,hintFn:t=>`${t.id}: ${t.x}y · £${t.y.toLocaleString()}`,...x(X),onClick:t=>Y.current?.(t)})
33
+ const G=X.registerFocusApply(()=>{k(U,t=>t.id,X.getFocusOpacity)
34
+ const t=X.getHoverKey(),r=X.getSelectedKey()
35
+ U.attr("r",e=>L(R,e.id===t||e.id===r))})
36
+ return()=>{G(),U.on("mouseenter mouseleave click",null)}},[R,B,q,P,X.getFocusOpacity,X.getHoverKey,X.getSelectedKey,X.onHover,X.onHoverEnd,X.onPointerMove,X.onToggleSelect,X.registerFocusApply,O,Q,A,H])
37
+ const W=e(()=>{const r=q.map((t,r)=>({id:t,label:t,color:d[r%d.length],swatchStyle:{borderRadius:"999px"}}))
38
+ return M&&r.push({id:"trend",label:"Trend",color:"transparent",swatchStyle:{backgroundColor:"transparent",borderBottom:`2px dashed ${l.primary}`,borderRadius:0,height:"0.2rem",opacity:.55}}),t(h,{ariaLabel:"Groups",hiddenIds:E,onToggle:K,items:r})},[q,E,M,K])
39
+ return t(m,{title:D,description:F,height:R?Math.min(P,280):P,compact:R,...y(X),svgRef:V,legend:W})}export{B as ScatterPlot}
@@ -13,5 +13,6 @@ export type ScatterPlotProps = {
13
13
  xLabel?: string;
14
14
  yLabel?: string;
15
15
  showTrendLine?: boolean;
16
+ hoverHint?: string;
16
17
  onPointClick?: (point: ScatterPoint) => void;
17
18
  };
@@ -2,8 +2,8 @@
2
2
  import{jsxs as e,jsx as i}from"react/jsx-runtime"
3
3
  import*as r from"@stylexjs/stylex"
4
4
  import{chartPalette as l}from"../../tokens.stylex.js"
5
- import{Sparkline as t}from"../shared/Sparkline/index.js"
6
- import{styles as s}from"../shared/index.styles.js"
7
- import{styles as n}from"./index.styles.js"
8
- function d({series:d,title:o="CI activity sparklines",description:h="Compact trend lines for weekly create, update, and retire volumes.",height:a=240,compact:c=!1,showArea:m=!0}){const p=Math.max(a,d.length*(c?44:52)+(c?24:32))
9
- return e("div",{...r.props(s.root),children:[i("div",{...r.props(s.header,c?s.headerCompact:null),children:e("div",{children:[i("h4",{...r.props(s.headerTitle),children:o}),!c&&h?i("p",{...r.props(s.headerDescription),children:h}):null]})}),i("div",{...r.props(s.canvasWrap),style:{height:p},role:"img","aria-label":o,children:i("div",{...r.props(n.canvas,c?n.canvasCompact:null),children:d.map((s,d)=>e("div",{...r.props(n.row,c?n.rowCompact:null),children:[i("span",{...r.props(n.label,c?n.labelCompact:null),children:s.label}),i("div",{...r.props(n.sparklineWrap),children:i(t,{values:s.values,color:s.color??l[d%l.length],height:c?28:32,showArea:m})})]},s.id))})})]})}export{d as SparklineChart}
5
+ import{Sparkline as s}from"../shared/Sparkline/index.js"
6
+ import{styles as n}from"../shared/index.styles.js"
7
+ import{styles as t}from"./index.styles.js"
8
+ function d({series:d,title:o="Sparklines",description:h,height:c=240,compact:a=!1,showArea:m=!0}){const p=Math.max(c,d.length*(a?44:52)+(a?24:32))
9
+ return e("div",{...r.props(n.root),children:[i("div",{...r.props(n.header,a?n.headerCompact:null),children:e("div",{children:[i("h4",{...r.props(n.headerTitle),children:o}),!a&&h?i("p",{...r.props(n.headerDescription),children:h}):null]})}),i("div",{...r.props(n.canvasWrap),style:{height:p},role:"img","aria-label":o,children:i("div",{...r.props(t.canvas,a?t.canvasCompact:null),children:d.map((n,d)=>e("div",{...r.props(t.row,a?t.rowCompact:null),children:[i("span",{...r.props(t.label,a?t.labelCompact:null),children:n.label}),i("div",{...r.props(t.sparklineWrap),children:i(s,{values:n.values,color:n.color??l[d%l.length],height:a?28:32,showArea:m})})]},n.id))})})]})}export{d as SparklineChart}
@@ -1,2 +1,2 @@
1
1
  import type { StackedBarChartProps } from './types';
2
- export declare function StackedBarChart({ data, seriesKeys, title, description, height, compact, onSegmentClick }: Readonly<StackedBarChartProps>): import("react").JSX.Element;
2
+ export declare function StackedBarChart({ data, seriesKeys, title, description, height, compact, valueLabel, hoverHint, onSegmentClick }: Readonly<StackedBarChartProps>): import("react").JSX.Element;