Radar Charts
Relevant source files
The following files were used as context for generating this wiki page:
Radar charts in SwiftDitherKit provide a circular visualization of multivariate data, where each variable is represented by a "spoke" radiating from the center. These charts use ordered dithering textures to render overlapping polygons, each representing a distinct data series.
The system is built using SwiftUI and leverages an asynchronous rendering pipeline to generate dithered rasters off-main-thread. It supports interactive features such as legend-based selection, hover tooltips, and accessibility descriptors for assistive technologies. Sources: README.md, Sources/SwiftDitherKit/DitherPolarCharts.swift:116-121, Sources/SwiftDitherKit/DitherRenderers.swift:101-110
Architecture and Components
The Radar Chart implementation is divided into public-facing SwiftUI views, private plot components, and a specialized rendering engine.
Component Hierarchy
The top-level component is DitherRadarChart, which manages the layout of the plot and the legend. It encapsulates a DitherRadarPlot which handles the actual drawing of the radial frame and the dithered raster.
The hierarchy of views involved in displaying a Radar Chart. Sources: Sources/SwiftDitherKit/DitherPolarCharts.swift:158-185, Sources/SwiftDitherKit/DitherPolarCharts.swift:348-400
Core Data Structures
| Type | Description |
|---|---|
DitherDatum | Represents a single axis/spoke with values mapped to series IDs. |
DitherSeries | Visual configuration (color, variant) for a specific data polygon. |
DitherPolarOptions | Configuration for margins, bloom, animation, and accessibility. |
DitherRadarRenderInput | A stable struct used to trigger re-renders only when relevant data changes. |
Sources: Sources/SwiftDitherKit/DitherTypes.swift:89-106, Sources/SwiftDitherKit/DitherTypes.swift:115-132, Sources/SwiftDitherKit/DitherPolarCharts.swift:4-37, Sources/SwiftDitherKit/DitherRenderers.swift:23-30
Rendering Pipeline
Radar charts are rendered pixel-by-pixel into a DitherRaster. The renderer calculates the geometry of polygons for every series and uses a point-in-polygon test to determine fill status.
Geometry Calculation
For each series, the renderer calculates a RadarPolygon consisting of CGPoint values. The angle for each point is derived by dividing $2\pi$ by the number of axes, starting from the top ($- \pi/2$). The radius is determined by the datum's value relative to the global maximum of all series. Sources: Sources/SwiftDitherKit/DitherRenderers.swift:112-127
The Dithering Process
The renderer iterates through every pixel of the backing size and determines the appropriate color and alpha based on overlapping polygons:
- Edge Detection: If a point is near a polygon edge (distance < 1.4), it is painted with a solid color.
- Dither Application: If deep inside a polygon, the alpha is determined by a combination of the
DitherKernelthreshold and the chosenDitherVariant(e.g.,.dotted,.hatched). - Selection State: Series that are not selected (if a selection exists) are rendered with reduced opacity (0.3).
Pixel-level rendering logic for radar polygons. Sources: Sources/SwiftDitherKit/DitherRenderers.swift:132-166
Interaction and State Management
Radar charts support selection via the legend and hover-based inspection. Unlike Cartesian charts, Radar series are selected through the legend because overlapping dithered polygons make direct tapping ambiguous. Sources: Sources/SwiftDitherKit/SwiftDitherKit.docc/InteractionAndAccessibility.md:20-22
Hover and Tooltips
The DitherRadarInteractionLayer tracks pointer movement. It calculates the normalized angle from the center to determine which axis (spoke) is being hovered. When an axis is active:
- A
DitherTooltipappears at the top of the chart. - The tooltip displays values for all series at that specific axis.
- Visual markers appear on the polygon vertices for that axis.
Sources: Sources/SwiftDitherKit/DitherPolarCharts.swift:391-412, Sources/SwiftDitherKit/DitherRenderers.swift:180-194
Accessibility
SwiftDitherKit uses AXChartDescriptorRepresentable to expose radar data to assistive technologies. The DitherRadarChartDescriptor provides a semantic representation of the radial data.
| Property | Implementation Detail |
|---|---|
| X-Axis | AXCategoricalDataAxisDescriptor using DitherDatum labels. |
| Y-Axis | AXNumericDataAxisDescriptor representing the value range of the data. |
| Direction | Set to .radialClockwise to match radar geometry. |
| Summary | Dynamically includes the count of axes, series, and the currently selected series name. |
Sources: Sources/SwiftDitherKit/DitherRadarAccessibility.swift:5-52, Tests/SwiftDitherKitTests/DitherValueAndAccessibilityTests.swift:94-96
Animation and Motion
Radar charts utilize crossfades and radial reveal styles during transitions.
- Entrance Animation: Triggered on first appearance or via
replayToken. It uses a.radialreveal style. - Data Transitions: When data or selection changes,
DitherAsyncRastermanages a crossfade between the previous and current dithered images. - Reduce Motion: If enabled via system settings, movement-based transitions are replaced by a simple opacity fade.
Sources: Sources/SwiftDitherKit/DitherMotion.swift:7-40, Sources/SwiftDitherKit/DitherPolarCharts.swift:362-371
Conclusion
Radar charts in SwiftDitherKit integrate complex radial geometry with a specialized dithering engine. By separating the visual frame from the dithered raster and providing robust interaction layers, the system achieves a unique aesthetic while maintaining modern standards for accessibility and performance.