
A lightweight, interactive charting library for SwiftUI with beautiful animations and customizable styling.
- Stacked Bar Charts - Display multiple data categories in a single bar with distinct color segments
- Interactive Selection - Tap bars to reveal detailed breakdowns with smooth spring animations
- Customizable Styling - Configure colors, spacing, fonts, and more via
SVBarChartStyle - Average Line Overlay - Optional dashed line showing the average value across all bars
- Legend Support - Automatic category legend with color indicators
- Pure SwiftUI - No external dependencies, works seamlessly with SwiftUI's animation system
SwiftViz provides an opinionated, interactive charting experience with built-in detail views, making it ideal when you want a polished UX without building custom gesture handling and overlays. Swift Charts does offer more flexibility and chart types, but SwiftViz gives you a specific look and feel out of the box.
Add SwiftViz to your project using Xcode:
Note: Requires iOS 17.0+ / macOS 14.0+ and Swift 6.0+
- Go to File → Add Package Dependencies...
- Enter the repository URL:
https://github.com/omarsinan/SwiftViz.git - Select the version rule (e.g., "Up to Next Major Version")
- Click Add Package
Or add it directly to your Package.swift:
dependencies: [
.package(url: "https://github.com/omarsinan/SwiftViz.git", from: "1.0.0")
]Then add the dependency to your target:
.target(
name: "YourApp",
dependencies: ["SwiftViz"]
)Full DocC documentation is available here.
The quickest way to get started is to just pass values and labels:
import SwiftUI
import SwiftViz
struct ContentView: View {
var body: some View {
SVBarChart(
values: [120, 85, 150, 95, 130, 70, 50],
labels: ["M", "T", "W", "T", "F", "S", "S"],
color: .blue
)
.padding()
}
}For multiple data categories per bar:
import SwiftUI
import SwiftViz
struct ContentView: View {
let categories = [
SVCategory(name: "Food", color: .blue),
SVCategory(name: "Transport", color: .green),
SVCategory(name: "Entertainment", color: .orange)
]
let data: [[Double]] = [
[120, 45, 30],
[85, 60, 25],
[150, 35, 55],
[95, 50, 40],
[130, 40, 60],
[70, 20, 80],
[50, 15, 45]
]
let labels = ["M", "T", "W", "T", "F", "S", "S"]
var body: some View {
SVBarChart(
data: data,
categories: categories,
labels: labels
)
.padding()
}
}The main chart component. Supports both simple single-series charts and stacked multi-category charts.
For basic bar charts with a single data series:
SVBarChart(
values: [Double], // Required: Array of values (one per bar)
labels: [String]?, // Optional: X-axis labels
expandedLabels: [String]?, // Optional: Full labels for selection
color: Color, // Optional: Bar color (default: .blue)
valueFormatter: SVValueFormatter?, // Optional: Custom value formatting
title: String?, // Optional: Chart title
style: SVBarChartStyle // Optional: Styling configuration
)| Parameter | Type | Default | Description |
|---|---|---|---|
values |
[Double] |
An array of values, one per bar. | |
labels |
[String]? |
nil |
Optional labels displayed below each bar on the x-axis. Count must match the number of bars if provided. |
expandedLabels |
[String]? |
nil |
Optional longer labels shown in the detail view when a bar is selected. Falls back to labels if not provided. |
color |
Color |
.blue |
The color for all bars. |
valueFormatter |
SVValueFormatter? |
nil |
Optional closure to format values in the detail view (e.g., for currencies). |
title |
String? |
nil |
Optional title displayed above the chart. |
style |
SVBarChartStyle |
.default |
Configuration object for customizing the chart appearance. Legend is automatically hidden for simple charts. |
For charts with multiple data categories per bar:
SVBarChart(
data: [[Double]], // Required: 2D array of values
categories: [SVCategory], // Required: Category definitions
labels: [String]?, // Optional: X-axis labels
expandedLabels: [String]?, // Optional: Full labels for selection
valueFormatter: SVValueFormatter?, // Optional: Custom value formatting
title: String?, // Optional: Chart title
style: SVBarChartStyle // Optional: Styling configuration
)| Parameter | Type | Default | Description |
|---|---|---|---|
data |
[[Double]] |
A 2D array where each inner array contains values for each category in a single bar. The outer array represents bars (left to right), and inner arrays represent category values (must match categories order). |
|
categories |
[SVCategory] |
Array of categories defining the data series. Each category has a name and color. | |
labels |
[String]? |
nil |
Optional labels displayed below each bar on the x-axis. Count must match the number of bars if provided. |
expandedLabels |
[String]? |
nil |
Optional longer labels shown in the detail view when a bar is selected. Falls back to labels if not provided. |
valueFormatter |
SVValueFormatter? |
nil |
Optional closure to format values in the detail view (e.g., for currencies). |
title |
String? |
nil |
Optional title displayed above the chart. |
style |
SVBarChartStyle |
.default |
Configuration object for customizing the chart appearance. |
Represents a data series with a name and color.
// Using SwiftUI Color
let category = SVCategory(name: "Food", color: .blue)
// Using hex string
let category = SVCategory(name: "Transport", colorHex: "#4CAF50")| Initializer | Description |
|---|---|
init(name: String, color: Color) |
Creates a category with a SwiftUI Color |
init(name: String, colorHex: String) |
Creates a category with a hex color string (supports #RGB, RGB, #RRGGBB, RRGGBB, #AARRGGBB) |
Customize the chart's appearance with SVBarChartStyle.
SVBarChart(
data: data,
categories: categories,
labels: labels,
style: SVBarChartStyle(
chartHeight: 250,
barSpacing: 12,
barCornerRadius: 10,
showAverageLine: false,
showLegend: true
)
)| Property | Type | Default | Description |
|---|---|---|---|
chartHeight |
CGFloat |
200 |
The height of the chart area (excluding labels and legend) |
barSpacing |
CGFloat |
8 |
The spacing between adjacent bars |
barCornerRadius |
CGFloat |
8 |
The corner radius for bar shapes |
backgroundBarColor |
Color |
.gray.opacity(0.2) |
The color of the unfilled bar background |
showAverageLine |
Bool |
true |
Whether to display the average line overlay |
averageLineColor |
Color |
.gray.opacity(0.6) |
The color of the average line |
averageLineDash |
[CGFloat] |
[5, 8] |
The dash pattern for the average line |
showLegend |
Bool |
true |
Whether to display the category legend below the chart |
yAxisFont |
Font |
.body.bold() |
The font used for y-axis labels |
xAxisFont |
Font |
.body.bold() |
The font used for x-axis labels |
legendFont |
Font |
.system(size: 14).bold() |
The font used for legend items |
titleFont |
Font |
.title3.bold() |
The font used for the chart title |
showYAxis |
Bool |
true |
Whether to show the y-axis labels |
showXAxis |
Bool |
true |
Whether to show the x-axis labels |
selectionAnimation |
Animation |
.spring(duration: 0.25) |
The animation used for bar selection transitions |
isInteractive |
Bool |
true |
Whether bars are tappable to show the detail view |
Use valueFormatter to customize how values appear in the detail view. The formatter receives:
value: The numeric valuecategoryIndex: Index of the category (for multi-category charts)barIndex: Index of the bar being displayed
SVBarChart(
data: data,
categories: categories,
labels: labels,
valueFormatter: { value, categoryIndex, barIndex in
String(format: "%.2f QAR", value)
}
)let currencies = ["USD", "EUR", "GBP"]
SVBarChart(
data: data,
categories: categories,
labels: labels,
valueFormatter: { value, categoryIndex, barIndex in
let currency = currencies[categoryIndex]
return String(format: "%.0f %@", value, currency)
}
)SVBarChart(
values: values,
labels: labels,
valueFormatter: { value, _, _ in
String(format: "%.1f%%", value)
}
)SVBarChart(
values: [45, 62, 38, 71, 55, 48],
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
color: .purple
)SVBarChart(
values: [120, 95, 140, 85],
labels: ["Q1", "Q2", "Q3", "Q4"],
expandedLabels: ["Quarter 1", "Quarter 2", "Quarter 3", "Quarter 4"],
color: .teal,
style: SVBarChartStyle(chartHeight: 180)
)let categories = [
SVCategory(name: "Revenue", color: .green),
SVCategory(name: "Expenses", color: .red)
]
let data: [[Double]] = [
[1200, 800],
[1500, 900],
[1100, 750],
[1800, 1200]
]
SVBarChart(
data: data,
categories: categories,
labels: ["Q1", "Q2", "Q3", "Q4"]
)let categories = [
SVCategory(name: "Food", colorHex: "#6B99D6"),
SVCategory(name: "Transport", colorHex: "#4CAF50"),
SVCategory(name: "Shopping", colorHex: "#FF9800")
]
let data: [[Double]] = [
[50, 20, 30],
[45, 25, 15],
[60, 30, 40],
[55, 20, 25],
[70, 35, 50],
[40, 10, 80],
[30, 5, 20]
]
SVBarChart(
data: data,
categories: categories,
labels: ["M", "T", "W", "T", "F", "S", "S"],
expandedLabels: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
style: SVBarChartStyle(
chartHeight: 220,
barSpacing: 10,
showAverageLine: true
)
)SVBarChart(
data: data,
categories: categories,
labels: labels,
style: SVBarChartStyle(
chartHeight: 300,
barSpacing: 16,
barCornerRadius: 12,
backgroundBarColor: .blue.opacity(0.1),
showAverageLine: false,
showLegend: true,
yAxisFont: .caption.bold(),
xAxisFont: .caption2,
legendFont: .footnote,
selectionAnimation: .spring(duration: 0.3, bounce: 0.2)
)
)The data parameter is a 2D array structured as follows:
data[barIndex][categoryIndex] = value
For example, with 3 categories and 4 bars:
let data: [[Double]] = [
[100, 50, 25], // Bar 0: Category 0 = 100, Category 1 = 50, Category 2 = 25
[80, 60, 30], // Bar 1
[120, 40, 35], // Bar 2
[90, 70, 20] // Bar 3
]The order of values in each inner array must match the order of categories.
-
Bar charts - Pie charts
- Donut charts
- Line charts
SwiftViz is available under the MIT license. See the LICENSE file for more info.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request





