Radar Chart
An animated radar (spider) chart with a grow-from-center animation, axis labels, and click-to-reveal tooltips via the legend. Powered by a Power Apps collection. Inspired by the shadcn/ui Radar Chart.
Radar Chart
Showing total visitors for the last 6 months
Trending up by 5.2% this month
January - June 2024
How to Use
- Create the collection: ClearCollect(colRdrData, {label: "January", value: 186}, {label: "February", value: 305}, {label: "March", value: 237}, {label: "April", value: 273}, {label: "May", value: 209}, {label: "June", value: 214})
- On your screen's OnVisible, add: Set(varRdrAnimate, true); Set(varTooltipRdrIdx, 0)
- Paste the copied YAML into Power Apps Studio (Ctrl+V).
- The radar polygon grows from the center outward. Click a legend item to show a tooltip for that data point.
- To replay the animation: Set(varRdrAnimate, false); Set(varRdrAnimate, true)
- Replace colRdrData with any data source that has {label, value} columns. The chart adapts to any number of data points: axes, grid, and polygon all adjust automatically.
Problem Solved
Renders an animated SVG radar chart inside a Power Apps Image control, fully dynamic for any number of data points. Uses Cos()/Sin() with (Value-1)*2*Pi()/N-Pi()/2 for axis angles, replacing the previous hardcoded Switch values. Grid and axis lines are drawn by a safe Concat that runs before the polygon Concat.
Integration
Bind colRdrData to any data source with {label, value} columns. The chart supports any number of rows: the grid, axis lines, and polygon all adapt automatically when the collection changes.
YAML Code
- conRdrChart:
Control: GroupContainer@1.5.0
Variant: ManualLayout
Properties:
BorderColor: =RGBA(226, 232, 240, 1)
BorderStyle: =BorderStyle.Solid
BorderThickness: =1
DropShadow: =DropShadow.Regular
Fill: =RGBA(255, 255, 255, 1)
Height: =452
RadiusBottomLeft: =12
RadiusBottomRight: =12
RadiusTopLeft: =12
RadiusTopRight: =12
Width: =560
Children:
- tmrRdrAnimate:
Control: Timer@2.1.0
Properties:
Duration: =1200
Height: =1
OnTimerEnd: =Set(varRdrAnimate, false)
Repeat: =false
Start: =varRdrAnimate
Visible: =false
Width: =1
X: =0
Y: =0
- lblRdrTitle:
Control: Label@2.5.1
Properties:
Align: =Align.Center
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Bold
Height: =24
Size: =14
Text: ="Radar Chart"
Width: =520
X: =20
Y: =14
- lblRdrDesc:
Control: Label@2.5.1
Properties:
Align: =Align.Center
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =20
Size: =12
Text: ="Showing total visitors for the last 6 months"
Width: =520
X: =20
Y: =44
- rectRdrHeader:
Control: Rectangle@2.3.0
Properties:
Fill: =RGBA(226, 232, 240, 1)
Height: =1
Width: =560
X: =0
Y: =68
- imgRdrChart:
Control: Image@2.2.3
Properties:
Height: =270
Width: =416
X: =72
Y: =72
Image: |-
=With(
{
_n: CountRows(colRdrData),
_max: Max(1, Max(colRdrData, value)),
_p: Min(1, tmrRdrAnimate.Value / 1200),
_col: "#3b82f6"
},
With(
{ _ease: 1 - Power(Max(0, 1 - _p), 3) },
"data:image/svg+xml;utf8, " & EncodeUrl(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 416 270'>" &
Concat(Sequence(CountRows(colRdrData)),
With(
{
_ga1: (Value - 1) * 2 * Pi() / CountRows(colRdrData) - Pi() / 2,
_ga2: Value * 2 * Pi() / CountRows(colRdrData) - Pi() / 2
},
With(
{ _c1: Cos(_ga1), _s1: Sin(_ga1), _c2: Cos(_ga2), _s2: Sin(_ga2) },
"<line x1='208' y1='148' x2='" & Text(Round(208 + 80*_c1, 1)) & "' y2='" & Text(Round(148 + 80*_s1, 1)) & "' stroke='#e2e8f0' stroke-width='1'/>" &
"<line x1='" & Text(Round(208+80*_c1,1)) & "' y1='" & Text(Round(148+80*_s1,1)) & "' x2='" & Text(Round(208+80*_c2,1)) & "' y2='" & Text(Round(148+80*_s2,1)) & "' stroke='#e2e8f0' stroke-width='1'/>" &
"<line x1='" & Text(Round(208+60*_c1,1)) & "' y1='" & Text(Round(148+60*_s1,1)) & "' x2='" & Text(Round(208+60*_c2,1)) & "' y2='" & Text(Round(148+60*_s2,1)) & "' stroke='#e2e8f0' stroke-width='1'/>" &
"<line x1='" & Text(Round(208+40*_c1,1)) & "' y1='" & Text(Round(148+40*_s1,1)) & "' x2='" & Text(Round(208+40*_c2,1)) & "' y2='" & Text(Round(148+40*_s2,1)) & "' stroke='#e2e8f0' stroke-width='1'/>" &
"<line x1='" & Text(Round(208+20*_c1,1)) & "' y1='" & Text(Round(148+20*_s1,1)) & "' x2='" & Text(Round(208+20*_c2,1)) & "' y2='" & Text(Round(148+20*_s2,1)) & "' stroke='#e2e8f0' stroke-width='1'/>" &
"<text x='" & Text(Round(208+100*_c1, 1)) & "' y='" & Text(Round(148+100*_s1, 1)) & "' text-anchor='middle' font-family='Arial,sans-serif' font-size='10' fill='#64748b'>" & Index(colRdrData, Value).label & "</text>"
)
)
) &
If(_ease > 0,
"<polygon points='" &
Concat(Sequence(_n),
With(
{
_v: Index(colRdrData, Value).value,
_a: (Value - 1) * 2 * Pi() / _n - Pi() / 2
},
With(
{ _r: _ease * _v / _max * 80 },
Text(Round(208 + _r * Cos(_a), 1)) & "," &
Text(Round(148 + _r * Sin(_a), 1)) & " "
)
)
) &
"' fill='" & _col & "' fill-opacity='0.6' stroke='" & _col & "' stroke-width='2' stroke-linejoin='round'/>",
""
) &
"</svg>"
)
)
)
- galRdrLegend:
Control: Gallery@2.15.0
Variant: Horizontal
Properties:
Fill: =RGBA(0, 0, 0, 0)
Height: =36
Items: =Sequence(CountRows(colRdrData))
ShowScrollbar: =false
TemplatePadding: =0
TemplateSize: =560 / Max(1, CountRows(colRdrData))
Width: =560
X: =0
Y: =350
Children:
- imgRdrLegDot:
Control: Image@2.2.3
Properties:
Fill: =RGBA(59, 130, 246, 1)
Height: =10
Image: =Blank()
RadiusBottomLeft: =5
RadiusBottomRight: =5
RadiusTopLeft: =5
RadiusTopRight: =5
Width: =10
X: =8
Y: =13
- lblRdrLegLabel:
Control: Label@2.5.1
Properties:
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
Height: =14
Size: =10
Text: =Index(colRdrData, ThisItem.Value).label
Width: =75
X: =22
Y: =11
- btnRdrLegHit:
Control: Button@0.0.45
Properties:
Appearance: ='ButtonCanvas.Appearance'.Transparent
Height: =Parent.TemplateHeight
OnSelect: =If(varTooltipRdrIdx = ThisItem.Value, Set(varTooltipRdrIdx, 0), Set(varTooltipRdrIdx, ThisItem.Value))
Text: =""
Width: =Parent.TemplateWidth
X: =0
Y: =0
- rectRdrFooter:
Control: Rectangle@2.3.0
Properties:
Fill: =RGBA(226, 232, 240, 1)
Height: =1
Width: =560
X: =0
Y: =394
- lblRdrTrend:
Control: Label@2.5.1
Properties:
Align: =Align.Center
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
Height: =20
Size: =12
Text: ="Trending up by 5.2% this month"
Width: =520
X: =20
Y: =404
- lblRdrDateRange:
Control: Label@2.5.1
Properties:
Align: =Align.Center
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =20
Size: =11
Text: ="January - June 2024"
Width: =520
X: =20
Y: =428
- conRdrTooltip:
Control: GroupContainer@1.5.0
Variant: ManualLayout
Properties:
BorderColor: =RGBA(226, 232, 240, 1)
BorderStyle: =BorderStyle.Solid
BorderThickness: =1
DropShadow: =DropShadow.Regular
Fill: =RGBA(255, 255, 255, 1)
Height: =68
RadiusBottomLeft: =8
RadiusBottomRight: =8
RadiusTopLeft: =8
RadiusTopRight: =8
Visible: =varTooltipRdrIdx > 0
Width: =130
X: =Max(10, Min(420, (varTooltipRdrIdx - 1) * (560 / Max(1, CountRows(colRdrData))) + 47 - 65))
Y: =270
Children:
- lblRdrTipLabel:
Control: Label@2.5.1
Properties:
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Semibold
Height: =18
Size: =10
Text: =If(varTooltipRdrIdx > 0, Index(colRdrData, varTooltipRdrIdx).label, "")
Width: =116
X: =8
Y: =6
- imgRdrTipDot:
Control: Image@2.2.3
Properties:
Fill: =RGBA(59, 130, 246, 1)
Height: =8
Image: =Blank()
RadiusBottomLeft: =4
RadiusBottomRight: =4
RadiusTopLeft: =4
RadiusTopRight: =4
Width: =8
X: =8
Y: =30
- lblRdrTipValue:
Control: Label@2.5.1
Properties:
Color: =RGBA(15, 23, 42, 1)
Font: =Font.'Open Sans'
FontWeight: =FontWeight.Bold
Height: =18
Size: =13
Text: =If(varTooltipRdrIdx > 0, Text(Index(colRdrData, varTooltipRdrIdx).value), "")
Width: =100
X: =22
Y: =28
- lblRdrTipSeries:
Control: Label@2.5.1
Properties:
Color: =RGBA(100, 116, 139, 1)
Font: =Font.'Open Sans'
Height: =16
Size: =10
Text: ="Desktop"
Width: =116
X: =8
Y: =48Implementation
Copies the customized component code directly to your clipboard. Use Ctrl+V in Power Apps Studio.
Something not pasting right? Troubleshooting guide