Repository: dagronf/DSFSparkline
Branch: master
Commit: 37caab4b80f7
Files: 228
Total size: 1.6 MB
Directory structure:
gitextract_xvgxkjtt/
├── .github/
│ └── workflows/
│ └── swift.yml
├── .gitignore
├── .swiftpm/
│ └── xcode/
│ ├── package.xcworkspace/
│ │ └── contents.xcworkspacedata
│ └── xcshareddata/
│ └── xcschemes/
│ └── DSFSparkline.xcscheme
├── Demos/
│ ├── Documentation Project/
│ │ ├── Documentation Project/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AccentColor.colorset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── Main.storyboard
│ │ │ ├── CenteringClipView.swift
│ │ │ ├── Documentation_Project.entitlements
│ │ │ ├── Info.plist
│ │ │ ├── Utilities.swift
│ │ │ └── ViewController.swift
│ │ └── Documentation Project.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── project.xcworkspace/
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata/
│ │ └── swiftpm/
│ │ └── Package.resolved
│ ├── Playground/
│ │ ├── Sparklines Playground.playground/
│ │ │ ├── Contents.swift
│ │ │ ├── contents.xcplayground
│ │ │ └── playground.xcworkspace/
│ │ │ └── contents.xcworkspacedata
│ │ └── Sparklines Playground.xcworkspace/
│ │ └── contents.xcworkspacedata
│ ├── Samples/
│ │ ├── Demos.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ └── contents.xcworkspacedata
│ │ │ ├── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ ├── All Demos.xcscheme
│ │ │ │ ├── SwiftUI Sparkline Demo (iOS).xcscheme
│ │ │ │ ├── SwiftUI Sparkline Demo (macOS).xcscheme
│ │ │ │ ├── iOS Sparkline Demo.xcscheme
│ │ │ │ ├── macOS Sparkline Demo Objc.xcscheme
│ │ │ │ ├── macOS Sparkline Demo.xcscheme
│ │ │ │ ├── macOS Table Demo.xcscheme
│ │ │ │ └── tvOS Sparkline Demo.xcscheme
│ │ │ └── xcuserdata/
│ │ │ └── dford.xcuserdatad/
│ │ │ └── xcdebugger/
│ │ │ └── Breakpoints_v2.xcbkptlist
│ │ ├── SwiftUI Sparkline Crossplatform/
│ │ │ ├── Shared/
│ │ │ │ ├── ActiveView.swift
│ │ │ │ ├── ActivityGridView.swift
│ │ │ │ ├── Assets.xcassets/
│ │ │ │ │ ├── AccentColor.colorset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── AttributedString.swift
│ │ │ │ ├── BarDemoContentView.swift
│ │ │ │ ├── BitmapGenerationView.swift
│ │ │ │ ├── CircularGaugeView.swift
│ │ │ │ ├── CircularProgress.swift
│ │ │ │ ├── ContentView.swift
│ │ │ │ ├── DataBarGraphContent.swift
│ │ │ │ ├── DotGraphView.swift
│ │ │ │ ├── Extensions.swift
│ │ │ │ ├── LineDemoContentView.swift
│ │ │ │ ├── PercentBarView.swift
│ │ │ │ ├── PieGraphDemoView.swift
│ │ │ │ ├── ReportView.swift
│ │ │ │ ├── StackLineDemoContentView.swift
│ │ │ │ ├── StripesDemoView.swift
│ │ │ │ ├── SwiftUI-Overlays/
│ │ │ │ │ ├── OverlayView.swift
│ │ │ │ │ ├── StripesOverlaidView.swift
│ │ │ │ │ ├── SuperCoolLineSpark.swift
│ │ │ │ │ ├── SwiftUIContentView.swift
│ │ │ │ │ └── SwiftUIView.swift
│ │ │ │ ├── SwiftUI_Sparkline_DemoApp.swift
│ │ │ │ ├── TabletView.swift
│ │ │ │ ├── TestingView.swift
│ │ │ │ ├── WinLossGraphContentView.swift
│ │ │ │ └── WiperGaugeDemoView.swift
│ │ │ ├── iOS/
│ │ │ │ └── Info.plist
│ │ │ └── macOS/
│ │ │ ├── Info.plist
│ │ │ └── macOS.entitlements
│ │ ├── iOS Sparkline Demo/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ ├── SceneDelegate.swift
│ │ │ ├── ViewController.swift
│ │ │ └── iOS Sparkline Demo.entitlements
│ │ ├── macOS Sparkline Demo/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ ├── ViewController.swift
│ │ │ ├── cpuUsage.swift
│ │ │ └── macOS_Sparkline_Demo.entitlements
│ │ ├── macOS Sparkline Demo Objc/
│ │ │ ├── AppDelegate.h
│ │ │ ├── AppDelegate.m
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ ├── ViewController.h
│ │ │ ├── ViewController.m
│ │ │ ├── macOS_Sparkline_Demo_Objc.entitlements
│ │ │ └── main.m
│ │ ├── macOS Table Demo/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── Main.storyboard
│ │ │ ├── GridViewController.swift
│ │ │ ├── Info.plist
│ │ │ ├── ViewController.swift
│ │ │ └── macOS_Table_Demo.entitlements
│ │ └── tvOS Sparkline Demo/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── App Icon & Top Shelf Image.brandassets/
│ │ │ │ ├── App Icon - App Store.imagestack/
│ │ │ │ │ ├── Back.imagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Front.imagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Middle.imagestacklayer/
│ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── App Icon.imagestack/
│ │ │ │ │ ├── Back.imagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── Front.imagestacklayer/
│ │ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ │ └── Contents.json
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Middle.imagestacklayer/
│ │ │ │ │ ├── Content.imageset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Top Shelf Image Wide.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Top Shelf Image.imageset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ └── ViewController.swift
│ └── Simple Wiper Gauge/
│ ├── Simple Wiper Gauge/
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Base.lproj/
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── Simple_Wiper_Gauge.entitlements
│ │ └── ViewController.swift
│ └── Simple Wiper Gauge.xcodeproj/
│ ├── project.pbxproj
│ └── project.xcworkspace/
│ ├── contents.xcworkspacedata
│ └── xcshareddata/
│ └── swiftpm/
│ └── Package.resolved
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│ └── DSFSparkline/
│ ├── DSFSparklines.h
│ ├── Info.plist
│ ├── datasource/
│ │ ├── DSFSparkline+DataSource.swift
│ │ ├── DSFSparkline+StaticDataSource.swift
│ │ └── data-core/
│ │ ├── SparklineData.swift
│ │ └── SparklineWindow.swift
│ ├── overlay/
│ │ ├── renderers/
│ │ │ ├── core/
│ │ │ │ ├── DSFSparklineOverlay+Centerable.swift
│ │ │ │ ├── DSFSparklineOverlay+DataSource.swift
│ │ │ │ ├── DSFSparklineOverlay+StaticDataSource.swift
│ │ │ │ └── DSFSparklineOverlay.swift
│ │ │ ├── overlays-components/
│ │ │ │ ├── DSFSparklineOverlay+GridLines.swift
│ │ │ │ ├── DSFSparklineOverlay+RangeHighlight.swift
│ │ │ │ └── DSFSparklineOverlay+ZeroLine.swift
│ │ │ └── overlays-graph/
│ │ │ ├── DSFSparklineOverlay+ActivityGrid.swift
│ │ │ ├── DSFSparklineOverlay+Bar.swift
│ │ │ ├── DSFSparklineOverlay+CircularGauge.swift
│ │ │ ├── DSFSparklineOverlay+CircularProgress.swift
│ │ │ ├── DSFSparklineOverlay+DataBar.swift
│ │ │ ├── DSFSparklineOverlay+Dot.swift
│ │ │ ├── DSFSparklineOverlay+Line.swift
│ │ │ ├── DSFSparklineOverlay+PercentBar.swift
│ │ │ ├── DSFSparklineOverlay+Pie.swift
│ │ │ ├── DSFSparklineOverlay+Stackline.swift
│ │ │ ├── DSFSparklineOverlay+Stripes.swift
│ │ │ ├── DSFSparklineOverlay+Tablet.swift
│ │ │ ├── DSFSparklineOverlay+WinLossTie.swift
│ │ │ └── DSFSparklineOverlay+WiperGauge.swift
│ │ ├── surfaces/
│ │ │ ├── DSFSparklineSurface+AttributedString.swift
│ │ │ ├── DSFSparklineSurface+Bitmap.swift
│ │ │ ├── DSFSparklineSurface+SwiftUI.swift
│ │ │ ├── DSFSparklineSurface+View.swift
│ │ │ └── DSFSparklineSurface.swift
│ │ └── types/
│ │ ├── DSFSparkline+ActivityGridDefinition.swift
│ │ ├── DSFSparkline+GradientBucket.swift
│ │ ├── DSFSparkline+GridLinesDefinition.swift
│ │ ├── DSFSparkline+HighlightRangeDefinition.swift
│ │ ├── DSFSparkline+Palette.swift
│ │ ├── DSFSparkline+ValueBasedFill.swift
│ │ ├── DSFSparkline+ZeroLineDefinition.swift
│ │ ├── DSFSparkline.swift
│ │ └── fill/
│ │ ├── DSFSparkline+FillColor.swift
│ │ ├── DSFSparkline+FillGradient.swift
│ │ └── DSFSparkline+Fillable.swift
│ ├── prebuilt/
│ │ ├── DSFSparklineActivityGridView+SwiftUI.swift
│ │ ├── DSFSparklineActivityGridView.swift
│ │ ├── DSFSparklineBarGraphView+SwiftUI.swift
│ │ ├── DSFSparklineBarGraphView.swift
│ │ ├── DSFSparklineCircularGaugeView+SwiftUI.swift
│ │ ├── DSFSparklineCircularGaugeView.swift
│ │ ├── DSFSparklineCircularProgressView+SwiftUI.swift
│ │ ├── DSFSparklineCircularProgressView.swift
│ │ ├── DSFSparklineDataBarGraphView+SwiftUI.swift
│ │ ├── DSFSparklineDataBarGraphView.swift
│ │ ├── DSFSparklineDotGraphView+SwiftUI.swift
│ │ ├── DSFSparklineDotGraphView.swift
│ │ ├── DSFSparklineLineGraphView+SwiftUI.swift
│ │ ├── DSFSparklineLineGraphView.swift
│ │ ├── DSFSparklinePercentBarGraphView+SwiftUI.swift
│ │ ├── DSFSparklinePercentBarGraphView.swift
│ │ ├── DSFSparklinePieGraphView+SwiftUI.swift
│ │ ├── DSFSparklinePieGraphView.swift
│ │ ├── DSFSparklineStackLineGraphView+SwiftUI.swift
│ │ ├── DSFSparklineStackLineGraphView.swift
│ │ ├── DSFSparklineStripesGraphView+SwiftUI.swift
│ │ ├── DSFSparklineStripesGraphView.swift
│ │ ├── DSFSparklineTabletGraphView+SwiftUI.swift
│ │ ├── DSFSparklineTabletGraphView.swift
│ │ ├── DSFSparklineWinLossGraphView+SwiftUI.swift
│ │ ├── DSFSparklineWinLossGraphView.swift
│ │ ├── DSFSparklineWiperGaugeGraphView+SwiftUI.swift
│ │ ├── DSFSparklineWiperGaugeGraphView.swift
│ │ └── ui/
│ │ ├── DSFSparklineDataSourceView.swift
│ │ └── DSFSparklineZeroLinedGraphView.swift
│ └── util/
│ ├── Angle.swift
│ ├── ArbitraryAnimator.swift
│ ├── CGColor+BackwardsCompatibility.swift
│ ├── CGContext+extensions.swift
│ ├── CGPath+Hermite.swift
│ ├── CGPath+innerShadow.swift
│ ├── DSFSparkline+Shadow.swift
│ ├── LayerInvalidating.swift
│ ├── NSShadow+extensions.swift
│ └── Utilities.swift
├── Tests/
│ └── DSFSparklineTests/
│ ├── CircularGaugeTests.swift
│ ├── CircularProgressTests.swift
│ ├── DSFSparklineTests.swift
│ ├── LineGraphTests.swift
│ ├── PresentationTests.swift
│ └── utils/
│ └── TestFilesContainer.swift
└── Tools/
└── sanity-check.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/swift.yml
================================================
# This workflow will build a Swift project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift
name: Swift
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
macos_build_test:
name: Build/test on macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: swift build -v
- name: Run tests
run: swift test -v
ios_tests:
name: Build/test on iOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Run tests on iOS
run: xcodebuild -scheme DSFSparkline test -destination "platform=iOS Simulator,name=iPhone SE (3rd generation),OS=latest"
================================================
FILE: .gitignore
================================================
Demos/Demos.xcodeproj/project.xcworkspace/xcshareddata
Demos/Demos.xcodeproj/project.xcworkspace/xcuserdata
Demos/Demos.xcworkspace/xcshareddata
Podfile.lock
Demos/Demos.xcodeproj/xcshareddata
Demos/Pods
xcschememanagement.plist
Demos/Demos.xcworkspace/xcuserdata
.swiftpm/xcode/package.xcworkspace/xcuserdata
.swiftpm/xcode/xcuserdata
Demos/Demos.xcodeproj/xcuserdata
Demos/Documentation Project/Documentation Project.xcodeproj/xcuserdata
Demos/Documentation Project/Documentation Project.xcodeproj/project.xcworkspace/xcuserdata
Demos/Samples/Demos.xcworkspace/xcuserdata
Demos/Sparkline Demos/Sparkline Demos.xcodeproj/project.xcworkspace/xcuserdata/dford.xcuserdatad/UserInterfaceState.xcuserstate
Demos/Sparkline Demos/Sparkline Demos.xcodeproj/xcuserdata/dford.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
Demos/Sparkline Demos/Sparkline Demos.xcodeproj/xcuserdata
Demos/Sparkline Demos/Sparkline Demos.xcodeproj/project.xcworkspace/xcuserdata
Demos/Playground/Sparklines Playground.xcworkspace/xcuserdata
Demos/Playground/Sparklines Playground.playground/playground.xcworkspace/xcuserdata
Demos/Playground/Sparklines Playground.xcworkspace/xcshareddata
.build
Demos/Samples/Demos.xcodeproj/project.xcworkspace/xcuserdata
Demos/Simple Wiper Gauge/Simple Wiper Gauge.xcodeproj/xcuserdata
IDEWorkspaceChecks.plist
Demos/Simple Wiper Gauge/Simple Wiper Gauge.xcodeproj/project.xcworkspace/xcuserdata
================================================
FILE: .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: .swiftpm/xcode/xcshareddata/xcschemes/DSFSparkline.xcscheme
================================================
================================================
FILE: Demos/Documentation Project/Documentation Project/AppDelegate.swift
================================================
//
// AppDelegate.swift
// Documentation project
//
// Created by Darren Ford on 14/2/21.
//
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
================================================
FILE: Demos/Documentation Project/Documentation Project/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Documentation Project/Documentation Project/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Documentation Project/Documentation Project/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Documentation Project/Documentation Project/Base.lproj/Main.storyboard
================================================
Default
Left to Right
Right to Left
Default
Left to Right
Right to Left
================================================
FILE: Demos/Documentation Project/Documentation Project/CenteringClipView.swift
================================================
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
Contains the definition for `CenteringClipView` which is a specialized clip view subclass to center its document.
*/
import Cocoa
/**
`CenteringClipView` is a clip view subclass that centers smaller documents views
within its inset clip bounds (as described by the set `contentInsets`).
*/
class CenteringClipView: NSClipView {
override func constrainBoundsRect(_ proposedBounds: NSRect) -> NSRect {
guard let documentView = documentView else { return super.constrainBoundsRect(proposedBounds) }
var newClipBoundsRect = super.constrainBoundsRect(proposedBounds)
// Get the `contentInsets` scaled to the future bounds size.
let insets = convertedContentInsetsToProposedBoundsSize(newClipBoundsRect.size)
// Get the insets in terms of the view geometry edges, accounting for flippedness.
let minYInset = isFlipped ? insets.top : insets.bottom
let maxYInset = isFlipped ? insets.bottom : insets.top
let minXInset = insets.left
let maxXInset = insets.right
/*
Get and outset the `documentView`'s frame by the scaled contentInsets.
The outset frame is used to align and constrain the `newClipBoundsRect`.
*/
let documentFrame = documentView.frame
let outsetDocumentFrame = NSRect(x: documentFrame.minX - minXInset,
y: documentFrame.minY - minYInset,
width: (documentFrame.width + (minXInset + maxXInset)),
height: documentFrame.height + (minYInset + maxYInset))
if newClipBoundsRect.width > outsetDocumentFrame.width {
/*
If the clip bounds width is larger than the document, center the
bounds around the document.
*/
newClipBoundsRect.origin.x = outsetDocumentFrame.minX - (newClipBoundsRect.width - outsetDocumentFrame.width) / 2.0
}
else if newClipBoundsRect.width < outsetDocumentFrame.width {
/*
Otherwise, the document is wider than the clip rect. Make sure that
the clip rect stays within the document frame.
*/
if newClipBoundsRect.maxX > outsetDocumentFrame.maxX {
// The clip rect is outside the maxX edge of the document, bring it in.
newClipBoundsRect.origin.x = outsetDocumentFrame.maxX - newClipBoundsRect.width
}
else if newClipBoundsRect.minX < outsetDocumentFrame.minX {
// The clip rect is outside the minX edge of the document, bring it in.
newClipBoundsRect.origin.x = outsetDocumentFrame.minX
}
}
if newClipBoundsRect.height > outsetDocumentFrame.height {
/*
If the clip bounds height is larger than the document, center the
bounds around the document.
*/
newClipBoundsRect.origin.y = outsetDocumentFrame.minY - (newClipBoundsRect.height - outsetDocumentFrame.height) / 2.0
}
else if newClipBoundsRect.height < outsetDocumentFrame.height {
/*
Otherwise, the document is taller than the clip rect. Make sure
that the clip rect stays within the document frame.
*/
if newClipBoundsRect.maxY > outsetDocumentFrame.maxY {
// The clip rect is outside the maxY edge of the document, bring it in.
newClipBoundsRect.origin.y = outsetDocumentFrame.maxY - newClipBoundsRect.height
}
else if newClipBoundsRect.minY < outsetDocumentFrame.minY {
// The clip rect is outside the minY edge of the document, bring it in.
newClipBoundsRect.origin.y = outsetDocumentFrame.minY
}
}
return backingAlignedRect(newClipBoundsRect, options: .alignAllEdgesNearest)
}
/**
The `contentInsets` scaled to the scale factor of a new potential bounds
rect. Used by `constrainBoundsRect(NSRect)`.
*/
fileprivate func convertedContentInsetsToProposedBoundsSize(_ proposedBoundsSize: NSSize) -> NSEdgeInsets {
// Base the scale factor on the width scale factor to the new proposedBounds.
let fromBoundsToProposedBoundsFactor = bounds.width > 0 ? (proposedBoundsSize.width / bounds.width) : 1.0
// Scale the set `contentInsets` by the width scale factor.
var newContentInsets = contentInsets
newContentInsets.top *= fromBoundsToProposedBoundsFactor
newContentInsets.left *= fromBoundsToProposedBoundsFactor
newContentInsets.bottom *= fromBoundsToProposedBoundsFactor
newContentInsets.right *= fromBoundsToProposedBoundsFactor
return newContentInsets
}
}
/*
Sample code project: Exhibition: An Adaptive OS X App
Version: 1.2
IMPORTANT: This Apple software is supplied to you by Apple
Inc. ("Apple") in consideration of your agreement to the following
terms, and your use, installation, modification or redistribution of
this Apple software constitutes acceptance of these terms. If you do
not agree with these terms, please do not use, install, modify or
redistribute this Apple software.
In consideration of your agreement to abide by the following terms, and
subject to these terms, Apple grants you a personal, non-exclusive
license, under Apple's copyrights in this original Apple software (the
"Apple Software"), to use, reproduce, modify and redistribute the Apple
Software, with or without modifications, in source and/or binary forms;
provided that if you redistribute the Apple Software in its entirety and
without modifications, you must retain this notice and the following
text and disclaimers in all such redistributions of the Apple Software.
Neither the name, trademarks, service marks or logos of Apple Inc. may
be used to endorse or promote products derived from the Apple Software
without specific prior written permission from Apple. Except as
expressly stated in this notice, no other rights or licenses, express or
implied, are granted by Apple herein, including but not limited to any
patent rights that may be infringed by your derivative works or by other
works in which the Apple Software may be incorporated.
The Apple Software is provided by Apple on an "AS IS" basis. APPLE
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Copyright (C) 2016 Apple Inc. All Rights Reserved.
*/
================================================
FILE: Demos/Documentation Project/Documentation Project/Documentation_Project.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-write
================================================
FILE: Demos/Documentation Project/Documentation Project/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
Main
NSPrincipalClass
NSApplication
================================================
FILE: Demos/Documentation Project/Documentation Project/Utilities.swift
================================================
//
// Utilities.swift
// Documentation Project
//
// Created by Darren Ford on 14/4/21.
//
import Foundation
import CoreGraphics
/// A CGPath creation function using a block
/// - Parameter block: The block used to add to a mutablepath object
/// - Returns: The created path object
///
/// Example :-
/// ```
/// let path = CGPath.Create { path in
/// path.move(to: CGPoint(x: 0, y: 5))
/// path.addLine(to: CGPoint(x: 10, y: 5))
/// path.move(to: CGPoint(x: 5, y: 0))
/// path.addLine(to: CGPoint(x: 10, y: 5))
/// }
/// ```
@inlinable func CGPath(_ block: (CGMutablePath) throws -> Void) rethrows -> CGPath {
let pth = CGMutablePath()
try block(pth)
return pth.copy()!
}
================================================
FILE: Demos/Documentation Project/Documentation Project/ViewController.swift
================================================
//
// ViewController.swift
// Documentation project
//
// Created by Darren Ford on 14/2/21.
//
import Cocoa
import DSFSparkline
import SwiftImageReadWrite
class FlippedClipView: NSClipView {
override var isFlipped: Bool {
return true
}
}
public extension NSView {
func snapshot() -> NSImage? {
guard let bitmapRep = self.bitmapImageRepForCachingDisplay(in: self.bounds) else { return nil }
self.cacheDisplay(in: self.bounds, to: bitmapRep)
let image = NSImage()
image.addRepresentation(bitmapRep)
return image
}
}
// Temperture anomolies
// https://www.ncdc.noaa.gov/cag/global/time-series/globe/land_ocean/ytd/12/1880-2019
let land_ocean_temp_anomolies: [CGFloat] = [
-0.12, -0.09, -0.10, -0.19, -0.27, -0.26, -0.25, -0.29, -0.14, -0.10, -0.36,
-0.27, -0.32, -0.34, -0.32, -0.25, -0.10, -0.11, -0.28, -0.16, -0.08, -0.16,
-0.26, -0.37, -0.46, -0.28, -0.21, -0.39, -0.43, -0.44, -0.40, -0.44, -0.34,
-0.32, -0.14, -0.09, -0.32, -0.39, -0.30, -0.24, -0.23, -0.16, -0.24, -0.25,
-0.24, -0.18, -0.07, -0.17, -0.18, -0.33, -0.11, -0.06, -0.13, -0.26, -0.11,
-0.16, -0.12, -0.01, -0.02, 0.01, 0.16, 0.27, 0.11, 0.11, 0.28, 0.18, -0.01,
-0.04, -0.05, -0.08, -0.15, 0.00, 0.04, 0.13, -0.10, -0.13, -0.18,
0.07, 0.13, 0.08, 0.05, 0.09, 0.11, 0.12, -0.14, -0.07, -0.01,
0.00, -0.03, 0.11, 0.06, -0.07, 0.04, 0.19, -0.06, 0.01, -0.07,
0.21, 0.12, 0.23, 0.28, 0.32, 0.19, 0.36, 0.17, 0.16, 0.24,
0.38, 0.39, 0.29, 0.45, 0.39, 0.24, 0.28, 0.34, 0.47, 0.32,
0.51, 0.65, 0.44, 0.42, 0.57, 0.62, 0.64, 0.58, 0.67, 0.64,
0.62, 0.54, 0.64, 0.72, 0.57, 0.64, 0.68, 0.74, 0.93, 1.00,
0.91, 0.83, 0.95
]
var SimpleDataSet: DSFSparkline.DataSource = {
let s = DSFSparkline.DataSource(values: [1, 5, 2, 3, 6, 1, 4])
s.range = 0 ... 7
return s
}()
var SimpleWinLossDataSet: DSFSparkline.DataSource = {
let s = DSFSparkline.DataSource(values: [1, -5, -2, 3, 6, -1, 4])
return s
}()
class ViewController: NSViewController {
@IBOutlet weak var primaryScrollView: NSScrollView!
@IBOutlet weak var lineStandardView: DSFSparklineLineGraphView!
@IBOutlet weak var lineCenteredView: DSFSparklineLineGraphView!
@IBOutlet weak var lineInterpolatedStandardView: DSFSparklineLineGraphView!
@IBOutlet weak var lineInterpolatedCenteredView: DSFSparklineLineGraphView!
@IBOutlet weak var lineMarkersStandardView: DSFSparklineLineGraphView!
@IBOutlet weak var lineMarkersCenteredView: DSFSparklineLineGraphView!
@IBOutlet weak var lineMarkersCustomMarkersView1: DSFSparklineLineGraphView!
@IBOutlet weak var lineMarkersCustomMarkersView2: DSFSparklineLineGraphView!
@IBOutlet weak var barStandardView: DSFSparklineBarGraphView!
@IBOutlet weak var barCenteredView: DSFSparklineBarGraphView!
@IBOutlet weak var stackStandardView: DSFSparklineStackLineGraphView!
@IBOutlet weak var stackCenteredView: DSFSparklineStackLineGraphView!
@IBOutlet weak var dotStandardView: DSFSparklineDotGraphView!
@IBOutlet weak var dotSecondView: DSFSparklineDotGraphView!
@IBOutlet weak var winLoss: DSFSparklineWinLossGraphView!
@IBOutlet weak var winLossTie: DSFSparklineWinLossGraphView!
@IBOutlet weak var tablet1: DSFSparklineTabletGraphView!
@IBOutlet weak var stripes1: DSFSparklineStripesGraphView!
@IBOutlet weak var stripes2: DSFSparklineStripesGraphView!
@IBOutlet weak var pie1: DSFSparklinePieGraphView!
@IBOutlet weak var pie2: DSFSparklinePieGraphView!
@IBOutlet weak var pie3: DSFSparklinePieGraphView!
@IBOutlet weak var pie4: DSFSparklinePieGraphView!
@IBOutlet weak var pie5: DSFSparklinePieGraphView!
@IBOutlet weak var pie6: DSFSparklinePieGraphView!
@IBOutlet weak var pie7: DSFSparklinePieGraphView!
@IBOutlet weak var pie8: DSFSparklinePieGraphView!
@IBOutlet weak var pieContainerView: NSView!
@IBOutlet weak var databarPercent1: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarPercent2: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarPercent3: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarPercent4: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarPercent5: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarPercent6: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarContainerView: NSView!
@IBOutlet weak var databarTotal1: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotal2: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotal3: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotal4: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotal5: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotal6: DSFSparklineDataBarGraphView!
@IBOutlet weak var databarTotalContainerView: NSView!
@IBOutlet weak var percentBarTotalContainerView: NSView!
@IBOutlet weak var percentBarTotalContainerView2: NSView!
@IBOutlet weak var wiperGauge: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var percentBarGraph1: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBarGraph2: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBarGraph3: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBarGraph11: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBarGraph12: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBarGraph13: DSFSparklinePercentBarGraphView!
@IBOutlet weak var wiperContainer: NSView!
@IBOutlet weak var wiperGauge1: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge2: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge3: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge4: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge5: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge6: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var wiperGauge7: DSFSparklineWiperGaugeGraphView!
@IBOutlet weak var activityGrid1: DSFSparklineActivityGridView!
@IBOutlet weak var activityGrid2: DSFSparklineActivityGridView!
var bitmapMap: [String: Data] = [:]
var nameMap: [String: NSView] = [:]
func buildNameMap() {
nameMap["line-standard"] = lineStandardView
nameMap["line-centered"] = lineCenteredView
nameMap["line-interpolated"] = lineInterpolatedStandardView
nameMap["line-interpolated-centered"] = lineInterpolatedCenteredView
nameMap["line-markers"] = lineMarkersStandardView
nameMap["line-markers-centered"] = lineMarkersCenteredView
nameMap["line-custom-marker-1"] = self.lineMarkersCustomMarkersView1
nameMap["line-custom-marker-2"] = self.lineMarkersCustomMarkersView2
nameMap["bar-standard"] = barStandardView
nameMap["bar-centered"] = barCenteredView
nameMap["stackline-standard"] = stackStandardView
nameMap["stackline-centered"] = stackCenteredView
nameMap["dot-standard"] = dotStandardView
nameMap["dot-inverted"] = dotSecondView
nameMap["win-loss"] = self.winLoss
nameMap["win-loss-tie"] = self.winLossTie
nameMap["tablet"] = self.tablet1
nameMap["pie"] = self.pieContainerView
nameMap["databar"] = self.databarContainerView
nameMap["databar-max"] = self.databarTotalContainerView
nameMap["stripes-standard"] = self.stripes1
nameMap["stripes-integral"] = self.stripes2
nameMap["percent-bar"] = self.percentBarTotalContainerView
nameMap["percent-bar-2"] = self.percentBarTotalContainerView2
nameMap["wiper-gauge"] = self.wiperContainer
nameMap["activity-grid-1"] = self.activityGrid1
nameMap["activity-grid-2"] = self.activityGrid2
}
fileprivate var lineSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.3)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.87, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
fileprivate var dotSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 70, range: 0 ... 1)
d.push(values: [
0.73, 0.86, 0.72, 0.35, 0.36, 0.09, 0.06, 0.89, 0.94, 0.22,
0.52, 0.74, 0.53, 0.89, 0.34, 0.67, 0.88, 0.00, 0.78, 0.99,
0.84, 0.55, 0.02, 0.96, 0.66, 0.94, 0.70, 0.27, 0.18, 0.02,
0.18, 0.03, 0.00, 0.23, 0.93, 0.17, 0.48, 0.34, 0.89, 0.56,
0.70, 0.59, 0.12, 0.77, 0.98, 0.31, 0.10, 0.47, 0.42, 0.06,
1.00, 0.12, 0.50, 0.18, 0.02, 0.90, 0.33, 0.05, 0.60, 0.17,
0.53, 0.84, 0.72, 0.39, 0.56, 0.57, 0.61, 0.23, 0.96, 0.85
])
return d
}()
fileprivate var winLossDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: -1.0 ... 1)
d.push(values: [1, -1, 0, 1, -1, -1, 1, -1, 0, 1])
return d
}()
fileprivate var landOceanTempAnomolies: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: UInt(land_ocean_temp_anomolies.count))
e.set(values: land_ocean_temp_anomolies)
return e
}()
fileprivate var twiggle: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: 50)
var v: Int = 0
let vv: [CGFloat] = (0 ..< 24).map { value in
let d = v
v = (v + 1) % 3
if d == 0 { return -1 }
if d == 1 { return 0 }
return 1
}
e.set(values: vv)
e.windowSize = 50
return e
}()
fileprivate var world: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: UInt(rawData.count))
e.set(values: rawData)
return e
}()
fileprivate var australianAnomaly: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: UInt(temperature.count))
e.set(values: temperature)
return e
}()
override func viewDidLoad() {
super.viewDidLoad()
do {
let winloss = DSFSparkline.DataSource(values: [1, 1, 0, -1, 1, 1, 1, 0, 1, -1])
let bitmap = DSFSparklineSurface.Bitmap()
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = winloss
// zeroLine.strokeWidth = 1.0
// zeroLine.strokeColor = NSColor.textColor.cgColor
bitmap.addOverlay(zeroLine)
let graph = DSFSparklineOverlay.WinLossTie()
graph.dataSource = winloss
bitmap.addOverlay(graph)
// Generate an image with retina scale
let image = bitmap.image(width: 75, height: 16, scale: 2)
assert(image != nil)
}
self.buildNameMap()
self.percentBarGraph1.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
self.percentBarGraph2.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
self.percentBarGraph3.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
self.percentBarGraph11.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
self.percentBarGraph12.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
self.percentBarGraph13.displayStyle.barEdgeInsets = NSEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
do {
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
let attr = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)!
let message = NSMutableAttributedString(string: "Inlined ")
message.append(attr)
message.append(NSAttributedString(string: " line graph"))
Swift.print(message)
}
// do {
// guard let scroll = self.primaryScrollView,
// let documentView = scroll.documentView else {
// return
// }
//
// scroll.translatesAutoresizingMaskIntoConstraints = false
//
// let clip = scroll.contentView
// clip.translatesAutoresizingMaskIntoConstraints = false
//
// scroll.addConstraint(NSLayoutConstraint(item: clip, attribute: .left, relatedBy: .equal, toItem: scroll, attribute: .left, multiplier: 1, constant: 0))
// scroll.addConstraint(NSLayoutConstraint(item: clip, attribute: .top, relatedBy: .equal, toItem: scroll, attribute: .top, multiplier: 1, constant: 0))
// scroll.addConstraint(NSLayoutConstraint(item: clip, attribute: .right, relatedBy: .equal, toItem: scroll, attribute: .right, multiplier: 1, constant: 0))
// scroll.addConstraint(NSLayoutConstraint(item: clip, attribute: .bottom, relatedBy: .equal, toItem: scroll, attribute: .bottom, multiplier: 1, constant: 0))
//
// documentView.translatesAutoresizingMaskIntoConstraints = false
//
// clip.addConstraint(NSLayoutConstraint(item: clip, attribute: .left, relatedBy: .equal, toItem: documentView, attribute: .left, multiplier: 1, constant: 0))
// clip.addConstraint(NSLayoutConstraint(item: clip, attribute: .top, relatedBy: .equal, toItem: documentView, attribute: .top, multiplier: 1, constant: 0))
// //clip.addConstraint(NSLayoutConstraint(item: clip, attribute: .right, relatedBy: .equal, toItem: documentView, attribute: .right, multiplier: 1, constant: 0))
// }
self.lineStandardView.dataSource = lineSource
self.lineCenteredView.dataSource = lineSource
self.lineInterpolatedStandardView.dataSource = lineSource
self.lineInterpolatedCenteredView.dataSource = lineSource
self.lineMarkersStandardView.dataSource = lineSource
self.lineMarkersCenteredView.dataSource = lineSource
self.lineMarkersCustomMarkersView1.dataSource = lineSource
self.lineMarkersCustomMarkersView1.markerDrawingBlock = { context, markerFrames in
let maxV = markerFrames.min { (a, b) -> Bool in a.value > b.value }!
let minV = markerFrames.min { (a, b) -> Bool in a.value < b.value }!
// Min
context.setFillColor(DSFColor.systemRed.cgColor)
context.fill(minV.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(minV.rect)
// Max
context.setFillColor(DSFColor.systemGreen.cgColor)
context.fill(maxV.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(maxV.rect)
}
self.lineMarkersCustomMarkersView2.dataSource = lineSource
self.lineMarkersCustomMarkersView2.markerDrawingBlock = { context, markerFrames in
let lastFrames = markerFrames.suffix(5)
lastFrames.forEach { marker in
let path = CGPath { p in
p.move(to: CGPoint(x: marker.rect.minX, y: marker.rect.midY))
p.addLine(to: CGPoint(x: marker.rect.maxX, y: marker.rect.midY))
p.move(to: CGPoint(x: marker.rect.midX, y: marker.rect.minY))
p.addLine(to: CGPoint(x: marker.rect.midX, y: marker.rect.maxY))
}
context.addPath(path)
context.setStrokeColor(DSFColor.systemBlue.cgColor)
context.setLineWidth(2)
context.strokePath()
}
}
self.barStandardView.dataSource = lineSource
self.barCenteredView.dataSource = lineSource
self.stackStandardView.dataSource = lineSource
self.stackCenteredView.dataSource = lineSource
self.dotStandardView.dataSource = dotSource
self.dotSecondView.dataSource = dotSource
self.winLoss.dataSource = winLossDataSource1
self.winLossTie.dataSource = winLossDataSource1
self.tablet1.dataSource = winLossDataSource1
self.pie1.dataSource = DSFSparkline.StaticDataSource([10, 30, 20])
self.pie2.dataSource = DSFSparkline.StaticDataSource([33, 33, 33])
self.pie3.dataSource = DSFSparkline.StaticDataSource([40, 5, 80])
self.pie4.dataSource = DSFSparkline.StaticDataSource([1, 2, 3])
self.pie5.dataSource = DSFSparkline.StaticDataSource([66.7, 66, 66.9])
self.pie6.dataSource = DSFSparkline.StaticDataSource([9, 9, 4])
self.pie7.dataSource = DSFSparkline.StaticDataSource([0.5, 0.1, 0.1])
self.pie8.dataSource = DSFSparkline.StaticDataSource([1000, 2000, 300])
let palette = DSFSparkline.Palette([
DSFColor(deviceRed: 0, green: 0, blue: 1, alpha: 1),
DSFColor(deviceRed: 0.33, green: 0.33, blue: 1, alpha: 1),
DSFColor(deviceRed: 0.66, green: 0.66, blue: 1, alpha: 1)
])
self.pie2.palette = DSFSparkline.Palette.sharedGrays
self.pie4.palette = palette
self.pie6.palette = palette
self.pie6.palette = DSFSparkline.Palette.sharedGrays
self.pie8.palette = palette
///
self.databarPercent1.dataSource = DSFSparkline.StaticDataSource([10, 30, 20])
self.databarPercent2.dataSource = DSFSparkline.StaticDataSource([33, 33, 33])
self.databarPercent3.dataSource = DSFSparkline.StaticDataSource([40, 5, 80])
self.databarPercent4.dataSource = DSFSparkline.StaticDataSource([1, 2, 3])
self.databarPercent5.dataSource = DSFSparkline.StaticDataSource([66.7, 66, 66.9])
self.databarPercent6.dataSource = DSFSparkline.StaticDataSource([9, 9, 4])
self.databarTotal1.dataSource = DSFSparkline.StaticDataSource([8, 19, 20])
self.databarTotal1.maximumTotalValue = 60
self.databarTotal2.dataSource = DSFSparkline.StaticDataSource([20, 20, 20])
self.databarTotal2.maximumTotalValue = 60
self.databarTotal3.dataSource = DSFSparkline.StaticDataSource([30, 5, 12])
self.databarTotal3.maximumTotalValue = 60
self.databarTotal4.dataSource = DSFSparkline.StaticDataSource([10, 15, 20])
self.databarTotal4.maximumTotalValue = 60
self.databarTotal5.dataSource = DSFSparkline.StaticDataSource([25, 10, 12])
self.databarTotal5.maximumTotalValue = 60
self.databarTotal6.dataSource = DSFSparkline.StaticDataSource([9, 9, 4])
self.databarTotal6.maximumTotalValue = 60
let gradient1 = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(r: 0, g: 0, b: 1, location: 0.0),
DSFSparkline.GradientBucket.Post(r: 1, g: 1, b: 1, location: 0.5),
DSFSparkline.GradientBucket.Post(r: 1, g: 0, b: 0, location: 1.0),
])
let gradient2 = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: NSColor.systemYellow.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(r: 0.3, g: 0, b: 0.3, location: 1.0)
])
self.stripes1.gradient = gradient1
self.stripes1.integral = false
self.stripes1.dataSource = world
//self.stripes1.dataSource = landOceanTempAnomolies
//self.stripes1.dataSource = twiggle
///
self.stripes2.dataSource = australianAnomaly
australianAnomaly.range = -1.5 ... 1.5
self.stripes2.gradient = gradient2
self.stripes2.barSpacing = 1
self.stripes2.integral = true
do {
activityGrid1.cellTooltipString = { index in "\(index)" }
activityGrid1.cellFillScheme = DSFSparkline.ActivityGrid.CellStyle.DefaultLight
activityGrid1.setValues((0 ..< 1000).map { _ in CGFloat.random(in: 0 ... 1)}, range: 0 ... 1)
let palette2 = [
DSFColor(red: 0.706, green: 0.020, blue: 0.151, alpha: 1.0),
DSFColor(red: 0.845, green: 0.324, blue: 0.265, alpha: 1.0),
DSFColor(red: 0.932, green: 0.520, blue: 0.408, alpha: 1.0),
DSFColor(red: 0.970, green: 0.678, blue: 0.562, alpha: 1.0),
DSFColor(red: 0.949, green: 0.795, blue: 0.720, alpha: 1.0),
DSFColor(red: 0.867, green: 0.867, blue: 0.867, alpha: 1.0),
DSFColor(red: 0.752, green: 0.832, blue: 0.960, alpha: 1.0),
DSFColor(red: 0.621, green: 0.745, blue: 1.000, alpha: 1.0),
DSFColor(red: 0.484, green: 0.621, blue: 0.978, alpha: 1.0),
DSFColor(red: 0.352, green: 0.469, blue: 0.889, alpha: 1.0),
DSFColor(red: 0.230, green: 0.299, blue: 0.751, alpha: 1.0),
]
activityGrid2.cellFillScheme = .init(colors: palette2)
activityGrid2.cellDimension = 9
activityGrid2.verticalCellCount = 8
activityGrid2.cellSpacing = 3
activityGrid2.cellBorderColor = .black.withAlphaComponent(1)
activityGrid2.cellBorderWidth = 0.5
activityGrid2.setValues((0 ..< 1000).map { _ in CGFloat.random(in: 0 ... 1)}, range: 0 ... 1)
let surface = DSFSparklineSurface.Bitmap()
let grid = DSFSparklineOverlay.ActivityGrid()
grid.dataSource = DSFSparkline.StaticDataSource((0 ... 1000).map { _ in CGFloat.random(in: 0 ... 1) }, range: 0 ... 1)
grid.verticalCellCount = 1
grid.cellStyle = .init(fillScheme: DSFSparkline.ActivityGrid.CellStyle.DefaultLight, borderColor: .black, borderWidth: 0.5, cellDimension: 11, cellSpacing: 2.5)
surface.addOverlay(grid)
let img = surface.image(width: 200, height: 14, scale: 2)!
let pngdata = try! img.representation.png(dpi: 144)
bitmapMap["activity-grid-mini.png"] = pngdata
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
@IBAction func generate(_ sender: Any) {
let dialog = NSOpenPanel();
dialog.title = "Choose a folder";
dialog.showsResizeIndicator = true
dialog.allowsMultipleSelection = false
dialog.canChooseDirectories = true
dialog.canChooseFiles = false
dialog.canCreateDirectories = true
dialog.beginSheetModal(for: self.view.window!, completionHandler: { response in
guard response == NSApplication.ModalResponse.OK,
let url = dialog.url else {
return
}
DispatchQueue.main.async { [weak self] in
self?.generate(path: url)
}
})
}
func generate(path: URL) {
self.nameMap.forEach { (name, view) in
let name = "\(name).png"
let outputFile = path.appendingPathComponent(name)
guard let image = view.snapshot(),
let tiff = image.tiffRepresentation,
let imageRep = NSBitmapImageRep(data: tiff),
let pngData = imageRep.representation(using: .png, properties: [:]) else {
return
}
do {
try pngData.write(to: outputFile)
}
catch {
Swift.print("\(error)")
}
}
self.bitmapMap.forEach { (name: String, value: Data) in
let outputFile = path.appendingPathComponent(name)
do {
try value.write(to: outputFile)
}
catch {
Swift.print("\(error)")
}
}
}
}
/////
// https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/download.html
// https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/time_series/HadCRUT.4.6.0.0.annual_ns_avg.txt
// Data format :- https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/series_format.html
let rawData: [CGFloat] = [
-0.373,
-0.218,
-0.228,
-0.269,
-0.248,
-0.272,
-0.358,
-0.461,
-0.467,
-0.284,
-0.343,
-0.407,
-0.524,
-0.278,
-0.494,
-0.279,
-0.251,
-0.321,
-0.238,
-0.262,
-0.276,
-0.335,
-0.227,
-0.304,
-0.368,
-0.395,
-0.384,
-0.075,
0.035,
-0.230,
-0.227,
-0.200,
-0.213,
-0.296,
-0.409,
-0.389,
-0.367,
-0.418,
-0.307,
-0.171,
-0.416,
-0.330,
-0.455,
-0.473,
-0.410,
-0.390,
-0.186,
-0.206,
-0.412,
-0.289,
-0.203,
-0.259,
-0.402,
-0.479,
-0.520,
-0.377,
-0.283,
-0.465,
-0.511,
-0.522,
-0.490,
-0.544,
-0.437,
-0.424,
-0.244,
-0.141,
-0.383,
-0.468,
-0.333,
-0.275,
-0.247,
-0.187,
-0.302,
-0.276,
-0.294,
-0.215,
-0.108,
-0.210,
-0.206,
-0.350,
-0.137,
-0.087,
-0.137,
-0.273,
-0.131,
-0.178,
-0.147,
-0.026,
-0.006,
-0.052,
0.014,
0.020,
-0.027,
-0.004,
0.144,
0.025,
-0.071,
-0.038,
-0.039,
-0.074,
-0.173,
-0.052,
0.028,
0.097,
-0.129,
-0.190,
-0.267,
-0.007,
0.046,
0.017,
-0.049,
0.038,
0.014,
0.048,
-0.223,
-0.140,
-0.068,
-0.074,
-0.113,
0.032,
-0.027,
-0.186,
-0.065,
0.062,
-0.214,
-0.149,
-0.241,
0.047,
-0.062,
0.057,
0.092,
0.140,
0.011,
0.194,
-0.014,
-0.030,
0.045,
0.192,
0.198,
0.118,
0.296,
0.254,
0.105,
0.148,
0.208,
0.325,
0.183,
0.390,
0.539,
0.306,
0.294,
0.441,
0.496,
0.505,
0.447,
0.545,
0.506,
0.491,
0.395,
0.506,
0.560,
0.425,
0.470,
0.514,
0.579,
0.763,
0.797,
0.677,
0.597,
0.736,
0.768
]
// Annual mean temperature anomaly
// http://www.bom.gov.au/climate/change/#tabs=Tracker&tracker=timeseries&tQ=graph%3Dtmean%26area%3Daus%26season%3D0112
let temperature: [CGFloat] = [
-0.50,
-0.68,
-0.20,
-0.87,
0.12,
0.07,
-0.57,
-1.24,
-0.54,
-0.15,
-0.53,
-0.23,
-0.47,
-0.38,
-0.69,
-0.77,
-0.17,
-0.51,
0.16,
-0.87,
-0.24,
-0.59,
-0.42,
-0.45,
-0.36,
-0.50,
-0.14,
-0.36,
0.19,
-0.62,
-0.24,
-0.55,
0.08,
-0.62,
-0.40,
-0.29,
-0.73,
-0.25,
-0.45,
-0.94,
-0.61,
-0.43,
-0.43,
-0.45,
-0.36,
-0.32,
-0.92,
0.04,
0.14,
0.24,
-0.66,
0.05,
-0.11,
-0.13,
-0.22,
0.25,
-0.50,
-0.22,
-0.39,
-0.03,
-0.10,
-0.22,
0.15,
0.54,
-0.70,
-0.22,
-0.75,
-0.04,
-0.31,
0.37,
0.74,
0.27,
-0.04,
0.33,
-0.38,
0.21,
0.22,
0.17,
0.73,
-0.02,
0.47,
0.60,
0.12,
0.31,
0.18,
0.16,
0.60,
0.30,
0.97,
0.32,
-0.04,
0.05,
0.71,
0.69,
0.54,
1.16,
0.50,
0.76,
0.45,
0.93,
0.33,
-0.00,
0.24,
1.33,
1.04,
0.94,
0.99,
1.06,
1.12,
1.52,
1.15
]
================================================
FILE: Demos/Documentation Project/Documentation Project.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
236A660C2626C0C0004B70C9 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236A660B2626C0C0004B70C9 /* Utilities.swift */; };
239088F82B0D6A4C0059761E /* SwiftImageReadWrite in Frameworks */ = {isa = PBXBuildFile; productRef = 239088F72B0D6A4C0059761E /* SwiftImageReadWrite */; };
23EE63DF25D875C7000C85E4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EE63DE25D875C7000C85E4 /* AppDelegate.swift */; };
23EE63E125D875C7000C85E4 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EE63E025D875C7000C85E4 /* ViewController.swift */; };
23EE63E325D875C8000C85E4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23EE63E225D875C8000C85E4 /* Assets.xcassets */; };
23EE63E625D875C8000C85E4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23EE63E425D875C8000C85E4 /* Main.storyboard */; };
23EE640925D87663000C85E4 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 23EE640825D87663000C85E4 /* DSFSparkline */; };
23EE640C25D877E7000C85E4 /* CenteringClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23EE640B25D877E7000C85E4 /* CenteringClipView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
236A660B2626C0C0004B70C9 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; };
23EE63DB25D875C7000C85E4 /* Documentation Project.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Documentation Project.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23EE63DE25D875C7000C85E4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23EE63E025D875C7000C85E4 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23EE63E225D875C8000C85E4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23EE63E525D875C8000C85E4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23EE63E725D875C8000C85E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23EE63E825D875C8000C85E4 /* Documentation_Project.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Documentation_Project.entitlements; sourceTree = ""; };
23EE640525D8763F000C85E4 /* DSFSparkline */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DSFSparkline; path = ../..; sourceTree = ""; };
23EE640B25D877E7000C85E4 /* CenteringClipView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CenteringClipView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
23EE63D825D875C7000C85E4 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
239088F82B0D6A4C0059761E /* SwiftImageReadWrite in Frameworks */,
23EE640925D87663000C85E4 /* DSFSparkline in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
23EE63D225D875C7000C85E4 = {
isa = PBXGroup;
children = (
23EE640525D8763F000C85E4 /* DSFSparkline */,
23EE63DD25D875C7000C85E4 /* Documentation Project */,
23EE63DC25D875C7000C85E4 /* Products */,
23EE640725D87663000C85E4 /* Frameworks */,
);
sourceTree = "";
};
23EE63DC25D875C7000C85E4 /* Products */ = {
isa = PBXGroup;
children = (
23EE63DB25D875C7000C85E4 /* Documentation Project.app */,
);
name = Products;
sourceTree = "";
};
23EE63DD25D875C7000C85E4 /* Documentation Project */ = {
isa = PBXGroup;
children = (
23EE63DE25D875C7000C85E4 /* AppDelegate.swift */,
23EE63E025D875C7000C85E4 /* ViewController.swift */,
23EE63E225D875C8000C85E4 /* Assets.xcassets */,
23EE63E425D875C8000C85E4 /* Main.storyboard */,
23EE640B25D877E7000C85E4 /* CenteringClipView.swift */,
23EE63E725D875C8000C85E4 /* Info.plist */,
23EE63E825D875C8000C85E4 /* Documentation_Project.entitlements */,
236A660B2626C0C0004B70C9 /* Utilities.swift */,
);
path = "Documentation Project";
sourceTree = "";
};
23EE640725D87663000C85E4 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
23EE63DA25D875C7000C85E4 /* Documentation Project */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23EE63EB25D875C8000C85E4 /* Build configuration list for PBXNativeTarget "Documentation Project" */;
buildPhases = (
23EE63D725D875C7000C85E4 /* Sources */,
23EE63D825D875C7000C85E4 /* Frameworks */,
23EE63D925D875C7000C85E4 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Documentation Project";
packageProductDependencies = (
23EE640825D87663000C85E4 /* DSFSparkline */,
239088F72B0D6A4C0059761E /* SwiftImageReadWrite */,
);
productName = "Documentation Project";
productReference = 23EE63DB25D875C7000C85E4 /* Documentation Project.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
23EE63D325D875C7000C85E4 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1240;
LastUpgradeCheck = 1240;
TargetAttributes = {
23EE63DA25D875C7000C85E4 = {
CreatedOnToolsVersion = 12.4;
};
};
};
buildConfigurationList = 23EE63D625D875C7000C85E4 /* Build configuration list for PBXProject "Documentation Project" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 23EE63D225D875C7000C85E4;
packageReferences = (
239088F62B0D6A4C0059761E /* XCRemoteSwiftPackageReference "SwiftImageReadWrite" */,
);
productRefGroup = 23EE63DC25D875C7000C85E4 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
23EE63DA25D875C7000C85E4 /* Documentation Project */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
23EE63D925D875C7000C85E4 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23EE63E325D875C8000C85E4 /* Assets.xcassets in Resources */,
23EE63E625D875C8000C85E4 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
23EE63D725D875C7000C85E4 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23EE63E125D875C7000C85E4 /* ViewController.swift in Sources */,
23EE63DF25D875C7000C85E4 /* AppDelegate.swift in Sources */,
236A660C2626C0C0004B70C9 /* Utilities.swift in Sources */,
23EE640C25D877E7000C85E4 /* CenteringClipView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
23EE63E425D875C8000C85E4 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23EE63E525D875C8000C85E4 /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
23EE63E925D875C8000C85E4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
23EE63EA25D875C8000C85E4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 11.1;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
23EE63EC25D875C8000C85E4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Documentation Project/Documentation_Project.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "Documentation Project/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFSparkline.Documentation-Project";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
23EE63ED25D875C8000C85E4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Documentation Project/Documentation_Project.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = "Documentation Project/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.DSFSparkline.Documentation-Project";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
23EE63D625D875C7000C85E4 /* Build configuration list for PBXProject "Documentation Project" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23EE63E925D875C8000C85E4 /* Debug */,
23EE63EA25D875C8000C85E4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23EE63EB25D875C8000C85E4 /* Build configuration list for PBXNativeTarget "Documentation Project" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23EE63EC25D875C8000C85E4 /* Debug */,
23EE63ED25D875C8000C85E4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
239088F62B0D6A4C0059761E /* XCRemoteSwiftPackageReference "SwiftImageReadWrite" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/dagronf/SwiftImageReadWrite";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 1.6.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
239088F72B0D6A4C0059761E /* SwiftImageReadWrite */ = {
isa = XCSwiftPackageProductDependency;
package = 239088F62B0D6A4C0059761E /* XCRemoteSwiftPackageReference "SwiftImageReadWrite" */;
productName = SwiftImageReadWrite;
};
23EE640825D87663000C85E4 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
productName = DSFSparkline;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 23EE63D325D875C7000C85E4 /* Project object */;
}
================================================
FILE: Demos/Documentation Project/Documentation Project.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Demos/Documentation Project/Documentation Project.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
================================================
{
"object": {
"pins": [
{
"package": "SwiftImageReadWrite",
"repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite",
"state": {
"branch": null,
"revision": "42ace2412279f18bc264bc306e96b51c36e12a33",
"version": "1.9.2"
}
}
]
},
"version": 1
}
================================================
FILE: Demos/Playground/Sparklines Playground.playground/Contents.swift
================================================
import Cocoa
import DSFSparkline
var str = "Playground demonstrating the generation of a sparkline bitmap"
// Set this to 'true' to save the generated bitmaps out to the /tmp folder
let shouldSaveImage = false
func SaveImage(image: NSImage, path: URL) {
guard shouldSaveImage else {
return
}
guard let tiff = image.tiffRepresentation,
let imageRep = NSBitmapImageRep(data: tiff),
let pngData = imageRep.representation(using: .png, properties: [:]) else {
return
}
do {
try pngData.write(to: path)
}
catch {
Swift.print("\(error)")
}
}
// - MARK: Simple definitions
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let biggersource = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 7, 6, 7, 8, 3, 3, 5, 3, 4, 1, 2, 9, 1, 3, 3], range: 0 ... 10)
let winloss = DSFSparkline.DataSource(values: [1, 1, 0, -1, 1, 1, 1, 0, 1, -1])
// Simple fill color
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
do {
// A method to replace a value within a DataSource
let replaceSource = DSFSparkline.DataSource(values: [0,1,2,3,4,5,6,7])
Swift.print(replaceSource)
var currentData = replaceSource.data
currentData.replaceSubrange(3...3, with: [33])
replaceSource.set(values: currentData)
Swift.print(replaceSource)
}
// MARK: - Simple line graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/line-simple-small.png"))
// Generate an image with retina scale
line.interpolated = true
let image2 = bitmap.image(width: 50, height: 25, scale: 2)!
SaveImage(image: image2, path: URL(fileURLWithPath: "/tmp/line-simple-small-interpolated.png"))
let attr = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)!
var message = NSMutableAttributedString(string: "Inlined ")
message.append(attr)
message.append(NSAttributedString(string: " line graph"))
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.markerSize = 3
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
}
}
// MARK: - Simple bar graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let bar = DSFSparklineOverlay.Bar() // Create a bar overlay
bar.dataSource = source // Assign the datasource to the overlay
bar.primaryStrokeColor = baseColor
bar.primaryFill = primaryFill
bitmap.addOverlay(bar) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/bar-simple-small.png"))
}
// MARK: - Simple stackline graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stackline = DSFSparklineOverlay.Stackline() // Create a stackline overlay
stackline.dataSource = source // Assign the datasource to the overlay
stackline.strokeWidth = 1
stackline.primaryStrokeColor = baseColor
stackline.primaryFill = primaryFill
bitmap.addOverlay(stackline) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/stackline-simple-small.png"))
}
// MARK: - Simple dot graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let dot = DSFSparklineOverlay.Dot() // Create a dot graph overlay
dot.dataSource = biggersource // Assign the datasource to the overlay
dot.onColor = primaryStroke
bitmap.addOverlay(dot) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 32, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/dot-simple-small.png"))
}
// MARK: - Simple win-loss graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let winLoss = DSFSparklineOverlay.WinLossTie() // Create a win-loss graph overlay
winLoss.dataSource = winloss // Assign the datasource to the overlay
winLoss.centerLine = .init(color: DSFColor.gray, lineWidth: 0.5, lineDashStyle: [0.5, 0.5])
winLoss.winStroke = primaryStroke
winLoss.winFill = primaryFill
winLoss.lossStroke = primaryStroke
winLoss.lossFill = primaryFill
bitmap.addOverlay(winLoss) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 75, height: 12, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/win-loss-small.png"))
}
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let winlosstie = DSFSparklineOverlay.WinLossTie() // Create a win-loss graph overlay
winlosstie.dataSource = winloss // Assign the datasource to the overlay
winlosstie.winStroke = primaryStroke
winlosstie.winFill = primaryFill
winlosstie.lossStroke = primaryStroke
winlosstie.lossFill = primaryFill
winlosstie.tieStroke = primaryStroke
winlosstie.tieFill = primaryFill
bitmap.addOverlay(winlosstie) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 75, height: 16, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/win-loss-tie-small.png"))
}
// MARK: - Simple tablet graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let tablet = DSFSparklineOverlay.Tablet() // Create a tablet graph overlay
tablet.dataSource = winloss // Assign the datasource to the overlay
tablet.lineWidth = 1
tablet.winStrokeColor = primaryStroke
tablet.winFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.7)!)
tablet.lossStrokeColor = primaryStroke
bitmap.addOverlay(tablet) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 90, height: 16, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/tablet-small.png"))
}
// MARK: - Simple stripes
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stripe = DSFSparklineOverlay.Stripes() // Create a stripe graph overlay
stripe.integral = true
stripe.barSpacing = 1
stripe.dataSource = .init(values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
bitmap.addOverlay(stripe) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 90, height: 16, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/stripes-small.png"))
}
// MARK: - Simple pie
do {
let bitmap = DSFSparklineSurface.Bitmap()
let pieGraph = DSFSparklineOverlay.Pie()
pieGraph.dataSource = DSFSparkline.StaticDataSource([10, 55, 20])
pieGraph.lineWidth = 0.5
pieGraph.strokeColor = CGColor.black
bitmap.addOverlay(pieGraph)
// Generate an image with retina scale
let image = bitmap.image(width: 18, height: 18, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/pie-simple.png"))
}
// MARK: - simple databar
do {
let bitmap = DSFSparklineSurface.Bitmap()
let databar = DSFSparklineOverlay.DataBar()
databar.dataSource = DSFSparkline.StaticDataSource([10, 20, 30])
databar.lineWidth = 0.5
databar.strokeColor = CGColor.black
bitmap.addOverlay(databar)
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 18, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/databar-simple.png"))
// databar with a maximum value defined
databar.maximumTotalValue = 100
databar.unsetColor = DSFColor.black.cgColor
// Generate an image with retina scale
let image2 = bitmap.image(width: 50, height: 18, scale: 2)!
SaveImage(image: image2, path: URL(fileURLWithPath: "/tmp/databar-simple-maxvalue.png"))
}
// MARK: - simple percent bar
do {
let style = DSFSparkline.PercentBar.Style()
style.underBarColor = CGColor(gray: 0.8, alpha: 1.0)
style.font = DSFFont(name: "Menlo", size: 10)!
style.barEdgeInsets = DSFEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
let bitmap = DSFSparklineSurface.Bitmap()
let percentbar = DSFSparklineOverlay.PercentBar(style: style, value: 0.3)
bitmap.addOverlay(percentbar)
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 18, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/percentbar.png"))
percentbar.value = 0.7
style.showLabel = false
percentbar.displayStyle = style
// Generate an image with retina scale
let image2 = bitmap.image(width: 50, height: 18, scale: 2)!
SaveImage(image: image2, path: URL(fileURLWithPath: "/tmp/percentbar2.png"))
}
/// MARK: - A more complex sparkline
do {
// Line DataSource
var LineSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, /*range: 0 ... 1,*/ zeroLineValue: 0.3)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.87, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
var bitmap = DSFSparklineSurface.Bitmap()
// highlight overlay 1
do {
let h1 = DSFSparklineOverlay.RangeHighlight()
h1.dataSource = LineSource1
h1.fill = DSFSparkline.Fill.Color(NSColor.gray.withAlphaComponent(0.2).cgColor)
h1.highlightRange = 0.3 ..< 0.7
bitmap.addOverlay(h1)
}
// highlight overlay 2
do {
let h2 = DSFSparklineOverlay.RangeHighlight()
h2.dataSource = LineSource1
h2.fill = DSFSparkline.Fill.Color(NSColor.systemRed.withAlphaComponent(0.1).cgColor)
h2.highlightRange = 0.0 ..< 0.3
bitmap.addOverlay(h2)
}
// zero-line
do {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = LineSource1
bitmap.addOverlay(zeroLine)
}
// Stack overlay
do {
let stack = DSFSparklineOverlay.Stackline()
stack.dataSource = LineSource1
stack.shadow = NSShadow(blurRadius: 1.0, offset: CGSize(width: 0.5, height: -0.5), color: DSFColor.black.withAlphaComponent(0.3))
stack.centeredAtZeroLine = true
stack.strokeWidth = 1
stack.primaryStrokeColor = NSColor.systemPurple.cgColor
stack.primaryFill = DSFSparkline.Fill.Color(NSColor.systemPurple.withAlphaComponent(0.7).cgColor)
stack.secondaryStrokeColor = NSColor.systemPink.cgColor
stack.secondaryFill = DSFSparkline.Fill.Color(NSColor.systemPink.withAlphaComponent(0.7).cgColor)
bitmap.addOverlay(stack)
}
// Generate a bitmap
let r = CGSize(width: 200, height: 40)
let image = bitmap.image(size: r, scale: 2)
}
// MARK: - Simple wiper graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let wiper = DSFSparklineOverlay.WiperGauge() // Create a wiper graph overlay
wiper.value = 0.75
wiper.valueColor = DSFSparkline.ValueBasedFill(flatColor: baseColor)
bitmap.addOverlay(wiper) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 40, height: 20, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/wiper-small.png"))
}
// MARK: - Activity Graph
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let activity = DSFSparklineOverlay.ActivityGrid()
let data: [CGFloat] = (0 ... 100).map { _ in CGFloat.random(in: 0...100) }
activity.dataSource = .init(data)
bitmap.addOverlay(activity) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 300, height: 100, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/activity-basic-1.png"))
activity.verticalCellCount = 10
activity.cellDimension = 6
activity.cellSpacing = 1
activity.cellFillScheme = DSFSparkline.ActivityGrid.CellStyle.DefaultLight
let image2 = bitmap.image(width: 300, height: 100, scale: 2)!
SaveImage(image: image2, path: URL(fileURLWithPath: "/tmp/activity-basic-2.png"))
}
// MARK: Circular Gauge
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let wiper = DSFSparklineOverlay.CircularGauge() // Create a wiper graph overlay
wiper.value = 0.65
//wiper.valueColor = DSFSparkline.ValueBasedFill(flatColor: baseColor)
bitmap.addOverlay(wiper) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 40, height: 40, scale: 2)!
SaveImage(image: image, path: URL(fileURLWithPath: "/tmp/circular-gauge-small.png"))
}
================================================
FILE: Demos/Playground/Sparklines Playground.playground/contents.xcplayground
================================================
================================================
FILE: Demos/Playground/Sparklines Playground.playground/playground.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Demos/Playground/Sparklines Playground.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 60;
objects = {
/* Begin PBXAggregateTarget section */
239716B923B554DD0034A4D4 /* All Demos */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 239716BC23B554DD0034A4D4 /* Build configuration list for PBXAggregateTarget "All Demos" */;
buildPhases = (
);
dependencies = (
23369E8525D65B2D00A40023 /* PBXTargetDependency */,
23369E8725D65B2D00A40023 /* PBXTargetDependency */,
239716C023B554E70034A4D4 /* PBXTargetDependency */,
239716BE23B554E50034A4D4 /* PBXTargetDependency */,
239716C223B554E80034A4D4 /* PBXTargetDependency */,
23E2279A23B5A6EF00E59122 /* PBXTargetDependency */,
);
name = "All Demos";
productName = "Add Demos";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
23102B5423B0245A001A621D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102B5323B0245A001A621D /* AppDelegate.swift */; };
23102B5623B0245A001A621D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102B5523B0245A001A621D /* ViewController.swift */; };
23102B5823B0245B001A621D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23102B5723B0245B001A621D /* Assets.xcassets */; };
23102B5B23B0245B001A621D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102B5923B0245B001A621D /* Main.storyboard */; };
23102BC423B02AED001A621D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102BC323B02AED001A621D /* AppDelegate.swift */; };
23102BC623B02AED001A621D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102BC523B02AED001A621D /* SceneDelegate.swift */; };
23102BC823B02AED001A621D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102BC723B02AED001A621D /* ViewController.swift */; };
23102BCB23B02AED001A621D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102BC923B02AED001A621D /* Main.storyboard */; };
23102BCD23B02AEE001A621D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23102BCC23B02AEE001A621D /* Assets.xcassets */; };
23102BD023B02AEE001A621D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102BCE23B02AEE001A621D /* LaunchScreen.storyboard */; };
23102BDD23B02B0A001A621D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 23102BDC23B02B0A001A621D /* AppDelegate.m */; };
23102BE023B02B0A001A621D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 23102BDF23B02B0A001A621D /* ViewController.m */; };
23102BE223B02B0B001A621D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23102BE123B02B0B001A621D /* Assets.xcassets */; };
23102BE523B02B0B001A621D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102BE323B02B0B001A621D /* Main.storyboard */; };
23102BE823B02B0B001A621D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 23102BE723B02B0B001A621D /* main.m */; };
23102BF423B02B32001A621D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102BF323B02B32001A621D /* AppDelegate.swift */; };
23102BF623B02B32001A621D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102BF523B02B32001A621D /* ViewController.swift */; };
23102BF923B02B32001A621D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102BF723B02B32001A621D /* Main.storyboard */; };
23102BFB23B02B33001A621D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23102BFA23B02B33001A621D /* Assets.xcassets */; };
23102BFE23B02B33001A621D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23102BFC23B02B33001A621D /* LaunchScreen.storyboard */; };
23102C0423B03315001A621D /* cpuUsage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23102C0323B03315001A621D /* cpuUsage.swift */; };
2322C06B262651D5007D8112 /* TestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2322C06A262651D5007D8112 /* TestingView.swift */; };
2322C06C262651D5007D8112 /* TestingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2322C06A262651D5007D8112 /* TestingView.swift */; };
232BAF6D25EB16210090C447 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BAF6C25EB16210090C447 /* SwiftUIView.swift */; };
232BAF6E25EB16210090C447 /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232BAF6C25EB16210090C447 /* SwiftUIView.swift */; };
232D9ECE25EC7133004DFAFC /* SuperCoolLineSpark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232D9ECD25EC7133004DFAFC /* SuperCoolLineSpark.swift */; };
232D9ECF25EC7133004DFAFC /* SuperCoolLineSpark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232D9ECD25EC7133004DFAFC /* SuperCoolLineSpark.swift */; };
232F732323B5BE2A00FA3A3F /* GridViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 232F732223B5BE2A00FA3A3F /* GridViewController.swift */; };
2346EF3025E8D13A00050570 /* SwiftUIContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2346EF2F25E8D13A00050570 /* SwiftUIContentView.swift */; };
2346EF3125E8D13A00050570 /* SwiftUIContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2346EF2F25E8D13A00050570 /* SwiftUIContentView.swift */; };
23529A7425E84D2A00DFA52D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23529A7325E84D2A00DFA52D /* Extensions.swift */; };
23529A7525E84D2A00DFA52D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23529A7325E84D2A00DFA52D /* Extensions.swift */; };
235A585B2603181A00266642 /* PercentBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235A585A2603181A00266642 /* PercentBarView.swift */; };
235A585C2603181A00266642 /* PercentBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 235A585A2603181A00266642 /* PercentBarView.swift */; };
236417E729628C0600C8A1D2 /* WiperGaugeDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236417E629628C0600C8A1D2 /* WiperGaugeDemoView.swift */; };
236417E829628C0600C8A1D2 /* WiperGaugeDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236417E629628C0600C8A1D2 /* WiperGaugeDemoView.swift */; };
236A57FB25C1392B00F2BACF /* SwiftUI_Sparkline_DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236A57E925C1392A00F2BACF /* SwiftUI_Sparkline_DemoApp.swift */; };
236A57FC25C1392B00F2BACF /* SwiftUI_Sparkline_DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236A57E925C1392A00F2BACF /* SwiftUI_Sparkline_DemoApp.swift */; };
236A57FD25C1392B00F2BACF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236A57EA25C1392A00F2BACF /* ContentView.swift */; };
236A57FE25C1392B00F2BACF /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 236A57EA25C1392A00F2BACF /* ContentView.swift */; };
236A57FF25C1392B00F2BACF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 236A57EB25C1392B00F2BACF /* Assets.xcassets */; };
236A580025C1392B00F2BACF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 236A57EB25C1392B00F2BACF /* Assets.xcassets */; };
237B824725E77B3800B3DE23 /* BarDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B824625E77B3800B3DE23 /* BarDemoContentView.swift */; };
237B824825E77B3800B3DE23 /* BarDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B824625E77B3800B3DE23 /* BarDemoContentView.swift */; };
237B827825E7891E00B3DE23 /* BitmapGenerationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B827725E7891D00B3DE23 /* BitmapGenerationView.swift */; };
237B827925E7891E00B3DE23 /* BitmapGenerationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 237B827725E7891D00B3DE23 /* BitmapGenerationView.swift */; };
238053C725DCC2B100B996B5 /* ActiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238053C625DCC2B100B996B5 /* ActiveView.swift */; };
238053C825DCC2B100B996B5 /* ActiveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238053C625DCC2B100B996B5 /* ActiveView.swift */; };
238053E525DCD14600B996B5 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238053E425DCD14600B996B5 /* OverlayView.swift */; };
238053E625DCD14600B996B5 /* OverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238053E425DCD14600B996B5 /* OverlayView.swift */; };
238DB6DC25EB2141009197AB /* StripesOverlaidView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238DB6DB25EB2141009197AB /* StripesOverlaidView.swift */; };
238DB6DD25EB2141009197AB /* StripesOverlaidView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238DB6DB25EB2141009197AB /* StripesOverlaidView.swift */; };
238DB6EA25EB3A2E009197AB /* TabletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238DB6E925EB3A2E009197AB /* TabletView.swift */; };
238DB6EB25EB3A2F009197AB /* TabletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238DB6E925EB3A2E009197AB /* TabletView.swift */; };
23905CA12B5B5D00007B7769 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23905CA02B5B5D00007B7769 /* CircularProgress.swift */; };
23905CA22B5B5D00007B7769 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23905CA02B5B5D00007B7769 /* CircularProgress.swift */; };
239DE4042BA177D300E13208 /* CircularGaugeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239DE4032BA177D300E13208 /* CircularGaugeView.swift */; };
239DE4052BA177D300E13208 /* CircularGaugeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239DE4032BA177D300E13208 /* CircularGaugeView.swift */; };
23B006F72DDD61C700D074A0 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 23B006F62DDD61C700D074A0 /* DSFSparkline */; };
23B006F92DDD61FB00D074A0 /* DSFSparkline-shared in Frameworks */ = {isa = PBXBuildFile; productRef = 23B006F82DDD61FB00D074A0 /* DSFSparkline-shared */; };
23B006FA2DDD61FB00D074A0 /* DSFSparkline-shared in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 23B006F82DDD61FB00D074A0 /* DSFSparkline-shared */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
23B006FD2DDD625E00D074A0 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 23B006FC2DDD625E00D074A0 /* DSFSparkline */; };
23B006FF2DDD63E900D074A0 /* DSFSparkline-shared in Frameworks */ = {isa = PBXBuildFile; productRef = 23B006FE2DDD63E900D074A0 /* DSFSparkline-shared */; };
23B007002DDD63E900D074A0 /* DSFSparkline-shared in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 23B006FE2DDD63E900D074A0 /* DSFSparkline-shared */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
23B007032DDD652800D074A0 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 23B007022DDD652800D074A0 /* DSFSparkline */; };
23B007052DDD65A800D074A0 /* DSFSparkline in Frameworks */ = {isa = PBXBuildFile; productRef = 23B007042DDD65A800D074A0 /* DSFSparkline */; };
23B007072DDD65B800D074A0 /* DSFSparkline-shared in Frameworks */ = {isa = PBXBuildFile; productRef = 23B007062DDD65B800D074A0 /* DSFSparkline-shared */; };
23B007082DDD65B800D074A0 /* DSFSparkline-shared in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 23B007062DDD65B800D074A0 /* DSFSparkline-shared */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
23BC66862B0DD82A0064FF62 /* ActivityGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23BC66852B0DD82A0064FF62 /* ActivityGridView.swift */; };
23BC66872B0DD82A0064FF62 /* ActivityGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23BC66852B0DD82A0064FF62 /* ActivityGridView.swift */; };
23D10B1025ED9EB500BBE555 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D10B0F25ED9EB500BBE555 /* AttributedString.swift */; };
23D10B1125ED9EB500BBE555 /* AttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D10B0F25ED9EB500BBE555 /* AttributedString.swift */; };
23E2278C23B5846E00E59122 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E2278B23B5846E00E59122 /* AppDelegate.swift */; };
23E2278E23B5846E00E59122 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23E2278D23B5846E00E59122 /* ViewController.swift */; };
23E2279023B5846E00E59122 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 23E2278F23B5846E00E59122 /* Assets.xcassets */; };
23E2279323B5846E00E59122 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 23E2279123B5846E00E59122 /* Main.storyboard */; };
23FC810025DB3FC50047A800 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC80FF25DB3FC40047A800 /* ReportView.swift */; };
23FC810125DB3FC50047A800 /* ReportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC80FF25DB3FC40047A800 /* ReportView.swift */; };
23FC812025DB47A20047A800 /* WinLossGraphContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC811F25DB47A20047A800 /* WinLossGraphContentView.swift */; };
23FC812125DB47A20047A800 /* WinLossGraphContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC811F25DB47A20047A800 /* WinLossGraphContentView.swift */; };
23FC814025DB4E5D0047A800 /* LineDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC813F25DB4E5D0047A800 /* LineDemoContentView.swift */; };
23FC814125DB4E5D0047A800 /* LineDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC813F25DB4E5D0047A800 /* LineDemoContentView.swift */; };
23FC817525DB547E0047A800 /* StackLineDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC817425DB547E0047A800 /* StackLineDemoContentView.swift */; };
23FC817625DB547E0047A800 /* StackLineDemoContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC817425DB547E0047A800 /* StackLineDemoContentView.swift */; };
23FC819325DB586B0047A800 /* PieGraphDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC819225DB586B0047A800 /* PieGraphDemoView.swift */; };
23FC819425DB586B0047A800 /* PieGraphDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC819225DB586B0047A800 /* PieGraphDemoView.swift */; };
23FC819F25DB59160047A800 /* DataBarGraphContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC819E25DB59160047A800 /* DataBarGraphContent.swift */; };
23FC81A025DB59160047A800 /* DataBarGraphContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC819E25DB59160047A800 /* DataBarGraphContent.swift */; };
23FC81B425DB59E70047A800 /* DotGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC81B325DB59E70047A800 /* DotGraphView.swift */; };
23FC81B525DB59E70047A800 /* DotGraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC81B325DB59E70047A800 /* DotGraphView.swift */; };
23FC81DD25DB60760047A800 /* StripesDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC81DC25DB60760047A800 /* StripesDemoView.swift */; };
23FC81DE25DB60760047A800 /* StripesDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23FC81DC25DB60760047A800 /* StripesDemoView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
23369E8425D65B2D00A40023 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 236A57EF25C1392B00F2BACF;
remoteInfo = "SwiftUI Sparkline Demo (iOS)";
};
23369E8625D65B2D00A40023 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 236A57F625C1392B00F2BACF;
remoteInfo = "SwiftUI Sparkline Demo (macOS)";
};
239716BD23B554E50034A4D4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 23102B4F23B0245A001A621D;
remoteInfo = "macOS Sparkline Demo";
};
239716BF23B554E70034A4D4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 23102BC023B02AED001A621D;
remoteInfo = "iOS Sparkline Demo";
};
239716C123B554E80034A4D4 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 23102BD823B02B0A001A621D;
remoteInfo = "macOS Sparkline Demo Objc";
};
23E2279923B5A6EF00E59122 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 23102B4523B0165B001A621D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 23E2278823B5846E00E59122;
remoteInfo = "macOS Table Demo";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
23B006FB2DDD61FB00D074A0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
23B006FA2DDD61FB00D074A0 /* DSFSparkline-shared in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
23B007012DDD63E900D074A0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
23B007002DDD63E900D074A0 /* DSFSparkline-shared in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
23B007092DDD65B800D074A0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
23B007082DDD65B800D074A0 /* DSFSparkline-shared in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
23102B5023B0245A001A621D /* macOS Sparkline Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "macOS Sparkline Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23102B5323B0245A001A621D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23102B5523B0245A001A621D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23102B5723B0245B001A621D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23102B5A23B0245B001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23102B5C23B0245B001A621D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23102B5D23B0245B001A621D /* macOS_Sparkline_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS_Sparkline_Demo.entitlements; sourceTree = ""; };
23102BC123B02AED001A621D /* iOS Sparkline Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Sparkline Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23102BC323B02AED001A621D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23102BC523B02AED001A621D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
23102BC723B02AED001A621D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23102BCA23B02AED001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23102BCC23B02AEE001A621D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23102BCF23B02AEE001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
23102BD123B02AEE001A621D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23102BD923B02B0A001A621D /* macOS Sparkline Demo Objc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "macOS Sparkline Demo Objc.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23102BDB23B02B0A001A621D /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
23102BDC23B02B0A001A621D /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
23102BDE23B02B0A001A621D /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
23102BDF23B02B0A001A621D /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; };
23102BE123B02B0B001A621D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23102BE423B02B0B001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23102BE623B02B0B001A621D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23102BE723B02B0B001A621D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
23102BE923B02B0B001A621D /* macOS_Sparkline_Demo_Objc.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS_Sparkline_Demo_Objc.entitlements; sourceTree = ""; };
23102BF123B02B32001A621D /* tvOS Sparkline Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "tvOS Sparkline Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23102BF323B02B32001A621D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23102BF523B02B32001A621D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23102BF823B02B32001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23102BFA23B02B33001A621D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23102BFD23B02B33001A621D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
23102BFF23B02B33001A621D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23102C0323B03315001A621D /* cpuUsage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = cpuUsage.swift; sourceTree = ""; };
2322C06A262651D5007D8112 /* TestingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingView.swift; sourceTree = ""; };
232BAF6C25EB16210090C447 /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = ""; };
232D9ECD25EC7133004DFAFC /* SuperCoolLineSpark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuperCoolLineSpark.swift; sourceTree = ""; };
232F732223B5BE2A00FA3A3F /* GridViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridViewController.swift; sourceTree = ""; };
2346EF2F25E8D13A00050570 /* SwiftUIContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIContentView.swift; sourceTree = ""; };
23529A5825E847B900DFA52D /* iOS Sparkline Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "iOS Sparkline Demo.entitlements"; sourceTree = ""; };
23529A7325E84D2A00DFA52D /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
235A585A2603181A00266642 /* PercentBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PercentBarView.swift; sourceTree = ""; };
236417E629628C0600C8A1D2 /* WiperGaugeDemoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WiperGaugeDemoView.swift; sourceTree = ""; };
236A57E925C1392A00F2BACF /* SwiftUI_Sparkline_DemoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUI_Sparkline_DemoApp.swift; sourceTree = ""; };
236A57EA25C1392A00F2BACF /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
236A57EB25C1392B00F2BACF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
236A57F025C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftUI Sparkline Demo (iOS).app"; sourceTree = BUILT_PRODUCTS_DIR; };
236A57F225C1392B00F2BACF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
236A57F725C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftUI Sparkline Demo (macOS).app"; sourceTree = BUILT_PRODUCTS_DIR; };
236A57F925C1392B00F2BACF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
236A57FA25C1392B00F2BACF /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; };
237B824625E77B3800B3DE23 /* BarDemoContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarDemoContentView.swift; sourceTree = ""; };
237B827725E7891D00B3DE23 /* BitmapGenerationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitmapGenerationView.swift; sourceTree = ""; };
238053C625DCC2B100B996B5 /* ActiveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveView.swift; sourceTree = ""; };
238053E425DCD14600B996B5 /* OverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlayView.swift; sourceTree = ""; };
238DB6DB25EB2141009197AB /* StripesOverlaidView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripesOverlaidView.swift; sourceTree = ""; };
238DB6E925EB3A2E009197AB /* TabletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabletView.swift; sourceTree = ""; };
23905CA02B5B5D00007B7769 /* CircularProgress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularProgress.swift; sourceTree = ""; };
239DE4032BA177D300E13208 /* CircularGaugeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularGaugeView.swift; sourceTree = ""; };
23BC66852B0DD82A0064FF62 /* ActivityGridView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityGridView.swift; sourceTree = ""; };
23D10B0F25ED9EB500BBE555 /* AttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedString.swift; sourceTree = ""; };
23E2278923B5846E00E59122 /* macOS Table Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "macOS Table Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
23E2278B23B5846E00E59122 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
23E2278D23B5846E00E59122 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
23E2278F23B5846E00E59122 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23E2279223B5846E00E59122 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
23E2279423B5846E00E59122 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
23E2279523B5846E00E59122 /* macOS_Table_Demo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS_Table_Demo.entitlements; sourceTree = ""; };
23FC80FF25DB3FC40047A800 /* ReportView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportView.swift; sourceTree = ""; };
23FC811F25DB47A20047A800 /* WinLossGraphContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WinLossGraphContentView.swift; sourceTree = ""; };
23FC813F25DB4E5D0047A800 /* LineDemoContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineDemoContentView.swift; sourceTree = ""; };
23FC817425DB547E0047A800 /* StackLineDemoContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackLineDemoContentView.swift; sourceTree = ""; };
23FC819225DB586B0047A800 /* PieGraphDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PieGraphDemoView.swift; sourceTree = ""; };
23FC819E25DB59160047A800 /* DataBarGraphContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBarGraphContent.swift; sourceTree = ""; };
23FC81B325DB59E70047A800 /* DotGraphView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotGraphView.swift; sourceTree = ""; };
23FC81DC25DB60760047A800 /* StripesDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripesDemoView.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
23102B4D23B0245A001A621D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B006F72DDD61C700D074A0 /* DSFSparkline in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BBE23B02AED001A621D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B006FF2DDD63E900D074A0 /* DSFSparkline-shared in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BD623B02B0A001A621D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B006F92DDD61FB00D074A0 /* DSFSparkline-shared in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BEE23B02B32001A621D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B007032DDD652800D074A0 /* DSFSparkline in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57ED25C1392B00F2BACF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B007072DDD65B800D074A0 /* DSFSparkline-shared in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57F425C1392B00F2BACF /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B007052DDD65A800D074A0 /* DSFSparkline in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23E2278623B5846E00E59122 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
23B006FD2DDD625E00D074A0 /* DSFSparkline in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
23102B4423B0165B001A621D = {
isa = PBXGroup;
children = (
23E2278A23B5846E00E59122 /* macOS Table Demo */,
23102B5223B0245A001A621D /* macOS Sparkline Demo */,
23102BDA23B02B0A001A621D /* macOS Sparkline Demo Objc */,
23102BC223B02AED001A621D /* iOS Sparkline Demo */,
23102BF223B02B32001A621D /* tvOS Sparkline Demo */,
238423FD25C74EE400A2A861 /* SwiftUI Sparkline Crossplatform */,
23B006F52DDD61C700D074A0 /* Frameworks */,
23102B5123B0245A001A621D /* Products */,
);
sourceTree = "";
};
23102B5123B0245A001A621D /* Products */ = {
isa = PBXGroup;
children = (
23102B5023B0245A001A621D /* macOS Sparkline Demo.app */,
23102BC123B02AED001A621D /* iOS Sparkline Demo.app */,
23102BD923B02B0A001A621D /* macOS Sparkline Demo Objc.app */,
23102BF123B02B32001A621D /* tvOS Sparkline Demo.app */,
23E2278923B5846E00E59122 /* macOS Table Demo.app */,
236A57F025C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS).app */,
236A57F725C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS).app */,
);
name = Products;
sourceTree = "";
};
23102B5223B0245A001A621D /* macOS Sparkline Demo */ = {
isa = PBXGroup;
children = (
23102B5323B0245A001A621D /* AppDelegate.swift */,
23102B5523B0245A001A621D /* ViewController.swift */,
23102C0323B03315001A621D /* cpuUsage.swift */,
23102B5723B0245B001A621D /* Assets.xcassets */,
23102B5923B0245B001A621D /* Main.storyboard */,
23102B5C23B0245B001A621D /* Info.plist */,
23102B5D23B0245B001A621D /* macOS_Sparkline_Demo.entitlements */,
);
path = "macOS Sparkline Demo";
sourceTree = "";
};
23102BC223B02AED001A621D /* iOS Sparkline Demo */ = {
isa = PBXGroup;
children = (
23529A5825E847B900DFA52D /* iOS Sparkline Demo.entitlements */,
23102BC323B02AED001A621D /* AppDelegate.swift */,
23102BC523B02AED001A621D /* SceneDelegate.swift */,
23102BC723B02AED001A621D /* ViewController.swift */,
23102BC923B02AED001A621D /* Main.storyboard */,
23102BCC23B02AEE001A621D /* Assets.xcassets */,
23102BCE23B02AEE001A621D /* LaunchScreen.storyboard */,
23102BD123B02AEE001A621D /* Info.plist */,
);
path = "iOS Sparkline Demo";
sourceTree = "";
};
23102BDA23B02B0A001A621D /* macOS Sparkline Demo Objc */ = {
isa = PBXGroup;
children = (
23102BDB23B02B0A001A621D /* AppDelegate.h */,
23102BDC23B02B0A001A621D /* AppDelegate.m */,
23102BDE23B02B0A001A621D /* ViewController.h */,
23102BDF23B02B0A001A621D /* ViewController.m */,
23102BE123B02B0B001A621D /* Assets.xcassets */,
23102BE323B02B0B001A621D /* Main.storyboard */,
23102BE623B02B0B001A621D /* Info.plist */,
23102BE723B02B0B001A621D /* main.m */,
23102BE923B02B0B001A621D /* macOS_Sparkline_Demo_Objc.entitlements */,
);
path = "macOS Sparkline Demo Objc";
sourceTree = "";
};
23102BF223B02B32001A621D /* tvOS Sparkline Demo */ = {
isa = PBXGroup;
children = (
23102BF323B02B32001A621D /* AppDelegate.swift */,
23102BF523B02B32001A621D /* ViewController.swift */,
23102BF723B02B32001A621D /* Main.storyboard */,
23102BFA23B02B33001A621D /* Assets.xcassets */,
23102BFC23B02B33001A621D /* LaunchScreen.storyboard */,
23102BFF23B02B33001A621D /* Info.plist */,
);
path = "tvOS Sparkline Demo";
sourceTree = "";
};
232BAF6B25EB16000090C447 /* SwiftUI-Overlays */ = {
isa = PBXGroup;
children = (
2346EF2F25E8D13A00050570 /* SwiftUIContentView.swift */,
238053E425DCD14600B996B5 /* OverlayView.swift */,
232BAF6C25EB16210090C447 /* SwiftUIView.swift */,
238DB6DB25EB2141009197AB /* StripesOverlaidView.swift */,
232D9ECD25EC7133004DFAFC /* SuperCoolLineSpark.swift */,
);
path = "SwiftUI-Overlays";
sourceTree = "";
};
236A57E825C1392A00F2BACF /* Shared */ = {
isa = PBXGroup;
children = (
238053C625DCC2B100B996B5 /* ActiveView.swift */,
23BC66852B0DD82A0064FF62 /* ActivityGridView.swift */,
236A57EB25C1392B00F2BACF /* Assets.xcassets */,
23D10B0F25ED9EB500BBE555 /* AttributedString.swift */,
239DE4032BA177D300E13208 /* CircularGaugeView.swift */,
23905CA02B5B5D00007B7769 /* CircularProgress.swift */,
237B824625E77B3800B3DE23 /* BarDemoContentView.swift */,
237B827725E7891D00B3DE23 /* BitmapGenerationView.swift */,
236A57EA25C1392A00F2BACF /* ContentView.swift */,
23FC819E25DB59160047A800 /* DataBarGraphContent.swift */,
23FC81B325DB59E70047A800 /* DotGraphView.swift */,
23529A7325E84D2A00DFA52D /* Extensions.swift */,
23FC813F25DB4E5D0047A800 /* LineDemoContentView.swift */,
235A585A2603181A00266642 /* PercentBarView.swift */,
23FC819225DB586B0047A800 /* PieGraphDemoView.swift */,
23FC80FF25DB3FC40047A800 /* ReportView.swift */,
23FC817425DB547E0047A800 /* StackLineDemoContentView.swift */,
23FC81DC25DB60760047A800 /* StripesDemoView.swift */,
236A57E925C1392A00F2BACF /* SwiftUI_Sparkline_DemoApp.swift */,
232BAF6B25EB16000090C447 /* SwiftUI-Overlays */,
238DB6E925EB3A2E009197AB /* TabletView.swift */,
23FC811F25DB47A20047A800 /* WinLossGraphContentView.swift */,
236417E629628C0600C8A1D2 /* WiperGaugeDemoView.swift */,
2322C06A262651D5007D8112 /* TestingView.swift */,
);
path = Shared;
sourceTree = "";
};
236A57F125C1392B00F2BACF /* iOS */ = {
isa = PBXGroup;
children = (
236A57F225C1392B00F2BACF /* Info.plist */,
);
path = iOS;
sourceTree = "";
};
236A57F825C1392B00F2BACF /* macOS */ = {
isa = PBXGroup;
children = (
236A57F925C1392B00F2BACF /* Info.plist */,
236A57FA25C1392B00F2BACF /* macOS.entitlements */,
);
path = macOS;
sourceTree = "";
};
238423FD25C74EE400A2A861 /* SwiftUI Sparkline Crossplatform */ = {
isa = PBXGroup;
children = (
236A57E825C1392A00F2BACF /* Shared */,
236A57F125C1392B00F2BACF /* iOS */,
236A57F825C1392B00F2BACF /* macOS */,
);
path = "SwiftUI Sparkline Crossplatform";
sourceTree = "";
};
23B006F52DDD61C700D074A0 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "";
};
23E2278A23B5846E00E59122 /* macOS Table Demo */ = {
isa = PBXGroup;
children = (
23E2278B23B5846E00E59122 /* AppDelegate.swift */,
23E2278D23B5846E00E59122 /* ViewController.swift */,
23E2278F23B5846E00E59122 /* Assets.xcassets */,
23E2279123B5846E00E59122 /* Main.storyboard */,
23E2279423B5846E00E59122 /* Info.plist */,
23E2279523B5846E00E59122 /* macOS_Table_Demo.entitlements */,
232F732223B5BE2A00FA3A3F /* GridViewController.swift */,
);
path = "macOS Table Demo";
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
23102B4F23B0245A001A621D /* macOS Sparkline Demo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23102B5E23B0245B001A621D /* Build configuration list for PBXNativeTarget "macOS Sparkline Demo" */;
buildPhases = (
23102B4C23B0245A001A621D /* Sources */,
23102B4D23B0245A001A621D /* Frameworks */,
23102B4E23B0245A001A621D /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "macOS Sparkline Demo";
productName = "macOS Sparkline Demo";
productReference = 23102B5023B0245A001A621D /* macOS Sparkline Demo.app */;
productType = "com.apple.product-type.application";
};
23102BC023B02AED001A621D /* iOS Sparkline Demo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23102BD423B02AEE001A621D /* Build configuration list for PBXNativeTarget "iOS Sparkline Demo" */;
buildPhases = (
23102BBD23B02AED001A621D /* Sources */,
23102BBE23B02AED001A621D /* Frameworks */,
23102BBF23B02AED001A621D /* Resources */,
23B007012DDD63E900D074A0 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "iOS Sparkline Demo";
productName = "iOS Sparkline Demo";
productReference = 23102BC123B02AED001A621D /* iOS Sparkline Demo.app */;
productType = "com.apple.product-type.application";
};
23102BD823B02B0A001A621D /* macOS Sparkline Demo Objc */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23102BEA23B02B0B001A621D /* Build configuration list for PBXNativeTarget "macOS Sparkline Demo Objc" */;
buildPhases = (
23102BD523B02B0A001A621D /* Sources */,
23102BD623B02B0A001A621D /* Frameworks */,
23102BD723B02B0A001A621D /* Resources */,
23B006FB2DDD61FB00D074A0 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "macOS Sparkline Demo Objc";
productName = "macOS Sparkline Demo Objc";
productReference = 23102BD923B02B0A001A621D /* macOS Sparkline Demo Objc.app */;
productType = "com.apple.product-type.application";
};
23102BF023B02B32001A621D /* tvOS Sparkline Demo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23102C0023B02B33001A621D /* Build configuration list for PBXNativeTarget "tvOS Sparkline Demo" */;
buildPhases = (
23102BED23B02B32001A621D /* Sources */,
23102BEE23B02B32001A621D /* Frameworks */,
23102BEF23B02B32001A621D /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "tvOS Sparkline Demo";
productName = "tvOS Sparkline Demo";
productReference = 23102BF123B02B32001A621D /* tvOS Sparkline Demo.app */;
productType = "com.apple.product-type.application";
};
236A57EF25C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 236A580525C1392B00F2BACF /* Build configuration list for PBXNativeTarget "SwiftUI Sparkline Demo (iOS)" */;
buildPhases = (
236A57EC25C1392B00F2BACF /* Sources */,
236A57ED25C1392B00F2BACF /* Frameworks */,
236A57EE25C1392B00F2BACF /* Resources */,
23B007092DDD65B800D074A0 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "SwiftUI Sparkline Demo (iOS)";
productName = "SwiftUI Sparkline Demo (iOS)";
productReference = 236A57F025C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS).app */;
productType = "com.apple.product-type.application";
};
236A57F625C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS) */ = {
isa = PBXNativeTarget;
buildConfigurationList = 236A580625C1392B00F2BACF /* Build configuration list for PBXNativeTarget "SwiftUI Sparkline Demo (macOS)" */;
buildPhases = (
236A57F325C1392B00F2BACF /* Sources */,
236A57F425C1392B00F2BACF /* Frameworks */,
236A57F525C1392B00F2BACF /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "SwiftUI Sparkline Demo (macOS)";
productName = "SwiftUI Sparkline Demo (macOS)";
productReference = 236A57F725C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS).app */;
productType = "com.apple.product-type.application";
};
23E2278823B5846E00E59122 /* macOS Table Demo */ = {
isa = PBXNativeTarget;
buildConfigurationList = 23E2279823B5846E00E59122 /* Build configuration list for PBXNativeTarget "macOS Table Demo" */;
buildPhases = (
23E2278523B5846E00E59122 /* Sources */,
23E2278623B5846E00E59122 /* Frameworks */,
23E2278723B5846E00E59122 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "macOS Table Demo";
productName = "macOS Table Demo";
productReference = 23E2278923B5846E00E59122 /* macOS Table Demo.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
23102B4523B0165B001A621D /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1240;
LastUpgradeCheck = 1130;
TargetAttributes = {
23102B4F23B0245A001A621D = {
CreatedOnToolsVersion = 11.3;
};
23102BC023B02AED001A621D = {
CreatedOnToolsVersion = 11.3;
};
23102BD823B02B0A001A621D = {
CreatedOnToolsVersion = 11.3;
};
23102BF023B02B32001A621D = {
CreatedOnToolsVersion = 11.3;
};
236A57EF25C1392B00F2BACF = {
CreatedOnToolsVersion = 12.4;
};
236A57F625C1392B00F2BACF = {
CreatedOnToolsVersion = 12.4;
};
239716B923B554DD0034A4D4 = {
CreatedOnToolsVersion = 11.3;
};
23E2278823B5846E00E59122 = {
CreatedOnToolsVersion = 11.3;
};
};
};
buildConfigurationList = 23102B4823B0165B001A621D /* Build configuration list for PBXProject "Demos" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 23102B4423B0165B001A621D;
packageReferences = (
23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */,
);
productRefGroup = 23102B5123B0245A001A621D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
239716B923B554DD0034A4D4 /* All Demos */,
23102B4F23B0245A001A621D /* macOS Sparkline Demo */,
23102BD823B02B0A001A621D /* macOS Sparkline Demo Objc */,
23E2278823B5846E00E59122 /* macOS Table Demo */,
23102BC023B02AED001A621D /* iOS Sparkline Demo */,
23102BF023B02B32001A621D /* tvOS Sparkline Demo */,
236A57EF25C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS) */,
236A57F625C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS) */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
23102B4E23B0245A001A621D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102B5823B0245B001A621D /* Assets.xcassets in Resources */,
23102B5B23B0245B001A621D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BBF23B02AED001A621D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BD023B02AEE001A621D /* LaunchScreen.storyboard in Resources */,
23102BCD23B02AEE001A621D /* Assets.xcassets in Resources */,
23102BCB23B02AED001A621D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BD723B02B0A001A621D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BE223B02B0B001A621D /* Assets.xcassets in Resources */,
23102BE523B02B0B001A621D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BEF23B02B32001A621D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BFE23B02B33001A621D /* LaunchScreen.storyboard in Resources */,
23102BFB23B02B33001A621D /* Assets.xcassets in Resources */,
23102BF923B02B32001A621D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57EE25C1392B00F2BACF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
236A57FF25C1392B00F2BACF /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57F525C1392B00F2BACF /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
236A580025C1392B00F2BACF /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23E2278723B5846E00E59122 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23E2279023B5846E00E59122 /* Assets.xcassets in Resources */,
23E2279323B5846E00E59122 /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
23102B4C23B0245A001A621D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102B5623B0245A001A621D /* ViewController.swift in Sources */,
23102C0423B03315001A621D /* cpuUsage.swift in Sources */,
23102B5423B0245A001A621D /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BBD23B02AED001A621D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BC823B02AED001A621D /* ViewController.swift in Sources */,
23102BC423B02AED001A621D /* AppDelegate.swift in Sources */,
23102BC623B02AED001A621D /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BD523B02B0A001A621D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BE023B02B0A001A621D /* ViewController.m in Sources */,
23102BE823B02B0B001A621D /* main.m in Sources */,
23102BDD23B02B0A001A621D /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23102BED23B02B32001A621D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23102BF623B02B32001A621D /* ViewController.swift in Sources */,
23102BF423B02B32001A621D /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57EC25C1392B00F2BACF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
238053C725DCC2B100B996B5 /* ActiveView.swift in Sources */,
238DB6EA25EB3A2E009197AB /* TabletView.swift in Sources */,
237B827825E7891E00B3DE23 /* BitmapGenerationView.swift in Sources */,
236417E729628C0600C8A1D2 /* WiperGaugeDemoView.swift in Sources */,
235A585B2603181A00266642 /* PercentBarView.swift in Sources */,
2322C06B262651D5007D8112 /* TestingView.swift in Sources */,
232BAF6D25EB16210090C447 /* SwiftUIView.swift in Sources */,
2346EF3025E8D13A00050570 /* SwiftUIContentView.swift in Sources */,
23FC81DD25DB60760047A800 /* StripesDemoView.swift in Sources */,
23FC819F25DB59160047A800 /* DataBarGraphContent.swift in Sources */,
23FC817525DB547E0047A800 /* StackLineDemoContentView.swift in Sources */,
238DB6DC25EB2141009197AB /* StripesOverlaidView.swift in Sources */,
237B824725E77B3800B3DE23 /* BarDemoContentView.swift in Sources */,
23905CA12B5B5D00007B7769 /* CircularProgress.swift in Sources */,
236A57FD25C1392B00F2BACF /* ContentView.swift in Sources */,
236A57FB25C1392B00F2BACF /* SwiftUI_Sparkline_DemoApp.swift in Sources */,
23BC66862B0DD82A0064FF62 /* ActivityGridView.swift in Sources */,
23D10B1025ED9EB500BBE555 /* AttributedString.swift in Sources */,
232D9ECE25EC7133004DFAFC /* SuperCoolLineSpark.swift in Sources */,
239DE4042BA177D300E13208 /* CircularGaugeView.swift in Sources */,
23FC810025DB3FC50047A800 /* ReportView.swift in Sources */,
23FC814025DB4E5D0047A800 /* LineDemoContentView.swift in Sources */,
23529A7425E84D2A00DFA52D /* Extensions.swift in Sources */,
23FC812025DB47A20047A800 /* WinLossGraphContentView.swift in Sources */,
238053E525DCD14600B996B5 /* OverlayView.swift in Sources */,
23FC81B425DB59E70047A800 /* DotGraphView.swift in Sources */,
23FC819325DB586B0047A800 /* PieGraphDemoView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
236A57F325C1392B00F2BACF /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
238053C825DCC2B100B996B5 /* ActiveView.swift in Sources */,
238DB6EB25EB3A2F009197AB /* TabletView.swift in Sources */,
237B827925E7891E00B3DE23 /* BitmapGenerationView.swift in Sources */,
236417E829628C0600C8A1D2 /* WiperGaugeDemoView.swift in Sources */,
235A585C2603181A00266642 /* PercentBarView.swift in Sources */,
2322C06C262651D5007D8112 /* TestingView.swift in Sources */,
232BAF6E25EB16210090C447 /* SwiftUIView.swift in Sources */,
2346EF3125E8D13A00050570 /* SwiftUIContentView.swift in Sources */,
23FC81DE25DB60760047A800 /* StripesDemoView.swift in Sources */,
23FC81A025DB59160047A800 /* DataBarGraphContent.swift in Sources */,
23FC817625DB547E0047A800 /* StackLineDemoContentView.swift in Sources */,
238DB6DD25EB2141009197AB /* StripesOverlaidView.swift in Sources */,
237B824825E77B3800B3DE23 /* BarDemoContentView.swift in Sources */,
23905CA22B5B5D00007B7769 /* CircularProgress.swift in Sources */,
236A57FE25C1392B00F2BACF /* ContentView.swift in Sources */,
236A57FC25C1392B00F2BACF /* SwiftUI_Sparkline_DemoApp.swift in Sources */,
23BC66872B0DD82A0064FF62 /* ActivityGridView.swift in Sources */,
23D10B1125ED9EB500BBE555 /* AttributedString.swift in Sources */,
232D9ECF25EC7133004DFAFC /* SuperCoolLineSpark.swift in Sources */,
239DE4052BA177D300E13208 /* CircularGaugeView.swift in Sources */,
23FC810125DB3FC50047A800 /* ReportView.swift in Sources */,
23FC814125DB4E5D0047A800 /* LineDemoContentView.swift in Sources */,
23529A7525E84D2A00DFA52D /* Extensions.swift in Sources */,
23FC812125DB47A20047A800 /* WinLossGraphContentView.swift in Sources */,
238053E625DCD14600B996B5 /* OverlayView.swift in Sources */,
23FC81B525DB59E70047A800 /* DotGraphView.swift in Sources */,
23FC819425DB586B0047A800 /* PieGraphDemoView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
23E2278523B5846E00E59122 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
23E2278E23B5846E00E59122 /* ViewController.swift in Sources */,
23E2278C23B5846E00E59122 /* AppDelegate.swift in Sources */,
232F732323B5BE2A00FA3A3F /* GridViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
23369E8525D65B2D00A40023 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 236A57EF25C1392B00F2BACF /* SwiftUI Sparkline Demo (iOS) */;
targetProxy = 23369E8425D65B2D00A40023 /* PBXContainerItemProxy */;
};
23369E8725D65B2D00A40023 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 236A57F625C1392B00F2BACF /* SwiftUI Sparkline Demo (macOS) */;
targetProxy = 23369E8625D65B2D00A40023 /* PBXContainerItemProxy */;
};
239716BE23B554E50034A4D4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 23102B4F23B0245A001A621D /* macOS Sparkline Demo */;
targetProxy = 239716BD23B554E50034A4D4 /* PBXContainerItemProxy */;
};
239716C023B554E70034A4D4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 23102BC023B02AED001A621D /* iOS Sparkline Demo */;
targetProxy = 239716BF23B554E70034A4D4 /* PBXContainerItemProxy */;
};
239716C223B554E80034A4D4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 23102BD823B02B0A001A621D /* macOS Sparkline Demo Objc */;
targetProxy = 239716C123B554E80034A4D4 /* PBXContainerItemProxy */;
};
23E2279A23B5A6EF00E59122 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 23E2278823B5846E00E59122 /* macOS Table Demo */;
targetProxy = 23E2279923B5A6EF00E59122 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
23102B5923B0245B001A621D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102B5A23B0245B001A621D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
23102BC923B02AED001A621D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102BCA23B02AED001A621D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
23102BCE23B02AEE001A621D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102BCF23B02AEE001A621D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
23102BE323B02B0B001A621D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102BE423B02B0B001A621D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
23102BF723B02B32001A621D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102BF823B02B32001A621D /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
23102BFC23B02B33001A621D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
23102BFD23B02B33001A621D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "";
};
23E2279123B5846E00E59122 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
23E2279223B5846E00E59122 /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
23102B4923B0165B001A621D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Debug;
};
23102B4A23B0165B001A621D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
};
name = Release;
};
23102B5F23B0245B001A621D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Sparkline Demo/macOS_Sparkline_Demo.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
23102B6023B0245B001A621D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Sparkline Demo/macOS_Sparkline_Demo.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = Release;
};
23102BD223B02AEE001A621D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "iOS Sparkline Demo/iOS Sparkline Demo.entitlements";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "iOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.iOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
};
name = Debug;
};
23102BD323B02AEE001A621D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "iOS Sparkline Demo/iOS Sparkline Demo.entitlements";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "iOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.iOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SUPPORTS_MACCATALYST = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2,6";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
23102BEB23B02B0B001A621D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Sparkline Demo Objc/macOS_Sparkline_Demo_Objc.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Sparkline Demo Objc/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Sparkline-Demo-Objc";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
};
name = Debug;
};
23102BEC23B02B0B001A621D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Sparkline Demo Objc/macOS_Sparkline_Demo_Objc.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Sparkline Demo Objc/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Sparkline-Demo-Objc";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
};
name = Release;
};
23102C0123B02B33001A621D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "tvOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.tvOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = appletvos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 14.0;
};
name = Debug;
};
23102C0223B02B33001A621D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "tvOS Sparkline Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.tvOS-Sparkline-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = appletvos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 3;
TVOS_DEPLOYMENT_TARGET = 14.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
236A580125C1392B00F2BACF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_PREVIEWS = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "SwiftUI Sparkline Crossplatform/iOS/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.SwiftUI-Sparkline-Demo";
PRODUCT_NAME = "SwiftUI Sparkline Demo (iOS)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
236A580225C1392B00F2BACF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_PREVIEWS = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "SwiftUI Sparkline Crossplatform/iOS/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.SwiftUI-Sparkline-Demo";
PRODUCT_NAME = "SwiftUI Sparkline Demo (iOS)";
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
236A580325C1392B00F2BACF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "SwiftUI Sparkline Crossplatform/macOS/macOS.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "SwiftUI Sparkline Crossplatform/macOS/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.SwiftUI-Sparkline-Demo";
PRODUCT_NAME = "SwiftUI Sparkline Demo (macOS)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
236A580425C1392B00F2BACF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "SwiftUI Sparkline Crossplatform/macOS/macOS.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_PREVIEWS = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "SwiftUI Sparkline Crossplatform/macOS/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 14.6;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.SwiftUI-Sparkline-Demo";
PRODUCT_NAME = "SwiftUI Sparkline Demo (macOS)";
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = Release;
};
239716BA23B554DD0034A4D4 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
239716BB23B554DD0034A4D4 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
23E2279623B5846E00E59122 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Table Demo/macOS_Table_Demo.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Table Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Table-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
23E2279723B5846E00E59122 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "macOS Table Demo/macOS_Table_Demo.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 3L6RK3LGGW;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
INFOPLIST_FILE = "macOS Table Demo/Info.plist";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.sparklines.macOS-Table-Demo";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
23102B4823B0165B001A621D /* Build configuration list for PBXProject "Demos" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23102B4923B0165B001A621D /* Debug */,
23102B4A23B0165B001A621D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23102B5E23B0245B001A621D /* Build configuration list for PBXNativeTarget "macOS Sparkline Demo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23102B5F23B0245B001A621D /* Debug */,
23102B6023B0245B001A621D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23102BD423B02AEE001A621D /* Build configuration list for PBXNativeTarget "iOS Sparkline Demo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23102BD223B02AEE001A621D /* Debug */,
23102BD323B02AEE001A621D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23102BEA23B02B0B001A621D /* Build configuration list for PBXNativeTarget "macOS Sparkline Demo Objc" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23102BEB23B02B0B001A621D /* Debug */,
23102BEC23B02B0B001A621D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23102C0023B02B33001A621D /* Build configuration list for PBXNativeTarget "tvOS Sparkline Demo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23102C0123B02B33001A621D /* Debug */,
23102C0223B02B33001A621D /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
236A580525C1392B00F2BACF /* Build configuration list for PBXNativeTarget "SwiftUI Sparkline Demo (iOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
236A580125C1392B00F2BACF /* Debug */,
236A580225C1392B00F2BACF /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
236A580625C1392B00F2BACF /* Build configuration list for PBXNativeTarget "SwiftUI Sparkline Demo (macOS)" */ = {
isa = XCConfigurationList;
buildConfigurations = (
236A580325C1392B00F2BACF /* Debug */,
236A580425C1392B00F2BACF /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
239716BC23B554DD0034A4D4 /* Build configuration list for PBXAggregateTarget "All Demos" */ = {
isa = XCConfigurationList;
buildConfigurations = (
239716BA23B554DD0034A4D4 /* Debug */,
239716BB23B554DD0034A4D4 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
23E2279823B5846E00E59122 /* Build configuration list for PBXNativeTarget "macOS Table Demo" */ = {
isa = XCConfigurationList;
buildConfigurations = (
23E2279623B5846E00E59122 /* Debug */,
23E2279723B5846E00E59122 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../../../DSFSparkline;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
23B006F62DDD61C700D074A0 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = DSFSparkline;
};
23B006F82DDD61FB00D074A0 /* DSFSparkline-shared */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = "DSFSparkline-shared";
};
23B006FC2DDD625E00D074A0 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = DSFSparkline;
};
23B006FE2DDD63E900D074A0 /* DSFSparkline-shared */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = "DSFSparkline-shared";
};
23B007022DDD652800D074A0 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = DSFSparkline;
};
23B007042DDD65A800D074A0 /* DSFSparkline */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = DSFSparkline;
};
23B007062DDD65B800D074A0 /* DSFSparkline-shared */ = {
isa = XCSwiftPackageProductDependency;
package = 23B006F42DDD61A400D074A0 /* XCLocalSwiftPackageReference "../../../DSFSparkline" */;
productName = "DSFSparkline-shared";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 23102B4523B0165B001A621D /* Project object */;
}
================================================
FILE: Demos/Samples/Demos.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/All Demos.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/SwiftUI Sparkline Demo (iOS).xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/SwiftUI Sparkline Demo (macOS).xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/iOS Sparkline Demo.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/macOS Sparkline Demo Objc.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/macOS Sparkline Demo.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/macOS Table Demo.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcshareddata/xcschemes/tvOS Sparkline Demo.xcscheme
================================================
================================================
FILE: Demos/Samples/Demos.xcodeproj/xcuserdata/dford.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
================================================
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/ActiveView.swift
================================================
//
// ActiveView.swift
// Demos
//
// Created by Darren Ford on 17/2/21.
//
import SwiftUI
import DSFSparkline
struct UpperGraph: View {
let label: String
let dataSource: DSFSparkline.DataSource
let graphColor: DSFColor
let showZeroLine: Bool
var zeroLineDefinition: DSFSparkline.ZeroLineDefinition = .shared
let interpolated: Bool
let lineShading: Bool
var shadowed: Bool = false
var body: some View {
DSFSparklineLineGraphView.SwiftUI(dataSource: dataSource,
graphColor: graphColor,
interpolated: interpolated,
lineShading: lineShading,
shadowed: shadowed,
showZeroLine: showZeroLine,
zeroLineDefinition: self.zeroLineDefinition)
.background(
Rectangle()
.fill(Color(.displayP3, white: 1.0, opacity: 0.1))
.shadow(color: .black, radius: 8, x: 4, y: -4)
)
.clipShape(RoundedRectangle(cornerRadius: 8))
.padding(4)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(.displayP3, white: 0.5, opacity: 0.1))
.shadow(color: .black, radius: 8, x: 4, y: -4)
)
.overlay(
VStack(alignment: .leading, spacing: nil, content: {
Text(self.label)
.shadow(color: .black, radius: 1)
Color.clear
}).padding(6), alignment: .leading
)
}
}
var PreviewUpperGraphDataSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: 0.0 ... 100.0, zeroLineValue: 25)
d.push(values: [20, 77, 90, 22, 4, 16, 66, 99, 88, 44])
return d
}()
struct UpperGraph_Previews: PreviewProvider {
static var previews: some View {
UpperGraph(label: "Testing",
dataSource: PreviewUpperGraphDataSource,
graphColor: DSFColor.systemOrange,
showZeroLine: true,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition(),
interpolated: true,
lineShading: true,
shadowed: true)
}
}
/////////////
var PreviewGlobalDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: 0 ... 1.0)
d.push(values: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1])
return d
}()
var PreviewGlobalDataSource2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: -1.0 ... 1.0)
d.push(values: [-0.5, -0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5])
return d
}()
var PreviewGlobalDataSource3: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0.0 ... 100.0)
d.push(values: [50, 40, 30, 20, 10, 0, 100, 90, 80, 70])
return d
}()
var PreviewGlobalDataSource4: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0.0 ... 100.0)
d.push(values: [20, 77, 90, 22, 4, 16, 66, 99, 88, 44])
return d
}()
var PreviewGlobalDataSource5: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0.0 ... 100.0)
d.push(values: [20, 77, 90, 22, 4, 16, 66, 99, 88, 44])
return d
}()
struct ActiveView_Previews: PreviewProvider {
static var previews: some View {
ActiveView(dataSource: DataSource(PreviewGlobalDataSource1,
PreviewGlobalDataSource2,
PreviewGlobalDataSource3,
PreviewGlobalDataSource4,
PreviewGlobalDataSource5)
)
}
}
func MakeActiveView() -> ActiveView {
return ActiveView(dataSource: globalSource)
}
struct ActiveView: View {
let dataSource: DataSource
let BigCyanZeroLine = DSFSparkline.ZeroLineDefinition(
color: .cyan,
lineWidth: 3,
lineDashStyle: [4, 1, 2, 1]
)
@State var selectedType = 1
let gradient = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: DSFColor.systemRed.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemOrange.cgColor, location: 1 / 6),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemYellow.cgColor, location: 2 / 6),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemGreen.cgColor, location: 3 / 6),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemBlue.cgColor, location: 4 / 6),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemIndigo.cgColor, location: 5 / 6),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemPurple.cgColor, location: 6 / 6),
])
var body: some View {
VStack {
HStack(alignment: .center, spacing: 8, content: {
UpperGraph(label: "Left", dataSource: dataSource.PreviewGlobalDataSource4, graphColor: DSFColor.systemOrange, showZeroLine: false, interpolated: false, lineShading: true).frame(height: 60)
UpperGraph(label: "Middle", dataSource: dataSource.PreviewGlobalDataSource4, graphColor: DSFColor.systemYellow, showZeroLine: true, interpolated: true, lineShading: true).frame(height: 60)
UpperGraph(label: "Right", dataSource: dataSource.PreviewGlobalDataSource4, graphColor: DSFColor.systemPurple, showZeroLine: false, interpolated: false, lineShading: false).frame(height: 60)
})
HStack {
VStack {
DSFSparklineBarGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource1,
graphColor: DSFColor.systemBlue,
barSpacing: 1,
showZeroLine: true,
showHighlightRange: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0 ..< 0.5,
fillColor: DSFColor.gray.withAlphaComponent(0.3).cgColor
),
])
.frame(height: 60)
DSFSparklineStripesGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource1,
barSpacing: 1,
gradient: self.gradient)
.frame(height: 60)
.padding(2)
}
DSFSparklineBarGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource2,
graphColor: DSFColor.systemGreen,
lineWidth: 2,
barSpacing: 2,
showZeroLine: true,
zeroLineDefinition: BigCyanZeroLine,
centeredAtZeroLine: true)
.frame(height: 60)
}
HStack {
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource3,
graphColor: DSFColor.systemBlue,
unsetGraphColor: DSFColor.darkGray.withAlphaComponent(0.2))
.frame(height: 60)
.padding(2)
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource3,
graphColor: DSFColor.systemRed,
unsetGraphColor: DSFColor.darkGray.withAlphaComponent(0.2),
upsideDown: true)
.frame(height: 60)
.padding(2)
VStack(alignment: .center, spacing: nil, content: {
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource3,
graphColor: DSFColor.systemGreen,
unsetGraphColor: DSFColor.darkGray.withAlphaComponent(0.2),
verticalDotCount: 10)
.frame(height: 60)
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource3,
graphColor: DSFColor.systemPink,
unsetGraphColor: DSFColor.darkGray.withAlphaComponent(0.2),
verticalDotCount: 10,
upsideDown: true)
.frame(height: 60)
})
}
#if os(macOS)
HStack {
Picker(selection: $selectedType, label: EmptyView()) {
Text("Line").tag(1)
Text("Line (Smooth)").tag(2)
Text("Bar").tag(3)
Text("Dot").tag(4)
Text("Stripes").tag(5)
}.pickerStyle(RadioGroupPickerStyle())
Group {
if self.selectedType == 1 {
DSFSparklineLineGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
showZeroLine: true)
}
else if self.selectedType == 2 {
DSFSparklineLineGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
interpolated: true,
showZeroLine: true)
}
else if self.selectedType == 3 {
DSFSparklineBarGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
barSpacing: 1,
showZeroLine: true)
}
else if self.selectedType == 4 {
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange)
}
else {
DSFSparklineStripesGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
barSpacing: 1, gradient: self.gradient)
}
}
.frame(height: 80)
.padding(2)
}
#else
VStack {
Picker(selection: $selectedType, label: EmptyView()) {
Text("Line").tag(1)
Text("Line (Smooth)").tag(2)
Text("Bar").tag(3)
Text("Dot").tag(4)
Text("Stripes").tag(5)
}
Group {
if self.selectedType == 1 {
DSFSparklineLineGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
showZeroLine: true)
}
else if self.selectedType == 2 {
DSFSparklineLineGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
interpolated: true,
showZeroLine: true)
}
else if self.selectedType == 3 {
DSFSparklineBarGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange,
barSpacing: 1,
showZeroLine: true)
}
else if self.selectedType == 4 {
DSFSparklineDotGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
graphColor: DSFColor.systemOrange)
}
else {
DSFSparklineStripesGraphView.SwiftUI(dataSource: dataSource.PreviewGlobalDataSource5,
barSpacing: 1, gradient: self.gradient)
}
}
.frame(height: 80)
.padding(2)
}
#endif
}.padding(20)
.onAppear {
self.dataSource.start()
}
.onDisappear {
self.dataSource.stop()
}
}
}
let globalSource = DataSource(
DSFSparkline.DataSource(windowSize: 30),
DSFSparkline.DataSource(range: -1.0 ... 1.0),
DSFSparkline.DataSource(range: -100 ... 100),
DSFSparkline.DataSource(range: 0 ... 100),
DSFSparkline.DataSource(range: 0 ... 100, zeroLineValue: 80)
)
class DataSource {
let PreviewGlobalDataSource1: DSFSparkline.DataSource
let PreviewGlobalDataSource2: DSFSparkline.DataSource
let PreviewGlobalDataSource3: DSFSparkline.DataSource
let PreviewGlobalDataSource4: DSFSparkline.DataSource
let PreviewGlobalDataSource5: DSFSparkline.DataSource
var shouldStop: Bool = false
init(_ d1: DSFSparkline.DataSource,
_ d2: DSFSparkline.DataSource,
_ d3: DSFSparkline.DataSource,
_ d4: DSFSparkline.DataSource,
_ d5: DSFSparkline.DataSource)
{
self.PreviewGlobalDataSource1 = d1
self.PreviewGlobalDataSource2 = d2
self.PreviewGlobalDataSource3 = d3
self.PreviewGlobalDataSource4 = d4
self.PreviewGlobalDataSource5 = d5
}
func start() {
self.shouldStop = false
self.PreviewGlobalDataSource3.windowSize = 100
self.PreviewGlobalDataSource4.windowSize = 40
_ = self.PreviewGlobalDataSource4.push(value: 50)
self.PreviewGlobalDataSource5.windowSize = 50
self.updateWithNewValues()
}
func stop() {
self.shouldStop = true
}
var sinusoid = 0.00
var lastSource4: CGFloat = 50.0
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let `self` = self else {
return
}
if self.shouldStop {
return
}
let val = sin(self.sinusoid)
self.sinusoid += 0.12
let cr = CGFloat(val)
_ = self.PreviewGlobalDataSource1.push(value: cr)
let cr2 = CGFloat.random(in: self.PreviewGlobalDataSource2.range!) // -1 ... 1)
_ = self.PreviewGlobalDataSource2.push(value: cr2)
let cr3 = CGFloat.random(in: self.PreviewGlobalDataSource3.range!) // -100 ... 100)
_ = self.PreviewGlobalDataSource3.push(value: cr3)
let cr4 = CGFloat.random(in: -20 ... 20)
let newVal = min(100, max(0, self.lastSource4 + cr4))
_ = self.PreviewGlobalDataSource4.push(value: newVal)
self.lastSource4 = newVal
let cr5 = CGFloat.random(in: self.PreviewGlobalDataSource5.range!)
_ = self.PreviewGlobalDataSource5.push(value: cr5)
self.updateWithNewValues()
}
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/ActivityGridView.swift
================================================
//
// TestingView.swift
// Demos
//
// Created by Darren Ford on 14/4/21.
//
import SwiftUI
import DSFSparkline
struct ActivityGridView: View {
@State var currentDate = Date()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
@State var inputData: [Double] = []
let cellStyleLight = DSFSparkline.ActivityGrid.CellStyle(fillScheme: DSFSparkline.ActivityGrid.CellStyle.DefaultLight)
let cellStyleDark = DSFSparkline.ActivityGrid.CellStyle(fillScheme: DSFSparkline.ActivityGrid.CellStyle.DefaultDark)
let gradient = DSFSparkline.GradientBucket(
colors: [
CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 1),
CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 1),
CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1),
]
)
let fiveColorGradient = DSFSparkline.GradientBucket(
colors: [
CGColor(red: 0.806, green: 0.164, blue: 0.287, alpha: 1.0),
CGColor(red: 0.978, green: 0.620, blue: 0.303, alpha: 1.0),
CGColor(red: 0.954, green: 0.431, blue: 0.221, alpha: 1.0),
CGColor(red: 0.935, green: 0.280, blue: 0.286, alpha: 1.0),
CGColor(red: 0.345, green: 0.155, blue: 0.259, alpha: 1.0),
]
)
let SmallerFullRange: [Double] = (0 ... 1000).map { _ in Double.random(in: 0 ... 1) }
@State var isLightMode: Bool = false
var body: some View {
VStack(spacing: 4) {
Toggle("Use Alternate Palette", isOn: $isLightMode)
Text("Github style").font(.title3)
DSFSparklineActivityGridView.SwiftUI(
values: inputData,
range: 0 ... 1,
cellStyle: isLightMode ? cellStyleLight : cellStyleDark
)
.tooltipStringForCell { index in
"Index[\(index)]"
}
.onReceive(timer) { input in
self.inputData.insert(Double.random(in: 0 ... 1), at: 0)
}
.border(.red)
Text("Defrag style").font(.title3)
DSFSparklineActivityGridView.SwiftUI(
values: inputData,
range: 0 ... 1,
cellStyle: isLightMode ? cellStyleLight : cellStyleDark,
layoutStyle: .defrag
)
.border(.red)
Divider()
.frame(maxWidth: .infinity)
VStack {
Text("Fixed row/column").font(.title3)
HStack {
DSFSparklineActivityGridView.SwiftUI(
values: inputData,
range: 0 ... 1,
verticalCellCount: 10,
horizontalCellCount: 7,
layoutStyle: .defrag,
fillScheme: isLightMode ? cellStyleLight.fillScheme : cellStyleDark.fillScheme,
borderColor: isLightMode ? .init(gray: 0, alpha: 0.2) : .init(gray: 1, alpha: 0.2),
borderWidth: 0.5
)
.border(.red)
DSFSparklineActivityGridView.SwiftUI(
values: inputData,
range: 0 ... 1,
verticalCellCount: 10,
horizontalCellCount: 7,
layoutStyle: .github,
fillScheme: isLightMode ? cellStyleLight.fillScheme : cellStyleDark.fillScheme,
borderColor: isLightMode ? .init(gray: 0, alpha: 0.2) : .init(gray: 1, alpha: 0.2),
borderWidth: 0.5
)
.border(.green)
DSFSparklineActivityGridView.SwiftUI(
values: SmallerFullRange,
range: 0 ... 1,
verticalCellCount: 7,
horizontalCellCount: 10,
cellStyle: .init(fillScheme: .init(gradient: gradient), cellDimension: 6, cellSpacing: 3),
layoutStyle: .defrag
)
.border(.blue)
DSFSparklineActivityGridView.SwiftUI(
values: SmallerFullRange,
range: 0 ... 1,
verticalCellCount: 7,
horizontalCellCount: 10,
cellStyle: .init(fillScheme: .init(gradient: gradient), cellDimension: 15, cellSpacing: 1),
layoutStyle: .github
)
.border(.yellow)
VStack(spacing: 0) {
HStack(spacing: 6) {
Text("S").font(.system(.caption, design: .monospaced))
Text("M").font(.system(.caption, design: .monospaced))
Text("T").font(.system(.caption, design: .monospaced))
Text("W").font(.system(.caption, design: .monospaced))
Text("T").font(.system(.caption, design: .monospaced))
Text("F").font(.system(.caption, design: .monospaced))
Text("S").font(.system(.caption, design: .monospaced))
}
.padding(3)
DSFSparklineActivityGridView.SwiftUI(
values: SmallerFullRange,
range: 0 ... 1,
verticalCellCount: 30,
horizontalCellCount: 7,
cellStyle: .init(fillScheme: .init(gradient: fiveColorGradient)),
layoutStyle: .github
)
.tooltipStringForCell { index in
"Tooltip[\(SmallerFullRange[index])]"
}
}
}
}
}
.padding()
}
}
struct ActivityGridView_Previews: PreviewProvider {
static var previews: some View {
ActivityGridView()
.frame(height: 1000)
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/AttributedString.swift
================================================
//
// AttributedString.swift
// Demos
//
// Appropriated from https://swiftui-lab.com/attributed-strings-with-swiftui/
// An NSTextView wrapped for SwiftUI containing an attributed string.
import SwiftUI
#if os(macOS)
import AppKit
struct AttributedText: View {
@State var size: CGSize = .zero
let attributedString: NSAttributedString
init(_ attributedString: NSAttributedString) {
self.attributedString = attributedString
}
var body: some View {
AttributedTextRepresentable(attributedString: attributedString, size: $size)
.frame(width: size.width, height: size.height)
}
struct AttributedTextRepresentable: NSViewRepresentable {
let attributedString: NSAttributedString
@Binding var size: CGSize
func makeNSView(context: Context) -> NSTextView {
let textView = NSTextView()
guard let textContainer = textView.textContainer else {
fatalError()
}
textContainer.widthTracksTextView = false
textContainer.containerSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.drawsBackground = false
return textView
}
func updateNSView(_ nsView: NSTextView, context: Context) {
nsView.textStorage?.setAttributedString(attributedString)
DispatchQueue.main.async {
if let textContainer = nsView.textContainer,
let layoutManager = textContainer.layoutManager {
layoutManager.ensureLayout(for: textContainer)
size = layoutManager.usedRect(for: textContainer).size
}
else {
size = .zero
}
// This doesn't work accurately.
//size = nsView.textStorage!.size()
}
}
}
}
#else
import UIKit
struct AttributedText: View {
@State private var size: CGSize = .zero
let attributedString: NSAttributedString
init(_ attributedString: NSAttributedString) {
self.attributedString = attributedString
}
var body: some View {
AttributedTextRepresentable(attributedString: attributedString, size: $size)
.frame(width: size.width, height: size.height)
}
struct AttributedTextRepresentable: UIViewRepresentable {
let attributedString: NSAttributedString
@Binding var size: CGSize
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.lineBreakMode = .byClipping
label.numberOfLines = 0
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.attributedText = attributedString
DispatchQueue.main.async {
size = uiView.sizeThatFits(uiView.superview?.bounds.size ?? .zero)
}
}
}
}
#endif
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/BarDemoContentView.swift
================================================
//
// StackLineDemoContentView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
struct BarBasic: View {
var body: some View {
Text("Bar")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.systemGray,
lineWidth: 2,
barSpacing: 2
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarZeroLine: View {
var body: some View {
Text("Bar with zero-line")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.systemPink,
lineWidth: 1,
barSpacing: 0,
showZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarCenteredZeroLine: View {
var body: some View {
Text("Bar centered around zero-line")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.systemBlue,
lineWidth: 2,
barSpacing: 4,
showZeroLine: true,
centeredAtZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarCenteredZeroLineColored: View {
var body: some View {
Text("Bar centered around zero-line, lower color")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.systemGreen,
lineWidth: 1,
//showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemRed
)
.frame(height: 60.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
private let primaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1),
CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 1),
])
private let secondaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 1),
CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 1),
])
struct BarCenteredZeroLineCustomFill: View {
var body: some View {
Text("Bar centered around zero-line, custom fill")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.black,
lineWidth: 1,
showZeroLine: true,
centeredAtZeroLine: true,
primaryFill: primaryFill,
secondaryFill: secondaryFill
)
.frame(height: 60.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarWithCustomFill: View {
var body: some View {
Text("Bar, custom fill")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource1,
graphColor: DSFColor.black,
lineWidth: 1,
primaryFill: primaryFill,
secondaryFill: secondaryFill
)
.frame(height: 60.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarRange: View {
var body: some View {
Text("Bar with range")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource2,
graphColor: DSFColor.systemGreen,
lineWidth: 1,
showZeroLine: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 20 ..< 80,
fillColor: DSFColor.systemGray.withAlphaComponent(0.2).cgColor
)
],
gridLines: .init(values: [0, 25, 50, 75, 100])
)
.frame(width: 250.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarNofill: View {
var body: some View {
Text("Bar no fill")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource3,
graphColor: DSFColor.systemYellow,
lineWidth: 3
)
.frame(width: 330.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarRange2: View {
var body: some View {
Text("Bar with range")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource4,
graphColor: DSFColor.systemRed,
lineWidth: 1,
showZeroLine: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0.3 ..< 0.7,
fillColor: DSFColor.systemPink.withAlphaComponent(0.1).cgColor
)
]
)
.frame(width: 330.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarCenteredZeroLine2: View {
var body: some View {
Text("Bar centered around zero-line")
DSFSparklineBarGraphView.SwiftUI(
dataSource: BarDataSource4,
graphColor: DSFColor.systemGreen,
lineWidth: 1,
showZeroLine: true,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition(
color: DSFColor.systemGray,
lineWidth: 1,
lineDashStyle: []),
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemPink
)
.frame(width: 100.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct BarDemoContentView: View {
var body: some View {
ScrollView {
VStack {
BarBasic()
BarZeroLine()
BarWithCustomFill()
BarCenteredZeroLine()
BarCenteredZeroLineColored()
BarCenteredZeroLineCustomFill()
BarRange()
BarNofill()
BarRange2()
BarCenteredZeroLine2()
}
}
}
}
struct BarDemoContentView_Previews: PreviewProvider {
static var previews: some View {
BarDemoContentView()
}
}
fileprivate var BarDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.4)
d.push(values: [
0.85, 0.04, 0.24, 0.13, 0.51, 0.93, 0.26, 0.69, 0.16, 0.39,
0.19, 0.12, 0.28, 0.42, 0.42, 0.48, 0.29, 0.05, 0.87, 0.28
])
return d
}()
fileprivate var BarDataSource2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 8, range: 0 ... 100)
d.push(values: [100, 0, 25, 50, 75, 10, 10, 88])
return d
}()
fileprivate var BarDataSource3: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 21, range: -1 ... 1)
var sins: CGFloat = 0.0
let r: Range = 0 ..< 21
let vars: [CGFloat] = r.map { sin(CGFloat($0)) }
d.push(values: vars)
return d
}()
fileprivate var BarDataSource4: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 21, range: 0 ... 1)
d.zeroLineValue = 0.5
d.push(values: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0])
return d
}()
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/BitmapGenerationView.swift
================================================
//
// BitmapGenerationView.swift
// Demos
//
// Created by Darren Ford on 25/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate let b1: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [1, 5, 3, 4], range: 0 ... 6)
let li = DSFSparklineOverlay.GridLines()
li.dataSource = dataSource
li.floatValues = [1, 3, 5]
li.strokeWidth = 0.5
li.dashStyle = [0.5, 0.5]
li.strokeColor = DSFColor.gray.withAlphaComponent(0.3).cgColor
b.addOverlay(li)
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.primaryTextColor.cgColor
l.primaryFill = DSFSparkline.Fill.Gradient(colors: [
DSFColor.systemRed.cgColor,
DSFColor.systemBlue.cgColor,
])
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b200: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [1, 5, 3, 4], range: 0 ... 6)
let li = DSFSparklineOverlay.GridLines()
li.dataSource = dataSource
li.floatValues = [1, 3, 5]
li.strokeWidth = 0.5
li.dashStyle = [0.5, 0.5]
li.strokeColor = DSFColor.gray.withAlphaComponent(0.3).cgColor
b.addOverlay(li)
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.primaryTextColor.cgColor
l.primaryFill = DSFSparkline.Fill.Gradient(colors: [
DSFColor.systemRed.cgColor,
DSFColor.systemBlue.cgColor,
], isHorizontal: true)
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b2: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [1, 5, 3, 4], range: 0 ... 6)
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.black.cgColor
l.strokeWidth = 1.0
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b3: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [1, 5, 3, 4])
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.systemPink.cgColor
l.strokeWidth = 1.0
l.shadow = NSShadow(blurRadius: 2.0, offset: CGSize(width: 1, height: -1), color: DSFColor.gray.withAlphaComponent(0.5))
l.markerSize = 6
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b4: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [1, 5, 3, 4])
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.systemPink.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemPink.withAlphaComponent(0.3).cgColor)
l.strokeWidth = 3.0
l.interpolated = true
l.markerSize = 6
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b5: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [4, 2, 9, 9, 0, 9, 0, 4])
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.systemIndigo.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemIndigo.withAlphaComponent(0.3).cgColor)
l.strokeWidth = 3.0
l.interpolated = true
l.markerSize = 6
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let b6: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let dataSource = DSFSparkline.DataSource(values: [4, 2, 9, 9, 0, 9, 0, 4])
let l = DSFSparklineOverlay.Line()
l.primaryStrokeColor = DSFColor.systemIndigo.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemIndigo.withAlphaComponent(0.3).cgColor)
l.strokeWidth = 3.0
l.interpolated = true
l.centeredAtZeroLine = true
l.markerSize = 6
dataSource.zeroLineValue = 5
l.dataSource = dataSource
l.secondaryStrokeColor = DSFColor.systemTeal.cgColor
l.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemTeal.withAlphaComponent(0.3).cgColor)
b.addOverlay(l)
return b
}()
fileprivate let tablet1: DSFSparklineSurface.Bitmap = {
let b = DSFSparklineSurface.Bitmap()
let l = DSFSparklineOverlay.Tablet()
l.winStrokeColor = DSFColor.systemGreen.withAlphaComponent(0.5).cgColor
l.winFill = DSFSparkline.Fill.Gradient(colors: [
DSFColor.systemGreen.withAlphaComponent(0.8).cgColor,
DSFColor.systemGreen.withAlphaComponent(0.2).cgColor
])
l.lossStrokeColor = DSFColor.systemRed.withAlphaComponent(0.5).cgColor
l.lossFill = DSFSparkline.Fill.Gradient(colors: [
DSFColor.systemRed.withAlphaComponent(0.2).cgColor,
DSFColor.systemRed.withAlphaComponent(0.6).cgColor
])
let dataSource = DSFSparkline.DataSource(
values: [-1, 5, 3, -7, -2, 2, 5, -1, 5, 3, -7, -2, 2, 5, -1, 5, 3, -7, -2, 2, 5])
l.dataSource = dataSource
b.addOverlay(l)
return b
}()
fileprivate let percentBar: DSFSparklineSurface.Bitmap = {
let bitmap = DSFSparklineSurface.Bitmap()
let percentbar = DSFSparklineOverlay.PercentBar(value: 0.42)
bitmap.addOverlay(percentbar)
// Generate an image with retina scale
return bitmap
}()
fileprivate let wiperGauge: DSFSparklineSurface.Bitmap = {
let bitmap = DSFSparklineSurface.Bitmap()
let wiperGauge = DSFSparklineOverlay.WiperGauge()
wiperGauge.value = 0.35
bitmap.addOverlay(wiperGauge)
// Generate an image with retina scale
return bitmap
}()
fileprivate let wiperGauge2: DSFSparklineSurface.Bitmap = {
let bitmap = DSFSparklineSurface.Bitmap()
let wiperGauge = DSFSparklineOverlay.WiperGauge()
wiperGauge.value = 0.8
wiperGauge.valueColor = .init(flatColor: CGColor(srgbRed: 0.3, green: 1, blue: 0.3, alpha: 1))
wiperGauge.valueBackgroundColor = CGColor(srgbRed: 1, green: 0.3, blue: 0.3, alpha: 1)
wiperGauge.gaugeUpperArcColor = CGColor(gray: 0, alpha: 1)
wiperGauge.gaugePointerColor = CGColor(gray: 0.3, alpha: 1)
wiperGauge.gaugeBackgroundColor = CGColor(srgbRed: 1, green: 1, blue: 0.4, alpha: 1)
bitmap.addOverlay(wiperGauge)
// Generate an image with retina scale
return bitmap
}()
struct BitmapGenerationView: View {
#if os(macOS)
func makeImage(_ nsImage: NSImage) -> Image {
return Image(nsImage: nsImage)
}
func generate(_ bitmap: DSFSparklineSurface.Bitmap, size: CGSize) -> NSImage {
guard let image = bitmap.image(size: size, scale: 2) else {
return NSImage(systemSymbolName: "exclamationmark.triangle.fill", accessibilityDescription: nil)!
}
return image
}
#else
func makeImage(_ uiImage: UIImage) -> Image {
return Image(uiImage: uiImage)
}
func generate(_ bitmap: DSFSparklineSurface.Bitmap, size: CGSize) -> UIImage {
guard let image = bitmap.image(size: size, scale: 2) else {
return UIImage(systemName: "exclamationmark.triangle.fill")!
}
return image
}
#endif
var body: some View {
VStack {
Text("Sparkline images created using DSFSparklineSurface.Bitmap")
VStack {
makeImage(self.generate(b1, size: CGSize(width: 200, height: 40)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(b200, size: CGSize(width: 200, height: 40)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(b2, size: CGSize(width: 200, height: 40)))
.padding(4)
.border(Color.gray)
}
Divider()
VStack {
makeImage(self.generate(b3, size: CGSize(width: 200, height: 40)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(b4, size: CGSize(width: 200, height: 40)))
.padding(4)
.border(Color.gray)
}
Divider()
HStack {
makeImage(self.generate(b5, size: CGSize(width: 100, height: 30)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(b6, size: CGSize(width: 100, height: 30)))
.padding(4)
.border(Color.gray)
}
Divider()
makeImage(self.generate(tablet1, size: CGSize(width: 400, height: 20)))
.padding(4)
.border(Color.gray)
Divider()
HStack {
makeImage(self.generate(percentBar, size: CGSize(width: 200, height: 20)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(wiperGauge, size: CGSize(width: 80, height: 40)))
.padding(4)
.border(Color.gray)
makeImage(self.generate(wiperGauge2, size: CGSize(width: 80, height: 40)))
.padding(4)
.border(Color.gray)
}
}
}
}
struct BitmapGenerationView_Previews: PreviewProvider {
static var previews: some View {
BitmapGenerationView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/CircularGaugeView.swift
================================================
//
// TestingView.swift
// Demos
//
// Created by Darren Ford on 14/4/21.
//
import SwiftUI
import DSFSparkline
struct TrackStyle {
var color: Color = .black
var width: Double = 10
var hasShadow: Bool = false
var shadowInner: Bool = false
var shadowColor: Color = Color(white: 0, opacity: 0.8)
var shadowOffset: CGSize = CGSize(width: 2, height: 2)
var shadowBlur: Double = 2
var hasStroke: Bool = false
var strokeColor: Color = .black
var strokeWidth: Double = 0.5
var shadow: DSFSparkline.Shadow? {
if hasShadow {
return DSFSparkline.Shadow(
blurRadius: shadowBlur,
offset: shadowOffset,
color: shadowColor.cgColor ?? CGColor.black,
isInner: shadowInner
)
}
return nil
}
var track: DSFSparklineOverlay.CircularGauge.TrackStyle {
DSFSparklineOverlay.CircularGauge.TrackStyle(
width: width,
fillColor: DSFSparkline.Fill.Color(color.cgColor ?? .black),
strokeWidth: strokeWidth,
strokeColor: hasStroke ? strokeColor.cgColor : nil,
shadow: shadow
)
}
}
struct CircularGaugeView: View {
@State var value: Double = 0.25
@State var trackStyle = TrackStyle(color: Color(red: 1, green: 0, blue: 0, opacity: 0.1), width: 40)
@State var lineStyle = TrackStyle(color: Color(red: 1, green: 0, blue: 0, opacity: 1), width: 20)
var body: some View {
//Self._printChanges()
return VStack {
DSFSparklineCircularGaugeView.SwiftUI(
value: value,
trackStyle: trackStyle.track,
lineStyle: lineStyle.track
)
.frame(width: 200, height: 200)
Slider(value: $value, in: 0 ... 1) {
Text("Value")
}
#if os(macOS)
HStack(spacing: 16) {
GroupBox("Track Settings") {
TrackSettingsView(style: $trackStyle)
.padding(8)
}
GroupBox("Value Settings") {
TrackSettingsView(style: $lineStyle)
.padding(8)
}
}
#else
HStack(spacing: 8) {
TrackSettingsView(style: $trackStyle)
TrackSettingsView(style: $lineStyle)
}
.frame(maxHeight: .infinity)
#endif
Divider()
AnimatedGaugeView()
}
.padding()
}
}
struct TrackSettingsView: View {
@Binding var style: TrackStyle
var body: some View {
//Self._printChanges()
return Form {
ColorPicker("Color", selection: $style.color)
Slider(value: $style.width, in: 0.1 ... 50) {
Text("Width")
}
Divider()
Toggle("Shadow", isOn: $style.hasShadow)
Toggle("Inner Shadow", isOn: $style.shadowInner)
.disabled(!style.hasShadow)
ColorPicker("Shadow Color", selection: $style.shadowColor)
.disabled(!style.hasShadow)
Slider(value: $style.shadowOffset.width, in: -10 ... 10) {
Text("X Offset")
}
.disabled(!style.hasShadow)
Slider(value: $style.shadowOffset.height, in: -10 ... 10) {
Text("Y Offset")
}
.disabled(!style.hasShadow)
Slider(value: $style.shadowBlur, in: 0.1 ... 10) {
Text("Blur")
}
.disabled(!style.hasShadow)
Divider()
Toggle("Stroke", isOn: $style.hasStroke)
ColorPicker("Stroke Color", selection: $style.strokeColor)
.disabled(!style.hasStroke)
Slider(value: $style.strokeWidth, in: 0.1 ... 5) {
Text("Stroke Width")
}
.disabled(!style.hasStroke)
}
}
}
struct AnimatedGaugeView: View {
@State var r1: Double = 0.25
@State var r1tStyle = TrackStyle(color: Color(red: 0, green: 1, blue: 0, opacity: 0.1), width: 15)
@State var r1lStyle = TrackStyle(color: Color(red: 0, green: 1, blue: 0, opacity: 1), width: 7)
@State var r2: Double = 0.5
@State var r2tStyle = TrackStyle(color: Color(red: 0, green: 0, blue: 1, opacity: 0.1), width: 15)
@State var r2lStyle = TrackStyle(color: Color(red: 0, green: 0, blue: 1, opacity: 1), width: 7)
@State var duration: Double = 0.25
var body: some View {
//Self._printChanges()
return HStack {
Slider(value: $duration, in: 0.01 ... 2) {
Text("Duration")
}
Button("Animate!") {
r1 = Double.random(in: 0 ... 1)
r2 = Double.random(in: 0 ... 1)
}
DSFSparklineCircularGaugeView.SwiftUI(
value: r1,
animationStyle: .init(duration: duration, function: .linear),
trackStyle: r1tStyle.track,
lineStyle: r1lStyle.track
)
.frame(width: 50, height: 50)
DSFSparklineCircularGaugeView.SwiftUI(
value: r2,
animationStyle: .init(duration: duration, function: .easeInEaseOut),
trackStyle: r2tStyle.track,
lineStyle: r2lStyle.track
)
.frame(width: 50, height: 50)
}
}
}
#Preview("All") {
CircularGaugeView()
.padding()
}
#Preview("Settings") {
TrackSettingsView(style: .constant(TrackStyle()))
.padding()
}
#Preview("Animated") {
AnimatedGaugeView()
.padding()
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/CircularProgress.swift
================================================
//
// CircularProgressView.swift
// Demos
//
// Created by Darren Ford on 16/2/24.
//
import SwiftUI
import DSFSparkline
func namedImage(_ name: String) -> CGImage? {
#if os(macOS)
if #available(macOS 11.0, *) {
return NSImage(systemSymbolName: name, accessibilityDescription: nil)?.cgImage(forProposedRect: nil, context: nil, hints: nil)
}
return nil
#else
return UIImage(systemName: name)?.cgImage
#endif
}
struct CircularProgressView: View {
let g = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 1.0),
CGColor(srgbRed: 0.891, green: 0.000, blue: 0.090, alpha: 1.0),
]
)
let g1 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 1.0),
CGColor(srgbRed: 0.601, green: 1.000, blue: 0.009, alpha: 1.0),
]
)
let g2 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 1.0),
CGColor(srgbRed: 0.015, green: 0.847, blue: 1.000, alpha: 1.0),
]
)
let g3 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 1.0),
CGColor(srgbRed: 0.996, green: 0.459, blue: 0.000, alpha: 1.0),
]
)
let i1: CGImage? = namedImage("arrow.right")
let i2: CGImage? = namedImage("arrow.up")
let i3: CGImage? = namedImage("arrow.triangle.swap")
@State var value: Double = 0.25
@State var padding: Double = 2
@State var trackWidth: Double = 20
@State var showIcons: Bool = false
@State var trackColor: CGColor?
var body: some View {
VStack(spacing: 8) {
HStack(spacing: 16) {
ZStack {
DSFSparklineCircularProgressView.SwiftUI(
value: value,
fillStyle: g,
trackWidth: trackWidth,
trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1),
trackIcon: showIcons ? i1 : nil
)
DSFSparklineCircularProgressView.SwiftUI(
value: value * 0.8,
fillStyle: g1,
trackWidth: trackWidth,
padding: 1 * (trackWidth + padding),
trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1),
trackIcon: showIcons ? i2 : nil
)
DSFSparklineCircularProgressView.SwiftUI(
value: value * 0.6,
fillStyle: g2,
trackWidth: trackWidth,
padding: 2 * (trackWidth + padding),
trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1),
trackIcon: showIcons ? i3 : nil
)
}
.frame(width: 200, height: 200)
ZStack {
DSFSparklineCircularProgressView.SwiftUI(
value: value,
fillStyle: DSFSparkline.Fill.Color(CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1)),
trackWidth: trackWidth,
trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1),
trackIcon: showIcons ? i1 : nil
)
DSFSparklineCircularProgressView.SwiftUI(
value: value * 0.8,
fillStyle: DSFSparkline.Fill.Color(CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 1)),
trackWidth: trackWidth,
padding: 1 * (trackWidth + padding),
trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1),
trackIcon: showIcons ? i2 : nil
)
DSFSparklineCircularProgressView.SwiftUI(
value: value * 0.6,
fillStyle: DSFSparkline.Fill.Color(CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 1)),
trackWidth: trackWidth,
padding: 2 * (trackWidth + padding),
trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1),
trackIcon: showIcons ? i3 : nil
)
}
.frame(width: 200, height: 200)
}
Form {
Slider(value: $value, in: 0 ... 2.5) { Text("Value") }
minimumValueLabel: { Text("0") }
maximumValueLabel: { Text("2.5") }
Slider(value: $trackWidth, in: 5 ... 25) { Text("Track Width") }
minimumValueLabel: { Text("5") }
maximumValueLabel: { Text("25") }
Slider(value: $padding, in: 0 ... 10) { Text("Track padding") }
minimumValueLabel: { Text("0") }
maximumValueLabel: { Text("10") }
Toggle(isOn: $showIcons) {
Text("Show icons")
}
}
#if os(iOS)
.frame(height: 250)
#endif
Divider()
HStack {
DSFSparklineCircularProgressView.SwiftUI(
value: 0.2,
fillStyle: DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1),
trackWidth: 5,
trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1)
)
DSFSparklineCircularProgressView.SwiftUI(
value: 0.80,
fillStyle: DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0, alpha: 1),
trackWidth: 5,
trackColor: CGColor(srgbRed: 1.000, green: 1.000, blue: 0, alpha: 0.1)
)
DSFSparklineCircularProgressView.SwiftUI(
value: 0.35,
fillStyle: DSFSparkline.Fill.Color(srgbRed: 0, green: 1, blue: 0, alpha: 1),
trackWidth: 5,
trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1)
)
DSFSparklineCircularProgressView.SwiftUI(
value: 0.80,
fillStyle: DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 1),
trackWidth: 5,
trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1)
)
DSFSparklineCircularProgressView.SwiftUI(
value: 1.3,
fillStyle: DSFSparkline.Fill.Color(gray: 1, alpha: 1),
trackWidth: 5
)
DSFSparklineCircularProgressView.SwiftUI(
value: 0.65,
fillStyle: DSFSparkline.Fill.Color(gray: 0, alpha: 1),
trackWidth: 5,
trackColor: CGColor(gray: 0, alpha: 0.3)
)
DSFSparklineCircularProgressView.SwiftUI(
value: 0.65,
fillStyle: DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 1),
trackWidth: 5,
trackColor: CGColor(red: 1, green: 1, blue: 0, alpha: 1)
)
}
.frame(height: 32)
}
.padding()
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/ContentView.swift
================================================
//
// ContentView.swift
// Shared
//
// Created by Darren Ford on 27/1/21.
//
import DSFSparkline
import SwiftUI
struct Item: Identifiable {
let id: Int
let name: String
}
struct ContentView: View {
let items: [Item] = [
Item(id: 0, name: "ReportView"),
Item(id: 1, name: "WinLossTie"),
Item(id: 2, name: "Tablet"),
Item(id: 3, name: "Line"),
Item(id: 4, name: "Stackline"),
Item(id: 5, name: "Pie Chart"),
Item(id: 6, name: "Databar"),
Item(id: 7, name: "Dot"),
Item(id: 8, name: "Stripes"),
Item(id: 9, name: "Active"),
Item(id: 10, name: "Bar"),
Item(id: 11, name: "Bitmap Testing"),
Item(id: 12, name: "SwiftUI Overlays"),
Item(id: 13, name: "Percent Bar"),
Item(id: 14, name: "Wiper Gauge"),
Item(id: 15, name: "Activity Grid"),
Item(id: 16, name: "Circular Progress"),
Item(id: 17, name: "Circular Gauge"),
Item(id: 99, name: "Testing Harness")
]
var body: some View {
NavigationView {
List(items) { item in
NavigationLink(destination: DetailView(item: item)) {
Text("\(item.name)")
}
} //.navigationBarTitle("Dynamic List")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct DetailView: View {
let item: Item
var body: some View {
//Self._printChanges()
return ScrollView([.vertical]) {
switch item.id {
case 0: ReportView()
case 1: WinLossCreate()
case 2: TabletView(shouldAnimate: true)
case 3: LineDemoContentView()
case 4: StackLineDemoContentView()
case 5: PieGraphDemoView()
case 6: DataBarGraphContent()
case 7: DotGraphView()
case 8: StripesDemoView()
case 9: MakeActiveView()
case 10: BarDemoContentView()
case 11: BitmapGenerationView()
case 12: SwiftUIView(shouldAnimate: true)
case 13: PercentBarView()
case 14: WiperGraphDemoView()
case 15: ActivityGridView()
case 16: CircularProgressView()
case 17: CircularGaugeView()
case 99: TestingView()
default: fatalError()
}
}
// .padding()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/DataBarGraphContent.swift
================================================
//
// DataBarGraphContent.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate let firstDataSourceDataBar = DSFSparkline.StaticDataSource([10, 40, 25])
fileprivate let secondDataSourceDataBar = DSFSparkline.StaticDataSource([33, 33, 33])
fileprivate let thirdDataSourceDataBar = DSFSparkline.StaticDataSource([85, 10, 19])
fileprivate let fourthDataSourceDataBar = DSFSparkline.StaticDataSource([3, 4, 5])
fileprivate let palette1 = DSFSparkline.Palette([.systemRed, .systemOrange, .systemYellow])
fileprivate let grays = DSFSparkline.Palette([
DSFColor.init(white: 0.80, alpha: 1),
DSFColor.init(white: 0.60, alpha: 1),
DSFColor.init(white: 0.40, alpha: 1)
])
struct DataBarGraphContent: View {
var body: some View {
VStack(spacing: 8) {
HStack(spacing: 8) {
Text("No stroke").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: firstDataSourceDataBar,
palette: palette1
)
.frame(width: 96, height: 24)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: secondDataSourceDataBar,
palette: palette1
)
.frame(width: 96, height: 24)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: thirdDataSourceDataBar,
palette: palette1
)
.frame(width: 96, height: 24)
}
HStack(spacing: 8) {
Text("Stroke").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: firstDataSourceDataBar,
strokeColor: DSFColor.black,
lineWidth: 1
)
.frame(width: 96, height: 24)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: secondDataSourceDataBar,
strokeColor: DSFColor.black,
lineWidth: 1
)
.frame(width: 96, height: 24)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: thirdDataSourceDataBar,
strokeColor: DSFColor.black,
lineWidth: 1
)
.frame(width: 96, height: 24)
}
HStack(spacing: 8) {
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: fourthDataSourceDataBar,
palette: grays
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.shared.colors.count)),
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.sharedGrays.colors.count)),
palette: .sharedGrays,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([5, 2, 4, 8]),
strokeColor: DSFColor.gray,
lineWidth: 1
)
.frame(width: 96, height: 36)
}
HStack(spacing: 8) {
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: fourthDataSourceDataBar,
palette: grays,
animationStyle: DSFSparkline.AnimationStyle(duration: 1, function: .easeInEaseOut)
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.shared.colors.count)),
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1,
animationStyle: .init(duration: 1, function: .easeInEaseOut)
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.sharedGrays.colors.count)),
palette: .sharedGrays,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1,
animationStyle: .init(duration: 1, function: .easeInEaseOut)
)
.frame(width: 96, height: 36)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([5, 2, 4, 8]),
strokeColor: DSFColor.gray,
lineWidth: 1,
animationStyle: .init(duration: 1, function: .easeInEaseOut)
)
.frame(width: 96, height: 36)
}
Text("Data Bar with maximum value defined")
VStack{
HStack(spacing: 20) {
VStack(spacing: 8) {
Text("No Background Color")
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([2, 4, 8]),
maximumTotalValue: 20,
palette: palette1,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .linear)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([5, 10, 5]),
maximumTotalValue: 20,
palette: palette1,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .linear)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([1, 3, 5]),
maximumTotalValue: 20,
palette: palette1,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .linear)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
}
.frame(width: 192)
VStack(spacing: 8) {
Text("Background color defined")
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([3, 3, 5]),
maximumTotalValue: 14,
palette: palette1,
unsetColor: DSFColor.black,
strokeColor: DSFColor.black,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .easeInEaseOut)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([12, 2, 5]),
maximumTotalValue: 20,
palette: palette1,
unsetColor: DSFColor.black,
strokeColor: DSFColor.black,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .easeInEaseOut)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([1, 10, 5]),
maximumTotalValue: 20,
palette: palette1,
unsetColor: DSFColor.black,
strokeColor: DSFColor.black,
lineWidth: 1,
animationStyle: .init(duration: 0.5, function: .easeInEaseOut)
)
.frame(height: 24)
.padding(3)
.border(Color.gray.opacity(0.3), width: 0.5)
}
.frame(width: 192)
}
}
}
.padding()
}
}
struct DataBarGraphContent_Previews: PreviewProvider {
static var previews: some View {
DataBarGraphContent()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/DotGraphView.swift
================================================
//
// DotGraphView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate var data1: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: 100, range: 0 ... 100)
let values: [CGFloat] = (0 ..< 100).map { _ in
CGFloat.random(in: 0 ... 100)
}
e.set(values: values)
return e
}()
fileprivate var animData1: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: 100, range: 0 ... 100)
return e
}()
struct DotStandard: View {
var body: some View {
VStack {
Text("Basic Dot")
DSFSparklineDotGraphView.SwiftUI(
dataSource: data1,
graphColor: DSFColor.systemGreen
)
.frame(height: 40)
.padding(4)
Text("Basic Dot Upside Down with unset color")
DSFSparklineDotGraphView.SwiftUI(
dataSource: data1,
graphColor: DSFColor.systemBlue,
unsetGraphColor: DSFColor.gray.withAlphaComponent(0.1),
upsideDown: true
)
.frame(height: 40)
.padding(4)
}
.frame(width: 400)
}
}
struct DotStandardAnim: View {
var body: some View {
VStack {
Text("Animated Dot")
DSFSparklineDotGraphView.SwiftUI(
dataSource: animData1,
graphColor: DSFColor.systemRed,
unsetGraphColor: DSFColor.gray.withAlphaComponent(0.1))
.frame(width: 300, height: 40)
.padding(4)
.onAppear(perform: {
startAnim()
})
}
}
func startAnim() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
animData1.push(value: CGFloat.random(in: 0 ... 100))
self.startAnim()
}
}
}
struct DotGraphView: View {
var body: some View {
VStack {
DotStandard()
DotStandardAnim()
}
}
}
struct DotGraphView_Previews: PreviewProvider {
static var previews: some View {
DotGraphView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/Extensions.swift
================================================
//
// Extensions.swift
// Demos
//
// Created by Darren Ford on 26/2/21.
//
import CoreGraphics
#if !os(macOS)
internal extension CGColor {
static var black: CGColor {
return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1])!
}
static var clear: CGColor {
return CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])!
}
}
#endif
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/LineDemoContentView.swift
================================================
//
// LineDemoContentView.swift
// SwiftUI Demo
//
// Created by Darren Ford on 27/1/21.
//
import SwiftUI
import DSFSparkline
var LineSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.5)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.95, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
let GraphColor = DSFColor.gray
struct LineDemoBasic: View {
var body: some View {
VStack {
Text("Line")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
lineShading: false,
shadowed: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
interpolated: true,
lineShading: false
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoBasic_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoBasic()
// }
//}
struct LineDemoBasicMarkers: View {
var body: some View {
VStack {
Text("Line with markers")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
lineShading: false,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
interpolated: true,
lineShading: false,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoBasicMarkers_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoBasicMarkers()
// }
//}
struct LineDemoMarkersAndShadow: View {
var body: some View {
VStack {
Text("Line with markers and shadow")
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
lineShading: false,
shadowed: true,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
//struct LineDemoMarkersAndShadow_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoMarkersAndShadow()
// }
//}
struct LineDemoBasicZeroLine: View {
var body: some View {
VStack {
Text("Line with zero-line added")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
lineShading: false,
showZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 1,
interpolated: true,
lineShading: false,
showZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoBasicZeroLine_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoBasicZeroLine()
// }
//}
struct LineDemoArea: View {
var body: some View {
VStack {
Text("Area")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
showZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
interpolated: true,
showZeroLine: true,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoArea_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoArea()
// }
//}
struct LineDemoAreaCentered: View {
var body: some View {
VStack {
Text("Line centered")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0)
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
interpolated: true,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0)
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoAreaCentered_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoAreaCentered()
// }
//}
struct LineDemoAreaCenteredMarkers: View {
var body: some View {
VStack {
Text("Line centered with markers")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0),
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
interpolated: true,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0),
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoAreaCenteredMarkers_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoAreaCenteredMarkers()
// }
//}
struct LineDemoAreaCenteredMarkersNoLowerColor: View {
var body: some View {
VStack {
Text("Line centered with markers, single color")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
showZeroLine: true,
centeredAtZeroLine: true,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: DSFColor.systemGreen,
interpolated: true,
showZeroLine: true,
centeredAtZeroLine: true,
markerSize: 4
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoAreaCenteredMarkersNoLowerColor_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoAreaCenteredMarkersNoLowerColor()
// }
//}
private let primaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1),
CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 1),
])
private let secondaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 1, blue: 1, alpha: 1),
CGColor(srgbRed: 0.3, green: 0.5, blue: 0.5, alpha: 1),
])
struct LineDemoLineRanges: View {
var body: some View {
VStack {
Text("Line with gradient fill")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 0.5,
centeredAtZeroLine: true,
primaryFill: primaryFill,
secondaryFill: secondaryFill
)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineWidth: 0.5,
interpolated: true,
centeredAtZeroLine: true,
primaryFill: primaryFill,
secondaryFill: secondaryFill
)
}
.frame(height: 80.0)
Text("Line with ranges")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineShading: false,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0.7 ..< 1.0,
fillColor: DSFColor.red.withAlphaComponent(0.15).cgColor
),
DSFSparkline.HighlightRangeDefinition(
range: 0.3 ..< 0.7,
fillColor: DSFColor.yellow.withAlphaComponent(0.15).cgColor
),
DSFSparkline.HighlightRangeDefinition(
range: 0.0 ..< 0.3,
fillColor: DSFColor.green.withAlphaComponent(0.15).cgColor
)
]
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
interpolated: true,
lineShading: false,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0.4 ..< 0.6,
fillColor: DSFColor.systemGray.withAlphaComponent(0.3).cgColor
)
]
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
Text("Line with grid lines")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
lineShading: false,
gridLines: .init(values: [0, 0.25, 0.5, 0.75, 1.0])
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineLineGraphView.SwiftUI(
dataSource: LineSource1,
graphColor: GraphColor,
interpolated: true,
lineShading: false,
gridLines: .init(values: [0, 0.4, 0.7, 0.9, 1.0])
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
//struct LineDemoLineRanges2_Previews: PreviewProvider {
// static var previews: some View {
// LineDemoLineRanges2()
// }
//}
struct LineDemoLineRanges2: View {
let c0 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.736, 0.169, 0.264, 1.000])!
let c1 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.927, 0.393, 0.265, 1.000])!
let c2 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.945, 0.593, 0.257, 1.000])!
let c3 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.950, 0.856, 0.373, 1.000])!
let c4 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.605, 0.815, 0.249, 1.000])!
let c5 = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.674, 0.938, 0.561, 1.000])!
let source: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 12, range: 0 ... 50)
d.push(values: [
12, 12, 3, 16, 18, 22, 11, 26, 22, 45, 13, 10
])
return d
}()
var body: some View {
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: source,
graphColor: DSFColor.white,
lineWidth: 4,
interpolated: true,
lineShading: false,
shadowed: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0 ..< 5,
fillColor: c0
),
DSFSparkline.HighlightRangeDefinition(
range: 5 ..< 10,
fillColor: c1
),
DSFSparkline.HighlightRangeDefinition(
range: 10 ..< 15,
fillColor: c2
),
DSFSparkline.HighlightRangeDefinition(
range: 15 ..< 20,
fillColor: c3
),
DSFSparkline.HighlightRangeDefinition(
range: 20 ..< 25,
fillColor: c4
),
DSFSparkline.HighlightRangeDefinition(
range: 25 ..< 50,
fillColor: c5
),
]
)
.frame(height: 120.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
struct LineDemoCustomMarkers: View {
let source: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.5)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.87, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
var drawCurrentValueMarker: DSFSparklineOverlay.Line.MarkerDrawingBlock = { context, markerFrames in
let l = markerFrames.last!
context.setFillColor(l.isPositiveValue ? DSFColor.systemGreen.cgColor : DSFColor.systemRed.cgColor)
context.fill(l.rect)
}
var customMarkerDrawing: DSFSparklineOverlay.Line.MarkerDrawingBlock = { context, markerFrames in
// Get the frames containing the minimum and maximum values
if let minMarker = markerFrames.min(by: { (a, b) -> Bool in a.value < b.value }),
let maxMarker = markerFrames.min(by: { (a, b) -> Bool in a.value > b.value }) {
// Draw minimum marker
context.setFillColor(DSFColor.systemRed.cgColor)
context.fill(minMarker.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(minMarker.rect)
// Draw maximum marker
context.setFillColor(DSFColor.systemGreen.cgColor)
context.fill(maxMarker.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(maxMarker.rect)
}
}
var body: some View {
HStack {
VStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: source,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0),
markerSize: 4,
markerDrawingBlock: self.drawCurrentValueMarker
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
Text("Current value marker").font(.footnote)
}
VStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: source,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
showZeroLine: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor(red: 0.7, green: 0, blue: 0, alpha: 1.0),
markerSize: 6,
markerDrawingBlock: self.customMarkerDrawing
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
Text("Min/Max marker").font(.footnote)
}
}
.onAppear(perform: {
startSomething()
})
}
func startSomething() {
if !IsRunningInPreviewPane {
updateWithNewValues()
}
}
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
source.push(value: CGFloat.random(in: 0.0 ... 1.0))
updateWithNewValues()
}
}
}
struct LineGraphShowingBug13: View {
let source: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, /*range: 0 ... 1,*/ zeroLineValue: 0)
d.push(values: [
0, 5, 4.5, 10, 8, 0, 60, 60, -60, -60, 60, 60, -60, -60, 60, 55
])
return d
}()
let source2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.25)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.99, 0.99, 0.02, 0.07,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
var body: some View {
VStack {
Text("Demonstrating interpolated line clipping")
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: self.source,
graphColor: DSFColor.systemIndigo,
lineWidth: 1,
lineShading: true,
shadowed: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemRed,
markerSize: 6,
gridLines: .init(values: [-60, -30, 0, 30, 60])
)
.border(Color.gray.opacity(0.2), width: 1)
.frame(width: 200, height: 100)
DSFSparklineLineGraphView.SwiftUI(
dataSource: self.source,
graphColor: DSFColor.systemIndigo,
lineWidth: 1,
interpolated: true,
lineShading: true,
shadowed: true,
markerSize: 6,
gridLines: .init(values: [-60, -30, 0, 30, 60])
)
.border(Color.gray.opacity(0.2), width: 1)
.frame(width: 200, height: 100)
DSFSparklineLineGraphView.SwiftUI(
dataSource: self.source,
graphColor: DSFColor.systemIndigo,
lineWidth: 1,
interpolated: true,
lineShading: true,
shadowed: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemRed,
markerSize: 6
)
.border(Color.gray.opacity(0.2), width: 1)
.frame(width: 200, height: 100)
}
HStack {
DSFSparklineSurface.SwiftUI([
DSFSparklineOverlay.GridLines(
dataSource: self.source2,
floatValues: [0, 0.25, 0.5, 0.75, 1.0],
strokeColor: CGColor(gray: 0.5, alpha: 0.3),
strokeWidth: 0.5,
dashStyle: [0.5, 0.5]
),
DSFSparklineOverlay.RangeHighlight(
dataSource: self.source2,
range: 0.75 ..< 1.0,
fill: DSFSparkline.Fill.Color(CGColor(red: 1, green: 0, blue: 0, alpha: 0.1))
),
DSFSparklineOverlay.RangeHighlight(
dataSource: self.source2,
range: 0.5 ..< 0.75,
fill: DSFSparkline.Fill.Color(CGColor(red: 1, green: 1, blue: 0, alpha: 0.1))
),
DSFSparklineOverlay.RangeHighlight(
dataSource: self.source2,
range: 0.0 ..< 0.5,
fill: DSFSparkline.Fill.Color(CGColor(red: 0, green: 1, blue: 0, alpha: 0.1))
),
DSFSparklineOverlay.ZeroLine(
dataSource: self.source2,
strokeColor: CGColor(red: 1, green: 0, blue: 1, alpha: 1),
strokeWidth: 1
),
{
let d = DSFSparklineOverlay.Line()
d.dataSource = self.source2
d.interpolated = true
d.primaryStrokeColor = CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1)
d.markerSize = 3
d.strokeWidth = 1.0
return d
}()
])
.frame(width: 200, height: 50)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
struct LineGraphShowingBug13_Previews: PreviewProvider {
static var previews: some View {
LineGraphShowingBug13()
}
}
struct LineDemoContentView: View {
var body: some View {
ScrollView([.vertical, .horizontal]) {
VStack(spacing: 8) {
VStack {
LineDemoBasic()
LineDemoBasicMarkers()
LineDemoMarkersAndShadow()
LineDemoBasicZeroLine()
LineDemoArea()
}
VStack {
LineDemoAreaCentered()
LineDemoAreaCenteredMarkers()
LineDemoAreaCenteredMarkersNoLowerColor()
LineDemoLineRanges()
LineDemoLineRanges2()
}
VStack {
Text("Custom markers").font(.headline)
LineDemoCustomMarkers()
}
VStack {
LineGraphShowingBug13()
}
}
.frame(width: 400.0)
}
}
}
struct LineDemoContentView_Previews: PreviewProvider {
static var previews: some View {
LineDemoContentView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/PercentBarView.swift
================================================
//
// PercentBar.swift
// Demos
//
// Created by Darren Ford on 18/3/21.
//
import SwiftUI
import DSFSparkline
struct PercentBarView: View {
// The actual line graph
var percentBarOverlay: DSFSparklineOverlay.PercentBar = {
let lineOverlay = DSFSparklineOverlay.PercentBar(value: 0)
return lineOverlay
}()
var percentBarOverlay2: DSFSparklineOverlay.PercentBar = {
let lineOverlay = DSFSparklineOverlay.PercentBar(value: 0)
return lineOverlay
}()
var percentBarOverlay3: DSFSparklineOverlay.PercentBar = {
let lineOverlay = DSFSparklineOverlay.PercentBar(value: 0)
return lineOverlay
}()
func style() -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.barColor = DSFColor.systemYellow.cgColor
s.barTextColor = .black
s.underBarColor = DSFColor.systemBlue.cgColor
s.underBarTextColor = CGColor(gray: 1.0, alpha: 1.0)
s.font = DSFFont(name: "Menlo", size: 13)!
return s
}
func style2() -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.barColor = DSFColor.systemIndigo.cgColor
s.underBarColor = CGColor(gray: 0.0, alpha: 0.1)
s.showLabel = false
return s
}
func style3() -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.barEdgeInsets = DSFEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
s.barColor = CGColor(gray: 0.0, alpha: 0.2)
s.barTextColor = .black
s.underBarColor = CGColor(gray: 0.0, alpha: 0.1)
s.underBarTextColor = .black
s.font = DSFFont(name: "Menlo", size: 13)!
return s
}
func style4(value: CGFloat) -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.barEdgeInsets = DSFEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
if value > 0.8 {
s.barColor = DSFColor.systemRed.cgColor
s.barTextColor = CGColor(gray: 1.0, alpha: 1.0)
}
else if value > 0.5 {
s.barColor = DSFColor.systemYellow.cgColor
s.barTextColor = CGColor(gray: 0.0, alpha: 1.0)
}
else {
s.barColor = DSFColor.systemGreen.cgColor
s.barTextColor = CGColor(gray: 0.0, alpha: 1.0)
}
s.underBarColor = CGColor(gray: 0.8, alpha: 1.0)
s.underBarTextColor = .black
s.font = DSFFont(name: "Menlo Bold", size: 16)!
return s
}
let fixedFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
func fixedFormatted(_ value: CGFloat) -> String {
return self.fixedFormatter.string(for: value) ?? ""
}
func style10() -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.barEdgeInsets = DSFEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
s.barColor = CGColor(gray: 0.0, alpha: 1.0)
s.barTextColor = CGColor(gray: 1.0, alpha: 1.0)
s.underBarColor = CGColor(gray: 0.8, alpha: 1.0)
s.underBarTextColor = .black
s.font = DSFFont(name: "Menlo", size: 11)!
return s
}
func style11() -> DSFSparkline.PercentBar.Style {
let s = DSFSparkline.PercentBar.Style()
s.showLabel = false
s.barEdgeInsets = DSFEdgeInsets(top: 1, left: 1, bottom: 1, right: 1)
s.barColor = CGColor(gray: 0.0, alpha: 1.0)
s.barTextColor = CGColor(gray: 1.0, alpha: 1.0)
s.underBarColor = CGColor(gray: 0.8, alpha: 1.0)
s.underBarTextColor = .black
s.font = DSFFont(name: "Menlo", size: 11)!
return s
}
@State var v1: CGFloat = 0.0
@State var v2: CGFloat = 1.0
@State var v3: CGFloat = 0.85
@State var v4: CGFloat = 0.05
@State var v5: CGFloat = 0.66
@State var v6: CGFloat = 0.5
@State var v10: CGFloat = 0.0
@State var v11: CGFloat = 0.2
@State var v12: CGFloat = 0.4
@State var v13: CGFloat = 0.6
@State var v14: CGFloat = 0.8
@State var v15: CGFloat = 1.0
var body: some View {
VStack {
VStack {
Text("Fixed")
.font(.subheadline)
DSFSparklinePercentBarGraphView.SwiftUI(style: style(), value: Double(v1))
.frame(height: 20)
DSFSparklinePercentBarGraphView.SwiftUI(style: style(), value: Double(0.5))
.frame(height: 20)
DSFSparklinePercentBarGraphView.SwiftUI(style: style(), value: Double(v2))
.frame(height: 20)
Divider()
VStack {
let animationStyle = DSFSparkline.AnimationStyle(duration: 0.25)
HStack {
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v10), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v11), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v12), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v13), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v14), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style10(), value: Double(v15), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
}
HStack {
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v10), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v11), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v12), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v13), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v14), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
DSFSparklinePercentBarGraphView.SwiftUI(style: style11(), value: Double(v15), animationStyle: animationStyle)
.frame(width: 50, height: 18, alignment: .center)
}
Button("Random") {
self.v10 = CGFloat(drand48())
self.v11 = CGFloat(drand48())
self.v12 = CGFloat(drand48())
self.v13 = CGFloat(drand48())
self.v14 = CGFloat(drand48())
self.v15 = CGFloat(drand48())
}
.frame(height: 18, alignment: .center)
}
Divider()
HStack {
DSFSparklinePercentBarGraphView.SwiftUI(style: style2(), value: Double(v1))
.frame(height: 20)
Text(fixedFormatted(self.v1))
.frame(width: 75, alignment: .trailing)
}
HStack {
DSFSparklinePercentBarGraphView.SwiftUI(style: style2(), value: Double(0.5))
.frame(height: 20)
Text(fixedFormatted(0.5))
.frame(width: 75, alignment: .trailing)
}
HStack {
DSFSparklinePercentBarGraphView.SwiftUI(style: style2(), value: Double(v2))
.frame(height: 20)
Text(fixedFormatted(self.v2))
.frame(width: 75, alignment: .trailing)
}
}
.frame(width: 320)
Divider()
VStack {
Text("Interactive")
.font(.subheadline)
DSFSparklinePercentBarGraphView.SwiftUI(style: style3(), value: Double(v6))
.frame(height: 26)
HStack(spacing: 8) {
Slider(value: $v6, in: 0 ... 1)
Text(fixedFormatted(self.v6))
.frame(width: 75, alignment: .trailing)
}
}
.padding(8)
Divider()
VStack {
Text("Animation")
.font(.subheadline)
DSFSparklinePercentBarGraphView.SwiftUI(style: style4(value: v3), value: Double(v3), animationStyle: .init(duration: 0.5))
.frame(height: 30)
DSFSparklinePercentBarGraphView.SwiftUI(style: style4(value: v4), value: Double(v4), animationStyle: .init(duration: 0.5))
.frame(height: 30)
DSFSparklinePercentBarGraphView.SwiftUI(style: style4(value: v5), value: Double(v5), animationStyle: .init(duration: 0.5))
.frame(height: 30)
Button("Random") {
self.v3 = CGFloat(drand48())
self.v4 = CGFloat(drand48())
self.v5 = CGFloat(drand48())
}
}
.frame(width: 250)
}
}
}
struct PercentBar_Previews: PreviewProvider {
static var previews: some View {
PercentBarView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/PieGraphDemoView.swift
================================================
//
// PieGraphDemoView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate let palette1 = DSFSparkline.Palette([.systemRed, .systemOrange, .systemYellow])
fileprivate let grays = DSFSparkline.Palette([
DSFColor.init(white: 1.00, alpha: 1),
DSFColor.init(white: 0.66, alpha: 1),
DSFColor.init(white: 0.33, alpha: 1)
])
fileprivate let firstDataSource = DSFSparkline.StaticDataSource([10, 40, 25])
fileprivate let secondDataSource = DSFSparkline.StaticDataSource([33, 33, 33])
fileprivate let thirdDataSource = DSFSparkline.StaticDataSource( [85, 10, 19])
fileprivate let fourthDataSource = DSFSparkline.StaticDataSource([3, 4, 5])
struct PieGraphDemoView: View {
var body: some View {
VStack(spacing: 8) {
HStack(spacing: 8) {
Text("No stroke").frame(width: 70, alignment: Alignment.trailing)
DSFSparklinePieGraphView.SwiftUI(
dataSource: firstDataSource,
palette: palette1
)
.frame(width: 24, height: 24)
DSFSparklinePieGraphView.SwiftUI(
dataSource: secondDataSource,
palette: palette1
)
.frame(width: 24, height: 24)
DSFSparklinePieGraphView.SwiftUI(
dataSource: thirdDataSource,
palette: palette1
)
.frame(width: 24, height: 24)
}
HStack(spacing: 8) {
Text("Stroke").frame(width: 70, alignment: Alignment.trailing)
DSFSparklinePieGraphView.SwiftUI(
dataSource: firstDataSource,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 24, height: 24)
DSFSparklinePieGraphView.SwiftUI(
dataSource: secondDataSource,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 24, height: 24)
DSFSparklinePieGraphView.SwiftUI(
dataSource: thirdDataSource,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 24, height: 24)
}
HStack(spacing: 8) {
DSFSparklinePieGraphView.SwiftUI(
dataSource: fourthDataSource,
palette: grays
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.shared.colors.count)),
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.sharedGrays.colors.count)),
palette: .sharedGrays,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([5, 2, 4, 8]),
strokeColor: DSFColor.gray,
lineWidth: 1
)
.frame(width: 36, height: 36)
}
HStack(spacing: 8) {
DSFSparklinePieGraphView.SwiftUI(
dataSource: fourthDataSource,
palette: grays,
animationStyle: .init(duration: 1.0, function: .easeInEaseOut)
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.shared.colors.count)),
strokeColor: DSFColor.black,
lineWidth: 2,
animationStyle: .init(duration: 1.0, function: .easeInEaseOut)
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([CGFloat](repeating: 10.0, count: DSFSparkline.Palette.sharedGrays.colors.count)),
palette: .sharedGrays,
strokeColor: DSFColor.black.withAlphaComponent(0.5),
lineWidth: 1,
animationStyle: .init(duration: 1.0, function: .easeInEaseOut)
)
.frame(width: 36, height: 36)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource([5, 2, 4, 8]),
strokeColor: DSFColor.gray,
lineWidth: 1,
animationStyle: .init(duration: 1.0, function: .easeInEaseOut)
)
.frame(width: 36, height: 36)
}
}
.padding()
}
}
struct PieGraphDemoView_Previews: PreviewProvider {
static var previews: some View {
PieGraphDemoView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/ReportView.swift
================================================
//
// ContentView.swift
// Shared
//
// Created by Darren Ford on 27/1/21.
//
import DSFSparkline
import SwiftUI
struct UserResults {
let name: String
var values: [Int]
let dataSource = DSFSparkline.DataSource(windowSize: 5)
init(name: String, values: [Int]) {
self.name = name
self.values = values
self.dataSource.set(values: self.values.map { CGFloat($0) })
}
}
struct SalesResult {
let name: String
var values: [CGFloat]
var maximumTotal: CGFloat
init(name: String, values: [CGFloat], maximumTotal: CGFloat) {
self.name = name
self.values = values
self.maximumTotal = maximumTotal
}
}
struct ReportView: View {
static func Make() -> ReportView {
return ReportView()
}
var gridItems1: [GridItem] = [
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
]
let userItems1: [UserResults] = [
UserResults(name: "Aiden", values: [90, 120, 110, 130, 115]),
UserResults(name: "Ethan", values: [100, 95, 115, 120, 118]),
UserResults(name: "Jackson", values: [120, 125, 140, 130, 135]),
UserResults(name: "Lucas", values: [118, 120, 125, 128, 135]),
UserResults(name: "Noah", values: [130, 130, 125, 120, 90]),
]
var gridItems2: [GridItem] = [
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
GridItem(.fixed(35), spacing: 2),
]
let userItems2: [UserResults] = [
UserResults(name: "Aiden", values: [1, 1, -1, 1, -1, 1, 1, 1]),
UserResults(name: "Ethan", values: [1, 1, -1, 1, 1, -1, 1, -1]),
UserResults(name: "Jackson", values: [-1, 1, 1, -1, -1, 1, 1, 1]),
UserResults(name: "Lucas", values: [1, -1, -1, 1, 1, -1, -1, 1]),
UserResults(name: "Noah", values: [-1, 1, 1, 1, -1, 1, 1, 1]),
]
let salesItems: [SalesResult] = [
SalesResult(name: "Aiden", values: [120, 200, 0, 270], maximumTotal: 1000),
SalesResult(name: "Ethan", values: [300, 400, 100, 90], maximumTotal: 1000),
SalesResult(name: "Jackson", values: [100, 140, 90, 110], maximumTotal: 1000),
SalesResult(name: "Lucas", values: [250, 250, 100, 100], maximumTotal: 1000),
SalesResult(name: "Noah", values: [300, 100, 200, 270], maximumTotal: 1000),
]
func sparklineAttributedString() -> NSAttributedString {
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
let attr = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)!
let message = NSMutableAttributedString(string: "Inlined - ")
message.append(attr)
message.append(NSAttributedString(string: " - line graph"))
message.addAttributes([.foregroundColor: DSFColor.primaryTextColor], range: NSRange(location: 0, length: message.length))
return message
}
var body: some View {
VStack {
ReportHeaderView(attrString: self.sparklineAttributedString())
.padding(EdgeInsets(top: 16, leading: 0, bottom: 25, trailing: 0))
ReportExamResults(gridItems1: self.gridItems1, userItems1: self.userItems1)
.frame(maxWidth: .infinity)
ReportTeamWinLosses(gridItems2: self.gridItems2, userItems2: self.userItems2)
.frame(maxWidth: .infinity)
ReportTeamSales(gridItems2: self.gridItems2, salesItems: self.salesItems)
.frame(maxWidth: .infinity)
}
}
}
struct ReportHeaderView: View {
let attrString: NSAttributedString
var body: some View {
VStack {
AttributedText(attrString)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(20)
HStack {
Text("NASDAQ Feb 2020 -> Feb 2021").frame(width: 300)
DSFSparklineLineGraphView.SwiftUI(dataSource: NasdaqFeb2020Feb2021DataSource,
graphColor: DSFColor.systemRed,
lineWidth: 0.5)
.frame(width: 75, height: 20)
}
HStack {
Text("Gold Feb 2020 -> Feb 2021").frame(width: 300)
DSFSparklineLineGraphView.SwiftUI(dataSource: GoldFeb2020Feb2021DataSource,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemRed)
.frame(width: 75, height: 20)
}
}
}
}
struct ReportExamResults: View {
let gridItems1: [GridItem]
let userItems1: [UserResults]
var body: some View {
VStack {
Text("Exam Results")
LazyHGrid(rows: gridItems1) {
ForEach(0 ..< 5, id: \.self) { number in
HStack {
Text(userItems1[number].name)
.frame(width: 80, alignment: .leading)
ForEach(0 ..< 5, id: \.self) { count in
Text("\(userItems1[number].values[count])")
.frame(width: 30, alignment: .center)
}
DSFSparklineLineGraphView.SwiftUI(
dataSource: userItems1[number].dataSource,
graphColor: DSFColor.systemBlue,
lineShading: false
)
.frame(width: 120)
}
.padding(8)
.background(number % 2 == 0 ? Color(Color.RGBColorSpace.sRGB, red: 0, green: 0, blue: 0, opacity: 0.1) : Color.clear)
}
}
}
}
}
struct ReportTeamWinLosses: View {
let gridItems2: [GridItem]
let userItems2: [UserResults]
var body: some View {
VStack {
Text("Team Wins/Losses")
LazyHGrid(rows: gridItems2) {
ForEach(0 ..< 5, id: \.self) { number in
HStack {
Text(userItems2[number].name)
.frame(width: 80, alignment: .leading)
ForEach(0 ..< 8, id: \.self) { count in
Text("\(userItems2[number].values[count])")
.frame(width: 25, alignment: .center)
}
DSFSparklineWinLossGraphView.SwiftUI(
dataSource: userItems2[number].dataSource,
barSpacing: 3,
showZeroLine: true,
zeroLineDefinition: .init(color: .systemGray,
lineWidth: 1,
lineDashStyle: [1, 1])
)
.frame(width: 120)
DSFSparklineTabletGraphView.SwiftUI(
dataSource: userItems2[number].dataSource,
barSpacing: 2
)
.frame(width: 200, height: 12)
}
.padding(4)
.background(number % 2 == 0 ? Color(Color.RGBColorSpace.sRGB, red: 0, green: 0, blue: 0, opacity: 0.1) : Color.clear)
}
}
}
}
}
struct ReportTeamSales: View {
let gridItems2: [GridItem]
let salesItems: [SalesResult]
var body: some View {
VStack {
Text("Team Sales")
LazyHGrid(rows: gridItems2) {
ForEach(0 ..< 5, id: \.self) { number in
HStack(spacing: 10) {
Text(salesItems[number].name)
.frame(width: 80, alignment: .leading)
ForEach(0 ..< 4, id: \.self) { count in
Text("\(salesItems[number].values[count], specifier: "%.2f")")
.frame(width: 60, alignment: .trailing)
}
DSFSparklineLineGraphView.SwiftUI(
dataSource: DSFSparkline.DataSource(values: salesItems[number].values),
graphColor: DSFColor.systemBlue,
lineShading: false,
markerSize: 6
)
.frame(width: 120)
DSFSparklinePieGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource(salesItems[number].values)
)
.frame(width: 28, height: 28)
DSFSparklineDataBarGraphView.SwiftUI(
dataSource: DSFSparkline.StaticDataSource(salesItems[number].values),
maximumTotalValue: salesItems[number].maximumTotal,
unsetColor: DSFColor.black.withAlphaComponent(0.2)
)
.frame(width: 120)
}
.padding(4)
.background(number % 2 == 0 ? Color(Color.RGBColorSpace.sRGB, red: 0, green: 0, blue: 0, opacity: 0.1) : Color.clear)
}
}
}
}
}
struct ReportView_Previews: PreviewProvider {
static var previews: some View {
ReportView()
}
}
let NasdaqFeb2020Feb2021DataSource = DSFSparkline.DataSource(values: NasdaqFeb2020Feb2021)
let NasdaqFeb2020Feb2021: [CGFloat] = [
8965.61,
8980.78,
8566.48,
8567.37,
8952.16,
8684.08,
9018.08,
8738.58,
8575.62,
7950.68,
8344.25,
7952.04,
7201.79,
7874.87,
6904.58,
7334.77,
6989.83,
7150.58,
6879.52,
6860.66,
7417.85,
7384.29,
7797.54,
7502.37,
7774.14,
7700.10,
7360.58,
7487.31,
7373.08,
7913.24,
7887.25,
8090.89,
8153.58,
8192.41,
8515.74,
8393.17,
8532.36,
8650.13,
8560.73,
8263.23,
8495.37,
8494.75,
8634.51,
8730.16,
8607.73,
8914.70,
8889.54,
8604.95,
8710.70,
8809.12,
8854.38,
8979.66,
9121.32,
9192.33,
9002.54,
8863.16,
8943.71,
9014.55,
9234.83,
9185.09,
9375.78,
9284.87,
9324.58,
9340.21,
9412.36,
9368.99,
9489.87,
9552.04,
9608.37,
9682.91,
9615.80,
9814.08,
9924.75,
9953.75,
10020.34,
9492.73,
9588.80,
9726.01,
9895.87,
9910.53,
9943.04,
9946.12,
10056.48,
10131.37,
9909.16,
10017.00,
9757.21,
9874.15,
10058.76,
10154.62,
10207.62,
10433.65,
10343.88,
10492.50,
10547.75,
10617.44,
10390.83,
10488.58,
10550.49,
10473.83,
10503.19,
10767.08,
10680.36,
10706.12,
10461.41,
10363.17,
10536.26,
10402.08,
10542.94,
10587.80,
10745.26,
10902.79,
10941.16,
10998.40,
11108.07,
11010.98,
10968.36,
10782.82,
11012.24,
11042.50,
11019.29,
11129.73,
11210.83,
11146.45,
11264.95,
11311.79,
11379.71,
11466.46,
11665.05,
11625.33,
11695.62,
11775.45,
11939.66,
12056.44,
11458.09,
11313.12,
10847.69,
11141.55,
10919.58,
10853.54,
11056.65,
11190.32,
11050.46,
10910.28,
10793.28,
10778.79,
10963.63,
10632.99,
10672.26,
10913.55,
11117.53,
11085.25,
11167.50,
11326.50,
11075.01,
11332.49,
11154.59,
11364.59,
11420.98,
11579.94,
11876.25,
11863.90,
11768.73,
11713.87,
11671.55,
11478.87,
11516.49,
11484.69,
11506.00,
11548.28,
11358.94,
11431.34,
11004.87,
11185.58,
10911.58,
10957.61,
11160.57,
11590.78,
11890.92,
11895.23,
11713.78,
11553.86,
11786.42,
11709.58,
11829.29,
11924.12,
11899.33,
11801.59,
11904.70,
11854.96,
11880.62,
12036.79,
12094.40,
12205.84,
12198.74,
12355.11,
12349.37,
12377.17,
12464.23,
12519.95,
12582.76,
12338.95,
12405.80,
12377.87,
12440.04,
12595.05,
12658.19,
12764.75,
12755.63,
12742.51,
12807.91,
12771.11,
12804.73,
12899.41,
12850.21,
12870.00,
12888.28,
12698.45,
12818.95,
12740.79,
13067.48,
13201.98,
13036.42,
13072.42,
13128.95,
13112.63,
12998.50,
13197.17,
13457.25,
13530.91,
13543.05,
13635.99,
13626.05,
13270.59,
13337.16,
13070.69,
13403.38,
13612.78,
13610.54,
13777.74,
13856.29,
13987.63,
14007.70,
13972.53,
14025.76,
14095.46,
14047.50,
13965.49,
13865.36,
13874.45,
13533.04,
13465.20,
13597.96,
13119.43,
]
let GoldFeb2020Feb2021DataSource: DSFSparkline.DataSource = {
let s = DSFSparkline.DataSource(zeroLineValue: 1775.4)
s.push(values: GoldFeb2020Feb2021)
return s
}()
let GoldFeb2020Feb2021: [CGFloat] = [
1646.90,
1640.00,
1640.00,
1564.09,
1592.30,
1642.09,
1641.09,
1666.40,
1670.80,
1674.50,
1659.09,
1641.40,
1589.30,
1515.69,
1485.90,
1524.90,
1477.30,
1478.59,
1484.00,
1567.00,
1660.19,
1632.30,
1650.09,
1623.90,
1622.00,
1583.40,
1578.19,
1625.69,
1633.69,
1677.00,
1664.80,
1665.40,
1736.19,
1744.80,
1756.69,
1727.19,
1720.40,
1689.19,
1701.59,
1678.19,
1728.69,
1733.30,
1723.50,
1711.90,
1710.50,
1703.40,
1684.19,
1694.50,
1706.90,
1704.40,
1684.19,
1721.80,
1709.90,
1695.30,
1704.40,
1713.90,
1738.09,
1753.40,
1731.80,
1744.19,
1750.59,
1720.50,
1734.59,
1704.80,
1710.30,
1713.30,
1736.90,
1737.80,
1725.19,
1697.80,
1718.90,
1676.19,
1698.30,
1714.69,
1713.30,
1732.00,
1729.30,
1720.30,
1729.59,
1729.19,
1724.80,
1745.90,
1756.69,
1772.09,
1765.80,
1762.09,
1772.50,
1774.80,
1793.00,
1773.19,
1784.00,
1788.50,
1804.19,
1815.50,
1799.19,
1798.19,
1811.00,
1810.59,
1811.40,
1798.69,
1808.30,
1815.90,
1842.40,
1864.09,
1889.09,
1897.30,
1931.00,
1944.69,
1953.50,
1942.30,
1962.80,
1966.00,
2001.19,
2031.09,
2051.50,
2010.09,
2024.40,
1932.59,
1934.90,
1956.69,
1937.00,
1985.00,
1999.40,
1958.69,
1933.80,
1934.59,
1927.69,
1911.80,
1940.69,
1921.59,
1964.59,
1967.59,
1968.19,
1934.40,
1927.59,
1923.90,
1933.00,
1944.69,
1954.19,
1937.80,
1953.09,
1956.30,
1960.19,
1940.00,
1952.09,
1901.19,
1898.59,
1859.90,
1868.30,
1857.69,
1872.80,
1894.30,
1887.50,
1908.40,
1900.19,
1912.50,
1901.09,
1883.59,
1888.59,
1919.50,
1922.50,
1888.50,
1901.30,
1903.19,
1900.80,
1906.40,
1910.40,
1924.59,
1901.09,
1902.00,
1902.69,
1908.80,
1876.19,
1865.59,
1877.40,
1890.40,
1908.50,
1894.59,
1945.30,
1950.30,
1853.19,
1875.40,
1860.69,
1872.59,
1885.69,
1887.30,
1884.50,
1873.50,
1861.09,
1872.59,
1837.80,
1804.80,
1805.69,
1775.69,
1814.09,
1825.69,
1836.80,
1835.90,
1861.80,
1870.80,
1834.59,
1833.59,
1839.80,
1828.69,
1852.30,
1856.09,
1887.19,
1885.69,
1879.19,
1866.59,
1874.69,
1877.19,
1879.69,
1891.00,
1893.09,
1944.69,
1952.69,
1906.90,
1912.30,
1834.09,
1849.59,
1842.90,
1853.59,
1850.30,
1829.30,
1839.50,
1865.90,
1865.30,
1855.69,
1854.90,
1850.69,
1844.90,
1837.90,
1847.30,
1860.80,
1830.50,
1832.19,
1788.90,
1810.90,
1831.90,
1835.30,
1840.59,
1824.90,
1821.59,
1797.19,
1771.09,
1773.40,
1775.80,
1806.69,
1804.40,
1796.40,
1770.30,
]
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/StackLineDemoContentView.swift
================================================
//
// StackLineDemoContentView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import DSFSparkline
import SwiftUI
private let primaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 0, blue: 0, alpha: 1),
CGColor(srgbRed: 0, green: 0, blue: 1, alpha: 1),
])
private let secondaryFill = DSFSparkline.Fill.Gradient(colors: [
CGColor(srgbRed: 1, green: 1, blue: 0, alpha: 1),
CGColor(srgbRed: 0, green: 1, blue: 0, alpha: 1),
])
struct StackLineBasic: View {
var body: some View {
Text("Stackline")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource1,
graphColor: DSFColor.systemGray,
lineWidth: 1,
shadowed: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineZeroLine: View {
var body: some View {
Text("Stackline with zero-line")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource1,
graphColor: DSFColor.systemPink,
lineWidth: 1,
showZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineCenteredZeroLine: View {
var body: some View {
Text("Stackline centered around zero-line")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource1,
graphColor: DSFColor.systemBlue,
lineWidth: 1,
showZeroLine: true,
centeredAtZeroLine: true
)
.frame(height: 40.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineCenteredZeroLineColored: View {
var body: some View {
Text("Stackline centered around zero-line, lower color, grid lines")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource1,
graphColor: DSFColor.systemGreen,
lineWidth: 1,
shadowed: true,
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemRed,
gridLines: .init(width: 0.5, values: [0, 0.1, 0.2, 0.3, 0.4 ,0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
)
.frame(height: 60.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineRange: View {
var body: some View {
Text("Stackline with range")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource2,
graphColor: DSFColor.systemGreen,
lineWidth: 1,
showZeroLine: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 20 ..< 80,
fillColor: DSFColor.systemGray.withAlphaComponent(0.2).cgColor
),
]
)
.frame(width: 250.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineNofill: View {
var body: some View {
Text("Stackline no fill")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource3,
graphColor: DSFColor.systemYellow,
lineWidth: 3,
lineShading: false,
shadowed: false
)
.frame(width: 330.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineRange2: View {
var body: some View {
Text("StackLine with range")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource4,
graphColor: DSFColor.systemRed,
lineWidth: 0.5,
shadowed: false,
showZeroLine: true,
highlightDefinitions: [
DSFSparkline.HighlightRangeDefinition(
range: 0.3 ..< 0.7,
fillColor: DSFColor.systemPink.withAlphaComponent(0.1).cgColor
),
]
)
.frame(width: 330.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineCenteredZeroLine2: View {
var body: some View {
Text("StackLine centered around zero-line")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource4,
graphColor: DSFColor.systemGreen,
lineWidth: 0.5,
shadowed: false,
showZeroLine: true,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition(
color: DSFColor.systemGray,
lineWidth: 1,
lineDashStyle: []
),
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemPink
)
.frame(width: 100.0, height: 59.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineCenteredZeroLineCustomFill: View {
var body: some View {
Text("StackLine centered around zero-line, custom fill")
DSFSparklineStackLineGraphView.SwiftUI(
dataSource: UpDataSource1,
graphColor: DSFColor.systemBlue,
lineWidth: 1,
showZeroLine: true,
centeredAtZeroLine: true,
primaryFill: primaryFill,
secondaryFill: secondaryFill
)
.frame(height: 80.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
struct StackLineDemoContentView: View {
var body: some View {
ScrollView {
VStack {
StackLineBasic()
StackLineZeroLine()
StackLineCenteredZeroLine()
StackLineCenteredZeroLineColored()
StackLineCenteredZeroLineCustomFill()
StackLineRange()
StackLineNofill()
StackLineRange2()
StackLineCenteredZeroLine2()
}
}
}
}
struct StackLineDemoContentView_Previews: PreviewProvider {
static var previews: some View {
StackLineDemoContentView()
}
}
private var UpDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.4)
// d.push(values: [0.0, 0.3, 0.2, 0.1, 0.8, 0.7, 0.5, 0.6, 0.1, 0.9, 1])
d.push(values: [
0.85, 0.04, 0.24, 0.13, 0.51, 0.93, 0.26, 0.69, 0.16, 0.39,
0.19, 0.12, 0.28, 0.42, 0.42, 0.48, 0.29, 0.05, 0.87, 0.28,
])
return d
}()
private var UpDataSource2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 8, range: 0 ... 100)
d.push(values: [100, 0, 25, 50, 75, 10, 10, 88])
return d
}()
private var UpDataSource3: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 21, range: -1 ... 1)
var sins: CGFloat = 0.0
let r: Range = 0 ..< 21
let vars: [CGFloat] = r.map { sin(CGFloat($0)) }
d.push(values: vars)
return d
}()
private var UpDataSource4: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 21, range: 0 ... 1)
d.zeroLineValue = 0.5
d.push(values: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1, 0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.0])
return d
}()
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/StripesDemoView.swift
================================================
//
// StripesDemoView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
struct StripesDemoView: View {
let gradient = DSFSparkline.GradientBucket(
posts: [
DSFSparkline.GradientBucket.Post(color: CGColor(red: 0, green: 0, blue: 1, alpha: 1), location: 0),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 1, blue: 1, alpha: 1), location: 0.5),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0, blue: 0, alpha: 1), location: 1)
]
)
let gradientBucketed = DSFSparkline.GradientBucket(
posts: [
DSFSparkline.GradientBucket.Post(color: CGColor(red: 0, green: 0, blue: 1, alpha: 1), location: 0),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 1, blue: 1, alpha: 1), location: 0.5),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0, blue: 0, alpha: 1), location: 1)
],
bucketCount: 6
)
let gradient2: DSFSparkline.GradientBucket = {
let g = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: DSFColor.systemYellow.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(r: 0.3, g: 0, b: 0.3, location: 1.0)
])
g.bucketCount = 4
return g
}()
let gradient3: DSFSparkline.GradientBucket = {
let g = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: DSFColor.systemYellow.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(r: 0.3, g: 0, b: 0.3, location: 1.0)
])
g.bucketCount = 5
return g
}()
let gradient4: DSFSparkline.GradientBucket = {
let g = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(r: 0.0, g: 0.0, b: 0.0, location: 0),
DSFSparkline.GradientBucket.Post(r: 1.0, g: 1.0, b: 1.0, location: 1.0)
])
g.bucketCount = 8
return g
}()
var body: some View {
VStack {
VStack {
Text("Global annual mean temperature anomaly")
DSFSparklineStripesGraphView.SwiftUI(dataSource: WorldDataSource,
barSpacing: 1,
gradient: self.gradient)
.frame(height: 30)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
VStack {
Text("Global annual mean temperature anomaly (6 color buckets)")
DSFSparklineStripesGraphView.SwiftUI(dataSource: WorldDataSource,
barSpacing: 1,
gradient: self.gradientBucketed)
.frame(height: 30)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
DSFSparklineLineGraphView.SwiftUI(dataSource: WorldDataSource,
graphColor: DSFColor.systemTeal,
lineShading: false,
showZeroLine: true)
.frame(height: 30)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
VStack {
Text("Global annual mean temperature anomaly overlaid")
ZStack {
DSFSparklineStripesGraphView.SwiftUI(dataSource: WorldDataSource,
gradient: self.gradient)
DSFSparklineLineGraphView.SwiftUI(dataSource: WorldDataSource,
graphColor: DSFColor.black,
lineWidth: 1.5,
interpolated: true,
lineShading: true)
}
.frame(height: 40)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
VStack {
Text("Global annual mean temperature anomaly overlaid (6 color buckets)")
ZStack {
DSFSparklineStripesGraphView.SwiftUI(dataSource: WorldDataSource,
gradient: self.gradientBucketed)
DSFSparklineLineGraphView.SwiftUI(dataSource: WorldDataSource,
graphColor: DSFColor.black,
lineWidth: 1,
interpolated: true,
lineShading: true)
}
.frame(height: 40)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
Text("Australian annual mean temperature anomaly")
VStack {
HStack {
Text("integral").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineStripesGraphView.SwiftUI(dataSource: australianAnomaly,
integral: true,
barSpacing: 2,
gradient: self.gradient2)
.frame(height: 25)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
HStack {
Text("integral").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineStripesGraphView.SwiftUI(dataSource: australianAnomaly,
integral: true,
barSpacing: 0,
gradient: self.gradient2)
.frame(height: 25)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
HStack {
Text("fractional").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineStripesGraphView.SwiftUI(dataSource: australianAnomaly,
barSpacing: 1,
gradient: self.gradient2)
.frame(height: 25)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
HStack {
Text("fractional").frame(width: 70, alignment: Alignment.trailing)
DSFSparklineStripesGraphView.SwiftUI(dataSource: australianAnomaly,
gradient: self.gradient2)
.frame(height: 25)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
VStack {
HStack {
VStack {
DSFSparklineStripesGraphView.SwiftUI(dataSource: GradientTestDataSource,
gradient: self.gradient3)
.frame(height: 25)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineBarGraphView.SwiftUI(dataSource: GradientTestDataSource,
graphColor: DSFColor(red: 0.5, green: 0, blue: 0.5, alpha: 1.0),
centeredAtZeroLine: true,
lowerGraphColor: DSFColor.systemYellow)
.frame(height: 50)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
VStack {
DSFSparklineStripesGraphView.SwiftUI(dataSource: GradientTestDataSource2,
barSpacing: 1,
gradient: self.gradient4)
.frame(height: 50)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
}
}
}
}
.padding(4)
// .frame(width: 400)
}
}
struct StripesDemoView_Previews: PreviewProvider {
static var previews: some View {
StripesDemoView()
}
}
// MARK: -
fileprivate var GradientTestDataSource: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource()
e.set(values: [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0,
1, 2, 3, 4, 5, 6, 7, 8, 9])
return e
}()
fileprivate var GradientTestDataSource2: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource()
e.set(values: [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
return e
}()
// MARK: - World definition
let WorldDataSource: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: UInt(WorldRawData.count))
e.set(values: WorldRawData)
return e
}()
// https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/download.html
// https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/time_series/HadCRUT.4.6.0.0.annual_ns_avg.txt
// Data format :- https://www.metoffice.gov.uk/hadobs/hadcrut4/data/current/series_format.html
let WorldRawData: [CGFloat] = [
-0.373,
-0.218,
-0.228,
-0.269,
-0.248,
-0.272,
-0.358,
-0.461,
-0.467,
-0.284,
-0.343,
-0.407,
-0.524,
-0.278,
-0.494,
-0.279,
-0.251,
-0.321,
-0.238,
-0.262,
-0.276,
-0.335,
-0.227,
-0.304,
-0.368,
-0.395,
-0.384,
-0.075,
0.035,
-0.230,
-0.227,
-0.200,
-0.213,
-0.296,
-0.409,
-0.389,
-0.367,
-0.418,
-0.307,
-0.171,
-0.416,
-0.330,
-0.455,
-0.473,
-0.410,
-0.390,
-0.186,
-0.206,
-0.412,
-0.289,
-0.203,
-0.259,
-0.402,
-0.479,
-0.520,
-0.377,
-0.283,
-0.465,
-0.511,
-0.522,
-0.490,
-0.544,
-0.437,
-0.424,
-0.244,
-0.141,
-0.383,
-0.468,
-0.333,
-0.275,
-0.247,
-0.187,
-0.302,
-0.276,
-0.294,
-0.215,
-0.108,
-0.210,
-0.206,
-0.350,
-0.137,
-0.087,
-0.137,
-0.273,
-0.131,
-0.178,
-0.147,
-0.026,
-0.006,
-0.052,
0.014,
0.020,
-0.027,
-0.004,
0.144,
0.025,
-0.071,
-0.038,
-0.039,
-0.074,
-0.173,
-0.052,
0.028,
0.097,
-0.129,
-0.190,
-0.267,
-0.007,
0.046,
0.017,
-0.049,
0.038,
0.014,
0.048,
-0.223,
-0.140,
-0.068,
-0.074,
-0.113,
0.032,
-0.027,
-0.186,
-0.065,
0.062,
-0.214,
-0.149,
-0.241,
0.047,
-0.062,
0.057,
0.092,
0.140,
0.011,
0.194,
-0.014,
-0.030,
0.045,
0.192,
0.198,
0.118,
0.296,
0.254,
0.105,
0.148,
0.208,
0.325,
0.183,
0.390,
0.539,
0.306,
0.294,
0.441,
0.496,
0.505,
0.447,
0.545,
0.506,
0.491,
0.395,
0.506,
0.560,
0.425,
0.470,
0.514,
0.579,
0.763,
0.797,
0.677,
0.597,
0.736,
0.768
]
// MARK: - Australia Mean Temp Deviation
fileprivate var australianAnomaly: DSFSparkline.DataSource = {
let e = DSFSparkline.DataSource(windowSize: UInt(AustraliaMeanTempDeviation.count))
e.set(values: AustraliaMeanTempDeviation)
return e
}()
// http://www.bom.gov.au/climate/change/#tabs=Tracker&tracker=timeseries&tQ=graph%3Dtmean%26area%3Daus%26season%3D0112
let AustraliaMeanTempDeviation: [CGFloat] = [
-0.50,
-0.68,
-0.20,
-0.87,
0.12,
0.07,
-0.57,
-1.24,
-0.54,
-0.15,
-0.53,
-0.23,
-0.47,
-0.38,
-0.69,
-0.77,
-0.17,
-0.51,
0.16,
-0.87,
-0.24,
-0.59,
-0.42,
-0.45,
-0.36,
-0.50,
-0.14,
-0.36,
0.19,
-0.62,
-0.24,
-0.55,
0.08,
-0.62,
-0.40,
-0.29,
-0.73,
-0.25,
-0.45,
-0.94,
-0.61,
-0.43,
-0.43,
-0.45,
-0.36,
-0.32,
-0.92,
0.04,
0.14,
0.24,
-0.66,
0.05,
-0.11,
-0.13,
-0.22,
0.25,
-0.50,
-0.22,
-0.39,
-0.03,
-0.10,
-0.22,
0.15,
0.54,
-0.70,
-0.22,
-0.75,
-0.04,
-0.31,
0.37,
0.74,
0.27,
-0.04,
0.33,
-0.38,
0.21,
0.22,
0.17,
0.73,
-0.02,
0.47,
0.60,
0.12,
0.31,
0.18,
0.16,
0.60,
0.30,
0.97,
0.32,
-0.04,
0.05,
0.71,
0.69,
0.54,
1.16,
0.50,
0.76,
0.45,
0.93,
0.33,
-0.00,
0.24,
1.33,
1.04,
0.94,
0.99,
1.06,
1.12,
1.52,
1.15
]
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI-Overlays/OverlayView.swift
================================================
//
// OverlayView.swift
// Demos
//
// Created by Darren Ford on 17/2/21.
//
import SwiftUI
import DSFSparkline
extension DSFColor {
static var primaryTextColor: DSFColor {
#if os(macOS)
return NSColor.textColor
#else
return UIColor.label
#endif
}
}
fileprivate var dataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: -55 ... 55, zeroLineValue: 0)
d.set(values: [
18, -5, 11, 12, -21, 48, 41, -19, -28, 3,
28, -27, -21, -45, -48, -39, 33, -4, 35, 37]
)
return d
}()
struct OverlayView: View {
var barGraph: DSFSparklineOverlay = {
let b = DSFSparklineOverlay.Bar()
b.dataSource = dataSource1
b.strokeWidth = 2
b.primaryStrokeColor = DSFColor.systemPink.cgColor
b.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemPink.withAlphaComponent(0.5).cgColor)
return b
}()
var lineGraph: DSFSparklineOverlay = {
let l = DSFSparklineOverlay.Line()
l.dataSource = dataSource1
l.strokeWidth = 1
l.interpolated = true
l.primaryStrokeColor = DSFColor.primaryTextColor.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.primaryTextColor.withAlphaComponent(0.7).cgColor)
return l
}()
var barGraph2: DSFSparklineOverlay = {
let b = DSFSparklineOverlay.Bar()
b.dataSource = dataSource1
b.strokeWidth = 2
b.primaryStrokeColor = DSFColor.systemPink.cgColor
b.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemPink.withAlphaComponent(0.5).cgColor)
return b
}()
var lineGraph2: DSFSparklineOverlay = {
let l = DSFSparklineOverlay.Line()
l.dataSource = dataSource1
l.strokeWidth = 1
l.interpolated = true
l.primaryStrokeColor = DSFColor.primaryTextColor.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.primaryTextColor.withAlphaComponent(0.7).cgColor)
return l
}()
var body: some View {
VStack {
Text("Overlay two sparklines using the same data")
.font(.headline)
HStack {
VStack {
DSFSparklineSurface.SwiftUI([
barGraph, // bar on the bottom
lineGraph // line on the top
])
.frame(height: 40)
Text("Line on top").font(.caption)
}
VStack {
DSFSparklineSurface.SwiftUI([
lineGraph2, // line on the bottom
barGraph2 // bar on the top
])
.frame(height: 40)
Text("Bar on top").font(.caption)
}
}
}
.frame(width: 500)
.padding()
}
}
struct OverlayView_Previews: PreviewProvider {
static var previews: some View {
OverlayView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI-Overlays/StripesOverlaidView.swift
================================================
//
// StripesOverlaidView.swift
// Demos
//
// Created by Darren Ford on 28/2/21.
//
import SwiftUI
import DSFSparkline
struct StripesOverlaidView: View {
static let palette = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: DSFColor.systemGreen.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemGreen.cgColor, location: 0.1),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemYellow.cgColor, location: 0.5),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemRed.cgColor, location: 0.9),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemRed.cgColor, location: 1.0)
])
var stripesOverlay: DSFSparklineOverlay = {
let s = DSFSparklineOverlay.Stripes()
s.dataSource = WorldDataSource
s.integral = false
s.gradient = StripesOverlaidView.palette
return s
}()
var lineOverlay: DSFSparklineOverlay = {
let l = DSFSparklineOverlay.Line()
l.dataSource = WorldDataSource
l.strokeWidth = 1
l.interpolated = true
l.primaryStrokeColor = DSFColor.black.cgColor
l.primaryFill = DSFSparkline.Fill.Color(DSFColor.black.withAlphaComponent(0.3).cgColor)
return l
}()
var body: some View {
VStack {
Text("Overlay two sparklines using the same data")
.font(.headline)
DSFSparklineSurface.SwiftUI([
stripesOverlay,
lineOverlay
])
.frame(height: 40)
}
}
}
struct StripesOverlaidView_Previews: PreviewProvider {
static var previews: some View {
StripesOverlaidView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI-Overlays/SuperCoolLineSpark.swift
================================================
//
// SuperCoolLineSpark.swift
// Demos
//
// Created by Darren Ford on 1/3/21.
//
import SwiftUI
import DSFSparkline
fileprivate let SwiftUIDemoDataSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.5)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.87, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
struct SuperCoolLineSpark: View {
// The overlay representing the zero-line for the data source
var zeroOverlay: DSFSparklineOverlay = {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = SwiftUIDemoDataSource
zeroLine.dashStyle = []
return zeroLine
}()
// The overlay to draw a highlight between range 0 ..< 0.2
var rangeOverlay: DSFSparklineOverlay = {
let highlight = DSFSparklineOverlay.RangeHighlight()
highlight.dataSource = SwiftUIDemoDataSource
highlight.highlightRange = 0.0 ..< 0.2
highlight.fill = DSFSparkline.Fill.Color(DSFColor.gray.withAlphaComponent(0.4).cgColor)
return highlight
}()
// The actual line graph
var lineOverlay: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = SwiftUIDemoDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemBlue.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemBlue.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemYellow.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemYellow.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 1
lineOverlay.markerSize = 4
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
rangeOverlay, // range highlight overlay
zeroOverlay, // zero-line overlay
lineOverlay, // line graph overlay
])
.frame(width: 150, height: 40)
}
}
struct SuperCoolLineSpark_Previews: PreviewProvider {
static var previews: some View {
SuperCoolLineSpark()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI-Overlays/SwiftUIContentView.swift
================================================
//
// SwiftUIView.swift
// Demos
//
// Created by Darren Ford on 26/2/21.
//
import DSFSparkline
import SwiftUI
// A shared data source
private var lineOverlayDataSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: -0.1 ... 1.1, zeroLineValue: 0.5)
d.push(values: [
1.00, 1.00, 0.44, 0.16, 0.30, 0.58, 0.87, 0.44, 0.00, 0.00,
0.38, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50,
])
return d
}()
struct SwiftUIContentView_LineOnly: View {
// The actual line graph
fileprivate let lineOverlay1: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = lineOverlayDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemGreen.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemRed.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 2
lineOverlay.markerSize = 6
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
lineOverlay1, // overlay 1 - line graph
])
}
}
struct SwiftUIContentView_LineOnly_Previews: PreviewProvider {
static var previews: some View {
SwiftUIContentView_LineOnly()
}
}
//////
struct SwiftUIContentView_LineZeroLine: View {
// The actual line graph
fileprivate let lineOverlay2: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = lineOverlayDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemGreen.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemRed.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 2
lineOverlay.markerSize = 6
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
// The overlay representing the zero-line for the graph
fileprivate let lineZeroOverlay2: DSFSparklineOverlay = {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = LineSource1
zeroLine.dashStyle = []
return zeroLine
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
lineZeroOverlay2, // overlay 1 - zero-line
lineOverlay2, // overlay 2 - line graph
])
}
}
struct SwiftUIContentView_LineZeroLine_Previews: PreviewProvider {
static var previews: some View {
SwiftUIContentView_LineZeroLine()
}
}
//////
struct SwiftUIContentView_LineZeroLineGrid: View {
// The actual line graph
fileprivate let lineOverlay3: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = lineOverlayDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemGreen.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemRed.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 2
lineOverlay.markerSize = 6
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
// The overlay representing the zero-line for the graph
fileprivate let lineZeroOverlay3: DSFSparklineOverlay = {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = LineSource1
zeroLine.dashStyle = []
return zeroLine
}()
fileprivate let gridOverlay3: DSFSparklineOverlay = {
let grid = DSFSparklineOverlay.GridLines()
grid.dataSource = lineOverlayDataSource
grid.floatValues = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
grid.strokeColor = DSFColor.systemGray.withAlphaComponent(0.3).cgColor
grid.strokeWidth = 0.5
grid.dashStyle = [1, 1]
return grid
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
gridOverlay3, // overlay 1 - grid lines
lineZeroOverlay3, // overlay 2 - zero-line
lineOverlay3, // overlay 3 - line graph
])
}
}
struct SwiftUIContentView_LineZeroLineGrid_Previews: PreviewProvider {
static var previews: some View {
SwiftUIContentView_LineZeroLineGrid()
}
}
struct SwiftUIContentView_LineZeroLineGridRanges: View {
// The actual line graph
fileprivate let lineOverlay4: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = lineOverlayDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemGreen.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemRed.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 2
lineOverlay.markerSize = 6
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
// The overlay representing the zero-line for the graph
fileprivate let lineZeroOverlay4: DSFSparklineOverlay = {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = LineSource1
zeroLine.dashStyle = []
return zeroLine
}()
fileprivate let gridOverlay4: DSFSparklineOverlay = {
let grid = DSFSparklineOverlay.GridLines()
grid.dataSource = lineOverlayDataSource
grid.floatValues = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
grid.strokeColor = DSFColor.systemGray.withAlphaComponent(0.3).cgColor
grid.strokeWidth = 0.5
grid.dashStyle = [1, 1]
return grid
}()
fileprivate let rangeOverlay4: DSFSparklineOverlay = {
let highlight = DSFSparklineOverlay.RangeHighlight()
highlight.dataSource = lineOverlayDataSource
highlight.highlightRange = 0.0 ..< 0.2
highlight.fill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.1).cgColor)
return highlight
}()
fileprivate let rangeOverlay5: DSFSparklineOverlay = {
let highlight = DSFSparklineOverlay.RangeHighlight()
highlight.dataSource = lineOverlayDataSource
highlight.highlightRange = 0.8 ..< 1.0
highlight.fill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.1).cgColor)
return highlight
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
rangeOverlay4, // overlay 1 - lower highlight
rangeOverlay5, // overlay 2 - upper highlight
lineZeroOverlay4, // overlay 3 - zero-line
gridOverlay4, // overlay 4 - grid
lineOverlay4, // overlay 5 - line graph
])
}
}
struct SwiftUIContentView_LineZeroLineGridRanges_Previews: PreviewProvider {
static var previews: some View {
SwiftUIContentView_LineZeroLineGridRanges()
}
}
/////
struct SwiftUILineGraphContentView: View {
let shouldAnimate: Bool
fileprivate let animator = Animator()
var body: some View {
VStack {
Text("A SwiftUI view demonstrating overlays")
.font(.headline)
HStack {
VStack {
SwiftUIContentView_LineOnly()
.frame(width: 150, height: 50)
.padding(4)
.border(Color.gray.opacity(0.3))
Text("Line only").font(.footnote)
}
VStack {
SwiftUIContentView_LineZeroLine()
.frame(width: 150, height: 50)
.padding(4)
.border(Color.gray.opacity(0.3))
Text("Line, zero-line").font(.footnote)
}
VStack {
SwiftUIContentView_LineZeroLineGrid()
.frame(width: 150, height: 50)
.padding(4)
.border(Color.gray.opacity(0.3))
Text("Line, zero-line, grid").font(.footnote)
}
VStack {
SwiftUIContentView_LineZeroLineGridRanges()
.frame(width: 150, height: 50)
.padding(4)
.border(Color.gray.opacity(0.3))
Text("Line, zero-line, grid, ranges").font(.footnote)
}
}
}
.onAppear() {
if shouldAnimate {
self.animator.updateWithNewValues()
}
}
}
}
fileprivate class Animator {
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let `self` = self else {
return
}
// push a new value into the graph's data source
let cr2 = CGFloat.random(in: lineOverlayDataSource.range!) // -1 ... 1)
_ = lineOverlayDataSource.push(value: cr2)
self.updateWithNewValues()
}
}
}
struct SwiftUIContentView_Previews: PreviewProvider {
static var previews: some View {
SwiftUILineGraphContentView(shouldAnimate: false)
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI-Overlays/SwiftUIView.swift
================================================
//
// SwiftUIView.swift
// Demos
//
// Created by Darren Ford on 28/2/21.
//
import SwiftUI
struct SwiftUIView: View {
let shouldAnimate: Bool
var body: some View {
ScrollView([.vertical, .horizontal]) {
VStack {
SuperCoolLineSpark()
OverlayView()
StripesOverlaidView()
SwiftUILineGraphContentView(shouldAnimate: shouldAnimate)
}
}
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView(shouldAnimate: false)
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/SwiftUI_Sparkline_DemoApp.swift
================================================
//
// SwiftUI_Sparkline_DemoApp.swift
// Shared
//
// Created by Darren Ford on 27/1/21.
//
import SwiftUI
let IsRunningInPreviewPane: Bool = ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1"
@main
struct SwiftUI_Sparkline_DemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/TabletView.swift
================================================
//
// TabletView.swift
// Demos
//
// Created by Darren Ford on 28/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate var TabletDataSource2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(range: -1 ... 1)
d.set(values: [1, -1, 1, -1, 1, 0, -1, -1, 1, 0, 1, -1, -1, 0])
return d
}()
struct TabletView: View {
let shouldAnimate: Bool
fileprivate let animator = Animator()
var TabletDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: -1 ... 1)
d.push(values: [1, 1, 1, -1, 1, 0, -1, -1, 1, 1, 1, -1, -1, 1, 1, 0, -1, 1, 1, 1])
return d
}()
var tabletOverlay: DSFSparklineOverlay = {
let t = DSFSparklineOverlay.Tablet()
t.dataSource = TabletDataSource2
t.winStrokeColor = DSFColor.primaryTextColor.cgColor
t.winFill = DSFSparkline.Fill.Color(DSFColor.systemGreen.withAlphaComponent(0.7).cgColor)
t.lossStrokeColor = DSFColor.primaryTextColor.cgColor
t.lossFill = DSFSparkline.Fill.Color(DSFColor.systemRed.withAlphaComponent(0.1).cgColor)
return t
}()
var body: some View {
VStack {
Text("Tablets using prebuilt types")
VStack {
DSFSparklineTabletGraphView.SwiftUI(
dataSource: TabletDataSource1,
winColor: .systemTeal,
lossColor: DSFColor.systemGray.withAlphaComponent(0.2),
barSpacing: 2
)
.frame(height: 20)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
DSFSparklineTabletGraphView.SwiftUI(
dataSource: TabletDataSource1,
lineWidth: 0.5,
barSpacing: 2
)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
.frame(width: 200, height: 20)
}
VStack {
Text("Tablet using overlays")
DSFSparklineSurface.SwiftUI([
self.tabletOverlay
])
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
.frame(height: 30)
.onAppear {
if shouldAnimate {
self.animator.updateWithNewValues()
}
}
}
.padding(EdgeInsets(top: 16, leading: 0, bottom: 0, trailing: 0))
}
}
}
struct TabletView_Previews: PreviewProvider {
static var previews: some View {
TabletView(shouldAnimate: false)
}
}
fileprivate class Animator {
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let `self` = self else {
return
}
// push a new value into the graph's data source
let cr2 = CGFloat.random(in: TabletDataSource2.range!) // -1 ... 1)
_ = TabletDataSource2.push(value: cr2)
self.updateWithNewValues()
}
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/TestingView.swift
================================================
//
// TestingView.swift
// Demos
//
// Created by Darren Ford on 14/4/21.
//
import SwiftUI
import DSFSparkline
struct TestingView: View {
var body: some View {
Text("hello")
.padding()
}
}
struct TestingView_Previews: PreviewProvider {
static var previews: some View {
TestingView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/WinLossGraphContentView.swift
================================================
//
// WinLossGraphContentView.swift
// SwiftUI Demo
//
// Created by Darren Ford on 26/1/21.
//
import SwiftUI
import DSFSparkline
func WinLossCreate() -> some View {
return WinLossGraphContentView(
leftDataSource: WinLossDataSource1,
rightDataSource: WinLossDataSource2,
upDataSource: WinLossDataSource3)
}
struct ProductSales: Identifiable {
var id: String { product }
static let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .percent
formatter.minimumIntegerDigits = 1
formatter.maximumIntegerDigits = 3
formatter.maximumFractionDigits = 3
return formatter
}()
let product: String
let q1: Double
var q1s: String { ProductSales.formatter.string(for: q1 / 100) ?? "" }
let q2: Double
var q2s: String { ProductSales.formatter.string(for: q2 / 100) ?? "" }
let q3: Double
var q3s: String { ProductSales.formatter.string(for: q3 / 100) ?? "" }
let q4: Double
var q4s: String { ProductSales.formatter.string(for: q4 / 100) ?? "" }
var wl: [CGFloat] { [q1, q2, q3, q4] }
}
struct WinLossGraphContentView: View {
let leftDataSource: DSFSparkline.DataSource
let rightDataSource: DSFSparkline.DataSource
let upDataSource: DSFSparkline.DataSource
@State private var sales: [ProductSales] = [
ProductSales(product: "AAA-001", q1: 6, q2: -4, q3: 9.80, q4: 10.20),
ProductSales(product: "BBB-002", q1: 12, q2: -11, q3: -10.6, q4: 5.8),
ProductSales(product: "CCC-003", q1: -9, q2: 7, q3: 5.20, q4: 6.70),
ProductSales(product: "DDD-004", q1: 5, q2: -9, q3: 1.80, q4: -5.90),
]
var body: some View {
VStack(spacing: 8) {
Text("Win/Loss")
.font(.title2).bold()
DSFSparklineWinLossGraphView.SwiftUI(
dataSource: leftDataSource
)
.frame(height: 60)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
Text("Win/Loss/Tie")
.font(.title2).bold()
DSFSparklineWinLossGraphView.SwiftUI(
dataSource: rightDataSource,
winColor: .systemIndigo,
lossColor: .systemTeal,
tieColor: DSFColor.systemGray.withAlphaComponent(0.1),
lineWidth: 3,
barSpacing: 6
)
.frame(height: 60)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
Text("Win/Loss/Tie with center-line")
.font(.title2).bold()
DSFSparklineWinLossGraphView.SwiftUI(
dataSource: upDataSource,
winColor: .systemGreen,
lossColor: .systemRed,
tieColor: DSFColor.systemYellow.withAlphaComponent(0.2),
barSpacing: 3,
showZeroLine: true,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition(color: .systemGray)
)
.frame(width: 330.0, height: 34.0)
.padding(5)
.border(Color.gray.opacity(0.2), width: 1)
Text("Product quarter")
.font(.title2).bold()
Table(sales) {
TableColumn("Product", value: \.product)
.alignment(.leading)
TableColumn("Quarter 1") { product in
Text(product.q1s)
}
.width(75)
.alignment(.trailing)
TableColumn("Quarter 2") { product in
Text(product.q2s)
}
.width(75)
.alignment(.trailing)
TableColumn("Quarter 3") { product in
Text(product.q3s)
}
.width(75)
.alignment(.trailing)
TableColumn("Quarter 4") { product in
Text(product.q4s)
}
.width(75)
.alignment(.trailing)
TableColumn("win-loss") { product in
DSFSparklineWinLossGraphView.SwiftUI(
dataSource: .init(values: product.wl)
)
.frame(height: 25)
.frame(minWidth: 150)
}
.width(150)
.alignment(.center)
}
.frame(height: 250)
}
.padding()
}
}
var WinLossDataSource1: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: -1.0 ... 1)
d.push(values: [1, -1, 0, 1, -1, -1, 1, -1, 0, 1])
return d
}()
var WinLossDataSource2: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 10, range: -1.0 ... 1.0)
d.push(values: [20, 10, 0, -10, -20, -30, 40, 50, 0, 70])
return d
}()
var WinLossDataSource3: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: -1 ... 1)
d.push(values: [1, 1, 1, -1, 1, 0, -1, -1, 1, 1, 1, -1, -1, 1, 1, 0, -1, 1, 1, 1])
return d
}()
struct WinLossGraphContentView_Previews: PreviewProvider {
static var previews: some View {
WinLossGraphContentView(leftDataSource: WinLossDataSource1,
rightDataSource: WinLossDataSource2,
upDataSource: WinLossDataSource3)
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/Shared/WiperGaugeDemoView.swift
================================================
//
// PieGraphDemoView.swift
// Demos
//
// Created by Darren Ford on 16/2/21.
//
import SwiftUI
import DSFSparkline
fileprivate let palette1 = DSFSparkline.Palette([.systemRed, .systemOrange, .systemYellow])
fileprivate let palette2 = DSFSparkline.Palette.shared
fileprivate let grays = DSFSparkline.Palette([.systemGreen, .systemRed])
fileprivate let coldHot = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: CGColor(red: 0, green: 0, blue: 1, alpha: 1), location: 0),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 0, green: 0, blue: 1, alpha: 1), location: 0.3),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0.581, blue: 0, alpha: 1), location: 0.6),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0, blue: 0, alpha: 1), location: 0.8),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0, blue: 0, alpha: 1), location: 1)
])
struct WiperGraphDemoView: View {
@State var randomValue: CGFloat = CGFloat.random(in: 0...1)
@State var sliderValue: CGFloat = 0.75
@State var animationDuration: Double = 0.5
var body: some View {
ScrollView {
VStack(spacing: 8) {
Text("Palette colors").font(.title2).bold()
HStack(spacing: 8) {
ForEach(0 ..< 7) {
DSFSparklineWiperGaugeGraphView.SwiftUI(valueColor: DSFSparkline.ValueBasedFill(palette: palette2), value: Double($0) / 7.0 + 0.1)
.frame(width: 48, height: 24)
}
}
Divider()
Text("Solid colors").font(.title2).bold()
HStack(spacing: 8) {
ForEach(0 ..< 6) {
DSFSparklineWiperGaugeGraphView.SwiftUI(
valueColor: DSFSparkline.ValueBasedFill(flatColor: CGColor(srgbRed: 0.1, green: 0.4, blue: 1.0, alpha: 1)),
value: Double($0) / 6.0 + 0.1
)
.frame(width: 48, height: 24)
}
}
Divider()
VStack {
Text("Palette colors (discrete)").font(.title2).bold()
HStack {
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(valueColor: DSFSparkline.ValueBasedFill(palette: palette2), value: randomValue)
.frame(width: 64, height: 32)
Text("default")
}
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(
valueColor: DSFSparkline.ValueBasedFill(palette: palette2),
value: randomValue,
animationStyle: .init(duration: animationDuration)
)
.frame(width: 64, height: 32)
Text("animated")
}
Divider().frame(height: 60)
VStack {
HStack {
Button("Random") {
randomValue = CGFloat.random(in: 0...1)
}
Button("Min") { randomValue = 0 }
Button("Max") { randomValue = 1 }
Slider(value: $animationDuration, in: 0 ... 2)
.frame(width: 100)
}
Text("\(randomValue)")
.font(.custom("FontNameMono", fixedSize: 12))
}
}
HStack {
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(
valueColor: DSFSparkline.ValueBasedFill(palette: palette2),
value: randomValue
)
.frame(width: 128, height: 64)
Text("default")
}
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(
valueColor: DSFSparkline.ValueBasedFill(palette: palette2),
value: randomValue,
animationStyle: .init(duration: animationDuration)
)
.frame(width: 128, height: 64)
Text("animated")
}
}
Divider()
VStack {
Text("Value color types").font(.title2).bold()
HStack {
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(valueColor: DSFSparkline.ValueBasedFill(flatColor: CGColor(srgbRed: 0.1, green: 0.4, blue: 0.8, alpha: 1)), value: sliderValue)
.frame(width: 200, height: 100)
Text("Solid color")
}
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(valueColor: DSFSparkline.ValueBasedFill(gradient: coldHot), value: sliderValue)
.frame(width: 200, height: 100)
Text("Smooth gradient")
}
VStack {
DSFSparklineWiperGaugeGraphView.SwiftUI(valueColor: DSFSparkline.ValueBasedFill.sharedPalette, value: sliderValue)
.frame(width: 200, height: 100)
Text("Bucket Color")
}
}
Slider(value: $sliderValue)
.frame(width: 200)
}
Divider()
VStack {
Text("Color components").font(.title2).bold()
DSFSparklineWiperGaugeGraphView.SwiftUI(
valueColor: DSFColor(red: 0.1, green: 0.4, blue: 0.8, alpha: 1),
value: sliderValue,
valueBackgroundColor: DSFColor.red,
upperArcColor: DSFColor.yellow,
pointerColor: DSFColor.green,
backgroundColor: DSFColor.purple
)
.frame(width: 200, height: 100)
}
}
}
}
}
}
struct WiperGraphDemoView_Previews: PreviewProvider {
static var previews: some View {
WiperGraphDemoView()
}
}
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/iOS/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSRequiresIPhoneOS
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
UIApplicationSupportsIndirectInputEvents
UILaunchScreen
UIRequiredDeviceCapabilities
armv7
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/macOS/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
================================================
FILE: Demos/Samples/SwiftUI Sparkline Crossplatform/macOS/macOS.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
================================================
FILE: Demos/Samples/iOS Sparkline Demo/AppDelegate.swift
================================================
//
// AppDelegate.swift
// iOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
================================================
FILE: Demos/Samples/iOS Sparkline Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/iOS Sparkline Demo/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/iOS Sparkline Demo/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: Demos/Samples/iOS Sparkline Demo/Base.lproj/Main.storyboard
================================================
================================================
FILE: Demos/Samples/iOS Sparkline Demo/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSRequiresIPhoneOS
UIApplicationSceneManifest
UIApplicationSupportsMultipleScenes
UISceneConfigurations
UIWindowSceneSessionRoleApplication
UISceneConfigurationName
Default Configuration
UISceneDelegateClassName
$(PRODUCT_MODULE_NAME).SceneDelegate
UISceneStoryboardFile
Main
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
armv7
UISupportedInterfaceOrientations
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationPortrait
UIInterfaceOrientationPortraitUpsideDown
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
================================================
FILE: Demos/Samples/iOS Sparkline Demo/SceneDelegate.swift
================================================
//
// SceneDelegate.swift
// iOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let _ = (scene as? UIWindowScene) else { return }
}
func sceneDidDisconnect(_ scene: UIScene) {
// Called as the scene is being released by the system.
// This occurs shortly after the scene enters the background, or when its session is discarded.
// Release any resources associated with this scene that can be re-created the next time the scene connects.
// The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
}
func sceneDidBecomeActive(_ scene: UIScene) {
// Called when the scene has moved from an inactive state to an active state.
// Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
}
func sceneWillResignActive(_ scene: UIScene) {
// Called when the scene will move from an active state to an inactive state.
// This may occur due to temporary interruptions (ex. an incoming phone call).
}
func sceneWillEnterForeground(_ scene: UIScene) {
// Called as the scene transitions from the background to the foreground.
// Use this method to undo the changes made on entering the background.
}
func sceneDidEnterBackground(_ scene: UIScene) {
// Called as the scene transitions from the foreground to the background.
// Use this method to save data, release shared resources, and store enough scene-specific state information
// to restore the scene back to its current state.
}
}
================================================
FILE: Demos/Samples/iOS Sparkline Demo/ViewController.swift
================================================
//
// ViewController.swift
// iOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import UIKit
import DSFSparkline
class ViewController: UIViewController {
@IBOutlet var spark1: DSFSparklineLineGraphView!
@IBOutlet var spark12: DSFSparklineLineGraphView!
var spark1ds = DSFSparkline.DataSource(windowSize: 50, range: -10 ... 30)
@IBOutlet var spark2: DSFSparklineLineGraphView!
var spark2ds = DSFSparkline.DataSource(windowSize: 50, range: -12 ... 12)
@IBOutlet var spark3: DSFSparklineBarGraphView!
var spark3ds = DSFSparkline.DataSource(windowSize: 30, range: -10 ... 10)
@IBOutlet var spark4: DSFSparklineBarGraphView!
var spark4ds = DSFSparkline.DataSource(windowSize: 30, range: -10 ... 10)
@IBOutlet var dot1: DSFSparklineDotGraphView!
var dot1ds = DSFSparkline.DataSource(windowSize: 80, range: -10 ... 10)
@IBOutlet var s1: DSFSparklineBarGraphView!
var ds1 = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet var s2: DSFSparklineBarGraphView!
var ds2 = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet var s3: DSFSparklineBarGraphView!
var ds3 = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet var s4: DSFSparklineBarGraphView!
var ds4 = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet var winLoss1: DSFSparklineWinLossGraphView!
var wl1 = DSFSparkline.DataSource(range: -1 ... 1)
@IBOutlet var winLoss2: DSFSparklineWinLossGraphView!
@IBOutlet var tabletView1: DSFSparklineTabletGraphView!
@IBOutlet var tabletView2: DSFSparklineTabletGraphView!
@IBOutlet var stacklineGraphView: DSFSparklineStackLineGraphView!
var sl1 = DSFSparkline.DataSource(range: 0 ... 10)
@IBOutlet var stacklineGraphView2: DSFSparklineStackLineGraphView!
@IBOutlet var pie0: DSFSparklinePieGraphView!
@IBOutlet var pie1: DSFSparklinePieGraphView!
@IBOutlet var pie2: DSFSparklinePieGraphView!
@IBOutlet var pie3: DSFSparklinePieGraphView!
@IBOutlet var pie4: DSFSparklinePieGraphView!
@IBOutlet weak var attributedStringSupportLabel: UILabel!
@IBOutlet weak var percentBar1: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBar2: DSFSparklinePercentBarGraphView!
@IBOutlet weak var percentBar3: DSFSparklinePercentBarGraphView!
override func viewDidLoad() {
super.viewDidLoad()
// Setup the inline attributed string
self.configureAttributedString()
self.spark1.dataSource = self.spark1ds
self.spark12.dataSource = self.spark1ds
self.spark2.dataSource = self.spark2ds
self.spark2ds.zeroLineValue = 0
self.spark3.dataSource = self.spark3ds
self.spark4.dataSource = self.spark4ds
self.spark4ds.zeroLineValue = -5
self.dot1.dataSource = self.dot1ds
self.s1.dataSource = self.ds1
self.s2.dataSource = self.ds2
self.s3.dataSource = self.ds3
self.s4.dataSource = self.ds4
self.winLoss1.dataSource = self.wl1
self.winLoss2.dataSource = self.wl1
self.tabletView1.dataSource = self.wl1
self.tabletView2.dataSource = self.wl1
self.sl1.zeroLineValue = 5
self.stacklineGraphView.dataSource = self.sl1
self.stacklineGraphView2.dataSource = self.sl1
let palette = DSFSparkline.Palette([
DSFColor(red: 0.6, green: 0.6, blue: 1, alpha: 1),
DSFColor(red: 0.4, green: 0.4, blue: 1, alpha: 1),
DSFColor(red: 0.2, green: 0.2, blue: 1, alpha: 1),
DSFColor(red: 0.0, green: 0.0, blue: 1, alpha: 1),
])
self.pie0.palette = .sharedGrays
self.pie0.dataSource = DSFSparkline.StaticDataSource([5, 5, 5, 5, 5])
self.pie1.palette = palette
self.pie2.palette = palette
self.percentBar1.animationStyle = DSFSparkline.AnimationStyle()
self.percentBar2.animationStyle = DSFSparkline.AnimationStyle()
self.percentBar3.animationStyle = DSFSparkline.AnimationStyle()
self.updateValues(self)
self.addNewValue2()
}
var sinusoid: CGFloat = 0.00
func addNewValue2() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
guard let `self` = self else {
return
}
_ = self.spark1ds.push(value: CGFloat.random(in: self.spark1ds.range!))
_ = self.spark2ds.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.spark3ds.push(value: CGFloat.random(in: -10.0 ... 10.0))
let val = sin(self.sinusoid)
self.sinusoid += 0.3
_ = self.spark4ds.push(value: val * 10.0)
_ = self.dot1ds.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.ds1.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.ds2.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.ds3.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.ds4.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.wl1.push(value: CGFloat(Int.random(in: -1 ... 1)))
_ = self.sl1.push(value: CGFloat(Int.random(in: 0 ... 10)))
self.addNewValue2()
}
}
func configureAttributedString() {
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
let attr = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)!
let message = NSMutableAttributedString(string: "Inlined ")
message.append(attr)
message.append(NSAttributedString(string: " line graph"))
self.attributedStringSupportLabel.attributedText = message
}
@IBAction func updateValues(_: Any) {
let v1 = UInt.random(count: 4, range: 1 ... 10).map { CGFloat($0) }
self.pie1.dataSource = DSFSparkline.StaticDataSource(v1)
self.pie2.dataSource = DSFSparkline.StaticDataSource(v1)
let v2 = UInt.random(count: 4, range: 1 ... 10).map { CGFloat($0) }
self.pie3.dataSource = DSFSparkline.StaticDataSource(v2)
self.pie4.dataSource = DSFSparkline.StaticDataSource(v2)
}
@IBAction func newValuesForPercentBar(_ sender: Any) {
self.percentBar1.value = CGFloat(drand48())
self.percentBar2.value = CGFloat(drand48())
self.percentBar3.value = CGFloat(drand48())
}
}
extension CGFloat {
static func random(count: UInt, range: ClosedRange) -> [CGFloat] {
return (0 ..< count).map { _ in CGFloat.random(in: range) }
}
}
extension UInt {
static func random(count: UInt, range: ClosedRange) -> [UInt] {
return (0 ..< count).map { _ in UInt.random(in: range) }
}
}
================================================
FILE: Demos/Samples/iOS Sparkline Demo/iOS Sparkline Demo.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.network.client
================================================
FILE: Demos/Samples/macOS Sparkline Demo/AppDelegate.swift
================================================
//
// AppDelegate.swift
// macOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import Cocoa
import DSFSparkline
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo/Base.lproj/Main.storyboard
================================================
Default
Left to Right
Right to Left
Default
Left to Right
Right to Left
================================================
FILE: Demos/Samples/macOS Sparkline Demo/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
Main
NSPrincipalClass
NSApplication
NSSupportsAutomaticTermination
NSSupportsSuddenTermination
================================================
FILE: Demos/Samples/macOS Sparkline Demo/ViewController.swift
================================================
//
// ViewController.swift
// macOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import Cocoa
import DSFSparkline
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
@IBOutlet weak var sparkCrash: DSFSparklineDataSourceView!
var sparkCrashDatasource = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet weak var sparkCrash2: DSFSparklineDataSourceView!
var sparkCrash2Datasource = DSFSparkline.DataSource(range: -13 ... 13)
@IBOutlet weak var sparkDeprecation: DSFSparklineDataSourceView!
var sparkDeprecationDatasource = DSFSparkline.DataSource(range: -10 ... 10)
@IBOutlet weak var sparkIntervention: DSFSparklineDataSourceView!
var sparkInterventionDatasource = DSFSparkline.DataSource()
@IBOutlet weak var sparkNetworkError: DSFSparklineDataSourceView!
var sparkNetworkErrorDatasource = DSFSparkline.DataSource(range: -10 ... 30)
@IBOutlet weak var sparkTransmissionError: DSFSparklineDataSourceView!
var sparkTransmissionErrorDatasource = DSFSparkline.DataSource(range: 0 ... 1)
@IBOutlet weak var sparkPacketRejection: DSFSparklineWinLossGraphView!
var sparkPacketRejectionDatasource = DSFSparkline.DataSource(windowSize: 10, range: -1 ... 1)
@IBOutlet weak var stackLineView: DSFSparklineStackLineGraphView!
var stackLineViewDatasource = DSFSparkline.DataSource(range: 0 ... 10)
@IBOutlet weak var fakeSparkCpu1: DSFSparklineDataSourceView!
var fakeSparkCpu1Datasource = DSFSparkline.DataSource(range: 0 ... 1, zeroLineValue: 0.8)
@IBOutlet weak var fakeSparkCpu2: DSFSparklineDataSourceView!
var fakeSparkCpu2Datasource = DSFSparkline.DataSource(range: 0 ... 1, zeroLineValue: 0.8)
@IBOutlet weak var fakeSparkCpu3: DSFSparklineDataSourceView!
var fakeSparkCpu3Datasource = DSFSparkline.DataSource(range: 0 ... 1, zeroLineValue: 0.8)
@IBOutlet weak var fakeSparkCpu4: DSFSparklineDataSourceView!
var fakeSparkCpu4Datasource = DSFSparkline.DataSource(range: 0 ... 1, zeroLineValue: 0.8)
@IBOutlet weak var fakeSparkCpu5: DSFSparklineDataSourceView!
var fakeSparkCpu5Datasource = DSFSparkline.DataSource(range: 0 ... 1, zeroLineValue: 0.8)
@IBOutlet weak var sparkStaticData: DSFSparklineLineGraphView!
var sparkStaticDatasource = DSFSparkline.DataSource(range: -20 ... 50)
@IBOutlet weak var cpuStack: NSStackView!
@IBOutlet weak var cpuDotView: DSFSparklineDotGraphView!
var cpuDotViewDatasource = DSFSparkline.DataSource(windowSize: 100, range: 0 ... 100)
@IBOutlet weak var cpu2DotView: DSFSparklineDotGraphView!
var cpu2DotViewDatasource = DSFSparkline.DataSource(windowSize: 100, range: 0 ... 100)
let cpuUsage = MyCpuUsage()
@IBOutlet weak var attributedStringTextField: NSTextField!
override func viewWillAppear() {
super.viewWillAppear()
self.configureAttributedString()
self.cpuStack.translatesAutoresizingMaskIntoConstraints = false
cpuUsage.delegate = self
sparkCrash.dataSource = sparkCrashDatasource
sparkCrash2.dataSource = sparkCrash2Datasource
sparkCrash2Datasource.zeroLineValue = -5
sparkDeprecation.dataSource = sparkDeprecationDatasource
sparkIntervention.dataSource = sparkInterventionDatasource
sparkNetworkError.dataSource = sparkNetworkErrorDatasource
sparkTransmissionError.dataSource = sparkTransmissionErrorDatasource
sparkPacketRejectionDatasource.windowSize = 20
sparkPacketRejection.dataSource = sparkPacketRejectionDatasource
stackLineView.dataSource = stackLineViewDatasource
//stackLineView.highlightRangeDefinition?.range = 3 ..< 7
stackLineView.highlightRangeVisible = true
fakeSparkCpu1.dataSource = fakeSparkCpu1Datasource
fakeSparkCpu2.dataSource = fakeSparkCpu2Datasource
fakeSparkCpu3.dataSource = fakeSparkCpu3Datasource
fakeSparkCpu4.dataSource = fakeSparkCpu4Datasource
fakeSparkCpu5.dataSource = fakeSparkCpu5Datasource
cpuDotView.dataSource = cpuDotViewDatasource
cpu2DotView.dataSource = cpu2DotViewDatasource
sparkStaticData.dataSource = sparkStaticDatasource
sparkStaticDatasource.set(values: (0 ... 10).map { _ in
CGFloat.random(in: -20.0 ... 50.0)
})
self.updateWithNewValues()
cpuUsage.updateInfo(Timer())
}
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
guard let `self` = self else {
return
}
let cr = CGFloat.random(in: -10.0 ... 10.0)
_ = self.sparkCrashDatasource.push(value: cr)
_ = self.sparkCrash2Datasource.push(value: cr)
_ = self.sparkDeprecationDatasource.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.sparkInterventionDatasource.push(value: CGFloat.random(in: -10.0 ... 10.0))
_ = self.sparkNetworkErrorDatasource.push(value: CGFloat.random(in: -10.0 ... 30.0))
_ = self.sparkTransmissionErrorDatasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.sparkPacketRejectionDatasource.push(value: CGFloat(Int.random(in: -1 ... 1)))
_ = self.stackLineViewDatasource.push(value: CGFloat(Int.random(in: 0 ... 10)))
/////
_ = self.fakeSparkCpu1Datasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.fakeSparkCpu2Datasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.fakeSparkCpu3Datasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.fakeSparkCpu4Datasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.fakeSparkCpu5Datasource.push(value: CGFloat.random(in: 0 ... 1))
_ = self.cpuDotViewDatasource.push(value: CGFloat.random(in: 0 ... 100))
_ = self.cpu2DotViewDatasource.push(value: CGFloat.random(in: 0 ... 100))
self.updateWithNewValues()
}
}
var cpuDataSources: [DSFSparkline.DataSource] = []
func configureAttributedString() {
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
let attr = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)!
let message = NSMutableAttributedString(string: "Inlined ")
message.append(attr)
message.append(NSAttributedString(string: " line graph"))
self.attributedStringTextField.attributedStringValue = message
}
}
extension ViewController: CPUDelegate {
func cpuUsage(usage: [Float]) {
DispatchQueue.main.async { [weak self] in
self?.updateOnMainThread(usage: usage)
}
}
func updateOnMainThread(usage: [Float]) {
if cpuDataSources.count == 0 {
self.cpuStack.arrangedSubviews.forEach { $0.removeFromSuperview() }
cpuDataSources.removeAll()
usage.forEach { _ in
let cpu = DSFSparklineBarGraphView(frame: CGRect(x: 0, y: 0, width: cpuStack.frame.width, height: 32))
let ds = DSFSparkline.DataSource(range: 0 ... 100)
ds.windowSize = 30
cpu.dataSource = ds
self.cpuDataSources.append(ds)
cpu.translatesAutoresizingMaskIntoConstraints = false
cpu.addConstraint(NSLayoutConstraint(item: cpu, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 48))
cpu.graphColor = .systemBlue
cpu.isHidden = false
cpu.barSpacing = 1
self.cpuStack.addArrangedSubview(cpu)
let box = NSBox(frame: NSRect(x: 0, y: 0, width: 20, height: 1))
box.translatesAutoresizingMaskIntoConstraints = false
box.boxType = .separator
self.cpuStack.addArrangedSubview(box)
}
}
usage.enumerated().forEach { (offset, element) in
_ = cpuDataSources[offset].push(value: CGFloat(element * 100.0))
}
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo/cpuUsage.swift
================================================
//
// cpuUsage.swift
// DSFSparklines Demo
//
// Created by Darren Ford on 3/7/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
import Foundation
// CPU usage credit VenoMKO: https://stackoverflow.com/a/6795612/1033581
protocol CPUDelegate {
func cpuUsage(usage: [Float])
}
class MyCpuUsage {
var cpuInfo: processor_info_array_t!
var prevCpuInfo: processor_info_array_t?
var numCpuInfo: mach_msg_type_number_t = 0
var numPrevCpuInfo: mach_msg_type_number_t = 0
var numCPUs: uint = 0
var updateTimer: Timer!
let CPUUsageLock: NSLock = NSLock()
init() {
let mibKeys: [Int32] = [ CTL_HW, HW_NCPU ]
// sysctl Swift usage credit Matt Gallagher: https://github.com/mattgallagher/CwlUtils/blob/master/Sources/CwlUtils/CwlSysctl.swift
mibKeys.withUnsafeBufferPointer() { mib in
var sizeOfNumCPUs: size_t = MemoryLayout.size
let status = sysctl(processor_info_array_t(mutating: mib.baseAddress), 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
if status != 0 {
numCPUs = 1
}
updateTimer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(updateInfo), userInfo: nil, repeats: true)
}
}
var delegate: CPUDelegate?
@objc func updateInfo(_ timer: Timer) {
var numCPUsU: natural_t = 0
let err: kern_return_t = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuInfo, &numCpuInfo);
if err == KERN_SUCCESS {
CPUUsageLock.lock()
var result = [Float]()
for i in 0 ..< Int32(numCPUs) {
var inUse: Int32
var total: Int32
if let prevCpuInfo = prevCpuInfo {
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
total = inUse + (cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
- prevCpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)])
} else {
inUse = cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_USER)]
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_SYSTEM)]
+ cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_NICE)]
total = inUse + cpuInfo[Int(CPU_STATE_MAX * i + CPU_STATE_IDLE)]
}
result.append(Float(inUse) / Float(total))
//print(String(format: "Core: %u Usage: %f", i, Float(inUse) / Float(total) * 100))
}
CPUUsageLock.unlock()
if let prevCpuInfo = prevCpuInfo {
// vm_deallocate Swift usage credit rsfinn: https://stackoverflow.com/a/48630296/1033581
let prevCpuInfoSize: size_t = MemoryLayout.stride * Int(numPrevCpuInfo)
vm_deallocate(mach_task_self_, vm_address_t(bitPattern: prevCpuInfo), vm_size_t(prevCpuInfoSize))
}
prevCpuInfo = cpuInfo
numPrevCpuInfo = numCpuInfo
cpuInfo = nil
numCpuInfo = 0
self.delegate?.cpuUsage(usage: result)
} else {
print("Error!")
}
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo/macOS_Sparkline_Demo.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/AppDelegate.h
================================================
//
// AppDelegate.h
// macOS Sparkline Demo Objc
//
// Created by Darren Ford on 23/12/19.
//
#import
@interface AppDelegate : NSObject
@end
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/AppDelegate.m
================================================
//
// AppDelegate.m
// macOS Sparkline Demo Objc
//
// Created by Darren Ford on 23/12/19.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
@end
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/Base.lproj/Main.storyboard
================================================
Default
Left to Right
Right to Left
Default
Left to Right
Right to Left
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
Main
NSPrincipalClass
NSApplication
NSSupportsAutomaticTermination
NSSupportsSuddenTermination
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/ViewController.h
================================================
//
// ViewController.h
// macOS Sparkline Demo Objc
//
// Created by Darren Ford on 23/12/19.
//
#import
@interface ViewController : NSViewController
@end
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/ViewController.m
================================================
//
// ViewController.m
// macOS Sparkline Demo Objc
//
// Created by Darren Ford on 23/12/19.
//
#import "ViewController.h"
#import
@import DSFSparkline;
@interface ViewController ()
@property (nonatomic, weak) IBOutlet DSFSparklineLineGraphView *lineGraph;
@property (nonatomic, strong) DSFSparklineDataSource* dataSource;
@property (weak) IBOutlet DSFSparklineBarGraphView* barGraph;
@property (nonatomic, strong) DSFSparklineDataSource* barDataSource;
@property (weak) IBOutlet DSFSparklineBarGraphView* centeredBarGraph;
@property (nonatomic, strong) DSFSparklineDataSource* centeredBarDataSource;
@property (weak) IBOutlet DSFSparklineDotGraphView *receiveGraph;
@property (nonatomic, strong) DSFSparklineDataSource* receiveDataSource;
@property (weak) IBOutlet DSFSparklineDotGraphView *sendGraph;
@property (nonatomic, strong) DSFSparklineDataSource* sendDataSource;
@property (weak) IBOutlet DSFSparklinePercentBarGraphView *percentBarThroughput;
@property (weak) IBOutlet DSFSparklineWiperGaugeGraphView *wiperGauge;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
srand48(time(0));
_dataSource = [[DSFSparklineDataSource alloc] init];
_barDataSource = [[DSFSparklineDataSource alloc] init];
_centeredBarDataSource = [[DSFSparklineDataSource alloc] init];
_receiveDataSource = [[DSFSparklineDataSource alloc] init];
_sendDataSource = [[DSFSparklineDataSource alloc] init];
[_wiperGauge setAnimationStyle:[[AnimationStyle alloc] initWithDuration:0.2 function: AnimatorFunctionTypeLinear]];
// Add a custom marker drawing function
[_lineGraph setMarkerDrawingBlock:^(CGContextRef context, NSArray * markers) {
// Just draw the markers for the 4 most recent values
id ms = [markers subarrayWithRange:NSMakeRange([markers count] - 4, 4)];
for (DSFSparklineOverlayLineMarker* m in ms) {
CGContextSetFillColorWithColor(context, NSColor.whiteColor.CGColor);
CGContextSetShadowWithColor(context, CGSizeMake(1, 1), 4, NSColor.linkColor.CGColor);
CGContextFillRect(context, [m rect]);
}
}];
[[self lineGraph] setDataSource:[self dataSource]];
[[self dataSource] setRangeWithLowerBound:-1.0 upperBound:1.0];
[[self lineGraph] setZeroLineDashStyleString: @"3,3"];
[[self barGraph] setDataSource:[self barDataSource]];
[[self barDataSource] setRangeWithLowerBound:0.0 upperBound:1.0];
[[self barDataSource] setZeroLineValue:0.2];
[[self barDataSource] setWindowSize:30];
CGColorRef blue = CGColorCreateGenericRGB(0, 0, 1, 0.5);
id high = [[DSFSparklineHighlightRangeDefinition alloc] initWithLowerBound:0.3
upperBound:0.7
fillColor:blue];
[[self barGraph] setHighlightRangeDefinition:@[high]];
[[self centeredBarGraph] setDataSource:[self centeredBarDataSource]];
[[self centeredBarDataSource] setRangeWithLowerBound:-1.0 upperBound:1.0];
//[[self centeredBarDataSource] setZeroLineValue:-0.2];
[[self centeredBarDataSource] setWindowSize:22];
[[self centeredBarDataSource] setWithValues:
@[@(0.0), @(0.1), @(0.2), @(0.3), @(0.4), @(0.5), @(0.6), @(0.7), @(0.8), @(0.9), @(1),
@(0.0), @(-0.1), @(-0.2), @(-0.3), @(-0.4), @(-0.5), @(-0.6), @(-0.7), @(-0.8), @(-0.9), @(-1)]];
[[self receiveGraph] setDataSource:[self receiveDataSource]];
[[self receiveDataSource] setRangeWithLowerBound:0.0 upperBound:1.0];
[[self sendGraph] setDataSource:[self sendDataSource]];
[[self sendDataSource] setRangeWithLowerBound:0.0 upperBound:1.0];
DSFSparklinePercentBarStyle* style = [[self percentBarThroughput] displayStyle];
[style setBarEdgeInsets: NSEdgeInsetsMake(1, 1, 1, 1)];
[self performUpdate];
}
- (void)performUpdate {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CGFloat v1 = drand48();
[[self wiperGauge] setValue:v1];
BOOL result = [[self dataSource] pushWithValue: (v1 * 2) - 1];
[[self percentBarThroughput] setValue:v1];
result = [[self barDataSource] pushWithValue: v1];
// v1 = drand48();
// result = [[self centeredBarDataSource] pushWithValue:(v1 * 2) - 1];
v1 = drand48();
result = [[self receiveDataSource] pushWithValue:v1];
v1 = drand48();
result = [[self sendDataSource] pushWithValue:v1];
[self performUpdate];
});
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
// Update the view, if already loaded.
}
@end
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/macOS_Sparkline_Demo_Objc.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
================================================
FILE: Demos/Samples/macOS Sparkline Demo Objc/main.m
================================================
//
// main.m
// macOS Sparkline Demo Objc
//
// Created by Darren Ford on 23/12/19.
//
#import
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
================================================
FILE: Demos/Samples/macOS Table Demo/AppDelegate.swift
================================================
//
// AppDelegate.swift
// macOS Table Demo
//
// Created by Darren Ford on 27/12/19.
//
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
================================================
FILE: Demos/Samples/macOS Table Demo/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "16x16",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "32x32",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "128x128",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Table Demo/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/macOS Table Demo/Base.lproj/Main.storyboard
================================================
Default
Left to Right
Right to Left
Default
Left to Right
Right to Left
================================================
FILE: Demos/Samples/macOS Table Demo/GridViewController.swift
================================================
//
// GridViewController.swift
// macOS Table Demo
//
// Created by Darren Ford on 27/12/19.
//
import Cocoa
import DSFSparkline
class GridViewController: NSViewController {
@IBOutlet weak var grid: NSGridView!
let sz = 32
var dataSources: [[DSFSparkline.DataSource]] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
grid.removeColumn(at: 0)
grid.removeColumn(at: 0)
grid.removeRow(at: 0)
grid.removeRow(at: 0)
grid.removeRow(at: 0)
(0 ... sz).forEach { row in
let item = (0 ... sz).map { _ in
DSFSparkline.DataSource(windowSize: 30, range: -1 ... 1)
}
dataSources.append(item)
}
(0 ... sz).forEach { row in
let vs = (0 ... sz).map { column -> DSFSparklineLineGraphView in
let i = DSFSparklineLineGraphView()
i.zeroLineVisible = false
i.graphColor = self.color(row: row, col: column)
i.lineWidth = 0.5
i.translatesAutoresizingMaskIntoConstraints = false
i.addConstraint(NSLayoutConstraint(item: i, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 32))
i.addConstraint(NSLayoutConstraint(item: i, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 32))
i.dataSource = self.dataSources[row][column]
let ranr: [CGFloat] = RandomArray(count: 30, range: -1 ... 1)
self.dataSources[row][column].push(values: ranr)
return i
}
self.grid.addColumn(with: vs)
}
self.updateWithNewValues()
}
private func lighter(_ color: NSColor) -> NSColor {
var h: CGFloat = 0
var s: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
let cl = color.usingColorSpace(.genericRGB)!
cl.getHue(&h, saturation: &s, brightness: &b, alpha: &a)
return NSColor(calibratedHue: h,
saturation: max(s + 0.3, 1.0),
brightness: max(b + 0.3, 1.0), alpha: a)
}
func color(row: Int, col: Int) -> NSColor {
// CMYK
let xpos = CGFloat(col) / CGFloat(sz)
let ypos = CGFloat(row) / CGFloat(sz)
let c = hypot(xpos, ypos) / 1.414
let m = hypot(1.0 - xpos, ypos) / 1.414
let y = hypot(1.0 - xpos, 1.0 - ypos) / 1.414
let k = hypot(xpos, 1.0 - ypos) / 1.414
let color = NSColor(deviceCyan: c, magenta: m, yellow: y, black: k, alpha: 1.0)
return self.lighter(color)
}
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let `self` = self else {
return
}
(0 ... self.sz).forEach { row in
(0 ... self.sz).forEach { column in
_ = self.dataSources[row][column].push(value: CGFloat.random(in: -1 ... 1))
}
}
self.updateWithNewValues()
}
}
}
/// Generate an array of random values for Swift value types that support random value generation
/// - Parameters:
/// - count: The number of random numbers to generate
/// - range: The range of values to generate
///
/// Example:
///
/// ```swift
/// let tenVals: [CGFloat] = RandomArray(count: 10, range: -10 ... 10)
/// ```
///
/// Note that the `[CGFloat]` in the definition is required, as explicit generic function instantiation isn't supported
/// in Swift.
func RandomArray(count: UInt, range: ClosedRange) -> Array where T: BinaryFloatingPoint, T.RawSignificand : FixedWidthInteger {
return (0 ..< count).map { _ in T.random(in: range) }
}
================================================
FILE: Demos/Samples/macOS Table Demo/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
Main
NSPrincipalClass
NSApplication
NSSupportsAutomaticTermination
NSSupportsSuddenTermination
================================================
FILE: Demos/Samples/macOS Table Demo/ViewController.swift
================================================
//
// ViewController.swift
// macOS Table Demo
//
// Created by Darren Ford on 27/12/19.
//
import Cocoa
import DSFSparkline
class ViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
let count = 200
var inDataSources: [DSFSparkline.DataSource] = []
var outDataSources: [DSFSparkline.DataSource] = []
var cpuDataSources: [DSFSparkline.DataSource] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
(0 ..< count).forEach { _ in
inDataSources.append(DSFSparkline.DataSource(windowSize: 50, range: -1.5 ... 1.5))
outDataSources.append(DSFSparkline.DataSource(windowSize: 50, range: -1.5 ... 1.5))
cpuDataSources.append(DSFSparkline.DataSource(windowSize: 100, range: 0 ... 100))
}
self.tableView.reloadData()
self.updateWithNewValues()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
guard let `self` = self else {
return
}
self.inDataSources.forEach {
_ = $0.push(value: CGFloat.random(in: -1 ... 1))
}
self.outDataSources.forEach {
_ = $0.push(value: CGFloat.random(in: -1 ... 1))
}
self.cpuDataSources.forEach {
_ = $0.push(value: CGFloat.random(in: 0 ... 100))
}
self.updateWithNewValues()
}
}
}
class Graphico: NSTableCellView {
@IBOutlet weak var sparkline: DSFSparklineLineGraphView!
}
class Graphicodot: NSTableCellView {
@IBOutlet weak var sparkline: DSFSparklineDotGraphView!
}
extension ViewController: NSTableViewDataSource, NSTableViewDelegate {
func numberOfRows(in tableView: NSTableView) -> Int {
return count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if tableColumn?.identifier == NSUserInterfaceItemIdentifier("ident"),
let tcv = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("identifier"), owner: self) as? NSTableCellView {
tcv.textField?.stringValue = "Input \(row)"
return tcv
}
else if tableColumn?.identifier == NSUserInterfaceItemIdentifier("ingraph"),
let tcv = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("ingraph"), owner: self) as? Graphico {
tcv.sparkline.dataSource = inDataSources[row]
return tcv
}
else if tableColumn?.identifier == NSUserInterfaceItemIdentifier("outgraph"),
let tcv = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("outgraph"), owner: self) as? Graphico {
tcv.sparkline.dataSource = outDataSources[row]
return tcv
}
else if tableColumn?.identifier == NSUserInterfaceItemIdentifier("cpuGraph"),
let tcv = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("cpuGraph"), owner: self) as? Graphicodot {
tcv.sparkline.dataSource = cpuDataSources[row]
return tcv
}
return nil
}
}
================================================
FILE: Demos/Samples/macOS Table Demo/macOS_Table_Demo.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/AppDelegate.swift
================================================
//
// AppDelegate.swift
// tvOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json
================================================
{
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Middle.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json
================================================
{
"layers" : [
{
"filename" : "Front.imagestacklayer"
},
{
"filename" : "Middle.imagestacklayer"
},
{
"filename" : "Back.imagestacklayer"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json
================================================
{
"assets" : [
{
"size" : "1280x768",
"idiom" : "tv",
"filename" : "App Icon - App Store.imagestack",
"role" : "primary-app-icon"
},
{
"size" : "400x240",
"idiom" : "tv",
"filename" : "App Icon.imagestack",
"role" : "primary-app-icon"
},
{
"size" : "2320x720",
"idiom" : "tv",
"filename" : "Top Shelf Image Wide.imageset",
"role" : "top-shelf-image-wide"
},
{
"size" : "1920x720",
"idiom" : "tv",
"filename" : "Top Shelf Image.imageset",
"role" : "top-shelf-image"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
},
{
"idiom" : "tv-marketing",
"scale" : "1x"
},
{
"idiom" : "tv-marketing",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json
================================================
{
"images" : [
{
"idiom" : "tv",
"scale" : "1x"
},
{
"idiom" : "tv",
"scale" : "2x"
},
{
"idiom" : "tv-marketing",
"scale" : "1x"
},
{
"idiom" : "tv-marketing",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Assets.xcassets/Contents.json
================================================
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Base.lproj/LaunchScreen.storyboard
================================================
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Base.lproj/Main.storyboard
================================================
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSRequiresIPhoneOS
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
Main
UIRequiredDeviceCapabilities
arm64
UIUserInterfaceStyle
Automatic
================================================
FILE: Demos/Samples/tvOS Sparkline Demo/ViewController.swift
================================================
//
// ViewController.swift
// tvOS Sparkline Demo
//
// Created by Darren Ford on 23/12/19.
//
import UIKit
import DSFSparkline
class ViewController: UIViewController {
@IBOutlet var green: DSFSparklineDataSourceView!
var greenDataSource = DSFSparkline.DataSource(range: -11 ... 11)
@IBOutlet var red: DSFSparklineLineGraphView!
var redDataSource = DSFSparkline.DataSource(range: -45 ... 15)
@IBOutlet weak var centeredRedDataSource: DSFSparklineLineGraphView!
@IBOutlet var purple: DSFSparklineDotGraphView!
var purpleDataSource = DSFSparkline.DataSource(range: 0 ... 50)
@IBOutlet var orange: DSFSparklineDotGraphView!
var orangeDataSource = DSFSparkline.DataSource(range: 0 ... 50)
@IBOutlet var winLoss: DSFSparklineWinLossGraphView!
var wlSource = DSFSparkline.DataSource(range: -1 ... 1)
@IBOutlet var pie1: DSFSparklinePieGraphView!
@IBOutlet var pie2: DSFSparklinePieGraphView!
@IBOutlet var pie3: DSFSparklinePieGraphView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.winLoss.dataSource = self.wlSource
let randWinLoss: [CGFloat] = (0 ..< 50).map { _ in
CGFloat(Int.random(in: -1 ... 1))
}
self.wlSource.set(values: randWinLoss)
self.green.dataSource = self.greenDataSource
self.red.dataSource = self.redDataSource
self.centeredRedDataSource.dataSource = self.redDataSource
self.purple.dataSource = self.purpleDataSource
self.orange.dataSource = self.orangeDataSource
self.pie1.animationStyle = DSFSparkline.AnimationStyle(duration: 2.0)
self.pie1.dataSource = DSFSparkline.StaticDataSource([1, 2, 3])
self.pie2.animationStyle = DSFSparkline.AnimationStyle(duration: 2.0)
self.pie2.dataSource = DSFSparkline.StaticDataSource([3, 2, 1])
self.pie3.animationStyle = DSFSparkline.AnimationStyle(duration: 2.0)
self.pie3.dataSource = DSFSparkline.StaticDataSource([1, 7, 4, 9])
self.updateWithNewValues()
}
func updateWithNewValues() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
guard let `self` = self else { return }
_ = self.greenDataSource.push(value: CGFloat.random(in: self.greenDataSource.range!))
_ = self.redDataSource.push(value: CGFloat.random(in: -40 ... 10))
_ = self.purpleDataSource.push(value: CGFloat.random(in: self.purpleDataSource.range!))
_ = self.orangeDataSource.push(value: CGFloat.random(in: self.orangeDataSource.range!))
_ = self.wlSource.push(value: CGFloat(Int.random(in: -1 ... 1)))
self.updateWithNewValues()
}
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/AppDelegate.swift
================================================
//
// AppDelegate.swift
// Simple Wiper Gauge
//
// Created by Darren Ford on 4/1/23.
//
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
"images" : [
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Assets.xcassets/Contents.json
================================================
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Base.lproj/Main.storyboard
================================================
Default
Left to Right
Right to Left
Default
Left to Right
Right to Left
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIconFile
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
1.0
CFBundleVersion
1
LSMinimumSystemVersion
$(MACOSX_DEPLOYMENT_TARGET)
NSMainStoryboardFile
Main
NSPrincipalClass
NSApplication
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/Simple_Wiper_Gauge.entitlements
================================================
com.apple.security.app-sandbox
com.apple.security.files.user-selected.read-only
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge/ViewController.swift
================================================
//
// ViewController.swift
// Simple Wiper Gauge
//
// Created by Darren Ford on 4/1/23.
//
import Cocoa
import DSFSparkline
class ViewController: NSViewController {
@IBOutlet weak var wiperGauge: DSFSparklineWiperGaugeGraphView!
override func viewDidLoad() {
super.viewDidLoad()
wiperGauge.value = 0.65
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
@IBAction func valueDidUpdate(_ sender: NSSlider) {
wiperGauge.value = CGFloat(sender.doubleValue)
}
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
2354FF5829651D9600376C3C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2354FF5729651D9600376C3C /* AppDelegate.swift */; };
2354FF5A29651D9600376C3C /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2354FF5929651D9600376C3C /* ViewController.swift */; };
2354FF5C29651D9700376C3C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2354FF5B29651D9700376C3C /* Assets.xcassets */; };
2354FF5F29651D9700376C3C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2354FF5D29651D9700376C3C /* Main.storyboard */; };
2354FF702965202600376C3C /* DSFSparkline-static in Frameworks */ = {isa = PBXBuildFile; productRef = 2354FF6F2965202600376C3C /* DSFSparkline-static */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
2354FF5429651D9600376C3C /* Simple Wiper Gauge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Simple Wiper Gauge.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2354FF5729651D9600376C3C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
2354FF5929651D9600376C3C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
2354FF5B29651D9700376C3C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
2354FF5E29651D9700376C3C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
2354FF6029651D9700376C3C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
2354FF6129651D9700376C3C /* Simple_Wiper_Gauge.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Simple_Wiper_Gauge.entitlements; sourceTree = ""; };
2354FF6829651DA400376C3C /* DSFSparkline */ = {isa = PBXFileReference; lastKnownFileType = folder; name = DSFSparkline; path = ../..; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
2354FF5129651D9600376C3C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
2354FF702965202600376C3C /* DSFSparkline-static in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
2354FF4B29651D9600376C3C = {
isa = PBXGroup;
children = (
2354FF6829651DA400376C3C /* DSFSparkline */,
2354FF5629651D9600376C3C /* Simple Wiper Gauge */,
2354FF5529651D9600376C3C /* Products */,
2354FF6B29651DC300376C3C /* Frameworks */,
);
sourceTree = "";
};
2354FF5529651D9600376C3C /* Products */ = {
isa = PBXGroup;
children = (
2354FF5429651D9600376C3C /* Simple Wiper Gauge.app */,
);
name = Products;
sourceTree = "";
};
2354FF5629651D9600376C3C /* Simple Wiper Gauge */ = {
isa = PBXGroup;
children = (
2354FF5729651D9600376C3C /* AppDelegate.swift */,
2354FF5929651D9600376C3C /* ViewController.swift */,
2354FF5B29651D9700376C3C /* Assets.xcassets */,
2354FF5D29651D9700376C3C /* Main.storyboard */,
2354FF6029651D9700376C3C /* Info.plist */,
2354FF6129651D9700376C3C /* Simple_Wiper_Gauge.entitlements */,
);
path = "Simple Wiper Gauge";
sourceTree = "";
};
2354FF6B29651DC300376C3C /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
2354FF5329651D9600376C3C /* Simple Wiper Gauge */ = {
isa = PBXNativeTarget;
buildConfigurationList = 2354FF6429651D9700376C3C /* Build configuration list for PBXNativeTarget "Simple Wiper Gauge" */;
buildPhases = (
2354FF5029651D9600376C3C /* Sources */,
2354FF5129651D9600376C3C /* Frameworks */,
2354FF5229651D9600376C3C /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Simple Wiper Gauge";
packageProductDependencies = (
2354FF6F2965202600376C3C /* DSFSparkline-static */,
);
productName = "Simple Wiper Gauge";
productReference = 2354FF5429651D9600376C3C /* Simple Wiper Gauge.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
2354FF4C29651D9600376C3C /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 1230;
LastUpgradeCheck = 1230;
TargetAttributes = {
2354FF5329651D9600376C3C = {
CreatedOnToolsVersion = 12.3;
};
};
};
buildConfigurationList = 2354FF4F29651D9600376C3C /* Build configuration list for PBXProject "Simple Wiper Gauge" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 2354FF4B29651D9600376C3C;
productRefGroup = 2354FF5529651D9600376C3C /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
2354FF5329651D9600376C3C /* Simple Wiper Gauge */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
2354FF5229651D9600376C3C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2354FF5C29651D9700376C3C /* Assets.xcassets in Resources */,
2354FF5F29651D9700376C3C /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
2354FF5029651D9600376C3C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
2354FF5A29651D9600376C3C /* ViewController.swift in Sources */,
2354FF5829651D9600376C3C /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
2354FF5D29651D9700376C3C /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
2354FF5E29651D9700376C3C /* Base */,
);
name = Main.storyboard;
sourceTree = "";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
2354FF6229651D9700376C3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
2354FF6329651D9700376C3C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
};
name = Release;
};
2354FF6529651D9700376C3C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Simple Wiper Gauge/Simple_Wiper_Gauge.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
INFOPLIST_FILE = "Simple Wiper Gauge/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = "org.dagronf.dsfsparkline.Simple-Wiper-Gauge";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Debug;
};
2354FF6629651D9700376C3C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "Simple Wiper Gauge/Simple_Wiper_Gauge.entitlements";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = 3L6RK3LGGW;
INFOPLIST_FILE = "Simple Wiper Gauge/Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = "org.dagronf.dsfsparkline.Simple-Wiper-Gauge";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
2354FF4F29651D9600376C3C /* Build configuration list for PBXProject "Simple Wiper Gauge" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2354FF6229651D9700376C3C /* Debug */,
2354FF6329651D9700376C3C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
2354FF6429651D9700376C3C /* Build configuration list for PBXNativeTarget "Simple Wiper Gauge" */ = {
isa = XCConfigurationList;
buildConfigurations = (
2354FF6529651D9700376C3C /* Debug */,
2354FF6629651D9700376C3C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCSwiftPackageProductDependency section */
2354FF6F2965202600376C3C /* DSFSparkline-static */ = {
isa = XCSwiftPackageProductDependency;
productName = "DSFSparkline-static";
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 2354FF4C29651D9600376C3C /* Project object */;
}
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
================================================
FILE: Demos/Simple Wiper Gauge/Simple Wiper Gauge.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
================================================
{
"object": {
"pins": [
{
"package": "SwiftImageReadWrite",
"repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite",
"state": {
"branch": null,
"revision": "42ace2412279f18bc264bc306e96b51c36e12a33",
"version": "1.9.2"
}
}
]
},
"version": 1
}
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2025 Darren Ford
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Package.resolved
================================================
{
"object": {
"pins": [
{
"package": "SwiftImageReadWrite",
"repositoryURL": "https://github.com/dagronf/SwiftImageReadWrite",
"state": {
"branch": null,
"revision": "42ace2412279f18bc264bc306e96b51c36e12a33",
"version": "1.9.2"
}
}
]
},
"version": 1
}
================================================
FILE: Package.swift
================================================
// swift-tools-version: 5.4
import PackageDescription
let package = Package(
name: "DSFSparkline",
platforms: [
.macOS(.v10_13),
.iOS(.v14),
.tvOS(.v14)
],
products: [
.library(name: "DSFSparkline", targets: ["DSFSparkline"]),
.library(name: "DSFSparkline-static", type: .static, targets: ["DSFSparkline"]),
.library(name: "DSFSparkline-shared", type: .dynamic, targets: ["DSFSparkline"]),
],
dependencies: [
.package(url: "https://github.com/dagronf/SwiftImageReadWrite", from: "1.9.2"),
],
targets: [
.target(
name: "DSFSparkline",
dependencies: []),
.testTarget(
name: "DSFSparklineTests",
dependencies: ["DSFSparkline", "SwiftImageReadWrite"]),
]
)
================================================
FILE: README.md
================================================
# Sparklines for macOS, iOS and tvOS
A lightweight sparkline component, supporting Swift, SwiftUI, macCatalyst and Objective-C.
## What is a sparkline?
A sparkline is a very small chart, typically drawn without axes or coordinates. It presents the general shape of the variation (typically over time) in some measurement, such as temperature or stock market price, in a simple and highly condensed way. Sparklines are small enough to be embedded in text, or several sparklines may be grouped together as elements of a small multiple. Whereas the typical chart is designed to show as much data as possible, and is set off from the flow of text, sparklines are intended to be succinct, memorable, and located where they are discussed.
[Source: Wikipedia](https://en.wikipedia.org/wiki/Sparkline)
## What ISN'T a sparkline?
`DSFSparkline` **IS NOT** designed to be a full-featured graphing library. It was built to be lightweight, to create small, memorable charts within an app.
If you need features like labelling, real-time updating, axis-labelling, interactivity, legends or beautiful charts at larger sizes, you might be better served by the [Charts library](https://github.com/danielgindi/Charts), [Core plot](https://github.com/core-plot/core-plot) or [SciChart (paid)](https://www.scichart.com/ios-chart-features/). You can find a whole lot more [here](https://iosexample.com/tag/charts/).
## Features
* Multiple graph styles support, such as line, bar, tablet etc.
* Support for sparkline customizations, such as zero-line, grid lines, highlighting.
* Prebuilt NSView/UIView/SwiftUI types for quick integration
* Independently scalable for sparklines at any size
* y-range can automatically grow or shrink to encompass the full y-range of data.
* y-range can be fixed and the sparkline will truncate to the specified range
* SwiftUI support for all sparkline types
* NSAttributedString support
* `IBDesignable` support for prebuilt types so you can see and configure your sparklines in interface builder.
* Optional drawing of a 'zero line' on the bar and line graphs (thanks [Tito Ciuro](https://github.com/tciuro))
* Playground support
## TL;DR - Show me something!
Create a retina-scale (144dpi) bitmap with a simple line overlay graph
```swift
// A datasource with a simple set of data
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stack = DSFSparklineOverlay.Line() // Create a line overlay
stack.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(stack) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)
// Embed a sparkline in an NSAttributedString
let attributedString = bitmap.attributedString(size: CGSize(width: 40, height: 18), scale: 2)
```
Create a Swift-UI line graph sparkline with zero-line and highlight range overlays
```swift
fileprivate let SwiftUIDemoDataSource: DSFSparkline.DataSource = {
let d = DSFSparkline.DataSource(windowSize: 20, range: 0 ... 1, zeroLineValue: 0.5)
d.push(values: [
0.72, 0.84, 0.15, 0.16, 0.30, 0.58, 0.87, 0.44, 0.02, 0.27,
0.48, 0.16, 0.15, 0.14, 0.81, 0.53, 0.67, 0.52, 0.07, 0.50
])
return d
}()
struct SuperCoolLineSpark: View {
// The overlay representing the zero-line for the data source
var zeroOverlay: DSFSparklineOverlay = {
let zeroLine = DSFSparklineOverlay.ZeroLine()
zeroLine.dataSource = SwiftUIDemoDataSource
zeroLine.dashStyle = []
return zeroLine
}()
// The overlay to draw a highlight between range 0 ..< 0.2
var rangeOverlay: DSFSparklineOverlay = {
let highlight = DSFSparklineOverlay.RangeHighlight()
highlight.dataSource = SwiftUIDemoDataSource
highlight.highlightRange = 0.0 ..< 0.2
highlight.fill = DSFSparkline.Fill.Color(DSFColor.gray.withAlphaComponent(0.4).cgColor)
return highlight
}()
// The actual line graph
var lineOverlay: DSFSparklineOverlay = {
let lineOverlay = DSFSparklineOverlay.Line()
lineOverlay.dataSource = SwiftUIDemoDataSource
lineOverlay.primaryStrokeColor = DSFColor.systemBlue.cgColor
lineOverlay.primaryFill = DSFSparkline.Fill.Color(DSFColor.systemBlue.withAlphaComponent(0.3).cgColor)
lineOverlay.secondaryStrokeColor = DSFColor.systemYellow.cgColor
lineOverlay.secondaryFill = DSFSparkline.Fill.Color(DSFColor.systemYellow.withAlphaComponent(0.3).cgColor)
lineOverlay.strokeWidth = 1
lineOverlay.markerSize = 4
lineOverlay.centeredAtZeroLine = true
return lineOverlay
}()
var body: some View {
DSFSparklineSurface.SwiftUI([
rangeOverlay, // range highlight overlay
zeroOverlay, // zero-line overlay
lineOverlay, // line graph overlay
])
.frame(width: 150, height: 40)
}
}
```
## Integration
Use Swift Package Manager to integrate `DSFSparkline` into your project
Add `https://github.com/dagronf/DSFSparkline` to your project.
### Note
When adding `DSFSparkline` to one of your projects, you need to choose a **SINGLE** 'package product' to
link to your target. Choosing multiple package products for your project will result in weird, inconsistent link errors.
| Package Product | Description |
|:-----------------------|:--------------------------------|
| `DSFSparkline` | The default style. If you are just trying this library out, this is the one to choose
| `DSFSparkline-static` | Add `DSFSparkline` as a STATIC library, meaning that all DSFSparkline code is linked directly into your target |
| `DSFSparkline-shared` | Add `DSFSparkline` as a SHARED library, meaning that you can use a single shared framework between multiple targets in your project to save space |
## Available graph types
### Line
A simple line sparkline. The line can be centered around a zero line to indicate positive and negative values. You can also add (optional) markers to the data points.
You can custom-draw the markers by supplying a drawing callback block (`markerDrawingBlock`) where you can customize which markers are drawn (for example, just the min and max values) and how they are drawn.
| Standard | Centered |
|------------|------------|
| | |
| Interpolated | Interpolated Centered |
|------------|------------|
| | |
| Standard Markers | Interpolated Markers |
|------------|------------|
| | |
| Custom Markers (min/max only) | Custom Markers (Last 5 values) |
|------------|------------|
| | |
Swift example using custom markers
```swift
//
// A custom marker drawing function that draws the maximum value in green, the minimum value in red
//
self.myLineView.markerDrawingBlock = { context, markerFrames in
// Get the frames containing the minimum and maximum values
if let minMarker = markerFrames.min(by: { (a, b) -> Bool in a.value < b.value }),
let maxMarker = markerFrames.min(by: { (a, b) -> Bool in a.value > b.value }) {
// Draw minimum marker
context.setFillColor(DSFColor.systemRed.cgColor)
context.fill(minMarker.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(minMarker.rect)
// Draw maximum marker
context.setFillColor(DSFColor.systemGreen.cgColor)
context.fill(maxMarker.rect)
context.setLineWidth(0.5)
context.setStrokeColor(DSFColor.white.cgColor)
context.stroke(maxMarker.rect)
}
}
```
### Bar
A simple barchart sparkline. The bar can be centered around a zero line to indicate positive and negative values.
| Standard | Centered |
|------------|------------|
| | |
### Stackline
A stackline sparkline is similar to a bar chart except that it doesn't separate out individual columns. The stackline can be centered around a zero line to indicate positive and negative values.
| Standard | Centered |
|------------|------------|
| | |
### Stripes
A stripes graph. A good example of a stripes graph is the 'warming stripes' [climate graph](https://www.climatecentral.org/showyourstripes).
Values from the datasource are mapped to a supplied gradient
| Standard | Integral (pixel boundaries) |
|------------|-------------------------------|
| | |
### Dot
A dot sparkline reminiscent of Activity Viewer's CPU history graphs
| Standard | Inverted |
|------------|------------|
| | |
### Win/Loss/Tie
A win-loss graph, where positive values in the datasource are represented as a 'win', negative values represented as a 'loss', and zero values are a 'tie'.
| Win/Loss | Win/Loss/Tie |
|------------|------------|
| | |
### Tablet
A tablet graph, where positive values in the datasource are represented as a filled circle, negative values represented as an unfilled circle. The behaviour is identical to the win/loss graph.
| Standard |
|------------|
| |
### Pie
A basic pie sparkline
| Standard |
|------------|
| |
### DataBar
A databar sparkline. Supports percentage and totals types.
| Percent | Total |
|------------|------------|
| | |
### PercentBar
A percentbar sparkline takes a single value (0 ... 1) and draws a horizontal bar chart containing the value with an optional text label.
| | |
|------------|------------|
| | |
### WiperGauge
A simple gauge with a single value (0 ... 1). The color displayed in the gauge can be one of
* a single flat color
* a color mapped from a gradient to the value
* a color mapped from a color bucket to the value
| Standard |
|------------|
| |
### Activity Grid
A Github-style activity grid. Each cell represents a discrete value in a progression with a color
* Configurable horizontal/vertical cell count
* Configurable cell spacing
* Configurable fill schemes
| | |
|------------|------------|
| | |
### Circular Progress
A circular progress graph
* Configurable track width
* Optional track background color
* Configurable track padding to allow overlaying multiple progress graphs to replicate the health rings.
* Configurable fill schemes
| Simulated health rings |
|------------------------|
| |
### Circular Gauge
A circular gauge
* Configurable track/line width
* Configurable fill/stroke schemes
* Supports shadows/inner shadows
* Animatable changes
| | | | |
|------|-------|-------|-------|
| | | | |
# Demos
You can find a lot of examples of sparklines in projects in the `Demos/Samples` subfolder, providing examples for macOS (swift and objc), iO, tvOS, macCatalyst and SwiftUI.
There's also a simple Xcode Playground available in the `Demos/Playground` subfolder where you can play with the different sparkline types
# Building your sparkline
### Using prebuilt views
A prebuilt view is useful to quickly add a sparkline using SwiftUI or via Interface Builder. These views a slightly more limited set of styling and customizations but are much quicker to implement than using overlays directly.
For the most part, the prebuilt types will be all you need.
If you've used `DSFSparklines` prior to v4, these are the original view types that you used to display your sparklines.
* **A datasource** - the set of values to draw
* **A prebuilt view type** - the NSView/UIView/SwiftUI view to draw your sparkline
### Using overlays
There are three fundamental building blocks for an overlay sparkline. Overlay sparklines are more flexible and configurable than the pre-built views, but are more complex to set up.
* **A surface** - where it will draw
* **A datasource** - the set of values to draw
* **One or more overlays** - the 'layers' which render different components of the sparkline
## Surface
A surface represents a destination for a sparkline. This library provides a number of built-in surfaces
* `DSFSparklineSurfaceView` - An `NSView`/`UIView` surface for displaying a sparkline
* `DSFSparklineSurface.SwiftUI` - A SwiftUI `View` surface.
* `DSFSparklineSurface.Bitmap` - A `NSImage`/`UIImage`/`CGImage`/`NSAttributedString` surface for creating a bitmap from a sparkline.
## DataSource
A data source provides data for a sparkline. A datasource can be shared between multiple overlays or prebuilt types (see below) to provide different 'views' of the data contained within the source. And if a `DataSource` is updated, all sparkline ovelays observing that source will be automatically re-rendered.
There are currently two types of datasource available
### DSFSparkline.DataSource
A DataSource that contains values that can be updated by pushing new values into the source.
More details
#### WindowSize
The DataSource defines a 'windowSize' - the maximum number of values to be drawn on the overlay. As values are pushed into the DataSource, any values that no longer `fit` within the DataSource window are discarded.
* If the window size is reduced, stored data is truncated.
* If the window size is increased, the data store is padded with zeros
Code example
```swift
/// Swift
dataSource.windowSize = 30
assert(dataSource.windowSize == 30)
```
```objective-c
/// Objective-C
[dataSource setWindowSize:30];
assert([dataSource windowSize] == 30);
```
#### Y-range
The range defines the upper and lower values to be displayed in the sparkline. Any values pushed into the datasource will be capped when drawn to this range.
If the range is not set (ie nil), then any overlays will automatically resize to fit the entire range of values within the source. For example, with values as [1, 2, 3, 4] the range is implicitly set as 1 ... 4. If the values are [-10, 100, 33] the range is implicitly set as -10 ... 100
Code example
```swift
/// Swift
dataSource.range = -1.0 ... 1.0
```
```objective-c
/// Objective-C
[dataSource setRangeWithLowerBound:-1.0 upperBound:1.0];
```
#### Zero-line value
The zero-line defines the point the sparkline overlays should consider to be 'zero'. For example, graphs that can be centered (line, bar and stackline) use the zero-line value to define where the graph is centered around.
The zero-line value defaults to zero.
You can draw a zero-line for a sparkline by adding a `DSFSparklineOverlay.ZeroLine` to your surface.
Code example
```swift
/// Swift
dataSource.zeroLineValue = 0.2
```
```objc
/// Objective-C
[dataSource setZeroLineValue:0.2];
```
#### Adding values
You can push new values into the datasource using the `push` functions. Values in the datasource older than the datasource's `windowSize` are discarded.
As values are pushed into the datasource, any overlays assigned this datasource will automatically update.
Code example
```swift
/// Swift
dataSource.push(value: 4.5)
dataSource.push(values: [6, 7, 8])
```
```objective-c
/// Objective-C
[dataSource pushWithValue:@(4.5)];
```
You replace all the values in a datasource the `set` functions. The set function also changes the `windowSize` for the datasource to the size of the values array passed in.
Any overlays assigned this datasource will automatically update.
Code example
```swift
/// Swift
datasource.set(values: [
0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
0.0, -0.1, -0.2, -0.3, -0.4, -0.5, -0.6, -0.7, -0.8, -0.9, -1.0
])
```
```objc
/// Objective-C
[datasource setWithValues:
@[@(0.0), @(0.1), @(0.2), @(0.3), @(0.4), @(0.5), @(0.6), @(0.7), @(0.8), @(0.9), @(1),
@(0.0), @(-0.1), @(-0.2), @(-0.3), @(-0.4), @(-0.5), @(-0.6), @(-0.7), @(-0.8), @(-0.9), @(-1)]];
```
### DSFSparkline.StaticDataSource
A datasource that contains a static set of values. Some types of sparkline use a single 'set' of data, providing no historical context.
More details
```swift
/// Swift
let dataSource = DSFSparkline.StaticDataSource([1, 2, 3])
```
```objc
/// Objective-C
DSFSparklineStaticDataSource* dataSource = [[DSFSparklineStaticDataSource alloc] init: @[@(1), @(2), @(3)]];
```
## Overlays
Overlays represent the individual visual components of a sparkline. You can add as many or as few to your surface in any order. For example, you could overlay two different graph types onto the same surface using the same. And as overlays can share their datasource, all overlays using the same source will automatically update if the data changes (for example, in reponse to a `push`)
For example, there is an overlay that highlights a y-range of data. Or, if you want some grid lines, you can add them using the gridlines overlay.
You can add different instances of an overlay to the same sparkline. For example, if you want to add multiple range highlights you add multiple 'highlight' overlays to the sparkline surface.
The order in which the overlays are added determine where in the z-order that they appear in the sparkline. For example, you can choose to draw the grid on top of the graph if you want by adding the graph overlay BEFORE you add the grid overlay
The overlay allows your sparkline to be as complex or as simple as you want.
### Graph types
#### Dynamic
A dynamic graph automatically updates its overlays as values are 'pushed' onto its datasource. As data is added the assigned overlay is automatically updated to reflect the new data. If more data is added via a push or set the data is added to the datasource, the associated view will automatically update to reflect the new data. Older data that no longer falls within the datasource window is discarded.
This provides the ability to show a historical data set over the breadth of the graph.
`DSFSparklineOverlay.Line`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryFill = primaryFill
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.StackLine`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stack = DSFSparklineOverlay.Stackline() // Create a stackline overlay
stack.dataSource = source // Assign the datasource to the overlay
stack.strokeWidth = 1
stack.primaryFill = primaryFill
bitmap.addOverlay(stack) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.Bar`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let bar = DSFSparklineOverlay.Bar() // Create a bar overlay
bar.dataSource = source // Assign the datasource to the overlay
bar.primaryFill = primaryFill
bitmap.addOverlay(bar) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.Dot`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let dot = DSFSparklineOverlay.Dot() // Create a dot graph overlay
dot = biggersource // Assign the datasource to the overlay
bitmap.addOverlay(dot) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 32, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.WinLossTie`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let winLossTie = DSFSparklineOverlay.WinLossTie() // Create a win-loss-tie overlay
winLossTie.dataSource = winloss // Assign the datasource
bitmap.addOverlay(winLossTie) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 75, height: 12, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.Tablet`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stack = DSFSparklineOverlay.Tablet() // Create a tablet overlay
stack.dataSource = winloss // Assign a datasource to the overlay
bitmap.addOverlay(stack) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 90, height: 16, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.Stripes`
```swift
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let stack = DSFSparklineOverlay.Stripes() // Create a stripes overlay
stack.dataSource = .init(values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
bitmap.addOverlay(stack) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 90, height: 16, scale: 2)
// Do something with 'image'
```
#### Static
A static graph has a fixed set of values (for example, a pie chart). The overlays update when a new static data source is assigned to it.
`DSFSparklineOverlay.Pie`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let pie = DSFSparklineOverlay.Pie()
pie.dataSource = DSFSparkline.StaticDataSource([10, 55, 20])
pie.lineWidth = 0.5
pie.strokeColor = CGColor.black
bitmap.addOverlay(pie)
// Generate an image with retina scale
let image = bitmap.image(width: 18, height: 18, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.DataBar`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let stack = DSFSparklineOverlay.DataBar()
stack.dataSource = DSFSparkline.StaticDataSource([10, 20, 30])
stack.lineWidth = 0.5
stack.strokeColor = CGColor.black
bitmap.addOverlay(stack)
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 18, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.PercentBar`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let percentBar = DSFSparklineOverlay.PercentBar(value: 0.3)
bitmap.addOverlay(percentBar)
// Generate an image with retina scale
let image = bitmap.image(width: 50, height: 18, scale: 2)!
// Do something with 'image'
```
`DSFSparklineOverlay.WiperGauge`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let wiperGauge = DSFSparklineOverlay.WiperGauge()
wiperGauge.value = 0.75
bitmap.addOverlay(wiperGauge)
// Generate an image of the wiper gauge with retina scale
let image = bitmap.image(width: 50, height: 25, scale: 2)
// Do something with 'image'
```
`DSFSparklineOverlay.ActivityGrid`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let activityGrid = DSFSparklineOverlay.ActivityGrid()
activityGrid.dataSource = DSFSparkline.StaticDataSource(values: [...])
activityGrid.verticalCellCount = 1
bitmap.addOverlay(activityGrid)
// Generate an image of the wiper gauge with retina scale
let image = bitmap.image(width: 200, height: 14, scale: 2)
// Do something with 'image'
```
`DSFSparklineOverlay.CircularGauge`
```swift
let bitmap = DSFSparklineSurface.Bitmap()
let gauge = DSFSparklineOverlay.CircularGauge()
gauge.value = 0.66
bitmap.addOverlay(gauge)
// Generate an image of the wiper gauge with retina scale
let image = bitmap.image(width: 40, height: 40, scale: 2)
// Do something with 'image'
```
### Component types
A component represents an overlay that isn't a graph in itself. Examples are grid lines, zero-lines, highlights etc. A component uses the same datasource so that it aligns with the graph it is associated with.
| Name | Description |
|------------|------------|
| `DSFSparklineOverlay.ZeroLine` | Draw a horizontal line at the 'zero-line' position of the sparkline. The zero-line is defined by the datasource and is by default zero, however this can be changed. |
| `DSFSparklineOverlay.RangeHighlight` | Highlight a range of y-values on the sparkline |
| `DSFSparklineOverlay.GridLines` | Draw lines at specified y-values on the sparkline |
## Using prebuilt views
DSFSparkline has a number of 'prebuilt' sparkline views available with a more limited scope, designed to be quicker to add to your project.
Every prebuilt sparkline view has a SwiftUI companion view.
### Available prebuilt types
* `DSFSparklineLineGraphView` / `DSFSparklineLineGraphView.SwiftUI`
* `DSFSparklineStackLineGraphView` / `DSFSparklineLineGraphView.SwiftUI`
* `DSFSparklineBarGraphView` / `DSFSparklineBarGraphView.SwiftUI`
* `DSFSparklineStripesGraphView` / `DSFSparklineStripesGraphView.SwiftUI`
* `DSFSparklineDotGraphView` / `DSFSparklineDotGraphView.SwiftUI`
* `DSFSparklineWinLossGraphView` / `DSFSparklineWinLossGraphView.SwiftUI`
* `DSFSparklineTabletGraphView` / `DSFSparklineTabletGraphView.SwiftUI`
* `DSFSparklinePieGraphView` / `DSFSparklinePieGraphView.SwiftUI`
* `DSFSparklineDataBarGraphView` / `DSFSparklineDataBarGraphView.SwiftUI`
* `DSFSparklinePercentBarGraphView` / `DSFSparklinePercentBarGraphView.SwiftUI`
* `DSFSparklineWiperGaugeGraphView` / `DSFSparklineWiperGaugeGraphView.SwiftUI`
* `DSFSparklineActivityGridView` / `DSFSparklineActivityGridView.SwiftUI`
* `DSFSparklineCircularGaugeView` / `DSFSparklineCircularGaugeView.SwiftUI`
Sample Swift code
```swift
// Create the view
let sparklineView = DSFSparklineLineGraphView(…)
sparklineView.graphColor = UIColor.blue
sparklineView.showZeroLine = true
// Create the datasource and assign to the view
let sparklineDataSource = DSFSparklineDataSource(windowSize: 30, range: -1.0 ... 1.0)
sparklineView.dataSource = sparklineDataSource
…
// Add a single new data element to the sparkline
sparklineDataSource.push(value: 0.7) // view automatically updates with new data
// Add a set of data to the sparkline
sparklineDataSource.push(values: [0.3, -0.2, 1.0]) // view automatically updates with new data
// Completely replace the sparkline data with a new set of data
sparklineDataSource.set(values: [0.2, -0.2, 0.0, 0.9, 0.8]) // view automatically resets to new data
```
Sample SwiftUI code
```swift
struct SparklineView: View {
let leftDataSource: DSFSparkline.DataSource
let rightDataSource: DSFSparkline.DataSource
let BigCyanZeroLine = DSFSparkline.ZeroLineDefinition(
color: .cyan,
lineWidth: 3,
lineDashStyle: [4,1,2,1]
)
var body: some View {
HStack {
DSFSparklineLineGraphView.SwiftUI(
dataSource: leftDataSource,
graphColor: DSFColor.red,
interpolated: true)
DSFSparklineBarGraphView.SwiftUI(
dataSource: rightDataSource,
graphColor: DSFColor.blue,
lineWidth: 2,
showZeroLine: true,
zeroLineDefinition: BigCyanZeroLine)
}
}
}
```
Prebuilt customizations
## View Types and settings
Represents the viewable settings and display. The current view types available are :-
### Common display customizations
| Setting | Type | Description |
|-----------------------|------------------------|---------------------------------------------------------|
| `graphColor` | `NSColor` `UIColor` | The color to use when drawing the sparkline |
#### Common elements for graphs that can display a zero line (Line/Bar/Stackline)
| Setting | Type | Description |
|-----------------------|-------------------------|---------------------------------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `showZeroLine` | `Bool` | Draw a dotted line at the zero line point on the y-axis |
| `zeroLineColor` | `NSColor` `UIColor` | The color of the 'zero line' on the y-axis. |
| `zeroLineWidth` | `CGFloat` | The width of the 'zero line' on the y-axis |
| `zeroLineDashStyle` | `[CGFloat]` | The dash pattern to use when drawing the zero line |
#### Common elements for graphs that can be centered around the zero-line (Line/Bar/Stackline)
| Setting | Type | Description |
|-----------------------|-------------------------|-------------------------------------------------------------------------------------------|
| `centeredAtZeroLine` | `Bool` | Should the graph be centered at the zero line? |
| `lowerGraphColor` | `NSColor` `UIColor` | The color used to draw values below the zero line. If nil, is the same as the graph color |
### Line graph customizations (`DSFSparklineLineGraphView`)
| Setting | Type | Description |
|-------------------|-----------------|----------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `lineWidth` | `CGFloat` | The width of the line |
| `interpolation` | `Bool` | Interpolate a curve between the points |
| `lineShading` | `Bool` | Shade the area under the line |
| `shadowed` | `Bool` | Draw a shadow under the line |
| `markerSize` | `CGFloat` | (optional) Draw a marker of the specified size at every data point in the graph using the line color at that point |
| `markerSize` | `CGFloat` | (optional) Draw a marker of the specified size at every data point in the graph using the line color at that point |
| `markerDrawingBlock` | | A callback block to allow custom drawing of markers (if `markerSize` is > 1) |
### Bar graph customizations (`DSFSparklineBarGraphView`)
| Setting | Type | Description |
|--------------|-----------------|----------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `lineWidth` | `CGFloat` | The width of the line |
| `barSpacing` | `CGFloat` | The spacing between each bar |
### Stripes graph customizations (`DSFSparklineStripesGraphView`)
| Setting | Type | Description |
|--------------|-----------------|---------------------------------------------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `integral` | `Bool` | If true, draws the bars on pixel boundaries to get nice clean lines |
| `gradient` | `DSFGradient` | The color gradient to use when mapping datasource values to colors. |
### Dot graph customizations (`DSFSparklineDotGraphView`)
| Setting | Type | Description |
|-------------------|------------------------|----------------------------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `upsideDown` | `Bool` | If true, draws from the top of the graph downwards |
| `unsetGraphColor` | `NSColor` `UIColor` | The color to use when drawing the background |
### Win/Loss graph customizations (`DSFSparklineWinLossGraphView`)
| Setting | Type | Description |
|----------------|------------------------|-----------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `lineWidth` | `CGFloat` | The line width for the stroke |
| `barSpacing` | `CGFloat` | The spacing between each bar |
| `winColor` | `NSColor` `UIColor` | The color to use for a 'win' |
| `lossColor` | `NSColor` `UIColor` | The color to use for a 'loss' |
| `tieColor` | `NSColor` `UIColor` | *(optional)* The color to use for a 'tie'. If nil, tie (0) values are not drawn By default, 'tie' values are not drawn. |
### Tablet graph customizations (`DSFSparklineTabletGraphView`)
| Setting | Type | Description |
|--------------|------------------------|--------------------------------------------------|
| `dataSource` | `DSFDataSource` | The source of data for the graph |
| `lineWidth` | `CGFloat` | The line width for the stroke |
| `barSpacing` | `CGFloat` | The spacing between each bar |
| `winColor` | `NSColor` `UIColor` | The color to draw the filled circle for a 'win' |
| `lossColor` | `NSColor` `UIColor` | The color to draw the filled circle for a 'loss' |
### Pie graph customizations (`DSFSparklinePieGraphView`)
| Setting | Type | Description |
|---------------------|------------------------|-----------------------------------------------------------------------------------------|
| `dataSource` | `[CGFloat]` | The data to display in the pie chart |
| `palette` | `DSFSparklinePalette` | The palette to use when drawing the chart |
| `strokeColor` | `NSColor` `UIColor` | (optional) The color of the line to draw between each segment. If nil, no line is drawn |
| `lineWidth` | `CGFloat` | The width of the lines to draw between each segment |
| `animated` | `Bool` | If true, when the data source is set the segments animate into view |
| `animationDuration` | `CGFloat ` | The duration of the animation |
### Databar graph customizations (`DSFSparklineDataBarGraphView`)
| Setting | Type | Description |
|---------------------|------------------------|------------------------------------------------------------------------------------------------------------------------|
| `dataSource` | `[CGFloat]` | The data to display in the pie chart |
| `maximumTotalValue` | `CGFloat` | If <= 0 the data represents a percentage of the total, if > 0 represents the rightmost value to the databar |
| `palette` | `DSFSparklinePalette` | The palette to use when drawing. |
| `strokeColor` | `NSColor` `UIColor` | (optional) The color of the line to draw between each segment. If nil, no line is drawn |
| `lineWidth` | `CGFloat` | The width of the lines to draw between each segment |
| `unsetColor` | `NSColor` `UIColor` | (optional) If the maximum value is set, if the segments don't fit the total this color is used as the background color |
| `animated` | `Bool` | If true, when the data source is set the segments animate into view |
| `animationDuration` | `CGFloat` | The duration of the animation |
### Percent Bar graph customizations (`DSFSparklinePercentBarGraphView`)
| Setting | Type | Description |
|---------------------|------------------------|--------------------------------------------------------|
| `value` | `CGFloat` | The value to display in the pie chart |
| `displayStyle` | `Style` | The style to apply when drawing the graph |
| `cornerRadius` | `CGFloat` | The corner radius for the bar. |
| `showLabel` | `Bool` | Should we draw a text label on the percent bar? |
| `underBarColor` | `NSColor` `UIColor` | The background of the bar color for the percent bar chart |
| `underBarTextColor` | `NSColor` `UIColor` | The color for text displayed on the background |
| `barColor` | `NSColor` `UIColor` | The bar color for the percent bar chart |
| `barTextColor` | `NSColor` `UIColor` | The color for text displayed on the bar |
| `fontName` | `String` | The name of the label font |
| `fontSize` | `CGFloat` | The size of the label font |
| `shouldAnimate` | `Bool` | If true, when the data source is set the segments animate into view |
| `animationDuration` | `CGFloat` | The duration of the animation |
| `leftInset`. | `CGFloat` | The bar's inset from the left of the control |
| `topInset` | `CGFloat` | The bar's inset from the top of the control |
| `rightInset` | `CGFloat` | The bar's inset from the right of the control |
| `bottomInset` | `CGFloat` | The bar's inset from the bottom of the control |
## Screenshots
### In app
| macOS dark | macOS light | iOS |
|----|----|----|
| | | |
### Interface Builder
| macOS | tvOS |
|----|----|
| | |
### SwiftUI
### NSAttributedString support
### Animated

## Changes
### `7.0.0`
* Removed support for macOS 10.11, 10.12
* Removed support for iOS 13, tvOS 13
* Removed CocoaPods support (if you need Cocoapods, stick with v6).
### `6.0.0`
* Added CircularGauge, CircularProgress sparkline types
* Removed support for `@IBDesignable` and `@IBInspectable` from the NSView/UIView implementations, as Xcode has indicated that it will be dropping support in the near future
### `5.2.0`
* Added grid-lines support for pre-built views (line, bar, stack) (NSView/UIView/SwiftUI)
### `5.1.0`
* Added Activity Grid
### `5.0.0`
* Fixed [occasional clipping](https://github.com/dagronf/DSFSparkline/issues/13) on interpolated line graphs.
### `4.6.0`
* Added WiperGauge sparkline type
### `4.3.0`
* Added the ability to custom-draw markers for the 'line' sparkline type [(raised issue)](https://github.com/dagronf/DSFSparkline/issues/6)
* Fixed a minor issue where insetting certain graph types to remove clipping (line with markers, bar) would mean that zeroline, highlight overlays and grids would be appear to be very slightly off.
### `4.2.0`
* Added 'percent bar' sparkline type.
### `4.1.2`
* Fixed compile issue [Cannot find type 'NSTextAttachment' in scope" crashes when using SwiftPackage](https://github.com/dagronf/DSFSparkline/issues/5)
### `4.1.1`
* Fixed gradient bucket count issue.
* Added AttributedString SwiftUI demo to the ReportView demo. Demo uses [The SwiftUI Lab Attributed String](https://swiftui-lab.com/attributed-strings-with-swiftui/) with AppKit bug fix for width calculation.
### `4.1.0`
* Embed sparklines in NSAttributedString.
### `4.0.0`
Substantial re-architect of the drawing code (that used to be directly in the views) into overlays and surfaces that are far more flexible (for example, being able to draw a sparkline bitmap without having to create a view)
The previous view/swiftui types are still available - they have been rebuilt on using the new overlay scheme and are referred to in documentation as 'prebuilt' types. This allowed backwards compatibility with previous versions of the library. Note however that given that the prebuilt views have been re-written there is a possibility of slight visual differences.
### `3.7.0`
* Added stripe graph
### `3.6.1`
* Fixed animations on iOS/tvOS
### `3.6.0`
* Added pie chart, databar chart.
* Added ability to show data markers for line graphs
### `3.5.2`
* Fixed Objective-C Demo app
* Added `snapshot` method to the base sparkline view class to produce an NSImage/UIImage version of the sparkline for embedded sparklines in text etc.
### `3.5.1`
* Fixed version in podspec
### `3.5.0`
* Added stackline sparkline type
* Added win/loss/tie sparkline type
* Added tablet sparkline type
### `3.4.0`
* Added support for centering line and bar graphs around their zero-line value.
### `3.3.0`
* Fixed issue where iOS background wasn't being drawn correctly in some cases.
* Fixed rare crash where a line graph with < 2 points would crash.
### `3.2.0`
* Changed the zero-line definition class to `DSFSparklineZeroLineDefinition` for clarity.
* More documentation, especially around SwiftUI. Attempted to make the documentation clearer around drawing parameters.
### `3.1.0`
* Add the ability to customize the zero-line display ([Tito Ciuro](https://github.com/tciuro))
* Changed `showZero` to `showZeroLine` for consistency with the new zero-line display values
### `3.0.0`
* Add the ability to set the 'zero' line value. Defaults to zero for backwards compatibility.
You can set where the 'zero' line draws via the `zeroLineValue` on the datasource.
### `2.0.0`
* The primary views have been renamed with a `View` postfix. So
`DSFSparklineLineGraph` -> `DSFSparklineLineGraphView`
`DSFSparklineBarGraph` -> `DSFSparklineBarGraphView`
`DSFSparklineDotGraph` -> `DSFSparklineDotGraphView`
* Renamed `SLColor` and `SLView` to `DSFColor` and `DSFView` for module naming consistency.
* I removed `windowSize` from the core `DSFSparklineDataSourceView`. `windowSize` is related to data, and should never have been part of the UI definition. I've provided a replacement purely for `IBDesignable` support called `graphWindowSize` which should only be called from Interface Builder. If you want to set the windowSize from your xib file, set the `graphWindowSize` inspectable.
If you see warnings in the log like
`2020-12-07 18:22:51.619867+1100 iOS Sparkline Demo[75174:1459637] Failed to set (windowSize) user defined inspected property on (DSFSparkline.DSFSparklineBarGraphView): [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key windowSize.
`
it means that you have a `windowSize` value set in your .xib file. Remove it and set the `graphWindowSize` value instead.
* For the Bar type, `lineWidth` and `barSpacing` now represent the pixel spacing between bars and the pixel width for the line. You may find that your line spacing and bar spacing are now incorrect if you have set fractional values for these in the past (for example, if you set lineWidth = 0.5). The reason for this change is to aid drawing lines on pixel boundaries and avoid antialiasing.
* Fix for zero line being upside-down
## License
MIT. Use it for anything you want, just attribute my work. Let me know if you do use it somewhere, I'd love to hear about it!
```
MIT License
Copyright (c) 2025 Darren Ford
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
================================================
FILE: Sources/DSFSparkline/DSFSparklines.h
================================================
//
// DSFSparklines.h
// DSFSparklines
//
// Created by Darren Ford on 20/6/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import
//! Project version number for DSFSparklines.
FOUNDATION_EXPORT double DSFSparklinesVersionNumber;
//! Project version string for DSFSparklines.
FOUNDATION_EXPORT const unsigned char DSFSparklinesVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import
================================================
FILE: Sources/DSFSparkline/Info.plist
================================================
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
$(PRODUCT_BUNDLE_IDENTIFIER)
CFBundleInfoDictionaryVersion
6.0
CFBundleName
$(PRODUCT_NAME)
CFBundlePackageType
FMWK
CFBundleShortVersionString
1.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSHumanReadableCopyright
Copyright © 2019 Darren Ford. All rights reserved.
================================================
FILE: Sources/DSFSparkline/datasource/DSFSparkline+DataSource.swift
================================================
//
// DSFSparkline+DataSource.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
public extension DSFSparkline {
/// A datasource for a sparkline
@objc(DSFSparklineDataSource) class DataSource: NSObject {
public static let DefaultWindowSize: UInt = 10
/// Notification sent when the content of the data source changes
@objc(DSFSparklineDataSourceDataChangedNotification)
static let DataChangedNotification = NSNotification.Name("DSFSparklineDataSource.DataChanged")
private let sparkline: SparklineWindow
@objc public override init() {
self.sparkline = SparklineWindow(windowSize: DataSource.DefaultWindowSize)
super.init()
}
/// Create a data source
/// - Parameters:
/// - windowSize: The size of the window to use
/// - range: (optional) the clamped y-range of data to display. Values outside this range are clamped
/// - zeroLineValue: the zero-line value for the source. Defaults to zero (0)
public init(windowSize: UInt = DataSource.DefaultWindowSize,
range: ClosedRange? = nil,
zeroLineValue: CGFloat = 0) {
self.sparkline = SparklineWindow(windowSize: windowSize)
self.sparkline.yRange = range
self.sparkline.zeroLineValue = zeroLineValue
super.init()
}
/// Create a datasource from a series of values
/// - Parameters:
/// - values: The values to initially assign to the datasource
/// - range: (optional) The maximum range to represent.
public convenience init(values: [CGFloat], range: ClosedRange? = nil) {
self.init(windowSize: UInt(values.count), range: range)
self.set(values: values)
}
}
}
public extension DSFSparkline.DataSource {
override var description: String {
"""
DataSource: windowSize: \(sparkline.windowSize), zeroLine: \(sparkline.zeroLineValue), range: \(String(describing: sparkline.yRange)), emptyCount: \(sparkline.emptyValueCount))
data: \(sparkline.raw)
norm: \(sparkline.normalized)
"""
}
}
// MARK: - Value handling
public extension DSFSparkline.DataSource {
/// The series of data points with the most recent being the last array entry
@objc var data: [CGFloat] {
self.sparkline.raw
}
/// Return the raw values in the data source scaled from 0.0 -> 1.0
@objc var normalized: [CGFloat] {
self.sparkline.normalized
}
/// The number of data points to display in the sparkline
@objc var windowSize: UInt {
get {
self.sparkline.windowSize
}
set {
self.sparkline.windowSize = newValue
self.notifyDataChange()
}
}
/// The number of unfilled buckets in the sparkline
@objc var emptyValueCount: UInt {
return self.sparkline.emptyValueCount
}
/// The 'zero' line for drawing the horizontal line. Should be in the range lowerBound ..< upperBound
@objc var zeroLineValue: CGFloat {
get {
self.sparkline.zeroLineValue
}
set {
self.sparkline.zeroLineValue = newValue
self.notifyDataChange()
}
}
// Is the value at the specified offset in the data source BELOW the zero-line value?
@inlinable func valueAtOffsetIsBelowZeroline(_ offset: Int) -> Bool {
return self.data[offset] < self.zeroLineValue
}
}
public extension DSFSparkline.DataSource {
/// Add a new value. If there are more values than the window size, the oldest value is discarded
@discardableResult
@objc func push(value: CGFloat) -> Bool {
defer {
self.notifyDataChange()
}
return self.sparkline.push(value: value)
}
/// Add a vector of new values. Equivalent to push(values[0]), push(values[1]), push(values[2]) etc.
@objc func push(values: [CGFloat]) {
self.sparkline.push(values: values)
self.notifyDataChange()
}
/// Set the sparkline data to the specified values. The window size is changed to reflect the extent of the input data
@objc func set(values: [CGFloat]) {
self.sparkline.set(values: values)
self.notifyDataChange()
}
/// Reset the data to the lower bound for all data points in the window
@objc func reset() {
self.sparkline.reset()
self.notifyDataChange()
}
}
// MARK: - Range support (Swift)
public extension DSFSparkline.DataSource {
/// The current minimum/maximum range for the values, or nil if there is no range specified
var range: ClosedRange? {
get {
self.sparkline.yRange
}
set {
self.sparkline.yRange = newValue
self.notifyDataChange()
}
}
}
// MARK: - Range support (Objc)
public extension DSFSparkline.DataSource {
/// Returns the lower bound for the current set of values. If no values are present, returns CGFloat.greatestFiniteMagnitude
@objc var lowerBound: CGFloat {
return self.sparkline.yRange?.lowerBound ?? CGFloat.greatestFiniteMagnitude
}
/// Returns the upper bound for the current set of values. If no values are present, returns CGFloat.greatestFiniteMagnitude
@objc var upperBound: CGFloat {
return self.sparkline.yRange?.upperBound ?? CGFloat.greatestFiniteMagnitude
}
/// Set a range using discrete upper and lower bounds
@objc func setRange(lowerBound: CGFloat, upperBound: CGFloat) {
assert(lowerBound < upperBound)
self.sparkline.yRange = lowerBound ... upperBound
self.notifyDataChange()
}
/// Set a range using discrete upper and lower bounds, drawing a line at the 'zero' point within the range
@objc func setRange(lowerBound: CGFloat, upperBound: CGFloat, zeroLinePoint: CGFloat) {
assert(lowerBound <= zeroLinePoint && zeroLinePoint <= upperBound)
self.sparkline.yRange = lowerBound ... upperBound
self.sparkline.zeroLineValue = zeroLinePoint
self.notifyDataChange()
}
/// Remove the range restrictions for the data source
@objc func resetRange() {
self.range = nil
self.notifyDataChange()
}
}
// MARK: - Internal
extension DSFSparkline.DataSource {
/// The number of items added since last reset
var counter: UInt {
return self.sparkline.counter
}
// Normalize the specified value within 0.0 ... 1.0 for the current data
func normalize(value: CGFloat) -> CGFloat {
return self.sparkline.normalize(value: value)
}
// Normalize the zero-line value within 0.0 ... 1.0 for the current data
var normalizedZeroLineValue: CGFloat {
return self.normalize(value: self.zeroLineValue)
}
fileprivate func notifyDataChange() {
NotificationCenter.default.post(name: DSFSparkline.DataSource.DataChangedNotification, object: self)
}
}
public extension DSFSparkline.DataSource {
/// Return the vertical fractional position within the data window that represents
/// zero for the current set of data.
func fractionalZeroPosition() -> CGFloat {
return fractionalPosition(for: 0.0)
}
/// Return the vertical fractional position within the data window that represents
/// the zero line value for the current set of data.
func fractionalPosition(for value: CGFloat) -> CGFloat {
let result: CGFloat
if let r = self.range {
// If a fixed range is specified, calculate the zero line from the specified range
let full = r.upperBound - r.lowerBound // full range width
result = abs(value - r.lowerBound) / full
}
else {
// If no fixed range is specified, calculate the zero line position using the current range of the data.
result = self.normalize(value: value)
}
// Clamp to 0.0 -> 1.0
return min(max(result, 0.0), 1.0)
}
}
// In order to clean up some of the code, I've moved the declaration of the data source into a
// DSFSparkline 'namespace' to match with the new DSFSparkline.StaticDataSource type
//
// A simple name change (DSFSparklineDataSource -> DSFSparkline.DataSource) in your code will fix this issue
@available(*, deprecated, message: "Move to using DSFSparkline.DataSource instead (simple name change)")
public typealias DSFSparklineDataSource = DSFSparkline.DataSource
================================================
FILE: Sources/DSFSparkline/datasource/DSFSparkline+StaticDataSource.swift
================================================
//
// DSFSparkline+StaticDataSource.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
public extension DSFSparkline {
/// A simple data source containing an array of values.
@objc(DSFSparklineStaticDataSource) class StaticDataSource: NSObject {
/// The datasource values
let values: [CGFloat]
/// The minimum value in the data source, excluding .infinite values
let minValue: CGFloat
/// The maximum value in the data source, excluding .infinite values
let maxValue: CGFloat
/// The total of all the values within the datasource, excluding .infinite values
@objc public let total: CGFloat
/// The allowable range of values for this source. If nil, there are no bounds for the source
public let valueBounds: ClosedRange?
/// Create an empty data source
@objc override public convenience init() {
self.init([])
}
/// Create a data source with the specified values
/// - Parameter values: The datasource values
@objc public init(_ values: [CGFloat]) {
self.values = values
self.valueBounds = nil
let nonInfiniteValues = values.filter { !$0.isInfinite }
self.minValue = nonInfiniteValues.min() ?? 0.0
self.maxValue = nonInfiniteValues.max() ?? 1.0
self.total = nonInfiniteValues.reduce(0) { $0 + $1 }
super.init()
}
/// Create a static data source with values and upper/lower bounds values
/// - Parameters:
/// - values: The values to be displayed
/// - lowerBound: The lower bounds of the data
/// - upperBound: The upper bounds of the data
@objc public convenience init(_ values: [CGFloat], lowerBound: CGFloat, upperBound: CGFloat) {
assert(lowerBound < upperBound)
self.init(values, range: lowerBound ... upperBound)
}
/// Create a static data source with values and upper/lower bounds values
/// - Parameters:
/// - values: The values to be displayed
/// - range: The allowable range for each value
public init(_ values: [CGFloat], range: ClosedRange) {
self.valueBounds = range
let clampedValues = values.map { $0.isInfinite ? .infinity : $0.clamped(to: range) }
self.values = clampedValues
let nonInfiniteValues = clampedValues.filter { !$0.isInfinite }
self.total = nonInfiniteValues.reduce(0) { $0 + $1 }
self.minValue = nonInfiniteValues.min() ?? 0.0
self.maxValue = nonInfiniteValues.max() ?? 1.0
super.init()
}
/// Return the fractional (0 ... 1) value for the specified value, or .infinity if the value is infinite
/// - Parameter value: The value to convert to a fractional value within the range of the datasource
/// - Returns: A fractional value, or nil when value == .infinity
@objc public func fractionalValue(for value: CGFloat) -> CGFloat {
if value == .infinity { return .infinity }
if let r = valueBounds {
let v = value.clamped(to: r)
return (v - r.lowerBound) / (r.upperBound - r.lowerBound)
}
else {
return (value - self.minValue) / (self.maxValue - self.minValue)
}
}
/// Return the fractional (0 ... 1) value for the specified value
/// - Parameter index: The indexed offset of the datasource to retrieve the fractional value for
/// - Returns:
/// * If the index is outside the scope of the data source, returns `.nan`
/// * If the value at the index is infinite, returns `.infinity`
/// * If the value at the index is not infinite, returns the fractional value
public func fractionalValue(at index: Int) -> CGFloat {
guard index < self.values.count else { return .nan }
return self.fractionalValue(for: self.values[index])
}
}
}
public extension DSFSparkline.StaticDataSource {
override var description: String {
"""
StaticDataSource: count: \(values.count), totalValue: \(self.total)
values: \(self.values)
"""
}
}
================================================
FILE: Sources/DSFSparkline/datasource/data-core/SparklineData.swift
================================================
//
// SparklineData.swift
// DSFSparklines
//
// Created by Darren Ford on 22/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
class SparklineData where T: BinaryFloatingPoint {
var yRange: ClosedRange? = nil {
didSet {
self.normalized = self.normalize()
}
}
var yMin: T? { return self.yRange?.lowerBound ?? self.yData.min() }
var yMax: T? { return self.yRange?.upperBound ?? self.yData.max() }
var yData: [T] = [] {
didSet {
self.normalized = self.normalize()
}
}
public private(set) var normalized: [T] = []
init(range: ClosedRange? = nil) {
self.yRange = range
}
/// Return a normalized (scaled 0.0 -> 1.0) of the supplied value
func normalize(value: T) -> T {
guard let min = yMin, let max = self.yMax else {
return 0.0
}
let diff = (max - min)
if diff == 0 {
return 0.0
}
return ((value - min) / diff)
}
/// Return a normalized (scaled 0.0 -> 1.0) of the current set of data
private func normalize() -> [T] {
guard let min = yMin, let max = self.yMax else {
return []
}
// Move the data to 0.0 -> 1.0 scale
let diff = (max - min)
if diff == 0 {
return Array(repeating: 0.0, count: yData.count)
}
return yData.map { val in
((val - min) / diff)
}
}
}
================================================
FILE: Sources/DSFSparkline/datasource/data-core/SparklineWindow.swift
================================================
//
// SparklineWindow.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
/// The sparkline 'window' represents the data window to be displayed in the graph
/// It is meant to represent the _data_ for the graph, not the way that the graph draws.
class SparklineWindow where T: BinaryFloatingPoint {
private let NormalizedRange: ClosedRange = 0.0 ... 1.0
private let data: SparklineData
/// Optional fixed range for the data being displayed
public var yRange: ClosedRange? {
get {
self.data.yRange
}
set {
self.data.yRange = newValue
}
}
/// The number of items added since last reset
var counter: UInt = 0
/// Raw values
var raw: [T] { return self.data.yData }
/// The normalized data (range 0.0 -> 1.0) depending on the range
var normalized: [T] { return self.data.normalized }
/// The size of the sparkline data window
var windowSize: UInt {
get {
return UInt(self.data.yData.count)
}
set {
let sz = self.data.yData.count
if newValue > sz {
let inserter = Array(repeating: self.data.yRange?.lowerBound ?? 0.0, count: Int(newValue) - sz)
self.data.yData.insert(contentsOf: inserter, at: 0)
}
else if newValue < sz {
self.data.yData.removeSubrange(0 ..< sz - Int(newValue))
}
}
}
/// The number if of 'empty' buckets in the current window
var emptyValueCount: UInt {
if self.counter > self.windowSize {
return 0
}
return self.windowSize - self.counter
}
/// The 'zero' line for drawing the horizontal line.
var zeroLineValue: T = 0.0
/// Create a sparkline data window with an optional data range
/// - Parameters:
/// - windowSize: The number of data points to be kept in the spark line
/// - dataRange: The max/min values
init(windowSize: UInt, dataRange: ClosedRange? = nil) {
assert(windowSize > 0)
self.data = SparklineData(range: dataRange)
self.data.yData = Array(repeating: dataRange?.lowerBound ?? 0.0, count: Int(windowSize))
}
/// Normalize a value to the current range of the sparkline
/// - Parameter value: the value to normalize
/// - Return a value 0.0 -> 1.0
public func normalize(value: T) -> T {
return self.data.normalize(value: value).clamped(to: NormalizedRange)
}
/// Push a new value into the sparkline. If the value is outside a specified range, then
/// - Parameter value: The value to push into the sparkline
/// - Return whether the
@discardableResult
public func push(value: T) -> Bool {
let plotValue: T
if let r = self.data.yRange, !r.contains(value) {
plotValue = min(r.upperBound, max(r.lowerBound, value))
Swift.print("WARN: Value \(value) outside specified range \(r), truncating to \(plotValue)")
}
else {
plotValue = value
}
var temp = self.data.yData
temp.removeFirst()
temp.append(plotValue)
self.data.yData = temp
self.counter += 1
return true
}
public func push(values: [T]) {
self.counter += UInt(values.count)
var temp = self.data.yData
// The difference between
let diff = temp.count - values.count
if diff < 0 {
// There are more data points than the window size
temp = values
temp.removeFirst(abs(diff))
}
else if diff == 0 {
temp = values
}
else {
temp.removeFirst(min(values.count, temp.count))
temp.append(contentsOf: values)
}
self.data.yData = temp
}
public func set(values: [T]) {
if let yRange = self.data.yRange {
self.data.yData = values.map { $0.clamped(to: yRange) }
}
else {
self.data.yData = values
}
self.counter = UInt(values.count)
}
public func reset() {
self.counter = 0
self.data.yData = Array(repeating: self.data.yRange?.lowerBound ?? 0.0,
count: Int(self.windowSize))
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/core/DSFSparklineOverlay+Centerable.swift
================================================
//
// DSFSparklineOverlay+Centerable.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// A graph that can be centered around the datasource's zero-line.
///
/// You don't generally create this class yourself, you inherit from it if your overlay type can be
/// centered around the zero-line of the data.
@objc(DSFSparklineOverlayCenterableGraph) class Centerable: DSFSparklineOverlay.DataSource {
/// Should the graph be centered at the zero line defined in the datasource?
@objc public var centeredAtZeroLine: Bool = false {
didSet {
self.setNeedsDisplay()
}
}
// MARK: - Primary
/// The primary color for the sparkline
@objc public var primaryStrokeColor: CGColor? = .black {
didSet {
self.setNeedsDisplay()
}
}
/// The primary fill color for the sparkline
@objc public var primaryFill: DSFSparklineFillable? {
didSet {
self.setNeedsDisplay()
}
}
// MARK: - Secondary
/// The color used to draw lines below the zero-line (if centeredAtZeroLine=true)
@objc public var secondaryStrokeColor: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// The fill color to use for parts of the graph below the zero-line (if centeredAtZeroLine=true)
@objc public var secondaryFill: DSFSparklineFillable? {
didSet {
self.setNeedsDisplay()
}
}
// MARK: - Initializers
@objc public init() {
super.init()
}
override public init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.centeredAtZeroLine = orig.centeredAtZeroLine
self.primaryStrokeColor = orig.primaryStrokeColor
self.primaryFill = orig.primaryFill?.copyFill()
self.secondaryStrokeColor = orig.secondaryStrokeColor
self.secondaryFill = orig.secondaryFill?.copyFill()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/core/DSFSparklineOverlay+DataSource.swift
================================================
//
// DSFSparklineOverlay+DataSource.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// An overlay that contains a DSFSparklineDataSource.
///
/// Generally you don't create one of these yourself, you subclass it
@objc(DSFSparklineDataSourceOverlay) class DataSource : DSFSparklineOverlay {
/// The datasource for displaying the overlay
@objc public var dataSource: DSFSparkline.DataSource? {
didSet {
// Update our observer to detect changes to data in this new DataSource
self.updateDataObserver()
// And tell any listeners that the datasource was changed
self.dataSourceContentDidChange()
}
}
// Listen for changes in the data and update appropriately
private var dataObserver: NSObjectProtocol?
// Update the observer to point to a new datasource
private func updateDataObserver() {
self.dataObserver = nil
if let datasource = self.dataSource {
self.dataObserver = NotificationCenter.default.addObserver(
forName: DSFSparkline.DataSource.DataChangedNotification,
object: datasource,
queue: nil, using: { [weak self] _ in
self?.dataSourceContentDidChange()
}
)
}
}
/// Overridable to allow overlays to be notified when the content of the data source changes
internal func dataSourceContentDidChange() {
self.setNeedsDisplay()
}
// MARK: Initializers
@objc public init(dataSource: DSFSparkline.DataSource? = nil) {
self.dataSource = dataSource
super.init()
}
override public init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.dataObserver = orig.dataObserver
self.dataSource = orig.dataSource
super.init(layer: layer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
deinit {
self.dataObserver = nil
self.dataSource = nil
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/core/DSFSparklineOverlay+StaticDataSource.swift
================================================
//
// DSFSparklineOverlay+StaticDataSource.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// A data source that displays a static set of data, like for a pie chart
@objc(DSFSparklineOverlayStaticDataSource) class StaticDataSource: DSFSparklineOverlay {
/// The data to be displayed in this graph
///
/// The values become a percentage of the total value stored within the
/// dataStore, and as such each value ends up being drawn as a fraction of the total.
/// So for example, if you want the pie chart to represent the number of red cars vs. number of
/// blue cars, you just set the values directly.
@objc public var dataSource = DSFSparkline.StaticDataSource() {
didSet {
self.staticDataSourceDidChange()
}
}
/// Datasource values
@objc public var values: [CGFloat] {
get { self.dataSource.values }
set { self.dataSource = .init(newValue) }
}
@objc public override init() {
super.init()
}
override public init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.dataSource = orig.dataSource
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
/// Overridable to allow overlays to be notified when the data source is changed
func staticDataSourceDidChange() {
// Do nothing
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/core/DSFSparklineOverlay.swift
================================================
//
// DSFSparklineOverlay.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
/// The core sparkline overlay class.
///
/// All sparkline renderers must inherit from this class
@objc public class DSFSparklineOverlay: CALayer {
override public init() {
super.init()
self.configure()
}
override public init(layer: Any) {
super.init(layer: layer)
self.configure()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
private func configure() {
self.anchorPoint = CGPoint(x: 0, y: 0)
self.isOpaque = false
// Disable the implicit animations on the layer to stop the fade when data changes
let newActions = [
"onOrderIn": NSNull(),
"onOrderOut": NSNull(),
"sublayers": NSNull(),
"contents": NSNull(),
"bounds": NSNull(),
]
self.actions = newActions
}
/// Return the minimum edge insets required to fit the graph within the specified rect
///
/// An example of this is a line graph with markers - if a data point is at zero, the marker will be clipped when drawn.
/// Overriding this var allows a graph to inset the drawing when required to handle clipping edge cases, while making
/// sure that grid lines, highlights etc. arrange themselves correctly draw within the range of the reduced graph size.
internal func edgeInsets(for rect: CGRect) -> DSFEdgeInsets {
return .zero
}
/// To be overridden by sub-classes to draw their content into the provided context
/// - Parameters:
/// - context: The context to draw into
/// - bounds: The bounds in which to draw (which may NOT be the same as the bounds for the context!)
/// - scale: The scale for the current drawing context (eg. retina == 2)
internal func drawGraph(context _: CGContext, bounds _: CGRect, scale _: CGFloat) {
fatalError("must be implemented in overridden classes")
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-components/DSFSparklineOverlay+GridLines.swift
================================================
//
// DSFSparklineOverlay+GridLines.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// An overlay that draws grid lines at specified vertical points on the sparkline
@objc(DSFSparklineOverlayGridLines) class GridLines: DSFSparklineOverlay.DataSource {
/// The color of the dotted line at the zero point on the y-axis
@objc public var strokeColor: CGColor {
didSet {
self.setNeedsDisplay()
}
}
/// The width of the dotted line at the zero point on the y-axis
@objc public var strokeWidth: CGFloat {
didSet {
self.setNeedsDisplay()
}
}
/// The line style for the dotted line. Use [] to specify a solid line.
@objc public var dashStyle: [CGFloat] = [1, 1] {
didSet {
self.setNeedsDisplay()
}
}
/// The y-values within the range of the datasource for the lines
@objc public var floatValues: [CGFloat] = [] {
didSet {
self.setNeedsDisplay()
}
}
@objc public init(dataSource: DSFSparkline.DataSource? = nil,
floatValues: [CGFloat] = [],
strokeColor: CGColor = DSFColor.gray.cgColor,
strokeWidth: CGFloat = 1.0,
dashStyle: [CGFloat] = [1.0, 1.0])
{
self.floatValues = floatValues
self.strokeColor = strokeColor
self.strokeWidth = strokeWidth
self.dashStyle = dashStyle
super.init(dataSource: dataSource)
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.floatValues = orig.floatValues
self.strokeColor = orig.strokeColor.copy() ?? .black
self.strokeWidth = orig.strokeWidth
self.dashStyle = orig.dashStyle
super.init(layer: layer)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawGridLines(context: context, bounds: bounds, scale: scale)
}
}
}
extension DSFSparklineOverlay.GridLines {
func drawGridLines(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard
self.floatValues.count > 0,
let dataSource = self.dataSource
else {
return
}
context.setLineWidth(self.strokeWidth)
context.setStrokeColor(self.strokeColor)
context.setLineDash(phase: 0.0, lengths: self.dashStyle)
self.floatValues.forEach { value in
let fractional = dataSource.fractionalPosition(for: value)
let zeroPos = bounds.minY + bounds.height - (fractional * bounds.height).rounded(.towardZero)
context.strokeLineSegments(between: [CGPoint(x: bounds.minX, y: zeroPos),
CGPoint(x: bounds.maxX, y: zeroPos)])
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-components/DSFSparklineOverlay+RangeHighlight.swift
================================================
//
// DSFSparklineOverlay+RangeHighlight.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// An overlay that draws a color range on the sparkline
@objc(DSFSparklineOverlayRangeHighlight) class RangeHighlight: DSFSparklineOverlay.DataSource {
static public let defaultFill =
DSFSparkline.Fill.Color(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(),
components: [0.5, 0.5, 0.5, 0.5])!)
/// The color to fill the specified range
@objc public var fill: DSFSparklineFillable = RangeHighlight.defaultFill {
didSet {
self.setNeedsDisplay()
}
}
/// The highlight range for the graph
public var highlightRange: Range? {
didSet {
self.setNeedsDisplay()
}
}
/// objective-c compatible highlight range setting
@objc public func setHighlightRange(lowerBound: CGFloat, upperBound: CGFloat) {
self.highlightRange = lowerBound ..< upperBound
}
public init(dataSource: DSFSparkline.DataSource? = nil,
range: Range? = nil,
fill: DSFSparklineFillable = RangeHighlight.defaultFill) {
self.highlightRange = range
self.fill = fill
super.init(dataSource: dataSource)
}
@objc public init(lowerBound: CGFloat, upperBound: CGFloat,
fill: DSFSparklineFillable = RangeHighlight.defaultFill) {
self.highlightRange = lowerBound ..< upperBound
self.fill = fill
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.highlightRange = orig.highlightRange
self.fill = orig.fill.copyFill()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource,
let range = self.highlightRange else {
return
}
let lb = 1.0 - dataSource.normalize(value: range.lowerBound)
let l = (lb * bounds.height) + bounds.minY
let ub = 1.0 - dataSource.normalize(value: range.upperBound)
let u = (ub * bounds.height) + bounds.minY
let destRect = CGRect(x: bounds.minX, y: u, width: bounds.width, height: l - u)
context.clip(to: destRect)
self.fill.fill(context: context, bounds: bounds)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-components/DSFSparklineOverlay+ZeroLine.swift
================================================
//
// DSFSparklineOverlay+ZeroLine.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
@objc(DSFSparklineOverlayZeroLine) class ZeroLine: DSFSparklineOverlay.DataSource {
/// The color of the dotted line at the zero point on the y-axis
@objc public var strokeColor: CGColor {
didSet {
self.setNeedsDisplay()
}
}
/// The width of the dotted line at the zero point on the y-axis
@objc public var strokeWidth: CGFloat {
didSet {
self.setNeedsDisplay()
}
}
/// The line style for the dotted line. Use [] to specify a solid line.
@objc public var dashStyle: [CGFloat] = [1, 1] {
didSet {
self.setNeedsDisplay()
}
}
@objc public init(dataSource: DSFSparkline.DataSource? = nil,
strokeColor: CGColor = DSFColor.gray.cgColor,
strokeWidth: CGFloat = 1.0,
dashStyle: [CGFloat] = [1.0, 1.0]) {
self.strokeColor = strokeColor
self.strokeWidth = strokeWidth
self.dashStyle = dashStyle
super.init(dataSource: dataSource)
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.strokeColor = orig.strokeColor.copy() ?? .black
self.strokeWidth = orig.strokeWidth
self.dashStyle = orig.dashStyle
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else { return }
let frac = dataSource.fractionalPosition(for: dataSource.zeroLineValue)
let zeroPos = bounds.height - (frac * bounds.height) + bounds.minY
context.setLineWidth(self.strokeWidth)
context.setStrokeColor(self.strokeColor)
context.setLineDash(phase: 0.0, lengths: self.dashStyle)
context.strokeLineSegments(
between: [CGPoint(x: bounds.minX, y: zeroPos), CGPoint(x: bounds.width + bounds.minX, y: zeroPos)])
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+ActivityGrid.swift
================================================
//
// DSFSparklineOverlay+ActivityGrid.swift
// DSFSparklines
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A GitHub-style activity grid.
@objc(DSFSparklineOverlayActivityGrid) class ActivityGrid: DSFSparklineOverlay.StaticDataSource {
/// The number of vertical cells in a column
@objc @LayerInvalidating(.display) public var verticalCellCount: Int = 7
/// The number of horizontal cells in the grid.
///
/// If `horizontalCellCount` == 0, cells will be added to fill the entire width of the view
@objc @LayerInvalidating(.display) public var horizontalCellCount: Int = 0
/// The layout style for the grid
@objc @LayerInvalidating(.display) public var layoutStyle: DSFSparkline.ActivityGrid.LayoutStyle = .github
// MARK: Cell style
/// The cell's drawing style
@objc @LayerInvalidating(.display) public var cellStyle: DSFSparkline.ActivityGrid.CellStyle = .init()
// MARK: Individual cell style setters/getters
/// The color scheme to use when fill cells
@objc public var cellFillScheme: DSFSparkline.ValueBasedFill {
get { self.cellStyle.fillScheme }
set { self.cellStyle = self.cellStyle.modify(fillScheme: newValue) }
}
/// The dimension of each cell
@objc public var cellDimension: CGFloat {
get { self.cellStyle.cellDimension }
set { self.cellStyle = self.cellStyle.modify(cellDimension: newValue) }
}
/// The spacing between each of the cells
@objc public var cellSpacing: CGFloat {
get { self.cellStyle.cellSpacing }
set { self.cellStyle = self.cellStyle.modify(cellSpacing: newValue) }
}
/// The color for the border of the cell
@objc public var cellBorderColor: CGColor? {
get { self.cellStyle.borderColor }
set { self.cellStyle = self.cellStyle.modify(borderColor: newValue) }
}
/// The cell's border width
@objc public var cellBorderWidth: CGFloat {
get { self.cellStyle.borderWidth }
set { self.cellStyle = self.cellStyle.modify(borderWidth: newValue) }
}
/// The cell's corner radius
@objc public var cellCornerRadius: CGFloat {
get { self.cellStyle.cornerRadius }
set { self.cellStyle = self.cellStyle.modify(cornerRadius: newValue) }
}
/// Called when the activity cells are updated
@objc public var cellsDidUpdateBlock: (() -> Void)?
// MARK: Drawing and updates
/// Called when the content of the data source changes
override func staticDataSourceDidChange() {
super.staticDataSourceDidChange()
self.setNeedsDisplay()
}
/// Draws the graph
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self._cells.removeAll()
switch self.layoutStyle {
case .github:
self.drawGithubStyle(context: context, bounds: bounds, scale: scale)
case .defrag:
self.drawDefragStyle(context: context, bounds: bounds, scale: scale)
}
self.cellsDidUpdateBlock?()
}
public var cells: [CGRect] { _cells }
// MARK: Private
private var _cells: [CGRect] = []
}
}
public extension DSFSparklineOverlay.ActivityGrid {
/// Returns the index within the datasource of the value at the given point
/// - Parameter point: The point within the activity grid to test
/// - Returns: The data source index for the point, or -1 if
/// 1. no cell was hit, or
/// 2. The cell was a skip cell (ie. its value is `.infinity`, or
/// 3. the cell hit was outside of bounds of the data source
@objc func indexAtPoint(_ point: CGPoint) -> Int {
if let index: Int = self._cells.firstIndex(where: { $0.contains(point) }) {
if index < self.dataSource.values.count,
self.dataSource.values[index] == .infinity
{
// It's an existing cell, but it's a skip cell
return -1
}
return index
}
return -1
}
/// Return the cell frame for the given index
/// - Parameter index: The index
/// - Returns: The cell bounds for the given index
@objc func cellFrame(for index: Int) -> CGRect {
if index < self._cells.count {
return self._cells[index]
}
// If we get here, the indexed cell was not visible on screen
return .zero
}
}
// MARK: - Sizing
extension DSFSparklineOverlay.ActivityGrid {
/// The expected height given the current settings
@objc public var intrinsicHeight: CGFloat {
if verticalCellCount <= 0 {
return DSFView.noIntrinsicMetric
}
return (CGFloat(self.verticalCellCount) * (self.cellStyle.cellDimension + self.cellStyle.cellSpacing)) + self.cellStyle.cellSpacing
}
/// Minimum width for displaying the current values without padding
@objc public var intrinsicWidth: CGFloat {
if horizontalCellCount <= 0 {
if verticalCellCount <= 0 {
return DSFView.noIntrinsicMetric
}
var columnCount = self.dataSource.values.count / self.verticalCellCount
if (self.dataSource.values.count % self.verticalCellCount) > 0 {
columnCount += 1
}
return (CGFloat(columnCount) * (self.cellStyle.cellDimension + self.cellStyle.cellSpacing)) + self.cellStyle.cellSpacing
}
else {
// Fixed horizontal count
return (CGFloat(horizontalCellCount) * (self.cellStyle.cellDimension + self.cellStyle.cellSpacing)) + self.cellStyle.cellSpacing
}
}
/// Intrinsic size for the grid
@objc public var intrinsicSize: CGSize { CGSize(width: self.intrinsicWidth, height: self.intrinsicHeight) }
}
extension DSFSparklineOverlay.ActivityGrid {
private func drawGithubStyle(context: CGContext, bounds: CGRect, scale: CGFloat) {
let style = self.cellStyle
var xOffset = bounds.width - style.cellSpacing - style.cellDimension
var dataOffset = 0
while xOffset > 0 {
(0 ..< verticalCellCount).reversed().forEach { index in
let cell = CGRect(
x: xOffset,
y: CGFloat(index) * (style.cellDimension + style.cellSpacing) + style.cellSpacing,
width: style.cellDimension,
height: style.cellDimension
)
self.drawCell(context: context, inRect: cell, index: dataOffset)
dataOffset += 1
}
xOffset -= (self.cellStyle.cellDimension + self.cellStyle.cellSpacing)
}
}
private func drawDefragStyle(context: CGContext, bounds: CGRect, scale: CGFloat) {
var dataOffset = 0
var yOffset = self.cellStyle.cellSpacing
let style = self.cellStyle
let sp = Int((bounds.width - style.cellSpacing) / (style.cellSpacing + style.cellDimension))
let horizontalCellCount = self.horizontalCellCount == 0 ? sp : self.horizontalCellCount
var xOffset = 0
while yOffset <= (bounds.height - style.cellSpacing - style.cellDimension) {
while xOffset < horizontalCellCount {
let cell = CGRect(
x: style.cellSpacing + (CGFloat(xOffset) * (style.cellSpacing + style.cellDimension)),
y: yOffset,
width: style.cellDimension,
height: style.cellDimension
)
self.drawCell(context: context, inRect: cell, index: dataOffset)
xOffset += 1
dataOffset += 1
}
xOffset = 0
yOffset += self.cellStyle.cellDimension + self.cellStyle.cellSpacing
}
}
}
private extension DSFSparklineOverlay.ActivityGrid {
func drawCell(context: CGContext, inRect cell: CGRect, index: Int) {
let style = self.cellStyle
// Store the cell information so we can access it later
self._cells.append(cell)
// The fractional value for the index
let fracValue = self.dataSource.fractionalValue(at: index)
if fracValue.isInfinite {
// Treat an infinite value as a skip cell. Draw nothing
return
}
let fraction = fracValue.isNaN ? 0.0 : fracValue
let color = style.fillScheme.color(atFraction: fraction)
context.addPath(
CGPath(
roundedRect: cell,
cornerWidth: style.cornerRadius,
cornerHeight: style.cornerRadius,
transform: nil
)
)
context.setFillColor(color)
context.fillPath()
if let c = self.cellStyle.borderColor {
context.addPath(
CGPath(
roundedRect: cell,
cornerWidth: style.cornerRadius,
cornerHeight: style.cornerRadius,
transform: nil
)
)
context.setFillColor(.clear)
context.setStrokeColor(c)
context.setLineWidth(style.borderWidth)
context.strokePath()
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Bar.swift
================================================
//
// DSFSparklineOverlay+Bar.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A bar graph overlay
@objc(DSFSparklineOverlayBar) class Bar: Centerable {
/// The width for the line drawn on the graph (in pixels)
@objc public var strokeWidth: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// The spacing between each bar (in pixels)
@objc public var barSpacing: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// Draw a shadow under the line
@objc public var shadow: NSShadow? {
didSet {
self.setNeedsDisplay()
}
}
public override init() {
super.init()
}
override public init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.strokeWidth = orig.strokeWidth
self.barSpacing = orig.barSpacing
self.shadow = orig.shadow?.copy() as? NSShadow
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func edgeInsets(for rect: CGRect) -> DSFEdgeInsets {
guard let dataSource = self.dataSource else { return .zero }
let integralRect = bounds.integral
let componentWidth = Int(integralRect.width) / Int(dataSource.windowSize)
// The left offset in order to center X the full range
let intOffset = Int(bounds.minX) + (Int(bounds.width) - (componentWidth * Int(dataSource.windowSize))) / 2
let xOffset = CGFloat(intOffset)
let yOffset = CGFloat(self.strokeWidth)
return DSFEdgeInsets(top: yOffset, left: xOffset, bottom: yOffset, right: xOffset)
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
if self.centeredAtZeroLine {
self.drawCenteredBarGraph(context: context, bounds: bounds, scale: scale)
}
else {
self.drawBarGraph(context: context, bounds: bounds, scale: scale)
}
}
}
}
extension DSFSparklineOverlay.Bar {
private func drawBarGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else { return }
let integralRect = bounds.integral
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth = Int(integralRect.width) / Int(dataSource.windowSize)
// The width of the BAR component
let barWidth = componentWidth - Int(barSpacing)
// The left offset in order to center X
let xOffset: Int = Int(bounds.minX) + (Int(bounds.width) - (componentWidth * Int(dataSource.windowSize))) / 2
// The available height range
let range: ClosedRange = 0 ... integralRect.maxY
let normy = dataSource.normalized
let xDiff = bounds.width / CGFloat(normy.count)
let points = normy.enumerated().map {
CGPoint(x: CGFloat($0.offset) * xDiff,
y: ($0.element * (integralRect.height - 1) + integralRect.minY).clamped(to: range))
}
context.usingGState { outer in
outer.setRenderingIntent(.relativeColorimetric)
outer.interpolationQuality = .none
outer.setShouldAntialias(false)
if dataSource.counter < dataSource.windowSize {
let pos = xOffset + (Int(dataSource.counter) * componentWidth)
let clipRect = bounds.divided(atDistance: CGFloat(pos), from: .maxXEdge).slice
outer.clip(to: clipRect)
}
var bars: [CGRect] = []
for point in points.enumerated() {
let yVal = Int(point.element.y.rounded(.down))
let r = CGRect(x: xOffset + point.offset * componentWidth,
y: Int(integralRect.minY) + Int(integralRect.height) - yVal,
width: barWidth,
height: yVal - Int(self.strokeWidth))
bars.append(r.integral)
}
let path = CGMutablePath()
path.addRects(bars)
if let primaryFill = self.primaryFill {
outer.usingGState { fillCtx in
fillCtx.addPath(path)
fillCtx.clip()
primaryFill.fill(context: fillCtx, bounds: bounds)
}
}
if let stroke = self.primaryStrokeColor {
outer.usingGState { strokeCtx in
if let shadow = self.shadow {
strokeCtx.setShadow(shadow)
}
strokeCtx.addPath(path)
strokeCtx.setLineWidth(1.0 / scale * CGFloat(self.strokeWidth))
strokeCtx.setStrokeColor(stroke)
strokeCtx.drawPath(using: .stroke)
}
}
}
}
private func drawCenteredBarGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else { return }
let drawRect = bounds
let height = drawRect.height - 1
let normy = dataSource.normalized
let xDiff = bounds.width / CGFloat(normy.count)
let centre = dataSource.normalizedZeroLineValue
let centroid = (1 - centre) * (drawRect.height - 1)
context.usingGState { outer in
outer.setRenderingIntent(.relativeColorimetric)
outer.interpolationQuality = .none
if dataSource.counter < dataSource.windowSize {
let pos = CGFloat(dataSource.counter) * xDiff + 1
let clipRect = bounds.divided(atDistance: pos, from: .maxXEdge).slice
outer.clip(to: clipRect)
}
let split = self.bounds.divided(atDistance: centroid, from: .minYEdge)
let positiveRegion: CGRect = split.slice
let negativeRegion: CGRect = split.remainder
var positivePath: [CGRect] = []
var negativePath: [CGRect] = []
for value in normy.enumerated() {
let x = bounds.minX + CGFloat(value.offset) * xDiff
if value.element >= centre {
let yy = bounds.minY + ((centre - value.element) * height)
let r = CGRect(
x: x,
y: centroid,
width: xDiff - 1 - (CGFloat(self.barSpacing)),
height: yy
)
positivePath.append(r.integral)
}
else {
let yy = bounds.minY + ((value.element - centre) * height)
let r = CGRect(
x: x,
y: centroid + 1,
width: xDiff - 1 - (CGFloat(self.barSpacing)),
height: -yy - CGFloat(self.strokeWidth) + 1
)
negativePath.append(r.integral)
}
}
outer.setShouldAntialias(false)
if !positivePath.isEmpty {
let path = CGMutablePath()
path.addRects(positivePath)
if let primaryFill = self.primaryFill {
outer.usingGState { fillCtx in
fillCtx.addPath(path)
fillCtx.clip()
primaryFill.fill(context: fillCtx, bounds: positiveRegion)
}
}
if let stroke = self.primaryStrokeColor {
outer.usingGState { strokeCtx in
strokeCtx.addPath(path)
strokeCtx.setLineWidth(1.0 / scale * CGFloat(self.strokeWidth))
strokeCtx.setStrokeColor(stroke)
strokeCtx.strokePath()
}
}
}
if !negativePath.isEmpty {
let path = CGMutablePath()
path.addRects(negativePath)
if let secondaryFill = self.secondaryFill {
outer.usingGState { fillCtx in
fillCtx.addPath(path)
fillCtx.clip()
secondaryFill.fill(context: fillCtx, bounds: negativeRegion)
}
}
if let stroke = self.secondaryStrokeColor {
outer.usingGState { strokeCtx in
strokeCtx.addPath(path)
strokeCtx.setLineWidth(1.0 / scale * CGFloat(self.strokeWidth))
strokeCtx.setStrokeColor(stroke)
strokeCtx.strokePath()
}
}
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+CircularGauge.swift
================================================
//
// DSFSparklineOverlay+CircularGauge.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import QuartzCore
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A circular gauge
@objc(DSFSparklineOverlayCircularGauge) class CircularGauge: DSFSparklineOverlay {
/// The default track style
@objc public static let DefaultTrackStyle = DSFSparklineOverlay.CircularGauge.TrackStyle(
width: 10,
fillColor: DSFSparkline.Fill.Color(red: 0, green: 0, blue: 0, alpha: 0.2)
)
/// The default value style
@objc public static let DefaultLineStyle = DSFSparklineOverlay.CircularGauge.TrackStyle(
width: 7,
fillColor: DSFSparkline.Fill.Color(red: 0, green: 0, blue: 0, alpha: 1)
)
/// The value assigned to the percent bar. A value between 0.0 and 1.0
@objc public var value: CGFloat = 0 {
willSet {
animationTransition = DSFSparkline.AnimationTransition(start: _value, stop: newValue)
}
didSet {
if isAnimated {
startAnimateIn()
}
else {
self._value = self.value.clamped(to: 0 ... 1)
}
}
}
private var _value: CGFloat = 0 {
didSet { self.setNeedsDisplay() }
}
/// The style to use when drawing the gauge's track
@objc public var trackStyle: TrackStyle = TrackStyle(
width: 5,
fillColor: DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 0, alpha: 0.2)
)
/// The style to use when drawing the gauge's value
@objc public var lineStyle: TrackStyle = TrackStyle(
width: 3,
fillColor: DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 0)
)
/// The animation style to apply
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil
private var isAnimated: Bool { animationStyle != nil }
override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
CATransaction.withDisabledActions { [weak self] in
self?.drawCircularGauge(context: context, bounds: bounds, scale: scale)
}
}
// MARK: Animation
private var animator = ArbitraryAnimator() //
private var animationTransition: DSFSparkline.AnimationTransition?
}
}
extension DSFSparklineOverlay.CircularGauge {
func startAnimateIn() {
guard let animStyle = animationStyle else { fatalError() }
// Stop any animation that is currently active
self.animator.progressBlock = nil
self.animator.stop()
self.animator.animationFunction = animStyle.function.function
self.animator.duration = animStyle.duration
self.animator.progressBlock = { [weak self] progress in
guard let `self` = self, let a = self.animationTransition else { return }
_value = a.start + (a.stop - a.start) * progress
}
self.animator.start()
}
}
public extension DSFSparklineOverlay.CircularGauge {
/// A circular gauge track style
@objc(DSFSparklineOverlayCircularGaugeTrackStyle) class TrackStyle: NSObject {
/// The width of the track
@objc public var width: CGFloat
/// The fill style to use
@objc public var fillColor: DSFSparklineFillable
/// The stroke width
@objc public var strokeWidth: CGFloat
/// The stroke color
@objc public var strokeColor: CGColor?
/// The shadow to use
@objc public var shadow: DSFSparkline.Shadow?
/// The line's cap style
@objc public var lineCap: CGLineCap
/// Create
@objc public init(
width: CGFloat,
fillColor: DSFSparklineFillable,
strokeWidth: CGFloat = 0.0,
strokeColor: CGColor? = nil,
shadow: DSFSparkline.Shadow? = nil,
lineCap: CGLineCap = .round
) {
self.width = width
self.fillColor = fillColor
self.strokeWidth = strokeWidth
self.strokeColor = strokeColor
self.shadow = shadow
self.lineCap = lineCap
super.init()
}
}
}
private extension DSFSparklineOverlay.CircularGauge {
func drawCircularGauge(context: CGContext, bounds: CGRect, scale: CGFloat) {
var drawRect = bounds
if drawRect.width != drawRect.height {
let m = min(drawRect.width, drawRect.height)
drawRect = CGRect(
x: drawRect.minX + ((drawRect.width - m) / 2),
y: drawRect.minY + ((drawRect.height - m) / 2),
width: m,
height: m
)
}
let baseRect = drawRect
// Inset so that the line drawing doesn't crop at the edges
let inset: CGFloat = {
var inset = max(trackStyle.width + trackStyle.strokeWidth, lineStyle.width + lineStyle.strokeWidth)
let trackW = trackStyle.shadow?.requiredShadowInset ?? 0
let valueW = lineStyle.shadow?.requiredShadowInset ?? 0
inset += max(trackW, valueW)
return inset
}()
drawRect = drawRect.insetBy(dx: inset / 2, dy: inset / 2)
let centerPoint = CGPoint(x: drawRect.midX, y: drawRect.midY)
let radius: CGFloat = drawRect.width > drawRect.height ? (drawRect.height / 2.0) : (drawRect.width / 2.0)
// The track
self.drawComponent(
context: context,
bounds: baseRect,
centerPoint: centerPoint,
radius: radius,
value: 1,
style: trackStyle
)
// The value
self.drawComponent(
context: context,
bounds: baseRect,
centerPoint: centerPoint,
radius: radius,
value: _value,
style: lineStyle
)
}
}
private extension DSFSparklineOverlay.CircularGauge {
func drawComponent(
context: CGContext,
bounds: CGRect,
centerPoint: CGPoint,
radius: CGFloat,
value: CGFloat,
style: TrackStyle
) {
let pth = CGMutablePath()
pth.addArc(
center: centerPoint,
radius: radius,
startAngle: Angle.degrees(135).radians,
endAngle: Angle.degrees(135 + (270 * value)).radians,
clockwise: false
)
let act = pth.copy(
strokingWithWidth: style.width,
lineCap: style.lineCap,
lineJoin: .round,
miterLimit: 1.0
)
if let shadow = style.shadow {
if shadow.isInner {
context.usingGState { ctx in
// Draw the track
ctx.addPath(act)
ctx.clip()
style.fillColor.fill(context: ctx, bounds: bounds)
// Overlay the inner shadow
ctx.drawInnerShadow(
in: act,
shadowColor: shadow.color,
offset: shadow.offset,
blurRadius: shadow.blurRadius
)
}
}
else {
context.usingGState { ctx in
ctx.addRect(bounds)
ctx.addPath(act)
ctx.clip(using: .evenOdd)
ctx.setShadow(shadow.shadow)
ctx.addPath(act)
ctx.setFillColor(shadow.color!)
ctx.fillPath()
}
context.usingGState { ctx in
ctx.addPath(act)
ctx.clip()
style.fillColor.fill(context: ctx, bounds: bounds)
}
}
}
else {
context.usingGState { ctx in
ctx.addPath(act)
ctx.clip()
style.fillColor.fill(context: ctx, bounds: bounds)
}
}
if let s = style.strokeColor {
context.usingGState { ctx in
ctx.addPath(act)
ctx.setStrokeColor(s)
ctx.setLineWidth(style.strokeWidth)
ctx.setLineCap(.round)
ctx.strokePath()
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+CircularProgress.swift
================================================
//
// DSFSparklineOverlay+CircularProgress.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import QuartzCore
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A circular progress sparkline
@objc(DSFSparklineOverlayCircularProgress) class CircularProgress: DSFSparklineOverlay {
/// The value assigned to the percent bar. A value between 0.0 and 1.0
@objc public var value: CGFloat = 0.25 {
didSet {
self.valueDidChange()
}
}
/// Default track width
@objc public static let DefaultTrackWidth: CGFloat = 10.0
/// The width of the circular ring track
@objc public var trackWidth: CGFloat = CircularProgress.DefaultTrackWidth {
didSet {
self.valueDidChange()
}
}
/// The padding (inset) for drawing the ring
@objc public var padding: CGFloat = 0.0 {
didSet {
self.valueDidChange()
}
}
/// Default fill style
@objc public static let DefaultFillStyle = DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1)
/// The fill style for the progress track
@objc public var fillStyle: DSFSparklineFillable = CircularProgress.DefaultFillStyle {
didSet {
self.valueDidChange()
}
}
/// Default track color
@objc public static let DefaultTrackColor = CGColor(gray: 0.5, alpha: 0.1)
/// The color of the track background
@objc public var trackColor: CGColor = CircularProgress.DefaultTrackColor {
didSet {
self.valueDidChange()
}
}
/// The icon appearing in the track at the top of the ring
@objc public var icon: CGImage? = nil {
didSet {
self._flippedIcon = icon?.flipped()
self.valueDidChange()
}
}
private var _flippedIcon: CGImage? = nil
override init() {
super.init()
self.drawsAsynchronously = true
}
init(value: CGFloat, trackWidth: CGFloat, fillStyle: DSFSparklineFillable, trackColor: CGColor = CircularProgress.DefaultTrackColor) {
self.value = value
self.trackWidth = trackWidth
self.fillStyle = fillStyle
self.trackColor = trackColor
super.init()
self.drawsAsynchronously = true
}
public override init(layer: Any) {
if let layer = layer as? CircularProgress {
self.value = layer.value
self.trackWidth = layer.trackWidth
self.fillStyle = layer.fillStyle
self.trackColor = layer.trackColor.copy()!
self.padding = layer.padding
self.icon = layer.icon?.copy()
}
else {
fatalError()
}
super.init(layer: layer)
self.drawsAsynchronously = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawCircularProgressGraph(context: context, bounds: bounds, scale: scale)
}
private let fullCircle = 2.0 * CGFloat.pi
}
}
private extension DSFSparklineOverlay.CircularProgress {
func valueDidChange() {
self.setNeedsDisplay()
}
func drawCircularProgressGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
ActivityRing.drawRing(
in: context,
destination: bounds,
value: self.value,
padding: self.padding,
trackWidth: self.trackWidth,
fillStyle: self.fillStyle,
trackColor: self.trackColor,
icon: self._flippedIcon
)
}
}
////
private class ActivityRing {
static func drawRing(
in ctx: CGContext,
destination: CGRect = .zero,
value: CGFloat,
padding: CGFloat,
trackWidth: CGFloat,
fillStyle: DSFSparklineFillable,
trackColor: CGColor,
icon: CGImage?
) {
assert(value >= 0.0)
guard destination.isEmpty == false else { return }
let _fullCircle = 2.0 * CGFloat.pi
var drawRect = destination
if drawRect.width != drawRect.height {
let m = min(drawRect.width, drawRect.height)
drawRect = CGRect(
x: drawRect.minX + ((drawRect.width - m) / 2),
y: drawRect.minY + ((drawRect.height - m) / 2),
width: m,
height: m
)
}
// Inset by the padding amount
drawRect = drawRect.insetBy(dx: padding, dy: padding)
guard drawRect.isEmpty == false else {
return
}
let centerPoint = CGPoint(x: drawRect.midX, y: drawRect.midY)
let dimension = drawRect.width > drawRect.height ? drawRect.height : drawRect.width
let radius: CGFloat = drawRect.width > drawRect.height ? (drawRect.height - trackWidth) / 2.0 : (drawRect.width - trackWidth) / 2.0
//
// Clip to the activity track
//
let pth = CGMutablePath()
pth.addArc(center: centerPoint, radius: radius, startAngle: 0, endAngle: _fullCircle, clockwise: false)
let act = pth.copy(strokingWithWidth: trackWidth, lineCap: .round, lineJoin: .round, miterLimit: 1.0)
ctx.addPath(act)
ctx.clip()
//
// Fill the background track
//
ctx.usingGState { ctx in
ctx.setFillColor(trackColor)
ctx.fill([drawRect])
}
//
// Draw the ring
//
var offset: CGFloat = 0
do {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let fill = CGContext(
data: nil,
width: Int(dimension),
height: Int(dimension),
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue
)!
// The full angle sweep (which may be > 1)
let totalAngle = _fullCircle * value
while offset <= totalAngle {
let fractionalValue = totalAngle == 0 ? 0 : (offset / totalAngle)
let c = fillStyle.color(at: fractionalValue)
let x = radius + (radius * cos(offset - (CGFloat.pi * 0.5)))
let y = radius + (radius * sin(offset - (CGFloat.pi * 0.5)))
offset += 0.01
let p = CGPath(
ellipseIn: CGRect(x: x - 0.1, y: y - 0.1, width: trackWidth + 0.2, height: trackWidth + 0.2),
transform: nil)
fill.addPath(p)
fill.setFillColor(c)
fill.fillPath()
}
/// An image containing the drawn ring
let ringImage = fill.makeImage()!
ctx.draw(ringImage, in: drawRect, byTiling: false)
}
//
// Draw a circle at the end
//
if value >= 0.92 {
ctx.usingGState { ctx in
let x = drawRect.minX + (radius + (radius * cos(offset - (CGFloat.pi * 0.5))))
let y = drawRect.minY + (radius + (radius * sin(offset - (CGFloat.pi * 0.5))))
let cp = CGPath(
ellipseIn: CGRect(x: x - 0.1, y: y - 0.1, width: trackWidth + 0.2, height: trackWidth + 0.2),
transform: nil
)
ctx.addPath(cp)
ctx.setFillColor(fillStyle.color(at: 1.0))
let shadowAngle = offset + 0.1
let sx = drawRect.minX + (radius + (radius * cos(shadowAngle - (CGFloat.pi * 0.5))))
let sy = drawRect.minY + (radius + (radius * sin(shadowAngle - (CGFloat.pi * 0.5))))
ctx.setShadow(
offset: CGSize(width: sx - x, height: sy - y),
blur: dimension / 25,
color: .black.copy(alpha: 0.4)
)
ctx.fillPath()
}
}
//
// Draw the icon
//
if let icon = icon {
let x = drawRect.minX + (radius + (radius * cos(-(CGFloat.pi * 0.5))))
let y = drawRect.minY + (radius + (radius * sin(-(CGFloat.pi * 0.5))))
let iconRect = CGRect(x: x, y: y, width: trackWidth, height: trackWidth).insetBy(dx: 2, dy: 2)
ctx.draw(icon, in: iconRect, byTiling: false)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+DataBar.swift
================================================
//
// DSFSparklineOverlay+DataBar.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
@objc(DSFSparklineOverlayDataBar) class DataBar: DSFSparklineOverlay.StaticDataSource {
/// The maximum _total_ value. If the datasource values total is greater than this value, it clips the display
@objc public var maximumTotalValue: CGFloat = -1 {
didSet {
self.setNeedsDisplay()
}
}
/// The 'undrawn' color for the graph.
///
/// If a maximum total value is defined, and the datasource doesn't completely fill the total, then
/// the empty section of the databar is drawn using this color.
@objc public var unsetColor: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// The stroke color for the line(s) to be drawn between each segment of the databar.
@objc public var strokeColor: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// The width of the stroke line to be drawn between each segment of the databar. if 0, no stroke is drawn
@objc public var lineWidth: CGFloat = 0.5 {
didSet {
self.setNeedsDisplay()
}
}
/// The palette to use when drawing the databar
@objc public var palette = DSFSparkline.Palette.shared {
didSet {
self.setNeedsDisplay()
}
}
// MARK: Animation support
/// The animation style
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil
private var animated: Bool { animationStyle != nil }
// MARK: Initializers
@objc public override init() {
super.init()
}
override public init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.maximumTotalValue = orig.maximumTotalValue
self.unsetColor = orig.unsetColor
self.strokeColor = orig.strokeColor
self.lineWidth = orig.lineWidth
self.palette = orig.palette.copyPalette()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: Data change
override func staticDataSourceDidChange() {
super.staticDataSourceDidChange()
if self.animated {
self.startAnimateIn()
}
else {
self.fractionComplete = 1.0
self.setNeedsDisplay()
}
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawDataBarGraph(context: context, bounds: bounds, scale: scale)
}
// MARK: - Privates
internal var animator = ArbitraryAnimator()
internal var fractionComplete: CGFloat = 0
}
}
private extension DSFSparklineOverlay.DataBar {
func startAnimateIn() {
guard let anim = self.animationStyle else { fatalError() }
// Stop any animation that is currently active
self.animator.progressBlock = nil
self.animator.stop()
self.fractionComplete = 0
self.animator.animationFunction = anim.function.function
self.animator.duration = anim.duration
self.animator.progressBlock = { [weak self] progress in
self?.fractionComplete = CGFloat(progress)
self?.setNeedsDisplay()
}
self.animator.start()
}
}
private extension DSFSparklineOverlay.DataBar {
func drawDataBarGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
if fractionComplete == 0 { return }
let total = self.maximumTotalValue > 0 ? self.maximumTotalValue : self.dataSource.total
let rect = bounds.integral
var position: CGFloat = rect.minX
let delta: CGFloat = (rect.width / total) * self.fractionComplete
context.clip(to: rect)
if self.maximumTotalValue > 0, let unsetColor = self.unsetColor {
let cbheight = max(2, rect.height / 6)
let center = rect.midY
let centerBar = CGRect(x: rect.minX, y: center - (cbheight / 2), width: rect.width, height: cbheight)
let pth = CGPath(roundedRect: centerBar,
cornerWidth: cbheight / 2, cornerHeight: cbheight / 2, transform: nil)
context.usingGState { state in
state.addPath(pth)
state.setFillColor(unsetColor)
state.fillPath()
}
}
for segment in self.dataSource.values.enumerated() {
context.usingGState { state in
state.setFillColor(self.palette.cgColorAtOffset(segment.offset))
let width = segment.element * delta
let path = CGPath(rect: CGRect(x: position, y: rect.minY, width: width, height: rect.height), transform: nil)
state.addPath(path)
state.fillPath()
if segment.offset > 0, let strokeColor = self.strokeColor {
state.usingGState { separator in
separator.setStrokeColor(strokeColor)
separator.setLineWidth(self.lineWidth)
separator.move(to: CGPoint(x: position, y: rect.minY))
separator.addLine(to: CGPoint(x: position, y: rect.maxY))
separator.strokePath()
}
}
position = position + width
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Dot.swift
================================================
//
// DSFSparklineOverlay+Dot.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// A dot graph
@objc(DSFSparklineOverlayDot) class Dot: DSFSparklineOverlay.DataSource {
/// The number of vertical buckets to break the input data up into
@objc public var verticalDotCount: UInt = 10 {
didSet {
self.setNeedsDisplay()
}
}
/// The color to use when a 'dot' within the bar is on
@objc public var onColor: CGColor = .black {
didSet {
self.setNeedsDisplay()
}
}
/// The color to use when a 'dot' within the bar is off
@objc public var offColor: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// Are the values drawn from the top down?
@objc public var upsideDown: Bool = false {
didSet {
self.setNeedsDisplay()
}
}
@objc public init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.verticalDotCount = orig.verticalDotCount
self.onColor = orig.onColor.copy() ?? .black
self.offColor = orig.offColor?.copy()
self.upsideDown = orig.upsideDown
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawDotGraph(context: context, bounds: bounds, scale: scale)
}
}
}
extension DSFSparklineOverlay.Dot {
private func drawDotGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource else { return }
let drawRect = bounds
let height = drawRect.height
let dotHeight = floor(height / CGFloat(self.verticalDotCount))
let xOffset: CGFloat = bounds.width.truncatingRemainder(dividingBy: dotHeight) / 2.0
let yOffset: CGFloat = bounds.height.truncatingRemainder(dividingBy: dotHeight) / 2.0
var position = drawRect.width - dotHeight - xOffset
// Map normalized values to box positions
let normalizedBoxed: [UInt] = dataSource.normalized.reversed().map { dataPoint in
let floatBoxPos = CGFloat(self.verticalDotCount) * dataPoint
return UInt(floatBoxPos.rounded(.awayFromZero))
}
var pv: [CGRect] = []
var uv: [CGRect] = []
for dataPoint in normalizedBoxed {
let boxCount = dataPoint
for c in 0 ..< self.verticalDotCount {
let pos = self.upsideDown
? (CGFloat(c) * dotHeight) + yOffset
: height - (CGFloat(c) * dotHeight) - dotHeight - yOffset
let r = CGRect(x: position, y: pos, width: dotHeight, height: dotHeight)
let ri = r.insetBy(dx: 0.5, dy: 0.5)
if c < boxCount {
pv.append(ri)
}
else {
uv.append(ri)
}
}
// Move left. If we've hit the lower bound, then stop
position -= dotHeight
if position < 0 {
break
}
}
if pv.count > 0 {
context.usingGState { state in
let path = CGMutablePath()
path.addRects(pv)
state.addPath(path)
state.setFillColor(self.onColor)
state.drawPath(using: .fill)
}
}
if uv.count > 0, let offColor = self.offColor {
context.usingGState { state in
let path = CGMutablePath()
path.addRects(uv)
state.addPath(path)
state.setFillColor(offColor)
state.drawPath(using: .fill)
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Line.swift
================================================
//
// Copyright © 2025 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A line graph sparkline
@objc(DSFSparklineOverlayLine) class Line: Centerable {
/// The width for the line drawn on the graph
@objc public var strokeWidth: CGFloat = 1.5 {
didSet {
self.setNeedsDisplay()
}
}
/// Interpolate a curve between the points
@objc public var interpolated: Bool = false {
didSet {
self.setNeedsDisplay()
}
}
/// Draw a shadow under the line
@objc public var shadow: NSShadow? {
didSet {
self.setNeedsDisplay()
}
}
/// The size of the markers to draw. If the markerSize is less than 0, markers will not draw
@objc public var markerSize: CGFloat = -1 {
didSet {
self.setNeedsDisplay()
}
}
// MARK: - Markers
/// Representation of a marker within the sparkline
@objc(DSFSparklineOverlayLineMarker) public class Marker: NSObject {
/// The raw data value for the marker
@objc public let value: CGFloat
/// The rect representing the marker
@objc public let rect: CGRect
/// If the graph is a centering graph, is this marker considered to be positive or negative?
@objc public let isPositiveValue: Bool
@objc public init(value: CGFloat, rect: CGRect, isPositiveValue: Bool) {
self.value = value
self.rect = rect
self.isPositiveValue = isPositiveValue
}
}
/// Marker drawing callback
/// - Parameters:
/// - context: The drawing context
/// - markerFrames: The Marker definitions for all of the markers within the current sparkline in left-to-right order
public typealias MarkerDrawingBlock = (_ context: CGContext, _ markerFrames: [Marker]) -> Void
/// An optional drawing function for custom drawing markers.
///
/// The `markerSize` value is used to determine the frameSize of each marker.
/// If `markerSize` is less than 1, this block will not be called.
///
/// Note that this function is called very frequently so make sure its performant!
@objc public lazy var markerDrawingBlock: MarkerDrawingBlock? = nil
// Return the drawing function to use when drawing markers
private var actualMarkerDrawingBlock: MarkerDrawingBlock {
return self.markerDrawingBlock ?? self.DefaultMarkerDrawingBlock
}
// Convert the default marker drawing function to a block
private lazy var DefaultMarkerDrawingBlock: MarkerDrawingBlock = { context, markers in
DSFSparklineOverlay.Line.DefaultMarkerDrawingFunc(what: self, context: context, markers: markers)
}
public override init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.strokeWidth = orig.strokeWidth
self.interpolated = orig.interpolated
self.shadow = orig.shadow?.copy() as? NSShadow
self.markerSize = orig.markerSize
super.init(layer: layer)
self.markerDrawingBlock = orig.markerDrawingBlock
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.markerDrawingBlock = nil
}
// MARK: - Draw handling
// Override the edge insets to make sure our line graph fits
override internal func edgeInsets(for rect: CGRect) -> DSFEdgeInsets {
// If there's a shadow, inset by the maximum shadow offset + blur radius
let shadowOffset: CGFloat = {
if let s = shadow {
return max(s.shadowOffset.width, s.shadowOffset.height) + s.shadowBlurRadius
}
else {
return 0
}
}()
// Standard inset taking into account marker sizes and shadow offsets
let inset = (self.markerSize > 0 ? self.markerSize / 2 : self.strokeWidth) + shadowOffset
// Interpolation inset
var interpolationInset: CGFloat = 0
if self.interpolated {
// Hermite curve matching can overshoot. Until I can find/implement a better curve algorithm that
// doesn't overshoot, we'll increase the height inset by a percentage of the height.
// The following number is somewhat magic - based on worst cast overshoot and some visual testing
let rrr = self.currentPath().boundingBoxOfPath
if rrr.minY < 0 {
interpolationInset = abs(rrr.minY) / 1.3
}
}
return DSFEdgeInsets(
top: inset + interpolationInset,
left: inset,
bottom: inset + interpolationInset,
right: inset
)
}
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
if self.centeredAtZeroLine {
self.drawCenteredGraph(context: context, bounds: bounds, scale: scale)
}
else {
self.drawLineGraph(context: context, bounds: bounds, scale: scale)
}
}
}
}
// MARK: - Default marker drawing
private extension DSFSparklineOverlay.Line {
// Default marker drawing
static func DefaultMarkerDrawingFunc(what: Line, context ctx: CGContext, markers: [Marker]) {
guard let primaryStrokeColor = what.primaryStrokeColor else { return }
let positiveMarkers = markers.filter { $0.isPositiveValue }
let negativeMarkers = markers.filter { $0.isPositiveValue == false }
// draw positives. For a non-centered graph, all values are 'positive'
if positiveMarkers.count > 0 {
ctx.usingGState { pCtx in
let path = CGMutablePath()
positiveMarkers.forEach { marker in
path.addPath(CGPath(ellipseIn: marker.rect, transform: nil))
}
pCtx.addPath(path)
pCtx.setFillColor(primaryStrokeColor)
if let shadow = what.shadow {
pCtx.setShadow(shadow)
}
pCtx.fillPath()
}
}
// draw negatives
if negativeMarkers.count > 0 {
let strokeColor = what.secondaryStrokeColor ?? primaryStrokeColor
ctx.usingGState { nCtx in
let path = CGMutablePath()
negativeMarkers.forEach { marker in
path.addPath(CGPath(ellipseIn: marker.rect, transform: nil))
}
nCtx.addPath(path)
nCtx.setFillColor(strokeColor)
if let shadow = what.shadow {
nCtx.setShadow(shadow)
}
nCtx.fillPath()
}
}
}
}
// MARK: - Sparkline drawing
private extension DSFSparklineOverlay.Line {
func currentPath() -> CGPath {
guard let dataSource = self.dataSource else { return CGPath(rect: .zero, transform: nil) }
let normy = dataSource.normalized
let xDiff = bounds.width / CGFloat(normy.count - 1)
let points = normy.enumerated().map {
CGPoint(
x: CGFloat($0.offset) * xDiff + bounds.minX,
y: bounds.height + bounds.minY - ($0.element * bounds.height)
)
}
return CGPath.pathWithPoints(points, smoothed: self.interpolated)
}
}
private extension DSFSparklineOverlay.Line {
func drawLineGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource,
dataSource.counter != 0 else
{
// There's no line if there's either no data or just a single point
// https://github.com/dagronf/DSFSparkline/issues/3#issuecomment-770324047
return
}
let normy = dataSource.normalized
let xDiff = bounds.width / CGFloat(normy.count - 1)
let points = normy.enumerated().map {
CGPoint(x: CGFloat($0.offset) * xDiff + bounds.minX,
y: bounds.height + bounds.minY - ($0.element * bounds.height))
}
let path = CGPath.pathWithPoints(points, smoothed: self.interpolated)
var markersBounds: [CGRect] = []
if self.markerSize > 0 {
points.forEach { point in
let w = self.markerSize / 2
let r = CGRect(x: point.x - w, y: point.y - w, width: self.markerSize, height: self.markerSize)
markersBounds.append(r)
}
}
context.usingGState { outer in
if dataSource.counter < dataSource.windowSize {
let pos = bounds.minX + (CGFloat(dataSource.counter) * xDiff)
let clipRect = self.bounds.divided(atDistance: pos, from: .maxXEdge).slice
outer.clip(to: clipRect)
}
if let fill = self.primaryFill {
outer.usingGState { ctx in
// Note that when using interpolated curves that the `bounds.maxY` value may NOT be zero as we've
// scaled the curved graph down to reduce curve clipping. The primary fill needs to extend
// down to the _full_ height of the graph or else we end up with a non-filled section at the lower
// part of the graph.
let clipper = path.mutableCopy()!
clipper.addLine(to: CGPoint(x: bounds.maxX, y: points.last!.y))
clipper.addLine(to: CGPoint(x: bounds.maxX, y: self.bounds.maxY))
clipper.addLine(to: CGPoint(x: bounds.minX, y: self.bounds.maxY))
clipper.addLine(to: CGPoint(x: bounds.minX, y: points.first!.y))
clipper.closeSubpath()
ctx.addPath(clipper)
ctx.clip()
fill.fill(context: ctx, bounds: self.bounds)
}
}
if let strokeColor = self.primaryStrokeColor {
outer.usingGState { ctx in
ctx.addPath(path)
ctx.setStrokeColor(strokeColor)
ctx.setLineWidth(self.strokeWidth)
if let shadow = self.shadow {
ctx.setShadow(shadow)
}
ctx.setLineJoin(.round)
ctx.setLineCap(.round)
ctx.strokePath()
}
}
/// Draw the markers
if !markersBounds.isEmpty {
outer.usingGState { ctx in
// For the standard line, all values are 'positive'
let markers = zip(dataSource.data, markersBounds).map { Marker(value: $0.0, rect: $0.1, isPositiveValue: true) }
self.actualMarkerDrawingBlock(ctx, markers)
}
}
}
}
func drawCenteredGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource,
dataSource.counter != 0 else
{
// There's no line if there's either no data or just a single point
// https://github.com/dagronf/DSFSparkline/issues/3#issuecomment-770324047
return
}
// Map the graph points within the updated bounds
let normy = dataSource.normalized
let xDiff = bounds.width / CGFloat(normy.count - 1)
let points = normy.enumerated().map {
CGPoint(x: CGFloat($0.offset) * xDiff + bounds.minX,
y: bounds.height + bounds.minY - ($0.element * bounds.height))
}
// Calculate the graph centroid
let frac = dataSource.fractionalPosition(for: dataSource.zeroLineValue)
let centroid = bounds.height - (frac * bounds.height) + bounds.minY
var markers: [Marker] = []
if self.markerSize > 0 {
points.enumerated().forEach { point in
let w = self.markerSize / 2
let r = CGRect(x: point.element.x - w, y: point.element.y - w,
width: self.markerSize, height: self.markerSize)
markers.append(
Marker(
value: dataSource.data[point.offset],
rect: r,
isPositiveValue: !dataSource.valueAtOffsetIsBelowZeroline(point.offset)))
}
}
let path = CGPath.pathWithPoints(points, smoothed: self.interpolated).mutableCopy()!
for which in 0 ... 1 {
// If the data source doesn't have enough data to fill the graph, then clip to the last x value
if dataSource.counter < dataSource.windowSize {
let pos = CGFloat(dataSource.counter) * xDiff
let clipRect = self.bounds.divided(atDistance: pos, from: .maxXEdge).slice
context.clip(to: clipRect)
}
context.usingGState { inner in
// We want to clip the drawing to the _full_ Y range, so that if an interpolated line graph is
// scaled to avoid clipping we don't end up with blank spaces at the top and bottom of the graph.
let split = self.bounds.divided(atDistance: centroid, from: .minYEdge)
let activeRegion = (which == 0) ? split.slice : split.remainder
inner.clip(to: activeRegion)
// Fill color
let fillItem = (which == 0) ? self.primaryFill : self.secondaryFill
if let fill = fillItem {
inner.usingGState { ctx in
// Note that when using interpolated curves that the `bounds.maxY` value may NOT be zero as we've
// scaled the curved graph down to reduce curve clipping. The primary fill needs to extend
// down to the _full_ height of the graph or else we end up with a non-filled section at the lower
// part of the graph.
let altY = which == 0 ? self.bounds.maxY : self.bounds.minY
let clipper = path.mutableCopy()!
clipper.addLine(to: CGPoint(x: bounds.maxX, y: points.last!.y))
clipper.addLine(to: CGPoint(x: bounds.maxX, y: altY))
clipper.addLine(to: CGPoint(x: bounds.minX, y: altY))
clipper.addLine(to: CGPoint(x: bounds.minX, y: points.first!.y))
clipper.closeSubpath()
ctx.addPath(clipper)
ctx.clip()
fill.fill(context: ctx, bounds: activeRegion)
}
}
let whichColor = (which == 0) ? self.primaryStrokeColor : self.secondaryStrokeColor
if let stroke = whichColor {
inner.usingGState { ctx in
ctx.addPath(path)
ctx.setStrokeColor(stroke)
ctx.setLineWidth(self.strokeWidth)
if let shadow = self.shadow {
ctx.setShadow(shadow)
}
ctx.setLineJoin(.round)
ctx.setLineCap(.round)
ctx.strokePath()
}
}
}
}
// Draw the markers
if self.markerSize > 0 {
context.usingGState { ctx in
self.actualMarkerDrawingBlock(ctx, markers)
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+PercentBar.swift
================================================
//
// DSFSparklineOverlay+PercentBar.swift
// DSFSparklines
//
// Created by Darren Ford on 16/3/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
import SwiftUI
public extension DSFSparkline {
@objc class PercentBar: NSObject {
/// Percent Bar style class. Assigned values should be in the range 0 ... 1
@objc(DSFSparklinePercentBarStyle) public class Style: NSObject {
// MARK: Public
/// The corner radius for the bar/underbar
@objc public var cornerRadius: CGFloat = 4
/// The graph background color
@objc public var underBarColor = CGColor.DefaultWhite
/// The color of text when drawn on the background
@objc public var underBarTextColor = CGColor.DefaultBlack
/// The color of the value bar
@objc public var barColor = CGColor.DefaultBlack
/// The color of text when drawn on top of the bar
@objc public var barTextColor = CGColor.DefaultWhite
/// The formatter to use
@objc public var numberFormatter = Style.DefaultFormatter
/// The font to use
@objc public var font = DSFFont.systemFont(ofSize: 12.0)
/// How much to inset the value bar from the bounds of the control
@objc public var barEdgeInsets = DSFEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
/// Should we display a text percentage value on the bar?
@objc public var showLabel: Bool = true
@objc public func label(for value: CGFloat) -> String {
return self.numberFormatter.string(from: NSNumber(value: Double(value))) ?? ""
}
// MARK: Private
fileprivate static let DefaultFormatter: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .percent
f.minimumFractionDigits = 0
f.maximumFractionDigits = 0
return f
}()
// Convenience
fileprivate var fontSize: CGFloat { return self.font.pointSize }
}
}
}
public extension DSFSparklineOverlay {
/// A percent bar sparkline
@objc(DSFSparklineOverlayPercentBar) class PercentBar: DSFSparklineOverlay {
/// The value assigned to the percent bar. A value between 0.0 and 1.0
@objc public var value: CGFloat = 0.0 {
didSet {
self.valueDidChange()
}
}
/// The value that is displayed in the control. This is the clamped version of `value`
@objc public private(set) var displayValue: CGFloat = 0.0
/// The style to apply to the percent bar
@objc public var displayStyle = DSFSparkline.PercentBar.Style() {
didSet {
self.displayStyleDidChange()
}
}
/// Animation style
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil
internal var animated: Bool { animationStyle != nil }
/// Creator
@objc public init(style: DSFSparkline.PercentBar.Style = DSFSparkline.PercentBar.Style(), value: CGFloat) {
super.init()
self.addSublayer(self.fractionLayer)
self.addSublayer(self.textLayer)
self.cornerRadius = style.cornerRadius
self.fractionLayer.zPosition = -3
self.fractionLayer.cornerRadius = self.displayStyle.cornerRadius
self.textLayer.contentsScale = 2
self.displayStyle = style
self.displayStyleDidChange()
self.value = value
self.valueDidChange()
self.setNeedsLayout()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.value = orig.value
self.displayValue = orig.displayValue
super.init(layer: layer)
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override internal func drawGraph(context _: CGContext, bounds: CGRect, scale _: CGFloat) {
// Do nothing. All the content is handled by layers
return
}
// MARK: Layers
private let textLayer = LCTextLayer() // Text layer
private let fractionLayer = CAShapeLayer() // Bar layer
// MARK: Value helpers
private var previousValue: CGFloat = 0.0 // The previously assigned fractional value (for animation)
// MARK: Animation
private var animator = ArbitraryAnimator() //
private var fractionComplete: CGFloat = 0
}
}
extension DSFSparklineOverlay.PercentBar {
override public func layoutSublayers() {
super.layoutSublayers()
self.layoutGraph()
}
private func layoutGraph() {
CATransaction.withDisabledActions {
let diff = self.displayValue - self.previousValue
let fDiff = diff * self.fractionComplete
let complete = fDiff + self.previousValue
let nb = bounds.inset(by: self.displayStyle.barEdgeInsets)
let divide = nb.divided(atDistance: complete * bounds.width,
from: .minXEdge)
let setRect = divide.slice
self.fractionLayer.frame = setRect
if self.displayStyle.showLabel {
let textSz = self.textLayer.textBounds(
for: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)).width
let leftRect = setRect.insetBy(dx: 3, dy: 0)
if textSz > leftRect.width {
self.textLayer.frame = divide.remainder.insetBy(dx: 3, dy: 0)
self.textLayer.alignmentMode = .left
self.textLayer.foregroundColor = self.displayStyle.underBarTextColor
}
else {
self.textLayer.frame = leftRect
self.textLayer.alignmentMode = .right
self.textLayer.foregroundColor = self.displayStyle.barTextColor
}
}
}
}
}
private extension DSFSparklineOverlay.PercentBar {
func valueDidChange() {
// Store the previous value for our animation
self.previousValue = self.displayValue
// Make sure our internal fraction value is always 0 ... 1
self.displayValue = self.value.clamped(to: 0 ... 1)
// Recreate the text for the label
self.updateLabel()
// And notify that data changed
self.dataDidChange()
}
func updateLabel() {
let label = self.displayStyle.label(for: self.displayValue)
CATransaction.withDisabledActions {
self.textLayer.string = label
}
}
func dataDidChange() {
if self.animated {
self.startAnimateIn()
}
else {
self.fractionComplete = 1.0
self.setNeedsDisplay()
}
}
func startAnimateIn() {
guard let anim = self.animationStyle else { fatalError() }
// Stop any animation that is currently active
self.animator.progressBlock = nil
self.animator.stop()
self.fractionComplete = 0
self.animator.animationFunction = anim.function.function
self.animator.duration = anim.duration
self.animator.progressBlock = { [weak self] progress in
self?.fractionComplete = CGFloat(progress)
self?.setNeedsLayout()
}
self.animator.start()
}
func displayStyleDidChange() {
CATransaction.withDisabledActions {
self.cornerRadius = self.displayStyle.cornerRadius
self.backgroundColor = self.displayStyle.underBarColor
self.fractionLayer.backgroundColor = self.displayStyle.barColor
self.fractionLayer.cornerRadius = self.displayStyle.cornerRadius
self.textLayer.isHidden = !self.displayStyle.showLabel
self.textLayer.font = CTFontCreateWithFontDescriptor(self.displayStyle.font.fontDescriptor, 0, nil)
self.textLayer.fontSize = self.displayStyle.fontSize
}
self.setNeedsDisplay()
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Pie.swift
================================================
//
// DSFSparklineOverlay+Pie.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// A pie graph
@objc(DSFSparklineOverlayPie) class Pie: DSFSparklineOverlay.StaticDataSource {
/// The palette to use when drawing the pie chart
@objc public var palette = DSFSparkline.Palette.shared {
didSet {
self.setNeedsDisplay()
}
}
/// The stroke color for the pie chart
@objc public var strokeColor: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// The width of the stroke line
@objc public var lineWidth: CGFloat = 0.5 {
didSet {
self.setNeedsDisplay()
}
}
/// Should the pie chart animate in?
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil
// Should we animate?
private var animated: Bool { animationStyle != nil }
/// Create
@objc public override init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.palette = orig.palette.copyPalette()
self.strokeColor = orig.strokeColor?.copy()
self.lineWidth = orig.lineWidth
self.animationStyle = orig.animationStyle
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func staticDataSourceDidChange() {
super.staticDataSourceDidChange()
if self.animated {
self.startAnimateIn()
}
else {
self.fractionComplete = 1.0
self.setNeedsDisplay()
}
}
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawPieGraph(context: context, bounds: bounds, scale: scale)
}
// MARK: - Privates
internal var animator = ArbitraryAnimator()
internal var fractionComplete: CGFloat = 0
}
}
private extension DSFSparklineOverlay.Pie {
func startAnimateIn() {
guard let anim = self.animationStyle else { fatalError() }
// Stop any animation that is currently active
self.animator.stop()
self.fractionComplete = 0
self.animator.animationFunction = anim.function.function
self.animator.duration = anim.duration
self.animator.progressBlock = { [weak self] progress in
self?.fractionComplete = CGFloat(progress)
self?.setNeedsDisplay()
}
self.animator.start()
}
}
private extension DSFSparklineOverlay.Pie {
func drawPieGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
if fractionComplete == 0 {
return
}
let rect = bounds.insetBy(dx: 1, dy: 1)
// radius is the half the frame's width or height (whichever is smallest)
let radius = min(rect.width, rect.height) * 0.5
// center of the view
let viewCenter = CGPoint(x: rect.midX, y: rect.midY)
// the starting angle is -90 degrees (top of the circle, as the context is flipped). By default, 0 is the right hand side of the circle, with the positive angle being in an anti-clockwise direction (same as a unit circle in maths).
var startAngle = -CGFloat.pi * 0.5
for segment in self.dataSource.values.enumerated() { // loop through the values array
context.usingGState { state in
// set fill color to the segment color
state.setFillColor(self.palette.cgColorAtOffset(segment.offset))
// update the end angle of the segment
let fraEndAngle = startAngle + (2 * .pi * (segment.element / self.dataSource.total)) * fractionComplete
// move to the center of the pie chart
state.move(to: viewCenter)
// add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
state.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: fraEndAngle, clockwise: false)
state.drawPath(using: .fill)
// update starting angle of the next segment to the ending angle of this segment
startAngle = fraEndAngle // endAngle
}
}
// We draw the strokes AFTER we draw ALL the segment fills to avoid unpleasant rendering
if let stroke = self.strokeColor {
var startAngle = -CGFloat.pi * 0.5
context.setStrokeColor(stroke)
context.setLineWidth(self.lineWidth)
for segment in self.dataSource.values.enumerated() { // loop through the values array
context.usingGState { state in
// update the end angle of the segment
let fraEndAngle = startAngle + (2 * .pi * (segment.element / self.dataSource.total)) * fractionComplete
// move to the center of the pie chart
state.move(to: viewCenter)
// add arc from the center for each segment (anticlockwise is specified for the arc, but as the view flips the context, it will produce a clockwise arc)
state.addArc(center: viewCenter, radius: radius, startAngle: startAngle, endAngle: fraEndAngle, clockwise: false)
state.drawPath(using: .stroke)
// update starting angle of the next segment to the ending angle of this segment
startAngle = fraEndAngle // endAngle
}
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Stackline.swift
================================================
//
// Copyright © 2025 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A stackline graph
@objc(DSFSparklineOverlayStackline) class Stackline: Centerable {
/// The width for the line drawn on the graph
@objc public var strokeWidth: CGFloat = 1.5 {
didSet {
self.setNeedsDisplay()
}
}
/// Interpolate a curve between the points
@objc public var barSpacing: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// Draw a shadow under the line
@objc public var shadow: NSShadow? {
didSet {
self.setNeedsDisplay()
}
}
@objc public override init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.strokeWidth = orig.strokeWidth
self.barSpacing = orig.barSpacing
self.shadow = orig.shadow?.copy() as? NSShadow
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
if self.centeredAtZeroLine {
self.drawCenteredStackLineGraph(context: context, bounds: bounds, scale: scale)
}
else {
self.drawStackLineGraph(context: context, bounds: bounds, scale: scale)
}
}
}
}
extension DSFSparklineOverlay.Stackline {
private func drawStackLineGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let integralRect = bounds.integral
let integralHeight: CGFloat = integralRect.height
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth = Int(integralRect.width) / Int(dataSource.windowSize)
// The left offset in order to center X
let xOffset: Int = (Int(bounds.width) - (componentWidth * Int(dataSource.windowSize))) / 2
// The available height range
let range: ClosedRange = 1 ... max(1, integralRect.maxY)
// The normalized (0 -> 1) data points
let normalized = dataSource.normalized
let points: [CGPoint] = normalized.enumerated().map {
let xVal = xOffset + ($0.offset * componentWidth)
let yVal = (integralHeight - ($0.element * integralHeight)).clamped(to: range)
return CGPoint(x: xVal, y: Int(yVal))
}
let ordered = points.enumerated()
let emptyBuckets = Int(dataSource.emptyValueCount)
let available = Array(ordered.dropFirst(emptyBuckets))
if available.count == 0 {
// Nothing to do!
return
}
context.usingGState { outer in
outer.setRenderingIntent(.relativeColorimetric)
outer.interpolationQuality = .none
outer.setShouldAntialias(false)
let linePath = CGMutablePath()
for point in available {
let currentPoint = point.element
if linePath.isEmpty {
// First point
linePath.move(to: currentPoint)
linePath.addLine(to: currentPoint.offsettingX(by: CGFloat(componentWidth)))
}
else {
linePath.addLine(to: currentPoint)
linePath.addLine(to: point.element.offsettingX(by: CGFloat(componentWidth)))
}
}
if linePath.isEmpty {
return
}
outer.usingGState { fillCtx in
let fillPath = linePath.mutableCopy()!
let bounds = fillPath.boundingBox
fillPath.addLine(to: CGPoint(x: bounds.maxX, y: integralRect.maxY))
fillPath.addLine(to: CGPoint(x: bounds.minX, y: integralRect.maxY))
fillPath.closeSubpath()
fillCtx.addPath(fillPath)
fillCtx.clip()
self.primaryFill?.fill(context: fillCtx, bounds: integralRect)
}
if let stroke = self.primaryStrokeColor {
outer.addPath(linePath)
outer.setStrokeColor(stroke)
outer.setLineWidth(self.strokeWidth)
if let shadow = self.shadow {
outer.setShadow(shadow)
}
outer.strokePath()
}
}
}
private func drawCenteredStackLineGraph(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let integralRect = bounds.integral
let integralHeight: CGFloat = integralRect.height
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth = Int(integralRect.width) / Int(dataSource.windowSize)
// The left offset in order to center X
let xOffset: Int = (Int(bounds.width) - (componentWidth * Int(dataSource.windowSize))) / 2
// The available height range
let range: ClosedRange = 1 ... max(1, integralRect.maxY)
// The normalized (0 -> 1) data points
let normalized = dataSource.normalized
let points: [CGPoint] = normalized.enumerated().map {
let xVal = xOffset + ($0.offset * componentWidth)
let yVal = (integralHeight - ($0.element * integralHeight)).clamped(to: range)
return CGPoint(x: xVal, y: Int(yVal))
}
let ordered = points.enumerated()
let emptyBuckets = Int(dataSource.emptyValueCount)
let available = Array(ordered.dropFirst(emptyBuckets))
if available.count == 0 {
// Nothing to do!
return
}
let centroid = (1 - dataSource.normalizedZeroLineValue) * (integralHeight - 1)
let first = available.first!
if first.offset != 0 {
var clip = bounds
clip.origin.x = first.element.x
clip.size.width -= first.element.x
context.clip(to: clip)
}
context.usingGState { outer in
outer.setRenderingIntent(.relativeColorimetric)
outer.interpolationQuality = .none
outer.setShouldAntialias(false)
let linePath = CGMutablePath()
linePath.move(to: CGPoint(x: integralRect.minX - 1, y: centroid))
linePath.addLine(to: CGPoint(x: integralRect.minX - 1, y: available.first!.element.y))
for point in available {
let currentPoint = point.element
linePath.addLine(to: currentPoint)
linePath.addLine(to: point.element.offsettingX(by: CGFloat(componentWidth)))
}
linePath.addLine(to: CGPoint(x: integralRect.maxX + 1, y: available.last!.element.y))
linePath.addLine(to: CGPoint(x: integralRect.maxX + 1, y: centroid))
if linePath.isEmpty {
return
}
let outerClip = integralRect.insetBy(dx: CGFloat(xOffset), dy: 0)
outer.clip(to: outerClip)
outer.usingGState { clipped in
let split = outerClip.divided(atDistance: centroid, from: .minYEdge)
for which in 0 ... 1 {
clipped.usingGState { inner in
let whichRegion = (which == 0) ? split.slice : split.remainder
inner.clip(to: whichRegion)
let fillItem = (which == 0) ? self.primaryFill : self.secondaryFill
if let fill = fillItem {
inner.usingGState { fillCtx in
fillCtx.addPath(linePath)
fillCtx.clip()
fill.fill(context: fillCtx, bounds: whichRegion)
}
}
let whichStroke = (which == 0) ? self.primaryStrokeColor : self.secondaryStrokeColor
if let strokeColor = whichStroke {
inner.addPath(linePath)
inner.setStrokeColor(strokeColor)
inner.setLineWidth(self.strokeWidth)
if let shadow = self.shadow {
inner.setShadow(shadow)
}
inner.strokePath()
}
}
}
}
}
return
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Stripes.swift
================================================
//
// DSFSparklineOverlay+Stripes.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
@objc(DSFSparklineOverlayStripes) class Stripes: DSFSparklineOverlay.DataSource {
// A default gradient pattern
static let defaultGradient = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: DSFColor.systemRed.cgColor, location: 0),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemOrange.cgColor, location: 1 / 5),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemYellow.cgColor, location: 2 / 5),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemGreen.cgColor, location: 3 / 5),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemBlue.cgColor, location: 4 / 5),
DSFSparkline.GradientBucket.Post(color: DSFColor.systemPurple.cgColor, location: 5 / 5),
])
/// The width of the stroke for the tablet
@objc public var integral: Bool = true {
didSet {
self.setNeedsDisplay()
}
}
/// The spacing (in pixels) between each bar
@objc public var barSpacing: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// The color gradient to use when rendering.
///
/// Note that transparent gradients display strangely and not as I would expect them to.
/// Stick with solid colors in your gradient for the current time.
@objc public var gradient: DSFSparkline.GradientBucket = Stripes.defaultGradient {
didSet {
self.setNeedsDisplay()
}
}
@objc public init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.integral = orig.integral
self.barSpacing = orig.barSpacing
self.gradient = orig.gradient.copyGradientBucket()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
if self.integral {
self.drawStripeGraph(context: context, bounds: bounds, scale: scale)
}
else {
self.drawStripeGraphFloat(context: context, bounds: bounds, scale: scale)
}
}
}
}
private extension DSFSparklineOverlay.Stripes {
func drawStripeGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let integralRect = bounds.integral
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth = Int(integralRect.width) / Int(dataSource.windowSize)
// The width of the BAR component
let barWidth = componentWidth - Int(barSpacing)
// The left offset in order to center X
let xOffset: Int = Int(self.bounds.minX) + (Int(bounds.width) - (componentWidth * Int(dataSource.windowSize))) / 2
let normalizedPoints = dataSource.normalized
context.usingGState { outer in
outer.setRenderingIntent(.perceptual)
outer.interpolationQuality = .default
outer.setShouldAntialias(false)
if dataSource.counter < dataSource.windowSize {
let pos = xOffset + (Int(dataSource.counter) * componentWidth)
let clipRect = bounds.divided(atDistance: CGFloat(pos), from: .maxXEdge).slice
outer.clip(to: clipRect)
}
for value in normalizedPoints.enumerated() {
outer.usingGState { inner in
let color = gradient.color(at: value.element)
inner.setFillColor(color)
let r = CGRect(x: CGFloat(xOffset + value.offset * componentWidth),
y: integralRect.minX,
width: CGFloat(barWidth) + (1.0 / scale),
height: integralRect.height)
inner.fill(r)
}
}
}
}
private func drawStripeGraphFloat(context: CGContext, bounds: CGRect, scale _: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let drawRect = bounds
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth = drawRect.width / CGFloat(dataSource.windowSize)
// The width of the BAR component
let barWidth = componentWidth - CGFloat(barSpacing)
let normalizedPoints = dataSource.normalized
context.usingGState { outer in
if dataSource.counter < dataSource.windowSize {
let pos = CGFloat(dataSource.counter) * componentWidth
let clipRect = bounds.divided(atDistance: CGFloat(pos), from: .maxXEdge).slice
outer.clip(to: clipRect)
}
for value in normalizedPoints.enumerated() {
outer.usingGState { inner in
let color = gradient.color(at: value.element)
inner.setFillColor(color)
let r = CGRect(
x: bounds.minX + CGFloat(value.offset) * componentWidth - (barSpacing == 0 ? 0.5 : 0),
y: drawRect.minY,
width: barWidth + (barSpacing == 0 ? 0.5 : 0),
height: drawRect.height
)
inner.addRect(r)
inner.drawPath(using: .fill)
}
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+Tablet.swift
================================================
//
// DSFSparklineOverlay+Tablet.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
/// A tablet-style graph.
@objc(DSFSparklineOverlayTablet) class Tablet: DSFSparklineOverlay.DataSource {
static let greenStroke = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1])!
static let redStroke = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1])!
static let greenFill = DSFSparkline.Fill.Color(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 0.3])!)
static let redFill = DSFSparkline.Fill.Color(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 0.3])!)
/// The width of the stroke for the tablet
@objc public var lineWidth: CGFloat = 1.0 {
didSet {
self.setNeedsDisplay()
}
}
/// The spacing (in pixels) between each tablet
@objc public var tabletSpacing: CGFloat = 1.0 {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the win tablets
@objc public var winStrokeColor: CGColor = Tablet.greenStroke {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'win' boxes
@objc public var winFill: DSFSparklineFillable? = Tablet.greenFill {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the win tablets
@objc public var lossStrokeColor: CGColor = Tablet.redStroke {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'win' boxes
@objc public var lossFill: DSFSparklineFillable? {
didSet {
self.setNeedsDisplay()
}
}
@objc public init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.lineWidth = orig.lineWidth
self.tabletSpacing = orig.tabletSpacing
self.winStrokeColor = orig.winStrokeColor
self.winFill = orig.winFill?.copyFill()
self.lossStrokeColor = orig.lossStrokeColor
self.lossFill = orig.lossFill?.copyFill()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawTabletGraph(context: context, bounds: bounds, scale: scale)
}
}
}
private extension DSFSparklineOverlay.Tablet {
func drawTabletGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let integralRect = bounds.insetBy(dx: 0, dy: 1)
let windowSize = CGFloat(dataSource.windowSize)
// The amount of space left in the rect once we've removed the bar spacing for all elements
let w = integralRect.width - (windowSize * (self.tabletSpacing + self.lineWidth)) - 2*self.lineWidth
// The size of a circle
let circleSize = min(w / CGFloat(windowSize), integralRect.height)
// This represents the _full_ width of a circle, including the spacing.
let componentWidth = circleSize + self.tabletSpacing + self.lineWidth
// The left offset in order to center X
let xOffset: CGFloat = (integralRect.width - (componentWidth * windowSize)) / 2
// Map the +ve values to true, the -ve (and 0) to false
let winLoss: [Int] = dataSource.data.map {
if $0 > 0 { return 1 }
return -1
}
let midPoint = bounds.midY
context.usingGState { outer in
if dataSource.counter < dataSource.windowSize {
let pos = CGFloat(dataSource.counter) * componentWidth
let clipRect = integralRect.divided(atDistance: CGFloat(pos + xOffset + CGFloat(self.tabletSpacing / 2)), from: .maxXEdge).slice
outer.clip(to: clipRect.integral)
}
let winPath = CGMutablePath()
let lossPath = CGMutablePath()
for point in winLoss.enumerated() {
let x = xOffset + CGFloat(point.offset) * componentWidth
if point.element == 1 {
let rect = CGRect(x: x, y: midPoint - (circleSize / 2), width: circleSize, height: circleSize)
winPath.addEllipse(in: rect.integral)
}
else if point.element == -1 {
let rect = CGRect(x: x, y: midPoint - (circleSize / 2), width: circleSize, height: circleSize)
lossPath.addEllipse(in: rect.integral)
}
}
if !winPath.isEmpty {
outer.usingGState { winState in
if let fill = self.winFill {
winState.usingGState { (fillCtx) in
winState.addPath(winPath)
winState.clip()
fill.fill(context: fillCtx, bounds: integralRect)
}
}
winState.addPath(winPath)
winState.setStrokeColor(self.winStrokeColor)
winState.setLineWidth(self.lineWidth)
winState.drawPath(using: .stroke)
}
}
if !lossPath.isEmpty {
outer.usingGState { lossState in
if let fill = self.lossFill {
lossState.usingGState { (fillCtx) in
lossState.addPath(lossPath)
lossState.clip()
fill.fill(context: fillCtx, bounds: integralRect)
}
}
lossState.addPath(lossPath)
lossState.setStrokeColor(self.lossStrokeColor)
lossState.setLineWidth(self.lineWidth)
lossState.drawPath(using: .stroke)
}
}
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+WinLossTie.swift
================================================
//
// DSFSparklineOverlay+WinLossTie.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import QuartzCore
public extension DSFSparklineOverlay {
@objc(DSFSparklineOverlayWinLossTie) class WinLossTie: DSFSparklineOverlay.DataSource {
static let greenStroke = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 1])!
static let greenFill = DSFSparkline.Fill.Color(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 1, 0, 0.3])!)
static let redStroke = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 1])!
static let redFill = DSFSparkline.Fill.Color(CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 0, 0, 0.3])!)
/// The width of the stroke for the tablet
@objc public var lineWidth: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// The spacing (in pixels) between each bar
@objc public var barSpacing: UInt = 1 {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'win' boxes
@objc public var winStroke: CGColor = WinLossTie.greenStroke {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'win' boxes
@objc public var winFill: DSFSparklineFillable? = WinLossTie.greenFill {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'loss' boxes
@objc public var lossStroke: CGColor = WinLossTie.redStroke {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'loss' boxes
@objc public var lossFill: DSFSparklineFillable? = WinLossTie.redFill {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'tie' boxes
@objc public var tieStroke: CGColor? {
didSet {
self.setNeedsDisplay()
}
}
/// The color to draw the 'tie' boxes
@objc public var tieFill: DSFSparklineFillable? {
didSet {
self.setNeedsDisplay()
}
}
/// The line to separate the win and loss sections of the sparkline
@objc public var centerLine: DSFSparkline.ZeroLineDefinition? {
didSet {
self.setNeedsDisplay()
}
}
@objc public init() {
super.init()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.lineWidth = orig.lineWidth
self.barSpacing = orig.barSpacing
self.winStroke = orig.winStroke
self.winFill = orig.winFill?.copyFill()
self.lossStroke = orig.lossStroke
self.lossFill = orig.lossFill?.copyFill()
self.tieStroke = orig.tieStroke
self.tieFill = orig.tieFill?.copyFill()
self.centerLine = orig.centerLine?.copyZeroLineDefinition()
super.init(layer: layer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
internal override func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
self.drawWinLossGraph(context: context, bounds: bounds, scale: scale)
}
}
}
private extension DSFSparklineOverlay.WinLossTie {
func drawWinLossGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
guard let dataSource = self.dataSource else {
return
}
let integralRect = bounds.integral
let windowSize: Int = Int(dataSource.windowSize)
// This represents the _full_ width of a bar within the graph, including the spacing.
let componentWidth: Int = Int(integralRect.width) / windowSize
// The width of the BAR component
let barWidth: Int = componentWidth - Int(self.barSpacing)
// The left offset in order to center X
let xOffset: Int = (Int(integralRect.width) - (componentWidth * windowSize)) / 2
// Map the +ve values to true, the -ve (and 0) to false
let winLoss: [Int] = dataSource.data.map {
if $0 > 0 { return 1 }
if $0 < 0 { return -1 }
return 0
}
let graphLineWidth: CGFloat = 1 / scale * CGFloat(self.lineWidth)
let midPoint = Int(bounds.midY.rounded())
let barHeight = Int(integralRect.midY) - Int(self.lineWidth)
context.usingGState { outer in
outer.setShouldAntialias(false)
outer.setRenderingIntent(.relativeColorimetric)
outer.interpolationQuality = .none
if let zeroLine = self.centerLine {
outer.usingGState { centerlineState in
let zeroPos = 0.5 * bounds.height
centerlineState.setLineWidth(zeroLine.lineWidth)
centerlineState.setStrokeColor(zeroLine.color.cgColor)
centerlineState.setLineDash(phase: 0.0, lengths: zeroLine.lineDashStyle)
centerlineState.strokeLineSegments(between: [CGPoint(x: 0.0, y: zeroPos), CGPoint(x: bounds.width, y: zeroPos)])
}
}
if dataSource.counter < dataSource.windowSize {
let pos = Int(dataSource.counter) * componentWidth
let clipRect = integralRect.divided(atDistance: CGFloat(pos + xOffset), from: .maxXEdge).slice
outer.clip(to: clipRect.integral)
}
let winPath = CGMutablePath()
let lossPath = CGMutablePath()
let tiePath = CGMutablePath()
for point in winLoss.enumerated() {
let x = xOffset + point.offset * componentWidth
if point.element == 1 {
let rect = CGRect(x: x, y: 1, width: barWidth, height: barHeight)
winPath.addRect(rect.integral)
}
else if point.element == -1 {
let rect = CGRect(x: x, y: midPoint + 1, width: barWidth, height: barHeight)
lossPath.addRect(rect.integral)
}
else {
let rect = CGRect(x: x, y: Int(integralRect.height) / 2 - (barHeight / 4), width: barWidth, height: barHeight / 2)
tiePath.addRect(rect.integral)
}
}
if !winPath.isEmpty {
outer.usingGState { winState in
if let fill = self.winFill {
winState.usingGState { fillCtx in
fillCtx.addPath(winPath)
fillCtx.clip()
fill.fill(context: fillCtx, bounds: integralRect)
}
}
winState.addPath(winPath)
winState.setStrokeColor(self.winStroke)
winState.setLineWidth(graphLineWidth)
winState.drawPath(using: .stroke)
}
}
if !lossPath.isEmpty {
outer.usingGState { lossState in
if let fill = self.lossFill {
lossState.usingGState { fillCtx in
fillCtx.addPath(lossPath)
fillCtx.clip()
fill.fill(context: fillCtx, bounds: integralRect)
}
}
lossState.addPath(lossPath)
lossState.setStrokeColor(self.lossStroke)
lossState.setLineWidth(graphLineWidth)
lossState.drawPath(using: .stroke)
}
}
if !tiePath.isEmpty, self.tieFill != nil || self.tieStroke != nil {
if let fill = self.tieFill {
outer.usingGState { tieState in
tieState.addPath(tiePath)
tieState.clip()
fill.fill(context: tieState, bounds: integralRect)
}
}
if let stroke = self.tieStroke {
outer.usingGState { tieState in
tieState.addPath(tiePath)
tieState.setLineWidth(graphLineWidth)
tieState.setStrokeColor(stroke)
tieState.drawPath(using: .stroke)
}
}
}
}
return
}
}
================================================
FILE: Sources/DSFSparkline/overlay/renderers/overlays-graph/DSFSparklineOverlay+WiperGauge.swift
================================================
//
// DSFSparklineOverlay+WiperGauge.swift
// DSFSparklines
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import QuartzCore
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparklineOverlay {
/// A pie graph
@objc(DSFSparklineOverlayWiperGauge) class WiperGauge: DSFSparklineOverlay.StaticDataSource {
// MARK: - Settings
/// The value to display in the gauge
@objc public var value: CGFloat = 0.0 {
didSet {
let v = max(0, min(1, self.value))
let tra = DSFSparkline.AnimationTransition(start: currentValue__, stop: v)
if isAnimated {
self.animate(tra)
}
else {
self.currentValue__ = v
}
}
}
/// The palette to use when drawing the value part of the gauge
@objc public var valueColor: DSFSparkline.ValueBasedFill = DSFSparkline.ValueBasedFill.sharedPalette {
didSet {
self.updatePalette()
}
}
/// The palette to use when drawing the unset value part of the gauge
@objc public var valueBackgroundColor: CGColor = _valueBackgroundColor {
didSet {
self.updatePalette()
}
}
/// The color to draw the dial and outer border
@objc public var gaugeUpperArcColor: CGColor = _gaugeUpperArcColor {
didSet {
self.updatePalette()
}
}
/// The color to draw the value in radial component
@objc public var gaugePointerColor: CGColor = _gaugePointerColor {
didSet {
self.updatePalette()
}
}
/// The color to draw in the background
@objc public var gaugeBackgroundColor: CGColor? = _gaugeBackgroundColor {
didSet {
self.updatePalette()
}
}
/// Should the pie chart animate in?
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil
private var isAnimated: Bool { self.animationStyle != nil }
// MARK: - Initializers
@objc public override init() {
super.init()
self.configure()
}
public override init(layer: Any) {
guard let orig = layer as? Self else { fatalError() }
self.value = orig.value
self.valueColor = orig.valueColor.copyColorContainer()
self.valueBackgroundColor = orig.valueBackgroundColor.copy() ?? CGColor(gray: 0, alpha: 0)
self.gaugePointerColor = orig.gaugePointerColor.copy() ?? CGColor(gray: 1, alpha: 1)
self.gaugeUpperArcColor = orig.gaugeUpperArcColor.copy() ?? CGColor(gray: 1, alpha: 1)
self.gaugeBackgroundColor = orig.gaugeBackgroundColor?.copy() ?? CGColor(gray: 0, alpha: 0)
self.animationStyle = orig.animationStyle
super.init(layer: layer)
self.configure()
}
@available(*, unavailable)
required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Private
override internal func drawGraph(context: CGContext, bounds: CGRect, scale: CGFloat) {
// Do nothing. All the content is handled by layers
return
}
// Private
private let wiperBackgroundShape = CAShapeLayer()
private let arcShape = CAShapeLayer()
private let arcInnerShape = CAShapeLayer()
private let arcColorInnerShape = CAShapeLayer()
private let pinion = CAShapeLayer()
private let arcline = CAShapeLayer()
private var animator = ArbitraryAnimator()
private var currentValue__: CGFloat = 0.0 {
didSet {
self.updatePalette()
self.updateLayout()
}
}
private static let _gaugeBackgroundColor: CGColor? = nil
#if os(macOS)
private static let _valueBackgroundColor = NSColor.quaternaryLabelColor.cgColor
private static let _gaugeUpperArcColor = NSColor.textColor.cgColor
private static let _gaugePointerColor = NSColor.textColor.cgColor
#else
private static let _valueBackgroundColor = UIColor.quaternaryLabel.cgColor
private static let _gaugeUpperArcColor = UIColor.label.cgColor
private static let _gaugePointerColor = UIColor.label.cgColor
#endif
}
}
extension DSFSparklineOverlay.WiperGauge {
private func configure() {
self.addSublayer(wiperBackgroundShape)
wiperBackgroundShape.zPosition = -110
self.addSublayer(arcShape)
arcShape.zPosition = -100
self.addSublayer(arcInnerShape)
arcInnerShape.zPosition = -90
self.addSublayer(arcColorInnerShape)
arcColorInnerShape.zPosition = -80
self.addSublayer(pinion)
pinion.zPosition = -70
self.addSublayer(arcline)
arcline.zPosition = -80
self.updatePalette()
}
// Update the colors used within the gauge
private func updatePalette() {
// The gauge's background color
wiperBackgroundShape.fillColor = self.gaugeBackgroundColor
if self.valueColor.isPalette == false {
// We want paletted colors to fade when the color changes
CATransaction.setDisableActions(true)
}
// The outer ring
arcShape.fillColor = .clear
arcShape.strokeColor = self.gaugeUpperArcColor
// The color component of the arc
arcColorInnerShape.fillColor = self.valueColor.color(atFraction: self.value)
arcColorInnerShape.strokeColor = .clear
// The background color component of the arc
arcInnerShape.fillColor = self.valueBackgroundColor
arcInnerShape.strokeColor = .clear
// The pointer and the pinion
arcline.strokeColor = self.gaugePointerColor
pinion.fillColor = self.gaugePointerColor
if self.valueColor.isPalette == false {
CATransaction.commit()
}
}
private func updateLayout() {
CATransaction.setDisableActions(true)
defer { CATransaction.commit() }
let bb = self.bounds
if bb.isEmpty { return }
let sx = bb.width
let sy = bb.height
var destWidth: CGFloat = 0
var destHeight: CGFloat = 0
let rr: CGRect = {
if (sx / 2) > sy {
// wider than it is higher
let dx = sy/(sx/2)
destWidth = bb.width * dx
destHeight = bb.height
return CGRect(
x: (bb.width - destWidth) / 2,
y: 0,
width: destWidth,
height: destHeight
)
}
else {
// higher than it is wide
let dy = (sx / 2) / sy
destWidth = bb.width
destHeight = bb.height * dy
let x = (bb.width - destWidth) / 2.0
let y = (bb.height - destHeight) / 2.0
return CGRect(
x: x,
y: y,
width: destWidth,
height: destHeight
)
}
}()
let sz = rr.size.height
let ptsize = sz / 12
let origin = CGPoint(
x: (rr.width / 2) + rr.origin.x,
y: rr.origin.y + ptsize
)
// The gauge's pinion point
let centroidLocation = CGPoint(x: origin.x, y: bb.height - (bb.height - rr.height) / 2 - ptsize)
// Draw the background
do {
let pth = CGMutablePath()
pth.addArc(center: centroidLocation, radius: sz - ptsize, startAngle: .pi, endAngle: .pi * 2, clockwise: false)
let brect = CGRect(
x: centroidLocation.x - (sz - ptsize),
y: centroidLocation.y,
width: (sz - ptsize) * 2,
height: ptsize
)
pth.addRect(brect)
pth.closeSubpath()
wiperBackgroundShape.path = pth
}
// Draw the outer ring
do {
let path = CGMutablePath()
path.addArc(center: centroidLocation, radius: sz - (ptsize * 2), startAngle: .pi + 0.2, endAngle: .pi - .pi - 0.2, clockwise: false)
arcShape.path = path
arcShape.lineWidth = ptsize
arcShape.lineCap = .round
}
let frac: CGFloat = self.currentValue__
let fullSweep: CGFloat = .pi - 0.2 - 0.2
// Draw the inner sweeps
do {
// Color fill
do {
let colorstart: CGFloat = ((1.0 - frac) * fullSweep)
let pth = CGMutablePath()
pth.addArc(center: centroidLocation, radius: ptsize * 2, startAngle: .pi + 0.2, endAngle: .pi * 2 - 0.2 - colorstart, clockwise: false)
pth.addArc(center: centroidLocation, radius: (sz - (ptsize * 2)) / 1.15, startAngle: .pi * 2 - 0.2 - colorstart, endAngle: .pi + 0.2, clockwise: true)
pth.closeSubpath()
arcColorInnerShape.path = pth
}
// Background fill
do {
let colorstart: CGFloat = (frac * fullSweep)
let pth = CGMutablePath()
pth.addArc(center: centroidLocation, radius: ptsize * 2, startAngle: .pi + 0.2 + colorstart, endAngle: .pi * 2 - 0.2, clockwise: false)
pth.addArc(center: centroidLocation, radius: (sz - (ptsize * 2)) / 1.15, startAngle: .pi * 2 - 0.2, endAngle: .pi + 0.2 + colorstart, clockwise: true)
pth.closeSubpath()
arcInnerShape.path = pth
}
}
// Draw the pinon (the circle where the pointer rotates around)
do {
let dialPoint = CGPoint(x: centroidLocation.x - ptsize, y: centroidLocation.y - ptsize)
let pointy = CGRect(origin: dialPoint, size: CGSize(width: ptsize*2, height: ptsize*2))
let path2 = CGPath(ellipseIn: pointy, transform: nil)
pinion.path = path2
}
// Draw the pointer
do {
let pth = CGMutablePath()
pth.move(to: CGPoint(x: 0, y: 0))
pth.addLine(to: CGPoint(x: sz / 1.4, y: 0))
let colorstart = ((1.0 - frac) * fullSweep)
let res = CGMutablePath()
let transform = CGAffineTransform(translationX: centroidLocation.x, y: centroidLocation.y)
.rotated(by: .pi - .pi - 0.2 - colorstart)
res.addPath(pth, transform: transform)
arcline.path = res
arcline.lineWidth = ptsize
arcline.lineCap = .round
}
}
public override func layoutSublayers() {
super.layoutSublayers()
self.updateLayout()
}
}
// MARK: Animation
private extension DSFSparklineOverlay.WiperGauge {
func animate(_ transition: DSFSparkline.AnimationTransition) {
guard let anim = self.animationStyle else { return }
// Stop any animation that is currently active
self.animator.progressBlock = nil
self.animator.stop()
self.animator.animationFunction = anim.function.function
self.animator.duration = anim.duration
self.animator.progressBlock = { [weak self] progress in
self?.currentValue__ = transition.start + (transition.distance * progress)
}
self.animator.start()
}
}
================================================
FILE: Sources/DSFSparkline/overlay/surfaces/DSFSparklineSurface+AttributedString.swift
================================================
//
// DSFSparklineSurface+AttributedString.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// NSAttributedString extensions for DSFSparklineSurface.Bitmap
import CoreGraphics
import Foundation
public extension DSFSparklineSurface.Bitmap {
/// Returns an NSAttributedString containing an image of the sparkline
/// - Parameters:
/// - size: The dimensions of the image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: An NSAttributedString containing the sparkline bitmap, or nil if the bitmap couldn't be generated
@objc(attributedString::) func attributedString(size: CGSize, scale: CGFloat = 1) -> NSAttributedString? {
guard let attachment = self.textAttachment(size: size, scale: scale) else {
return nil
}
return NSAttributedString(attachment: attachment)
}
}
// MARK: - AppKit Additions
#if os(macOS)
import AppKit
public extension DSFSparklineSurface.Bitmap {
/// Returns a TextAttachment containing an image of the sparkline
/// - Parameters:
/// - size: The dimensions of the image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: An NSTextAttachment containing the sparkline bitmap, or nil if the bitmap couldn't be generated
@objc(textAttachment::) func textAttachment(size: CGSize, scale: CGFloat = 1) -> NSTextAttachment? {
guard let image = self.image(size: size, scale: scale) else {
return nil
}
let attachment = NSTextAttachment()
let flipped = NSImage(size: size, flipped: false, drawingHandler: { (rect: NSRect) -> Bool in
image.draw(in: rect)
return true
})
attachment.image = flipped
return attachment
}
}
#else
// MARK: - UIKit Additions
import UIKit
public extension DSFSparklineSurface.Bitmap {
/// Returns an NSTextAttachment containing an image of the sparkline
/// - Parameters:
/// - size: The dimensions of the image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: An NSTextAttachment containing the sparkline bitmap, or nil if the bitmap couldn't be generated
@objc(textAttachment::) func textAttachment(size: CGSize, scale: CGFloat = 1) -> NSTextAttachment? {
guard let image = self.image(size: size, scale: scale) else {
return nil
}
let attachment = NSTextAttachment()
attachment.image = image
attachment.bounds = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)
return attachment
}
}
#endif
================================================
FILE: Sources/DSFSparkline/overlay/surfaces/DSFSparklineSurface+Bitmap.swift
================================================
//
// DSFSparklineSurface+Bitmap.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
public extension DSFSparklineSurface {
/// A surface for drawing a sparkline into an image
@objc(DSFSparklineSurfaceBitmap) class Bitmap: DSFSparklineSurface {
private func edgeInsets(for rect: CGRect) -> DSFEdgeInsets {
/// Calculate the total inset required
return self.overlays.reduce(DSFEdgeInsets.zero) { (result, overlay) in
result.combineMaximum(using: overlay.edgeInsets(for: rect))
}
}
/// Add a sparkline overlay to the surface
@objc public func addOverlay(_ overlay: DSFSparklineOverlay) {
self.overlays.append(overlay)
}
/// Return a CGImage representation of the sparklline
/// - Parameters:
/// - size: The dimension in pixels
/// - scale: The scale to use (eg. retina == 2)
/// - Returns: A CGImage representation, or nil if the image couldn't be generated
@objc public func cgImage(size: CGSize, scale: CGFloat = 2) -> CGImage? {
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
// Create the bitmap context to draw into
guard let bitmapContext = self.generateBitmapContext(rect: rect, scale: scale) else {
return nil
}
self.overlays.forEach { $0.frame = CGRect(origin: .zero, size: size) }
// Calculate the inset required
let bounds = rect.inset(by: self.edgeInsets(for: rect))
// Loop through each overlay and ask it to draw
self.overlays.forEach { overlay in
bitmapContext.usingGState { ctx in
overlay.frame = rect
overlay.setNeedsDisplay()
overlay.contentsScale = scale
#if os(macOS)
overlay.isGeometryFlipped = true
#endif
// Render the layer content
overlay.render(in: ctx)
// Render any bitmap content
overlay.drawGraph(context: ctx, bounds: bounds, scale: scale)
}
}
return bitmapContext.makeImage()
}
// MARK: Private
// The overlays to use when generating the image
private var overlays: [DSFSparklineOverlay] = []
}
}
// MARK: - AppKit Additions
#if os(macOS)
import AppKit
public extension DSFSparklineSurface.Bitmap {
/// Generate an NSImage with the contents of the surface
/// - Parameters:
/// - size: The dimensions of the image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: The created image, or nil if something went wrong
@objc func image(size: CGSize, scale: CGFloat = 1) -> NSImage? {
guard let cgImage = self.cgImage(size: size, scale: scale) else {
return nil
}
return NSImage(cgImage: cgImage, size: size)
}
/// Generate an NSImage with the contents of the surface
/// - Parameters:
/// - width: The width of the resultant image
/// - height: The height of the resultant image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: The created image, or nil if something went wrong
@objc func image(width: CGFloat, height: CGFloat, scale: CGFloat = 1) -> NSImage? {
let size = CGSize(width: width, height: height)
guard let cgImage = self.cgImage(size: size, scale: scale) else {
return nil
}
return NSImage(cgImage: cgImage, size: size)
}
}
#else
// MARK: - UIKit Additions
import UIKit
public extension DSFSparklineSurface.Bitmap {
/// Generate an NSImage with the contents of the surface
/// - Parameters:
/// - size: The dimensions of the image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: The created image, or nil if something went wrong
@objc func image(size: CGSize, scale: CGFloat = 1) -> UIImage? {
guard let cgImage = self.cgImage(size: size, scale: scale) else {
return nil
}
return UIImage(
cgImage: cgImage,
scale: scale,
orientation: UIImage.Orientation.up
)
}
/// Generate an NSImage with the contents of the surface
/// - Parameters:
/// - width: The width of the resultant image
/// - height: The height of the resultant image
/// - scale: The scale for the returned image. For example, for a retina scale (144dpi) image, scale == 2
/// - Returns: The created image, or nil if something went wrong
@objc func image(width: CGFloat, height: CGFloat, scale: CGFloat = 1) -> UIImage? {
guard let cgImage = self.cgImage(size: CGSize(width: width, height: height), scale: scale) else {
return nil
}
return UIImage(
cgImage: cgImage,
scale: scale,
orientation: UIImage.Orientation.up
)
}
}
#endif
// MARK: - Private
private extension DSFSparklineSurface.Bitmap {
// Generate a bitmap context for the specified rect and scale
func generateBitmapContext(rect: CGRect, scale: CGFloat) -> CGContext? {
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard let bitmapContext = CGContext(
data: nil,
width: Int(rect.width * scale),
height: Int(rect.height * scale),
bitsPerComponent: 8,
bytesPerRow: 0,
space: colorSpace,
bitmapInfo: bitmapInfo.rawValue
) else {
Swift.print("(ERROR) DSFSparklineBitmap unable to generate bitmap context for drawing")
return nil
}
// Need to flip
bitmapContext.scaleBy(x: scale, y: -scale)
bitmapContext.translateBy(x: 0, y: -rect.height)
return bitmapContext
}
}
================================================
FILE: Sources/DSFSparkline/overlay/surfaces/DSFSparklineSurface+SwiftUI.swift
================================================
//
// DSFSparklineSurface+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import SwiftUI
public extension DSFSparklineSurface {
/// A surface for creating a sparkline using overlays
struct SwiftUI {
let overlays: [DSFSparklineOverlay]
public init(_ overlays: [DSFSparklineOverlay]) {
self.overlays = overlays
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineSurface.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineSurfaceView
#else
public typealias UIViewType = DSFSparklineSurfaceView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineSurface.SwiftUI
init(_ sparkline: DSFSparklineSurface.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
private func makeSurface(_ context: Context) -> DSFSparklineSurfaceView {
let view = DSFSparklineSurfaceView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
let base = context.coordinator.parent
base.overlays.forEach { view.addOverlay($0) }
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineSurface.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineSurfaceView {
return self.makeSurface(context)
}
func updateUIView(_ view: DSFSparklineSurfaceView, context: Context) {
self.updateView(view, context: context)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineSurface.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineSurfaceView {
return self.makeSurface(context)
}
func updateNSView(_ nsView: DSFSparklineSurfaceView, context: Context) {
self.updateView(nsView, context: context)
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineSurface.SwiftUI {
func updateView(_ view: DSFSparklineSurfaceView, context: Context) {
}
}
================================================
FILE: Sources/DSFSparkline/overlay/surfaces/DSFSparklineSurface+View.swift
================================================
//
// DSFSparklineSurface+View.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import Cocoa
#else
import UIKit
#endif
/// A surface for drawing a sparkline into a view.
///
/// Represents the generic base class for a view.
@objc public class DSFSparklineSurfaceView: DSFView {
#if os(macOS)
override public var isFlipped: Bool {
return true
}
#endif
// Render delegate instance
lazy private var renderDelegate: RendererDelegate = {
return RendererDelegate(view: self)
}()
var rootLayer: CALayer {
#if os(macOS)
return self.layer!
#else
return self.layer
#endif
}
deinit {}
override public init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.setup()
}
private func setup() {
#if os(macOS)
self.wantsLayer = true
#else
// Configure iOS/tvOS to make the background transparent.
// If isOpaque is true (the default value) iOS assumes that you're drawing
// the ENTIRE content of the control (which we are not).
self.isOpaque = false
#endif
}
/// Multi-platform function for telling the view to update itself
public func updateDisplay() {
#if os(macOS)
self.needsDisplay = true
#else
self.setNeedsDisplay()
#endif
}
}
extension DSFSparklineSurfaceView {
var overlays: [DSFSparklineOverlay] {
return self.rootLayer.sublayers?.compactMap { $0 as? DSFSparklineOverlay } ?? []
}
/// Add a sparkline overlay to the view
public func addOverlay(_ overlay: DSFSparklineOverlay) {
self.rootLayer.addSublayer(overlay)
overlay.bounds = self.bounds
overlay.delegate = self.renderDelegate
self.syncLayers()
overlay.setNeedsLayout()
overlay.setNeedsDisplay()
}
/// Remove a sparkline overlay to the view
public func removeOverlay(_ overlay: DSFSparklineOverlay) {
overlay.removeFromSuperlayer()
}
func edgeInsets(for rect: CGRect) -> DSFEdgeInsets {
/// Calculate the total inset required
return self.overlays.reduce(DSFEdgeInsets.zero) { (result, overlay) in
result.combineMaximum(using: overlay.edgeInsets(for: rect))
}
}
private func syncLayers() {
CATransaction.withDisabledActions {
self.rootLayer.sublayers?.forEach { layer in
layer.bounds = self.bounds
layer.contentsScale = self.retinaScale()
layer.setNeedsDisplay()
}
}
}
#if os(macOS)
public override func layout() {
super.layout()
self.syncLayers()
}
public override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
self.syncLayers()
}
#else
public override func layoutSubviews() {
super.layoutSubviews()
self.syncLayers()
}
public override func didMoveToWindow() {
super.didMoveToWindow()
self.syncLayers()
}
#endif
}
// the draw delegate for the overlay layers
fileprivate class RendererDelegate: NSObject, CALayerDelegate {
let view: DSFSparklineSurfaceView
init(view: DSFSparklineSurfaceView) {
self.view = view
super.init()
}
func draw(_ layer: CALayer, in ctx: CGContext) {
if let l = layer as? DSFSparklineOverlay {
let scale = view.retinaScale()
l.contentsScale = scale
let insetBounds = view.edgeInsets(for: view.bounds)
l.drawGraph(context: ctx, bounds: view.bounds.inset(by: insetBounds), scale: scale)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/surfaces/DSFSparklineSurface.swift
================================================
//
// DSFSparklineSurface.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
@objc public class DSFSparklineSurface: NSObject {
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+ActivityGridDefinition.swift
================================================
//
// DSFSparkline+ActivityGridDefinition.swift
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
class ActivityGrid {
private init() { }
}
}
public extension DSFSparkline.ActivityGrid {
/// The drawing style for the activity grid
@objc(DSFSparklineActivityGridLayoutStyle)
enum LayoutStyle: Int {
/// 'Newest' value at bottom right, works up then left
case github = 0
/// 'Newest' value at top left, works right then down
case defrag = 1
}
}
public extension DSFSparkline.ActivityGrid {
/// The style for drawing cells in the activity grid
@objc(DSFSparklineActivityGridCellStyle)
class CellStyle: NSObject {
@objc public let fillScheme: DSFSparkline.ValueBasedFill
@objc public let borderColor: CGColor?
@objc public let borderWidth: CGFloat
@objc public let cellDimension: CGFloat
@objc public let cellSpacing: CGFloat
@objc public let cornerRadius: CGFloat
@objc public init(
fillScheme: DSFSparkline.ValueBasedFill,
borderColor: CGColor? = nil,
borderWidth: CGFloat = 1.0,
cellDimension: CGFloat = 11.0,
cellSpacing: CGFloat = 2.5,
cornerRadius: CGFloat = 2.5
) {
self.fillScheme = fillScheme
self.borderColor = borderColor
self.borderWidth = borderWidth
self.cellDimension = cellDimension
self.cellSpacing = cellSpacing
self.cornerRadius = cornerRadius
super.init()
}
/// Default style - dark github
@objc public convenience override init() {
self.init(fillScheme: CellStyle.DefaultDark)
}
/// Return a copy of this cell style changing the specified attribute values
public func modify(
fillScheme: DSFSparkline.ValueBasedFill? = nil,
borderColor: CGColor? = nil,
borderWidth: CGFloat? = nil,
cellDimension: CGFloat? = nil,
cellSpacing: CGFloat? = nil,
cornerRadius: CGFloat? = nil
) -> CellStyle {
let fs = fillScheme ?? self.fillScheme
let bc = borderColor ?? self.borderColor
let bw = borderWidth ?? self.borderWidth
let cd = cellDimension ?? self.cellDimension
let cs = cellSpacing ?? self.cellSpacing
let cr = cornerRadius ?? self.cornerRadius
return CellStyle(
fillScheme: fs,
borderColor: bc,
borderWidth: bw,
cellDimension: cd,
cellSpacing: cs,
cornerRadius: cr
)
}
/// A default palette used when no palette is specified.
public static let DefaultLight = DSFSparkline.ValueBasedFill(colors: [
DSFColor(red: 0.820, green: 0.830, blue: 0.842, alpha: 1.000),
DSFColor(red: 0.606, green: 0.914, blue: 0.657, alpha: 1.000),
DSFColor(red: 0.248, green: 0.768, blue: 0.387, alpha: 1.000),
DSFColor(red: 0.190, green: 0.633, blue: 0.306, alpha: 1.000),
DSFColor(red: 0.132, green: 0.432, blue: 0.222, alpha: 1.000),
])
public static let DefaultDark = DSFSparkline.ValueBasedFill(colors: [
DSFColor(red: 0.086, green: 0.106, blue: 0.132, alpha: 1.000),
DSFColor(red: 0.055, green: 0.269, blue: 0.159, alpha: 1.000),
DSFColor(red: 0.000, green: 0.429, blue: 0.194, alpha: 1.000),
DSFColor(red: 0.148, green: 0.649, blue: 0.257, alpha: 1.000),
DSFColor(red: 0.219, green: 0.829, blue: 0.323, alpha: 1.000),
])
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+GradientBucket.swift
================================================
//
// DSFSparkline+GradientBucket.swift
// DSFSparklines
//
// Created by Darren Ford on 15/2/2021.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
public extension DSFSparkline {
/// A class that represents buckets of color within a gradient within the RGB colorspace.
///
/// Defines a smooth transition between colors.
///
/// **Buckets**
/// A gradient object can also be 'bucketed', so that rather than a smooth transition the gradient output is
/// broken up into equal buckets containing a color
@objc(DSFGradientBucket) class GradientBucket: NSObject {
static let rgbSpace = CGColorSpaceCreateDeviceRGB()
static let EmptyColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])!
/// The number of buckets to create. If 0 or 1, the gradient is smooth.
@objc public var bucketCount: UInt = 0 {
didSet {
self.buildBuckets()
}
}
private func sorted(_ posts: [Post]) -> [Post] {
return posts.sorted(by: { (a, b) -> Bool in
a.location < b.location
})
}
// The posts sorted in order of their location 0.0 -> 1.0
private var sortedPosts: [Post] = []
// The color buckets
private var buckets: [Bucket] = []
/// Create a gradient
/// - Parameter posts: The color 'posts' within the gradient
@objc public init(posts: [Post], bucketCount: UInt = 0) {
self.bucketCount = bucketCount
super.init()
self.sortedPosts = self.sorted(posts)
self.buildBuckets()
}
/// Create a gradient
/// - Parameter colors: The colors to use evenly across the gradient fill
/// - Parameter bucketCount: The number of color buckets to create
public init(colors: [CGColor], bucketCount: UInt = 0) {
assert(colors.count > 1)
self.bucketCount = bucketCount
super.init()
var offset: CGFloat = 0.0
let diff = 1.0 / CGFloat(colors.count - 1)
var posts: [Post] = []
colors.forEach { color in
let clamped = offset.clamped(to: 0.0 ... 1.0)
let post = DSFSparkline.GradientBucket.Post(color: color, location: clamped)
posts.append(post)
offset += diff
}
self.sortedPosts = self.sorted(posts)
self.buildBuckets()
}
/// Make a copy of the gradient bucket
@objc public func copyGradientBucket() -> GradientBucket {
GradientBucket(
posts: self.sortedPosts.map { $0.copyPost() },
bucketCount: self.bucketCount
)
}
// MARK: - Buckets
private func buildBuckets() {
//
// |-----|-----|-----|-----|
// | | | | |
// | | | | |
// |-----|-----|-----|-----|
//
//
// If the bucket count is 4, we want the FIRST bucket to be equal
// to the first color in the gradient, and the LAST bucket to be
// equal to the last color in the gradient.
// Which means that the colors for bucket 2 and 3 are represented
// by the 1/3 and 2/3 colors to get an even spread across the buckets
//
self.buckets = []
guard self.bucketCount > 1 else {
return
}
if self.bucketCount == 2 {
self.buckets = [
Bucket(range: 0.0 ..< 0.5, color: self.color(at: 0)),
Bucket(range: 0.5 ..< .infinity, color: self.color(at: 1)),
]
return
}
// The diff for the buckets
let bucketDiff: CGFloat = 1.0 / CGFloat(self.bucketCount)
// The diff for the colors
let colordiff: CGFloat = 1.0 / CGFloat(self.bucketCount - 1)
// Add in the first bucket. We want the first bucket to be the first color in the gradient
let first = Bucket(range: -.infinity ..< bucketDiff, color: self.color(at: 0))
var bkts = [first]
// The offset within the middle buckets
var offset: CGFloat = 1
var rangeOffset = offset * bucketDiff
// We only need to fill the center buckets because we do first and last manually
(1 ..< self.bucketCount - 1).forEach { index in
let colorIndex = colordiff * CGFloat(index)
let color = self.color(at: colorIndex)
// let rangeOffset = offset * bucketDiff
let bucket = Bucket(range: rangeOffset ..< rangeOffset + bucketDiff, color: color)
bkts.append(bucket)
// Step to the next middle bucket
offset += 1
rangeOffset = rangeOffset + bucketDiff
}
// Add in the last bucket. We want the last bucket to be the last color in the gradient
let last = Bucket(range: (1 - bucketDiff) ..< .infinity, color: self.color(at: 1))
bkts.append(last)
self.buckets = bkts
}
// MARK: - Colors
// Returns the color at the specified fractional value
func color(at fraction: CGFloat) -> CGColor {
if self.sortedPosts.count == 0 {
return Self.EmptyColor
}
if self.sortedPosts.count == 1 {
// Just the first color
return self.sortedPosts[0].color.c
}
if fraction <= 0 {
// Just the first color
return self.sortedPosts.first!.color.c
}
if fraction >= 1 {
// Just the last color
return self.sortedPosts.last!.color.c
}
if self.buckets.isEmpty {
return self.gradientColor(at: fraction)
}
else {
return self.bucketColor(at: fraction)
}
}
private func gradientColor(at fraction: CGFloat) -> CGColor {
var location: Int?
for index in 0 ..< self.sortedPosts.count - 1 {
let range = self.sortedPosts[index].location ..< self.sortedPosts[index + 1].location
if range.contains(fraction) {
location = index
break
}
}
guard let loc = location else {
return self.sortedPosts.last!.color.c
}
let delta = self.sortedPosts[loc + 1].location - self.sortedPosts[loc].location
let divisor = (fraction - self.sortedPosts[loc].location) / delta
let c1 = self.sortedPosts[loc].color
let r1 = c1.r
let g1 = c1.g
let b1 = c1.b
let a1 = c1.a
let c2 = self.sortedPosts[loc + 1].color
let r2 = c2.r
let g2 = c2.g
let b2 = c2.b
let a2 = c2.a
let newR = r1 + ((r2 - r1) * divisor)
let newG = g1 + ((g2 - g1) * divisor)
let newB = b1 + ((b2 - b1) * divisor)
let newA = a1 + ((a2 - a1) * divisor)
return CGColor(colorSpace: Self.rgbSpace, components: [newR, newG, newB, newA])!
}
private func bucketColor(at fraction: CGFloat) -> CGColor {
/// Map the fraction to the bucket ranges
if let whichColor = self.buckets.first(where: { bucket in
bucket.range.contains(fraction)
})?.color {
return whichColor
}
return Self.EmptyColor
}
}
}
public extension DSFSparkline.GradientBucket {
/// A gradient 'post' represents an absolute color at a fractional point within the gradient.
@objc(DSFGradientBucketPost) class Post: NSObject {
/// The color at the location
let color: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat, c: CGColor)
/// The fractional (0 -> 1) location within the gradient for a color. Values outside this range are clamped
let location: CGFloat
private static let rgbSpace = CGColorSpaceCreateDeviceRGB()
/// Create a post
/// - Parameters:
/// - color: the color for the post
/// - location: the location for the color within the gradient (0.0 -> 1.0)
@objc public init(color: CGColor, location: CGFloat) {
self.location = max(0, min(location, 1))
let rgbColor = color.converted(to: Self.rgbSpace,
intent: .perceptual,
options: nil)!
assert(rgbColor.numberOfComponents == 4)
let r1 = rgbColor.components![0]
let g1 = rgbColor.components![1]
let b1 = rgbColor.components![2]
let a1 = rgbColor.components![3]
self.color = (r1, g1, b1, a1, rgbColor)
super.init()
}
/// Make a copy of this gradient bucket post
@objc public func copyPost() -> Post {
return Post(
r: self.color.r,
g: self.color.g,
b: self.color.b,
a: self.color.a,
location: self.location
)
}
/// Create a post
/// - Parameters:
/// - r: the red component (0.0 -> 1.0)
/// - g: the green component (0.0 -> 1.0)
/// - b: the blue component (0.0 -> 1.0)
/// - location: the location for the color within the gradient (0.0 -> 1.0)
@objc public init(r: CGFloat, g: CGFloat, b: CGFloat, location: CGFloat) {
self.location = max(0, min(location, 1))
self.color = (r, g, b, 1.0, CGColor(colorSpace: Self.rgbSpace,
components: [r, g, b, 1.0])!)
super.init()
}
/// Create a post
/// - Parameters:
/// - r: the red component (0.0 -> 1.0)
/// - g: the green component (0.0 -> 1.0)
/// - b: the blue component (0.0 -> 1.0)
/// - a: the alpha component (0.0 -> 1.0)
/// - location: the location for the color within the gradient (0.0 -> 1.0)
@objc public init(r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat, location: CGFloat) {
self.location = max(0, min(location, 1))
self.color = (r, g, b, a, CGColor(colorSpace: Self.rgbSpace,
components: [r, g, b, a])!)
super.init()
}
}
}
private extension DSFSparkline.GradientBucket {
/// Color bucket definition
struct Bucket: CustomDebugStringConvertible {
let range: Range
let color: CGColor
var debugDescription: String {
return "color: \(self.color), range: \(self.range)"
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+GridLinesDefinition.swift
================================================
//
// Created by Darren Ford on 25/01/21.
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// Grid lines definition
@objc(DSFSparklineGridLinesDefinition) class GridLinesDefinition: NSObject {
/// Grid lines color
@objc public let color: DSFColor
/// Grid lines width
@objc public let width: CGFloat
/// The dash style to use when drawing
@objc public let dashStyle: [CGFloat]
/// The positions to draw the gridlines for the data source
@objc public let values: [CGFloat]
/// Create a grid lines definition
/// - Parameters:
/// - color: The color of the grid lines
/// - width: The width to draw the grid lines
/// - dashStyle: The dash style for the grid lines
/// - values: The positions to draw the gridlines for the data source
@objc public init(
color: DSFColor = .init(white: 0.5, alpha: 0.5),
width: CGFloat = 1.0,
dashStyle: [CGFloat] = [1.0, 1.0],
values: [CGFloat] = []
) {
self.color = color
self.width = width
self.dashStyle = dashStyle
self.values = values
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+HighlightRangeDefinition.swift
================================================
//
// DSFSparkline+HighlightRangeDefinition.swift
// DSFSparklines
//
// Created by Darren Ford on 25/01/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// A highlight range definition
@objc(DSFSparklineHighlightRangeDefinition) class HighlightRangeDefinition: NSObject {
public static let DefaultFill = DSFSparkline.Fill.Color(DSFColor.systemGray.cgColor)
/// The range in the sparkline to highlight
public var range: Range
/// The highlight fill to use
@objc public var fill: DSFSparklineFillable
public init(range: Range, fill: DSFSparklineFillable = DefaultFill) {
self.range = range
self.fill = fill
super.init()
}
public init(range: Range, fillColor: CGColor) {
self.range = range
self.fill = DSFSparkline.Fill.Color(fillColor)
super.init()
}
/// Objective-C compatible initializer. Lowerbound MUST be less than upperbound!
@objc public init(lowerBound: CGFloat, upperBound: CGFloat, fill: DSFSparklineFillable = DefaultFill) {
assert(lowerBound < upperBound)
self.range = lowerBound ..< upperBound
self.fill = fill
super.init()
}
/// Objective-C compatible initializer. Lowerbound MUST be less than upperbound!
@objc public init(lowerBound: CGFloat, upperBound: CGFloat, fillColor: CGColor) {
assert(lowerBound < upperBound)
self.range = lowerBound ..< upperBound
self.fill = DSFSparkline.Fill.Color(fillColor)
super.init()
}
@objc public func copyHighlightRangeDefinition() -> HighlightRangeDefinition {
HighlightRangeDefinition(
lowerBound: self.range.lowerBound,
upperBound: self.range.upperBound,
fill: self.fill)
}
}
}
// MARK: - Objective-C helpers
public extension DSFSparkline.HighlightRangeDefinition {
@objc var lowerBound: CGFloat {
get {
return self.range.lowerBound
}
set {
self.range = newValue ..< self.range.upperBound
}
}
@objc var upperBound: CGFloat {
get {
return self.range.upperBound
}
set {
self.range = self.range.lowerBound ..< newValue
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+Palette.swift
================================================
//
// DSFSparkline+Palette.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// The palette to use when drawing the pie. The first value in the datasource uses the first color,
/// the second the second color etc. If there are more datapoints than colors (you shouldn't do this!) then
/// the chart will start back at the start of the palette.
///
/// These palettes can be safely shared between multiple pie views
@objc(DSFSparklinePalette) class Palette: NSObject {
/// The colors to be used when drawing segments
@objc public let colors: [DSFColor]
@objc public let cgColors: NSArray
/// A default palette used when no palette is specified.
@objc public static let shared = DSFSparkline.Palette([
DSFColor.systemRed,
DSFColor.systemOrange,
DSFColor.systemYellow,
DSFColor.systemGreen,
DSFColor.systemBlue,
DSFColor.systemPurple,
DSFColor.systemPink,
])
/// A default palette used when no palette is specified
@objc public static let sharedGrays = DSFSparkline.Palette([
DSFColor(white: 0.9, alpha: 1),
DSFColor(white: 0.7, alpha: 1),
DSFColor(white: 0.5, alpha: 1),
DSFColor(white: 0.3, alpha: 1),
DSFColor(white: 0.1, alpha: 1),
])
@objc public init(_ colors: [DSFColor]) {
self.colors = colors
self.cgColors = NSArray(array: colors.map { $0.cgColor })
super.init()
}
@objc public func copyPalette() -> Palette {
let copied = self.colors.compactMap { $0.copy() as? DSFColor ?? .black }
return Palette(copied)
}
@inlinable @objc func colorAtOffset(_ offset: Int) -> DSFColor {
return self.colors[offset % self.colors.count]
}
@inlinable @objc func cgColorAtOffset(_ offset: Int) -> CGColor {
return self.cgColors[offset % self.colors.count] as! CGColor
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+ValueBasedFill.swift
================================================
//
// DSFSparkline+FillColor.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import QuartzCore
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// A fill color that can change depending on a value
@objc(DSFSparklineValueBasedFill) class ValueBasedFill: NSObject {
/// Is this a simple flat color?
@objc public var isFlatColor: Bool { self.flatColor != nil }
/// Is this a color palette?
@objc public var isPalette: Bool { self.palette != nil }
/// Is this a gradient?
@objc public var isGradient: Bool { self.gradient != nil }
@objc public static let sharedPalette = ValueBasedFill(palette: DSFSparkline.Palette.shared)
private var flatColor: CGColor? = nil
private var palette: DSFSparkline.Palette? = nil
private var gradient: DSFSparkline.GradientBucket? = nil
@objc public init(flatColor: CGColor) {
self.flatColor = flatColor
}
@objc public init(palette: DSFSparkline.Palette) {
self.palette = palette
super.init()
}
/// Create a fill object containing an array of colors
/// - Parameter colors: The colors
@objc public convenience init(colors: [DSFColor]) {
self.init(palette: DSFSparkline.Palette(colors))
}
@objc public init(gradient: DSFSparkline.GradientBucket) {
self.gradient = gradient
}
init(flatColor: CGColor?, palette: DSFSparkline.Palette?, gradient: DSFSparkline.GradientBucket?) {
self.flatColor = flatColor?.copy()
self.palette = palette?.copyPalette()
self.gradient = gradient?.copyGradientBucket()
}
func copyColorContainer() -> ValueBasedFill {
return .init(flatColor: self.flatColor, palette: self.palette, gradient: self.gradient)
}
func color(atFraction fraction: CGFloat) -> CGColor {
let f = max(0, min(1 - CGFloat.ulpOfOne, fraction))
if let flatColor = self.flatColor {
return flatColor
}
else if let gradient = self.gradient {
return gradient.color(at: f)
}
else if let palette = self.palette {
let div = 1.0 / CGFloat(palette.colors.count)
let indexed = Int((f / div).rounded(.towardZero))
return palette.cgColorAtOffset(indexed)
}
return CGColor(red: 1, green: 0, blue: 0, alpha: 1)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline+ZeroLineDefinition.swift
================================================
//
// DSFSparkline+ZeroLineDefinition.swift
// DSFSparklines
//
// Created by Darren Ford on 25/01/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// Drawing definition for the zero-line operations
@objc(DSFSparklineZeroLineDefinition) class ZeroLineDefinition: NSObject {
#if os(macOS)
public static let DefaultColor = DSFColor.disabledControlTextColor
#else
public static let DefaultColor = DSFColor.systemGray
#endif
/// A shared 'default' drawing pattern
public static let shared = DSFSparkline.ZeroLineDefinition()
/// The color to draw the zero line
let color: DSFColor
/// The width of the zero line
let lineWidth: CGFloat
/// The pattern for drawing the line
let lineDashStyle: [CGFloat]
/// Drawing definition for the zero-line for a graph
/// - Parameters:
/// - color: The color to draw the zero line
/// - lineWidth: The width of the zero line
/// - lineDashStyle: The pattern for drawing the line. An array of values that specify the lengths, in user space coordinates, of the painted and unpainted segments of the dash pattern. For example, the array [2,3] sets a dash pattern that alternates between a 2-unit-long painted segment and a 3-unit-long unpainted segment. The array [1,3,4,2] sets the pattern to a 1-unit painted segment, a 3-unit unpainted segment, a 4-unit painted segment, and a 2-unit unpainted segment. Pass an empty array to clear the dash pattern so that all stroke drawing in the context uses solid lines.
public init(color: DSFColor = DefaultColor,
lineWidth: CGFloat = 1.0,
lineDashStyle: [CGFloat] = [1, 1])
{
self.color = color
self.lineWidth = lineWidth
self.lineDashStyle = lineDashStyle
}
@objc public func copyZeroLineDefinition() -> ZeroLineDefinition {
ZeroLineDefinition(
color: self.color.copy() as? DSFColor ?? .black,
lineWidth: self.lineWidth,
lineDashStyle: self.lineDashStyle
)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/DSFSparkline.swift
================================================
//
// DSFSparkline.swift
// DSFSparklines
//
// Created by Darren Ford on 26/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
@objc public class DSFSparkline: NSObject { }
================================================
FILE: Sources/DSFSparkline/overlay/types/fill/DSFSparkline+FillColor.swift
================================================
//
// DSFSparkline+FillColor.swift
// DSFSparklines
//
// Copyright © 2022 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
// MARK: - Solid color fill
public extension DSFSparkline.Fill {
/// The solid color fill
@objc(DSFSparklineFillColor) class `Color`: NSObject, DSFSparklineFillable {
/// Black color
@objc public static var black: DSFSparkline.Fill.Color { .init(gray: 0) }
/// White color
@objc public static var white: DSFSparkline.Fill.Color { .init(gray: 1) }
/// Clear color
@objc public static var clear: DSFSparkline.Fill.Color { .init(gray: 0, alpha: 0) }
/// The fill color
@objc public var color: CGColor
/// Create a color using a CGColor
/// - Parameter color: The color
@objc public init(_ color: CGColor) {
self.color = color
}
/// Create a fill color using an sRGB color
/// - Parameters:
/// - red: red component
/// - green: green component
/// - blue: blue component
/// - alpha: alpha component
@objc public init(srgbRed red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) {
let cs = CGColorSpace(name: CGColorSpace.sRGB)!
self.color = CGColor(colorSpace: cs, components: [red, green, blue, alpha]) ?? CGColor.black
}
/// Create a fill color using an rgb color
/// - Parameters:
/// - red: red component
/// - green: green component
/// - blue: blue component
/// - alpha: alpha component
@objc public init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) {
self.color = CGColor(red: red, green: green, blue: blue, alpha: alpha)
}
/// Create a fill color using an sRGB color
/// - Parameters:
/// - red: red component
/// - green: green component
/// - blue: blue component
/// - alpha: alpha component
@objc public init(gray: CGFloat, alpha: CGFloat = 1.0) {
self.color = CGColor(gray: gray, alpha: alpha)
}
public func fill(context: CGContext, bounds: CGRect) {
context.setFillColor(color)
context.fill(bounds)
}
@objc public func copyFill() -> DSFSparklineFillable {
return Color(self.color.copy() ?? .black)
}
@objc public func color(at fractionalValue: CGFloat) -> CGColor {
self.color
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/fill/DSFSparkline+FillGradient.swift
================================================
//
// DSFSparkline+FillGradient.swift
// DSFSparklines
//
// Copyright © 2022 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
// MARK: - Gradient fill
public extension DSFSparkline.Fill {
/// The gradient fill (vertical only at the moment)
@objc(DSFSparklineFillGradient) class `Gradient`: NSObject, DSFSparklineFillable {
/// The gradient to use when filling
@objc public var gradient: CGGradient
/// Is the gradient horizontal or vertical
@objc public var isHorizontal: Bool
/// A workaround for retrieving a color at a fractional location within a CGGradient
private lazy var peek: GradientPeek = {
GradientPeek(gradient: self.gradient)
}()
/// Create a fill gradient
@objc public init(gradient: CGGradient, isHorizontal: Bool = false) {
self.gradient = gradient
self.isHorizontal = isHorizontal
}
/// Create a fill gradient
public init(colors: [CGColor], isHorizontal: Bool = false) {
assert(colors.count >= 2)
self.isHorizontal = isHorizontal
let count = colors.count
let divisor = 1.0 / (CGFloat(count) - 1)
let locations = (0 ..< count - 1).map { Double($0) * divisor }.appending(1)
let gradient = CGGradient(
colorsSpace: nil,
colors: colors as CFArray,
locations: locations)
self.gradient = gradient!
}
/// Make a copy of a gradient
@objc public func copyFill() -> DSFSparklineFillable {
Gradient(gradient: self.gradient, isHorizontal: isHorizontal)
}
@objc public func fill(context: CGContext, bounds: CGRect) {
if isHorizontal {
context.drawLinearGradient(
gradient,
start: CGPoint(x: bounds.minX, y: bounds.maxY),
end: CGPoint(x: bounds.maxX, y: bounds.maxY),
options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]
)
}
else {
context.drawLinearGradient(
gradient,
start: CGPoint(x: 0.0, y: bounds.maxY),
end: CGPoint(x: 0.0, y: bounds.minY),
options: [.drawsAfterEndLocation, .drawsBeforeStartLocation]
)
}
}
/// Return the color at a fractional (0 -> 1) position within the gradient
@objc public func color(at fractionalValue: CGFloat) -> CGColor {
self.peek.color(at: fractionalValue)
}
}
}
private extension DSFSparkline.Fill.Gradient {
class GradientPeek {
static let divisor: CGFloat = 1.0 / 255.0
let snapshotSize = 4096
let gradient: CGGradient
private let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
private lazy var bitmap: [UInt8] = {
[UInt8](repeating: 0, count: (self.snapshotSize + 1) * 4)
}()
init(gradient: CGGradient) {
self.gradient = gradient
self.build()
}
func color(at fraction: CGFloat) -> CGColor {
let fraction = fraction.clamped(to: 0 ... 1)
let pixel = Int((CGFloat(self.snapshotSize) * fraction).rounded(.towardZero))
let offset = pixel * 4
let r = bitmap[offset + 0]
let g = bitmap[offset + 1]
let b = bitmap[offset + 2]
let a = bitmap[offset + 3]
return CGColor(
colorSpace: self.colorSpace,
components: [
CGFloat(r) * Self.divisor,
CGFloat(g) * Self.divisor,
CGFloat(b) * Self.divisor,
CGFloat(a) * Self.divisor
]
)!
}
private func build() {
let sz = self.snapshotSize + 1
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
guard
let ctx = CGContext(
data: &bitmap,
width: sz,
height: 1,
bitsPerComponent: 8,
bytesPerRow: sz * 4,
space: self.colorSpace,
bitmapInfo: bitmapInfo.rawValue
)
else {
fatalError()
}
ctx.drawLinearGradient(
gradient,
start: .init(x: 0, y: 0),
end: .init(x: sz, y: 0),
options: []
)
}
}
}
================================================
FILE: Sources/DSFSparkline/overlay/types/fill/DSFSparkline+Fillable.swift
================================================
//
// DSFSparkline+Fillable.swift
// DSFSparklines
//
// Copyright © 2022 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
/// A protocol definition for objects that can 'fill' a rectangle within a context with a color/gradient/pattern etc
@objc public protocol DSFSparklineFillable: NSObjectProtocol {
@objc func fill(context: CGContext, bounds: CGRect)
@objc func copyFill() -> DSFSparklineFillable
@objc func color(at fractionalValue: CGFloat) -> CGColor
}
public extension DSFSparkline {
/// Defining a namespace for fillables
class Fill {
private init() {}
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineActivityGridView+SwiftUI.swift
================================================
//
// DSFSparklineActivityGridView+SwiftUI.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import Foundation
import SwiftUI
#if os(macOS)
import AppKit
#else
import UIKit
#endif
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineActivityGridView {
/// The SwiftUI percent bar graph
struct SwiftUI {
let values: [Double]
let range: ClosedRange?
let verticalCellCount: UInt?
let horizontalCellCount: UInt
let cellStyle: DSFSparkline.ActivityGrid.CellStyle
let layoutStyle: DSFSparkline.ActivityGrid.LayoutStyle
var _tooltipStringForCell: ((Int) -> String?)?
/// Create an Activity Grid
/// - Parameters:
/// - values: The values to display
/// - range: The allowable upper/lower bounds for the input values
/// - verticalCellCount: The number of vertical cells
/// - cellStyle: The stying to apply to each cell
/// - layoutStyle: The style for drawing the activity grid
public init(
values: [Double],
range: ClosedRange? = nil,
verticalCellCount: UInt? = nil,
horizontalCellCount: UInt = 0,
cellStyle: DSFSparkline.ActivityGrid.CellStyle = .init(),
layoutStyle: DSFSparkline.ActivityGrid.LayoutStyle = .github
) {
self.values = values
self.range = range
self.verticalCellCount = verticalCellCount
self.horizontalCellCount = horizontalCellCount
self.cellStyle = cellStyle
self.layoutStyle = layoutStyle
}
/// Create an Activity Grid
/// - Parameters:
/// - values: The values to display
/// - range: The allowable upper/lower bounds for the input values
/// - verticalCellCount: The number of vertical cells, or 0 to fill vertically
/// - horizontalCellCount: The number of horizontal cells, or 0 to fill horizontally
/// - layoutStyle: The style for drawing the activity grid
/// - fillStyle: The fill mechanism
/// - borderColor: Cell border color
/// - borderWidth: Cell border width
/// - cellDimension: Cell dimension (cells are always square)
/// - cellSpacing: The spacing between cells
/// - cornerRadius: Cell corner radius
public init(
values: [Double],
range: ClosedRange? = nil,
verticalCellCount: UInt? = nil,
horizontalCellCount: UInt = 0,
layoutStyle: DSFSparkline.ActivityGrid.LayoutStyle = .github,
fillScheme: DSFSparkline.ValueBasedFill,
borderColor: CGColor? = nil,
borderWidth: CGFloat = 1,
cellDimension: CGFloat = 11,
cellSpacing: CGFloat = 2.5,
cornerRadius: CGFloat = 2.5
) {
self.values = values
self.range = range
self.verticalCellCount = verticalCellCount
self.horizontalCellCount = horizontalCellCount
self.layoutStyle = layoutStyle
self.cellStyle = DSFSparkline.ActivityGrid.CellStyle(
fillScheme: fillScheme,
borderColor: borderColor,
borderWidth: borderWidth,
cellDimension: cellDimension,
cellSpacing: cellSpacing,
cornerRadius: cornerRadius
)
}
/// A callback block for retrieving the tooltip text for a cell within the activity grid
public func tooltipStringForCell(_ block: @escaping (Int) -> String?) -> Self {
var copy = self
copy._tooltipStringForCell = block
return copy
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineActivityGridView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineActivityGridView
#else
public typealias UIViewType = DSFSparklineActivityGridView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineActivityGridView.SwiftUI
init(_ sparkline: DSFSparklineActivityGridView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeActivityGrid(_ context: Context) -> DSFSparklineActivityGridView {
let view = DSFSparklineActivityGridView(frame: .zero)
self.updateView(view)
view.cellTooltipString = self._tooltipStringForCell
return view
}
#if os(macOS)
@available(macOS 13.0, *)
public func sizeThatFits(_ proposal: ProposedViewSize, nsView: DSFSparklineActivityGridView, context: Context) -> CGSize? {
let w = (nsView.horizontalCellCount == 0) ? (proposal.width ?? nsView.activityLayer.intrinsicWidth) : nsView.activityLayer.intrinsicWidth
return CGSize(width: w, height: nsView.intrinsicContentSize.height)
}
#else
@available(iOS 16.0, tvOS 16.0, *)
public func sizeThatFits(_ proposal: ProposedViewSize, uiView: DSFSparklineActivityGridView, context: Context) -> CGSize? {
let w = (uiView.horizontalCellCount == 0) ? (proposal.width ?? uiView.activityLayer.intrinsicWidth) : uiView.activityLayer.intrinsicWidth
return CGSize(width: w, height: uiView.intrinsicContentSize.height)
}
#endif
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineActivityGridView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineActivityGridView {
return self.makeActivityGrid(context)
}
func updateUIView(_ view: DSFSparklineActivityGridView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineActivityGridView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineActivityGridView {
let v = self.makeActivityGrid(context)
return v
}
func updateNSView(_ view: DSFSparklineActivityGridView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineActivityGridView.SwiftUI {
func updateView(_ view: DSFSparklineActivityGridView) {
let v = self.values.map { CGFloat($0) }
if let vh = self.verticalCellCount { view.verticalCellCount = vh }
view.horizontalCellCount = self.horizontalCellCount
if let range = self.range {
view.setValues(v, range: CGFloat(range.lowerBound) ... CGFloat(range.upperBound))
}
else {
view.setValues(v)
}
view.cellStyle = self.cellStyle
view.layoutStyle = self.layoutStyle
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineActivityGridView.swift
================================================
//
// DSFSparklineActivityGridView.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
@objc public class DSFSparklineActivityGridView: DSFSparklineSurfaceView {
/// The view's data source
@objc public var dataSource: DSFSparkline.StaticDataSource {
get { self.activityLayer.dataSource }
set {
self.activityLayer.dataSource = newValue
self.setNeedsDisplay()
}
}
/// Set the values for the data source
/// - Parameters:
/// - values: The values
@objc public func setValues(_ values: [CGFloat]) {
self.dataSource = DSFSparkline.StaticDataSource(values)
}
/// Set the values and supported range for the data source
/// - Parameters:
/// - values: The values
/// - range: The acceptable range for the input data
public func setValues(_ values: [CGFloat], range: ClosedRange) {
self.dataSource = DSFSparkline.StaticDataSource(values, range: range)
}
/// Set the values and supported range for the data source
/// - Parameters:
/// - values: The values
/// - lowerBound: The acceptable lower bounds for the input data
/// - upperBound: The acceptable upper bounds for the input data
@objc public func setValues(_ values: [CGFloat], lowerBound: CGFloat, upperBound: CGFloat) {
self.activityLayer.dataSource = DSFSparkline.StaticDataSource(
values,
lowerBound: lowerBound,
upperBound: upperBound
)
}
/// The layout style for the grid
@objc public var cellStyle: DSFSparkline.ActivityGrid.CellStyle {
get { self.activityLayer.cellStyle }
set { self.activityLayer.cellStyle = newValue }
}
/// The layout style for the grid
@objc public var layoutStyle: DSFSparkline.ActivityGrid.LayoutStyle {
get { self.activityLayer.layoutStyle }
set { self.activityLayer.layoutStyle = newValue }
}
/// The number of vertical cells in a column
@objc public var verticalCellCount: UInt {
get { UInt(self.activityLayer.verticalCellCount) }
set { self.activityLayer.verticalCellCount = Int(newValue) }
}
/// The number of horizontal cells in a column.
@objc public var horizontalCellCount: UInt {
get { UInt(self.activityLayer.horizontalCellCount) }
set { self.activityLayer.horizontalCellCount = Int(newValue) }
}
/// The color scheme to use when filling cells
@objc public var cellFillScheme: DSFSparkline.ValueBasedFill {
get { self.activityLayer.cellFillScheme }
set { self.activityLayer.cellFillScheme = newValue }
}
/// The dimension of each cell
@objc public var cellDimension: CGFloat {
get { self.activityLayer.cellDimension }
set { self.activityLayer.cellDimension = newValue }
}
/// The spacing between each of the cells
@objc public var cellSpacing: CGFloat {
get { self.activityLayer.cellSpacing }
set { self.activityLayer.cellSpacing = newValue }
}
/// The border color for each individual cell
@objc public var cellBorderColor: DSFColor? {
get {
if let c = self.activityLayer.borderColor { return DSFColor(cgColor: c) }
return nil
}
set { self.activityLayer.cellBorderColor = newValue?.cgColor }
}
/// The cell's border width
@objc public var cellBorderWidth: CGFloat {
get { self.activityLayer.borderWidth }
set { self.activityLayer.borderWidth = newValue }
}
/// The cell's corner radius
@objc public var cellCornerRadius: CGFloat {
get { self.activityLayer.cellCornerRadius }
set { self.activityLayer.cellCornerRadius = newValue }
}
/// A block called to retrieve the tooltip text for a specific cell index
@objc public var cellTooltipString: ((Int) -> String?)? {
didSet {
self.cellsDidUpdate()
}
}
/// Initializer
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
/// Initializer
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.activityLayer)
self.activityLayer.cellsDidUpdateBlock = cellsDidUpdate
#if os(macOS)
self.updateTooltips()
#endif
}
public override var intrinsicContentSize: CGSize { self.activityLayer.intrinsicSize }
#if os(macOS)
public override func layout() {
super.layout()
self.activityLayer.frame = self.bounds
self.updateTooltips()
}
#else
public override func layoutSubviews() {
super.layoutSubviews()
self.activityLayer.frame = self.bounds
}
#endif
// private
internal let activityLayer = DSFSparklineOverlay.ActivityGrid()
#if os(macOS)
private var tooltipTags: [NSView.ToolTipTag] = []
#endif
}
internal extension DSFSparklineActivityGridView {
// Called when the activity grid layer updates
func cellsDidUpdate() {
#if os(macOS)
if let _ = self.cellTooltipString, self.tooltipTags.count == 0 {
self.updateTooltips()
}
#endif
}
}
#if os(macOS)
extension DSFSparklineActivityGridView: NSViewToolTipOwner {
func updateTooltips() {
guard let _ = self.cellTooltipString else { return }
if !Thread.isMainThread {
// Must be called on the main thread
DispatchQueue.main.async { [weak self] in self?.updateTooltips() }
return
}
self.tooltipTags.forEach { self.removeToolTip($0) }
self.tooltipTags.removeAll()
self.activityLayer.cells.enumerated().forEach { item in
let t = self.addToolTip(item.element, owner: self, userData: nil)
self.tooltipTags.append(t)
}
}
public func view(_ view: NSView, stringForToolTip tag: NSView.ToolTipTag, point: NSPoint, userData data: UnsafeMutableRawPointer?) -> String {
assert(Thread.isMainThread)
guard
let block = self.cellTooltipString,
let index = self.tooltipTags.firstIndex(where: { $0 == tag })
else {
return ""
}
return block(index) ?? ""
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineBarGraphView+SwiftUI.swift
================================================
//
// DSFSparklineBarGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 7/12/20.
// Copyright © 2020 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineBarGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The primary color for the sparkline
let graphColor: DSFColor
/// The line width (in pixels) to use when drawing the border of each bar
let lineWidth: UInt
/// The spacing (in pixels) between each bar
let barSpacing: UInt
/// Draw a dotted line at the zero point on the y-axis
let showZeroLine: Bool
/// The drawing definition for the zero line point
let zeroLineDefinition: DSFSparkline.ZeroLineDefinition
/// Should the line graph be centered around the zero-line?
let centeredAtZeroLine: Bool
/// The color used to draw values lower than the zero-line, or nil for the same as the graph color
let lowerGraphColor: DSFColor?
/// Highlight y-ranges within the graph
let highlightDefinitions: [DSFSparkline.HighlightRangeDefinition]
/// The grid lines definition
let gridLines: DSFSparkline.GridLinesDefinition?
/// Primary fill
let primaryFill: DSFSparklineFillable?
/// Secondary fill
let secondaryFill: DSFSparklineFillable?
/// Create a bar graph sparkline
/// - Parameters:
/// - dataSource: The data source for the graph
/// - graphColor: The color to draw the graph
/// - lineWidth: The width of the line around each bar
/// - barSpacing: The spacing between the bars
/// - showZeroLine: Show or hide a 'zero line' horizontal line
/// - zeroLineDefinition: the settings for drawing the zero line
/// - centeredAtZeroLine: Should the line graph be centered around the zero-line?
/// - lowerGraphColor: The color used to draw values lower than the zero-line, or nil for the same as the graph color
/// - highlightDefinitions: The style of the y-range highlight
/// - gridLines: The grid lines to draw on the graph
/// - primaryFill: The fill to use for values above the zero line
/// - secondaryFill: The fill to use for values below the zero line
public init(
dataSource: DSFSparkline.DataSource,
graphColor: DSFColor,
lineWidth: UInt = 1,
barSpacing: UInt = 1,
showZeroLine: Bool = false,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition = .shared,
centeredAtZeroLine: Bool = false,
lowerGraphColor: DSFColor? = nil,
showHighlightRange: Bool = false,
highlightDefinitions: [DSFSparkline.HighlightRangeDefinition] = [],
gridLines: DSFSparkline.GridLinesDefinition? = nil,
primaryFill: (any DSFSparklineFillable)? = nil,
secondaryFill: (any DSFSparklineFillable)? = nil
) {
self.dataSource = dataSource
self.graphColor = graphColor
self.showZeroLine = showZeroLine
self.zeroLineDefinition = zeroLineDefinition
self.centeredAtZeroLine = centeredAtZeroLine
self.lowerGraphColor = lowerGraphColor
self.lineWidth = lineWidth
self.barSpacing = barSpacing
self.highlightDefinitions = highlightDefinitions
self.gridLines = gridLines
self.primaryFill = primaryFill
self.secondaryFill = secondaryFill
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineBarGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineBarGraphView
#else
public typealias UIViewType = DSFSparklineBarGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineBarGraphView.SwiftUI
init(_ sparkline: DSFSparklineBarGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
private func makeBarGraph(_: Context) -> DSFSparklineBarGraphView {
let view = DSFSparklineBarGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.graphColor = self.graphColor
view.barSpacing = self.barSpacing
view.lineWidth = self.lineWidth
view.zeroLineVisible = self.showZeroLine
view.setZeroLineDefinition(self.zeroLineDefinition)
view.centeredAtZeroLine = self.centeredAtZeroLine
view.lowerGraphColor = self.lowerGraphColor
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineBarGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineBarGraphView {
return self.makeBarGraph(context)
}
func updateUIView(_ view: DSFSparklineBarGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineBarGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineBarGraphView {
return self.makeBarGraph(context)
}
func updateNSView(_ view: DSFSparklineBarGraphView, context _: Context) {
self.updateView(view)
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineBarGraphView.SwiftUI {
func updateView(_ view: DSFSparklineBarGraphView) {
UpdateIfNotEqual(result: &view.graphColor, val: self.graphColor)
UpdateIfNotEqual(result: &view.barSpacing, val: self.barSpacing)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.zeroLineVisible, val: self.showZeroLine)
view.setZeroLineDefinition(self.zeroLineDefinition)
UpdateIfNotEqual(result: &view.centeredAtZeroLine, val: self.centeredAtZeroLine)
UpdateIfNotEqual(result: &view.lowerGraphColor, val: self.lowerGraphColor)
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
else {
view.highlightRangeVisible = false
view.highlightRangeDefinition = []
}
if let gridLines = self.gridLines {
view.setGridLineDefinition(gridLines)
}
else {
view.gridLinesVisible = false
}
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineBarGraphView.swift
================================================
//
// DSFSparklineBarGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline graph that displays bars
@objc public class DSFSparklineBarGraphView: DSFSparklineZeroLineGraphView {
let overlay = DSFSparklineOverlay.Bar()
/// The line width (in pixels) to use when drawing the border of each bar
@objc public dynamic var lineWidth: UInt = 1 {
didSet {
self.overlay.strokeWidth = self.lineWidth
}
}
/// The spacing (in pixels) between each bar
@objc public dynamic var barSpacing: UInt = 1 {
didSet {
self.overlay.barSpacing = self.barSpacing
}
}
/// Draw a shadow under the line
@objc public dynamic var shadowed: Bool = false {
didSet {
self.overlay.shadow = self.shadowed ? NSShadow.sparklineDefault : nil
}
}
/// Should the graph be centered at the zero line?
@objc public dynamic var centeredAtZeroLine: Bool = false {
didSet {
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
}
}
/// The primary fill (for the area of the graph ABOVE the zero line)
@objc public dynamic var primaryFill: (any DSFSparklineFillable)? {
get { self.overlay.primaryFill }
set { self.overlay.primaryFill = newValue }
}
/// The secondary fill (for the area of the graph UNDER the zero line)
@objc public dynamic var secondaryFill: (any DSFSparklineFillable)? {
get { self.overlay.secondaryFill }
set { self.overlay.secondaryFill = newValue }
}
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.strokeWidth = self.lineWidth
self.overlay.barSpacing = self.barSpacing
self.overlay.primaryStrokeColor = self.graphColor.cgColor
self.overlay.secondaryStrokeColor = self.lowerColor.cgColor
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
// Backwards compatibility
let color = self.graphColor
let fill = DSFSparkline.Fill.Gradient(colors: [
color.withAlphaComponent(0.4).cgColor,
color.withAlphaComponent(0.2).cgColor
])
self.overlay.primaryFill = fill
if let lowerColor = self.lowerGraphColor {
let fill = DSFSparkline.Fill.Gradient(colors: [
lowerColor.withAlphaComponent(0.4).cgColor,
lowerColor.withAlphaComponent(0.2).cgColor
])
self.overlay.secondaryFill = fill
}
else {
// Fallback - if secondary fill not defined the compatibility view is to use the primary fill view
self.overlay.secondaryFill = self.overlay.primaryFill
}
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineCircularGaugeView+SwiftUI.swift
================================================
//
// DSFSparklineCircularGaugeView+SwiftUI.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineCircularGaugeView {
enum Animation {
case linear(Double)
case easeInEaseOut(Double)
}
/// A SwiftUI wrapper for the Circular Progress sparkline overlay
struct SwiftUI {
let value: Double
let animationStyle: DSFSparkline.AnimationStyle?
let trackStyle: DSFSparklineOverlay.CircularGauge.TrackStyle
let lineStyle: DSFSparklineOverlay.CircularGauge.TrackStyle
/// Create a Circular Gauge view
/// - Parameters:
/// - value: The value to display
/// - animationStyle: The animation style, or nil for no animation
/// - trackStyle: The style to use when drawing the track (background ring)
/// - lineStyle: The style to use when drawing the value line
public init(
value: Double,
animationStyle: DSFSparkline.AnimationStyle? = nil,
trackStyle: DSFSparklineOverlay.CircularGauge.TrackStyle,
lineStyle: DSFSparklineOverlay.CircularGauge.TrackStyle
) {
self.value = value
self.animationStyle = animationStyle
self.trackStyle = trackStyle
self.lineStyle = lineStyle
}
/// Create a Circular Gauge view
/// - Parameters:
/// - value: The value to display
/// - animationStyle: The animation style, or nil for no animation
/// - trackWidth: The width of the track
/// - trackFill: The fill style to use for the track
/// - valueWidth: The width of the value line
/// - valueFill: The fill style to use for the value line
public init(
value: Double,
animationStyle: DSFSparkline.AnimationStyle? = nil,
trackWidth: Double = 10,
trackFill: DSFSparklineFillable,
valueWidth: Double = 7,
valueFill: DSFSparklineFillable
) {
self.value = value
self.animationStyle = animationStyle
self.trackStyle = .init(width: trackWidth, fillColor: trackFill)
self.lineStyle = .init(width: valueWidth, fillColor: valueFill)
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineCircularGaugeView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineCircularGaugeView
#else
public typealias UIViewType = DSFSparklineCircularGaugeView
#endif
func makeCircularGauge(_: Context) -> DSFSparklineCircularGaugeView {
let view = DSFSparklineCircularGaugeView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.value = self.value
view.animationStyle = self.animationStyle
view.trackStyle = self.trackStyle
view.lineStyle = self.lineStyle
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineCircularGaugeView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineCircularGaugeView {
return self.makeCircularGauge(context)
}
func updateUIView(_ view: DSFSparklineCircularGaugeView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineCircularGaugeView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineCircularGaugeView {
return self.makeCircularGauge(context)
}
func updateNSView(_ view: DSFSparklineCircularGaugeView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineCircularGaugeView.SwiftUI {
func updateView(_ view: DSFSparklineCircularGaugeView) {
UpdateIfNotEqual(result: &view.value, val: self.value)
if view.animationStyle !== self.animationStyle {
view.animationStyle = self.animationStyle
}
if view.trackStyle !== self.trackStyle {
view.trackStyle = self.trackStyle
}
if view.lineStyle !== self.lineStyle {
view.lineStyle = self.lineStyle
}
}
}
#if DEBUG
@available(macOS 10.15, *)
struct DSFSparklineCircularGaugeViewPreviews: PreviewProvider {
static var previews: some SwiftUI.View {
let ge = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 1, green: 0.000, blue: 0.00, alpha: 1.0),
CGColor(srgbRed: 0, green: 0.0, blue: 1, alpha: 1.0),
]
)
let vals = [Double](stride(from: 0, through: 1, by: 0.2))
VStack {
HStack {
ForEach(vals, id: \.self) { value in
DSFSparklineCircularGaugeView.SwiftUI(
value: value,
trackStyle: .init(width: 20, fillColor: DSFSparkline.Fill.Color(gray: 0.5, alpha: 0.2)),
lineStyle: .init(width: 10, fillColor: DSFSparkline.Fill.Color(gray: 0.5, alpha: 1.0))
)
.frame(width: 64, height: 64)
}
}
Divider()
ForEach([0, 1, 2], id: \.self) { isShadowed in
HStack {
ForEach(vals, id: \.self) { value in
DSFSparklineCircularGaugeView.SwiftUI(
value: value,
trackStyle: .init(width: 20, fillColor: DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 1, alpha: 0.2), shadow: isShadowed == 1 ? .init(blurRadius: 2, offset: CGSize(width: 1, height: 1), color: .black) : nil),
lineStyle: .init(width: 10, fillColor: DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 1, alpha: 1), shadow: isShadowed == 2 ? .init(blurRadius: 2, offset: CGSize(width: 1, height: 1), color: .black) : nil)
)
.frame(width: 64, height: 64)
}
}
}
Divider()
ForEach([0, 1, 2], id: \.self) { isShadowed in
HStack {
ForEach(vals, id: \.self) { value in
DSFSparklineCircularGaugeView.SwiftUI(
value: value,
trackStyle: .init(
width: 20,
fillColor: DSFSparkline.Fill.Color(gray: 0.5, alpha: 0.2),
shadow: isShadowed == 1 ? .init(blurRadius: 1, offset: CGSize(width: 1, height: 1), color: .black.copy(alpha: 0.8)!, isInner: true) : nil
),
lineStyle: .init(
width: 10,
fillColor: ge,
strokeWidth: 0.2,
strokeColor: CGColor.standard.black,
shadow: isShadowed == 2 ? .init(blurRadius: 1, offset: CGSize(width: 1, height: 1), color: .black.copy(alpha: 0.8)!, isInner: true) : nil
)
)
.frame(width: 64, height: 64)
}
}
}
}
.padding()
}
}
#endif
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineCircularGaugeView.swift
================================================
//
// DSFSparklineCircularGaugeView.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A circular gauge
public class DSFSparklineCircularGaugeView: DSFSparklineSurfaceView {
/// The value to display (0 ... 1)
@objc public dynamic var value: CGFloat = 0.0 {
didSet {
self.overlay.value = self.value
}
}
/// The style to use when drawing the gauge's track
@objc public var trackStyle = DSFSparklineOverlay.CircularGauge.DefaultTrackStyle {
didSet {
self.overlay.trackStyle = self.trackStyle
}
}
/// The width of the track
@objc public dynamic var trackWidth: CGFloat {
get { self.overlay.trackStyle.width }
set { self.overlay.trackStyle.width = newValue }
}
/// Track color
@objc public dynamic var trackColor: DSFColor = .black.withAlphaComponent(0.1) {
didSet {
self.overlay.trackStyle.fillColor = DSFSparkline.Fill.Color(trackColor.cgColor)
}
}
/// The style to use when drawing the gauge's value
@objc public var lineStyle = DSFSparklineOverlay.CircularGauge.DefaultLineStyle {
didSet {
self.overlay.lineStyle = self.lineStyle
}
}
/// The width of the track
@objc public dynamic var lineWidth: CGFloat {
get { self.overlay.lineStyle.width }
set { self.overlay.lineStyle.width = newValue }
}
/// Line color
@objc public dynamic var lineColor: DSFColor = .black {
didSet {
self.overlay.lineStyle.fillColor = DSFSparkline.Fill.Color(lineColor.cgColor)
}
}
/// The animation style to use when the value changes
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil {
didSet {
self.overlay.animationStyle = animationStyle
}
}
// MARK: - Initializers
/// Create
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
/// Create
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
private let overlay = DSFSparklineOverlay.CircularGauge()
}
extension DSFSparklineCircularGaugeView {
func configure() {
self.addOverlay(self.overlay)
self.clipsToBounds = false
self.setNeedsDisplay()
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineCircularProgressView+SwiftUI.swift
================================================
//
// DSFSparklineCircularProgressView+SwiftUI.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineCircularProgressView {
/// A SwiftUI wrapper for the Circular Progress sparkline overlay
struct SwiftUI {
let value: Double
let trackWidth: Double
let fillStyle: DSFSparklineFillable
let padding: CGFloat
let trackColor: CGColor?
let trackIcon: CGImage?
public init(
value: Double,
fillStyle: DSFSparklineFillable = DSFSparklineOverlay.CircularProgress.DefaultFillStyle,
trackWidth: Double = DSFSparklineOverlay.CircularProgress.DefaultTrackWidth,
padding: Double = 0.0,
trackColor: CGColor? = nil,
trackIcon: CGImage? = nil
) {
self.value = value
self.trackWidth = trackWidth
self.fillStyle = fillStyle
self.padding = padding
self.trackColor = trackColor
self.trackIcon = trackIcon
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineCircularProgressView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineCircularProgressView
#else
public typealias UIViewType = DSFSparklineCircularProgressView
#endif
func makeProgressGraph(_: Context) -> DSFSparklineCircularProgressView {
let view = DSFSparklineCircularProgressView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.value = self.value
view.trackWidth = self.trackWidth
view.fillStyle = self.fillStyle.copyFill()
view.padding = self.padding
if let t = self.trackColor {
view.trackColor = DSFColor(cgColor: t)
}
view.trackIcon = trackIcon
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineCircularProgressView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineCircularProgressView {
return self.makeProgressGraph(context)
}
func updateUIView(_ view: DSFSparklineCircularProgressView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineCircularProgressView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineCircularProgressView {
return self.makeProgressGraph(context)
}
func updateNSView(_ view: DSFSparklineCircularProgressView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineCircularProgressView.SwiftUI {
func updateView(_ view: DSFSparklineCircularProgressView) {
UpdateIfNotEqual(result: &view.value, val: self.value)
UpdateIfNotEqual(result: &view.trackWidth, val: self.trackWidth)
UpdateIfNotEqual(result: &view.padding, val: self.padding)
if view.fillStyle !== self.fillStyle {
view.fillStyle = self.fillStyle
}
if view.trackColor !== self.trackColor {
if let t = self.trackColor {
view.trackColor = DSFColor(cgColor: t)
}
}
if view.trackIcon !== self.trackIcon {
view.trackIcon = self.trackIcon
}
}
}
#if DEBUG
func namedImage(_ name: String) -> CGImage? {
#if os(macOS)
if #available(macOS 11.0, *) {
let ns = NSImage(systemSymbolName: name, accessibilityDescription: nil)!
//ns.
return ns.cgImage(forProposedRect: nil, context: nil, hints: nil)
}
return nil
#else
let ui = UIImage(systemName: name)!
return ui.cgImage
#endif
}
@available(macOS 10.15, *)
struct DSFSparklineCircularProgressViewPreviews: PreviewProvider {
static var previews: some SwiftUI.View {
let ge = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 1, green: 0.000, blue: 0.00, alpha: 1.0),
CGColor(srgbRed: 0, green: 0.0, blue: 1, alpha: 1.0),
]
)
let g = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 1.0),
CGColor(srgbRed: 0.891, green: 0.000, blue: 0.090, alpha: 1.0),
]
)
let g1 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 1.0),
CGColor(srgbRed: 0.601, green: 1.000, blue: 0.009, alpha: 1.0),
]
)
let g2 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 1.0),
CGColor(srgbRed: 0.015, green: 0.847, blue: 1.000, alpha: 1.0),
]
)
let g3 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 1.0),
CGColor(srgbRed: 0.996, green: 0.459, blue: 0.000, alpha: 1.0),
]
)
let i1 = namedImage("arrow.right")
let i2 = namedImage("arrow.up")
let i3 = namedImage("arrow.triangle.swap")
let i4 = namedImage("phone.arrow.right")
ScrollView([.horizontal, .vertical]) {
VStack {
HStack(alignment: .center) {
DSFSparklineCircularProgressView.SwiftUI(value: 0, trackWidth: 20)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 0.4, trackWidth: 20)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 0.8, trackWidth: 20)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 1.2, trackWidth: 20)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 1.6, trackWidth: 20)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 2.0, trackWidth: 20)
.frame(width: 100, height: 100)
}
ForEach(0 ..< 2) { which in
HStack(alignment: .center) {
DSFSparklineCircularProgressView.SwiftUI(value: 0, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 0.4, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 0.8, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 1.2, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 1.6, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
DSFSparklineCircularProgressView.SwiftUI(value: 2.0, fillStyle: ge, trackWidth: 25, trackIcon: which == 1 ? i1 : nil)
.frame(width: 100, height: 100)
}
}
HStack(alignment: .center) {
ZStack {
DSFSparklineCircularProgressView.SwiftUI(value: 0, fillStyle: g, trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1) , trackIcon: i1)
DSFSparklineCircularProgressView.SwiftUI(value: 0, fillStyle: g1, padding: 12, trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1), trackIcon: i2)
DSFSparklineCircularProgressView.SwiftUI(value: 0, fillStyle: g2, padding: 24, trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1), trackIcon: i3)
DSFSparklineCircularProgressView.SwiftUI(value: 0, fillStyle: g3, padding: 36, trackColor: CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 0.1), trackIcon: i4)
}
.frame(width: 150, height: 150)
ZStack {
DSFSparklineCircularProgressView.SwiftUI(value: 0.4, fillStyle: g, trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1) , trackIcon: i1)
DSFSparklineCircularProgressView.SwiftUI(value: 0.3, fillStyle: g1, padding: 12, trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1), trackIcon: i2)
DSFSparklineCircularProgressView.SwiftUI(value: 0.2, fillStyle: g2, padding: 24, trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1), trackIcon: i3)
DSFSparklineCircularProgressView.SwiftUI(value: 0.1, fillStyle: g3, padding: 36, trackColor: CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 0.1), trackIcon: i4)
}
.frame(width: 150, height: 150)
ZStack {
DSFSparklineCircularProgressView.SwiftUI(value: 0.7, fillStyle: g, trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1) , trackIcon: i1)
DSFSparklineCircularProgressView.SwiftUI(value: 0.6, fillStyle: g1, padding: 12, trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1), trackIcon: i2)
DSFSparklineCircularProgressView.SwiftUI(value: 0.5, fillStyle: g2, padding: 24, trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1), trackIcon: i3)
DSFSparklineCircularProgressView.SwiftUI(value: 0.4, fillStyle: g3, padding: 36, trackColor: CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 0.1), trackIcon: i4)
}
.frame(width: 150, height: 150)
ZStack {
DSFSparklineCircularProgressView.SwiftUI(value: 1.4, fillStyle: g, trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1) , trackIcon: i1)
DSFSparklineCircularProgressView.SwiftUI(value: 1.3, fillStyle: g1, padding: 12, trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1), trackIcon: i2)
DSFSparklineCircularProgressView.SwiftUI(value: 1.2, fillStyle: g2, padding: 24, trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1), trackIcon: i3)
DSFSparklineCircularProgressView.SwiftUI(value: 1.1, fillStyle: g3, padding: 36, trackColor: CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 0.1), trackIcon: i4)
}
.frame(width: 150, height: 150)
ZStack {
DSFSparklineCircularProgressView.SwiftUI(value: 1.9, fillStyle: g, trackColor: CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1) , trackIcon: i1)
DSFSparklineCircularProgressView.SwiftUI(value: 1.8, fillStyle: g1, padding: 12, trackColor: CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1), trackIcon: i2)
DSFSparklineCircularProgressView.SwiftUI(value: 1.7, fillStyle: g2, padding: 24, trackColor: CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1), trackIcon: i3)
DSFSparklineCircularProgressView.SwiftUI(value: 1.6, fillStyle: g3, padding: 36, trackColor: CGColor(srgbRed: 0.996, green: 0.759, blue: 0.300, alpha: 0.1), trackIcon: i4)
}
.frame(width: 150, height: 150)
}
}
.padding()
}
}
}
#endif
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineCircularProgressView.swift
================================================
//
// DSFSparklineCircularProgressView.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
@objc public class DSFSparklineCircularProgressView: DSFSparklineSurfaceView {
@objc public dynamic var value: CGFloat = 0.0 {
didSet {
self.overlay.value = self.value
}
}
@objc public dynamic var trackWidth: CGFloat = 10 {
didSet {
self.overlay.trackWidth = self.trackWidth
}
}
/// The padding (inset) for drawing the ring
@objc public dynamic var padding: CGFloat = 0.0 {
didSet {
self.overlay.padding = self.padding
}
}
/// The stroke color for the pie chart
@objc public dynamic var trackColor: DSFColor? {
didSet {
if let t = self.trackColor?.cgColor {
self.overlay.trackColor = t
}
}
}
/// The stroke color for the pie chart
@objc public dynamic var progressColor: DSFColor? {
didSet {
if let t = self.progressColor?.cgColor {
self.overlay.fillStyle = DSFSparkline.Fill.Color(t)
}
}
}
/// The track's icon
@objc public dynamic var trackIcon: CGImage? {
didSet {
self.overlay.icon = self.trackIcon
}
}
/// The fill color for the value ring
@objc public var fillStyle: DSFSparklineFillable = DSFSparkline.Fill.Color.white {
didSet {
self.overlay.fillStyle = fillStyle
}
}
// MARK: - Initializers
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
private let overlay = DSFSparklineOverlay.CircularProgress()
}
extension DSFSparklineCircularProgressView {
func configure() {
self.addOverlay(self.overlay)
self.setNeedsDisplay()
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineDataBarGraphView+SwiftUI.swift
================================================
//
// DSFSparklineDataBarGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineDataBarGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.StaticDataSource
/// Maximum total value. If -1, this value is i
let maximumTotalValue: CGFloat
/// The 'undrawn' color for the graph
let unsetColor: DSFColor?
/// Palette to use when coloring the chart
let palette: DSFSparkline.Palette
/// Stroke Color
let strokeColor: DSFColor?
/// Stroke Width
let lineWidth: CGFloat
/// The animation style to apply when datasource changes
let animationStyle: DSFSparkline.AnimationStyle?
/// Create a databar graph
/// - Parameters:
/// - dataSource: The data source for the graph
/// - maximumTotalValue: The maximum _total_ value. If the datasource values total is greater than this value, it clips the display
/// - palette: The color palette to use when drawing the graph
/// - unsetColor: (optional) the color to use when drawing the background (useful when the maximumValue is also set)
/// - strokeColor: The color to draw the separator lines between data points
/// - lineWidth: The width of the separator lines
/// - animationStyle: The animation style, or nil for no animation
public init(
dataSource: DSFSparkline.StaticDataSource,
maximumTotalValue: CGFloat = -1,
palette: DSFSparkline.Palette = .shared,
unsetColor: DSFColor? = nil,
strokeColor: DSFColor? = nil,
lineWidth: CGFloat = 1.0,
animationStyle: DSFSparkline.AnimationStyle? = nil
) {
self.dataSource = dataSource
self.maximumTotalValue = maximumTotalValue
self.unsetColor = unsetColor
self.strokeColor = strokeColor
self.lineWidth = lineWidth
self.palette = palette
self.animationStyle = animationStyle
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineDataBarGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineDataBarGraphView
#else
public typealias UIViewType = DSFSparklineDataBarGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineDataBarGraphView.SwiftUI
init(_ sparkline: DSFSparklineDataBarGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeDataBarGraph(_: Context) -> DSFSparklineDataBarGraphView {
let view = DSFSparklineDataBarGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.strokeColor = self.strokeColor
view.unsetColor = self.unsetColor
view.lineWidth = self.lineWidth
view.palette = self.palette
view.animationStyle = self.animationStyle
view.dataSource = self.dataSource
view.maximumTotalValue = self.maximumTotalValue
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineDataBarGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineDataBarGraphView {
return self.makeDataBarGraph(context)
}
func updateUIView(_ view: DSFSparklineDataBarGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineDataBarGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineDataBarGraphView {
return self.makeDataBarGraph(context)
}
func updateNSView(_ view: DSFSparklineDataBarGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineDataBarGraphView.SwiftUI {
func updateView(_ view: DSFSparklineDataBarGraphView) {
UpdateIfNotEqual(result: &view.strokeColor, val: self.strokeColor)
UpdateIfNotEqual(result: &view.unsetColor, val: self.unsetColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.palette, val: self.palette)
UpdateIfNotEqual(result: &view.animationStyle, val: self.animationStyle)
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
UpdateIfNotEqual(result: &view.maximumTotalValue, val: self.maximumTotalValue)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineDataBarGraphView.swift
================================================
//
// DSFSparklineDataBarGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline that draws a simple pie chart
public class DSFSparklineDataBarGraphView: DSFSparklineSurfaceView {
let databarOverlay = DSFSparklineOverlay.DataBar()
/// The data to be displayed in the data bar.
///
/// The values become a percentage of the total value stored within the
/// dataStore, and as such each value ends up being drawn as a fraction of the total.
/// So for example, if you want the pie chart to represent the number of red cars vs. number of
/// blue cars, you just set the values directly.
@objc public var dataSource = DSFSparkline.StaticDataSource() {
didSet {
self.databarOverlay.dataSource = self.dataSource
}
}
// MARK: - Maximum Total
/// The maximum _total_ value. If the datasource values total is greater than this value, it clips the display
@objc public dynamic var maximumTotalValue: CGFloat = -1 {
didSet {
self.databarOverlay.maximumTotalValue = self.maximumTotalValue
}
}
// MARK: Optional background color
/// The 'undrawn' color for the graph
@objc public dynamic var unsetColor: DSFColor? {
didSet {
self.databarOverlay.unsetColor = self.unsetColor?.cgColor
}
}
// MARK: - Stroke
/// The stroke color for the pie chart
@objc public dynamic var strokeColor: DSFColor? {
didSet {
self.databarOverlay.strokeColor = self.strokeColor?.cgColor
}
}
/// The width of the stroke line
@objc public dynamic var lineWidth: CGFloat = 0.5 {
didSet {
self.updateDisplay()
}
}
/// The animation style to apply when datasource changes, or nil for no animation
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil {
didSet {
self.databarOverlay.animationStyle = self.animationStyle
}
}
/// The palette to use when drawing the pie chart
@objc public var palette = DSFSparkline.Palette.shared {
didSet {
self.databarOverlay.palette = self.palette
}
}
// MARK: - Initializers
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.databarOverlay)
self.databarOverlay.setNeedsDisplay()
self.databarOverlay.unsetColor = self.unsetColor?.cgColor
self.databarOverlay.strokeColor = self.strokeColor?.cgColor
self.databarOverlay.lineWidth = self.lineWidth
self.databarOverlay.maximumTotalValue = self.maximumTotalValue
self.databarOverlay.animationStyle = self.animationStyle
self.databarOverlay.dataSource = self.dataSource
}
// MARK: - Privates
internal var animator = ArbitraryAnimator()
internal var fractionComplete: CGFloat = 0
internal var total: CGFloat = 0.0
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineDotGraphView+SwiftUI.swift
================================================
//
// DSFSparklineDotGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 7/12/20.
// Copyright © 2020 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineDotGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The primary color for the sparkline
let graphColor: DSFColor
/// Are the values drawn from the top down?
let upsideDown: Bool
/// The number of vertical buckets to break the input data up into
let verticalDotCount: UInt
/// The secondary color for the sparkline
let unsetGraphColor: DSFColor
/// Create a sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
/// - Parameters:
/// - dataSource: The data source for the graph
/// - graphColor: The color of the dots that are set
/// - unsetGraphColor: The color of the dots that are not set
/// - verticalDotCount: The number of dots vertically
/// - upsideDown: Draw the graph upside down
public init(dataSource: DSFSparkline.DataSource,
graphColor: DSFColor,
unsetGraphColor: DSFColor = DSFColor.clear,
verticalDotCount: UInt = 10,
upsideDown: Bool = false)
{
self.dataSource = dataSource
self.graphColor = graphColor
self.verticalDotCount = verticalDotCount
self.upsideDown = upsideDown
self.unsetGraphColor = unsetGraphColor
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineDotGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineDotGraphView
#else
public typealias UIViewType = DSFSparklineDotGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineDotGraphView.SwiftUI
init(_ sparkline: DSFSparklineDotGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
private func makeDotGraph(_: Context) -> DSFSparklineDotGraphView {
let view = DSFSparklineDotGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.graphColor = self.graphColor
view.verticalDotCount = self.verticalDotCount
view.unsetGraphColor = self.unsetGraphColor
view.upsideDown = self.upsideDown
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 99.0, *)
public extension DSFSparklineDotGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineDotGraphView {
return self.makeDotGraph(context)
}
func updateUIView(_ view: DSFSparklineDotGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineDotGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineDotGraphView {
return self.makeDotGraph(context)
}
func updateNSView(_ view: DSFSparklineDotGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineDotGraphView.SwiftUI {
func updateView(_ view: DSFSparklineDotGraphView) {
UpdateIfNotEqual(result: &view.graphColor, val: self.graphColor)
UpdateIfNotEqual(result: &view.upsideDown, val: self.upsideDown)
UpdateIfNotEqual(result: &view.verticalDotCount, val: self.verticalDotCount)
UpdateIfNotEqual(result: &view.unsetGraphColor, val: self.unsetGraphColor)
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineDotGraphView.swift
================================================
//
// DSFSparklineDataSource.swift
// DSFSparklines
//
// Created by Darren Ford on 16/1/20.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
@objc public class DSFSparklineDotGraphView: DSFSparklineDataSourceView {
let overlay = DSFSparklineOverlay.Dot()
/// Are the values drawn from the top down?
@objc public dynamic var upsideDown: Bool = false {
didSet {
self.overlay.upsideDown = self.upsideDown
self.updateDisplay()
}
}
/// The number of vertical buckets to break the input data up into
@objc public dynamic var verticalDotCount: UInt = 10 {
didSet {
self.overlay.verticalDotCount = self.verticalDotCount
self.updateDisplay()
}
}
/// The secondary color for the sparkline
#if os(macOS)
@objc public dynamic var unsetGraphColor: NSColor = NSColor.clear {
didSet {
self.colorDidChange()
}
}
#else
@objc public dynamic var unsetGraphColor: UIColor = UIColor.clear {
didSet {
self.colorDidChange()
}
}
#endif
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.upsideDown = self.upsideDown
self.overlay.verticalDotCount = self.verticalDotCount
self.overlay.onColor = self.graphColor.cgColor
self.overlay.offColor = self.unsetGraphColor.cgColor
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineLineGraphView+SwiftUI.swift
================================================
//
// DSFSparklineLineGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 7/12/20.
// Copyright © 2020 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineLineGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The primary color for the sparkline
let graphColor: DSFColor
/// Draw a dotted line at the zero point on the y-axis
let showZeroLine: Bool
/// The drawing definition for the zero line point
let zeroLineDefinition: DSFSparkline.ZeroLineDefinition
/// The width for the line drawn on the graph
let lineWidth: CGFloat
/// The number of vertical buckets to break the input data up into
let interpolated: Bool
/// The secondary color for the sparkline
let lineShading: Bool
/// Draw a shadow under the line
let shadowed: Bool
/// Should the line graph be centered around the zero-line?
let centeredAtZeroLine: Bool
/// The color used to draw values lower than the zero-line, or nil for the same as the graph color
let lowerGraphColor: DSFColor?
/// Highlight y-ranges within the graph
let highlightDefinitions: [DSFSparkline.HighlightRangeDefinition]
/// The size of the markers to draw. If the markerSize is less than 0, markers will not draw
let markerSize: CGFloat
let markerDrawingBlock: DSFSparklineOverlay.Line.MarkerDrawingBlock?
/// The grid lines definition
let gridLines: DSFSparkline.GridLinesDefinition?
/// Primary fill
let primaryFill: DSFSparklineFillable?
/// Secondary fill
let secondaryFill: DSFSparklineFillable?
/// Create a sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
/// - Parameters:
/// - dataSource: The data source for the graph
/// - graphColor: The color of the dots that are set
/// - lineWidth: The width of the line
/// - interpolated: If true, curves the line through the graph points.
/// - lineShading: If true, shades the underside of the drawn line.
/// - shadowed: If true, draws a shadow under the line part of the graph.
/// - showZeroLine: Show or hide a 'zero line' horizontal line
/// - zeroLineDefinition: the settings for drawing the zero line
/// - centeredAtZeroLine: Should the line graph be centered around the zero-line?
/// - lowerGraphColor: The color used to draw values lower than the zero-line, or nil for the same as the graph color
/// - highlightDefinitions: The style of the y-range highlight
/// - markerSize: The size of the markers to draw. If the markerSize is less than 0, markers will not draw
/// - markerDrawingBlock: (optional) function to draw the markers
/// - gridLines: The grid lines to draw on the graph
public init(
dataSource: DSFSparkline.DataSource,
graphColor: DSFColor,
lineWidth: CGFloat = 1.5,
interpolated: Bool = false,
lineShading: Bool = true,
shadowed: Bool = false,
showZeroLine: Bool = false,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition = .shared,
centeredAtZeroLine: Bool = false,
lowerGraphColor: DSFColor? = nil,
highlightDefinitions: [DSFSparkline.HighlightRangeDefinition] = [],
markerSize: CGFloat = -1,
markerDrawingBlock: DSFSparklineOverlay.Line.MarkerDrawingBlock? = nil,
gridLines: DSFSparkline.GridLinesDefinition? = nil,
primaryFill: (any DSFSparklineFillable)? = nil,
secondaryFill: (any DSFSparklineFillable)? = nil
) {
self.dataSource = dataSource
self.graphColor = graphColor
self.showZeroLine = showZeroLine
self.zeroLineDefinition = zeroLineDefinition
self.centeredAtZeroLine = centeredAtZeroLine
self.lowerGraphColor = lowerGraphColor
self.lineWidth = lineWidth
self.interpolated = interpolated
self.lineShading = lineShading
self.shadowed = shadowed
self.highlightDefinitions = highlightDefinitions
self.markerSize = markerSize
self.markerDrawingBlock = markerDrawingBlock
self.gridLines = gridLines
self.primaryFill = primaryFill
self.secondaryFill = secondaryFill
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineLineGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineLineGraphView
#else
public typealias UIViewType = DSFSparklineLineGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineLineGraphView.SwiftUI
init(_ sparkline: DSFSparklineLineGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeLineGraph(_: Context) -> DSFSparklineLineGraphView {
let view = DSFSparklineLineGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.graphColor = self.graphColor
view.lineWidth = self.lineWidth
view.interpolated = self.interpolated
view.lineShading = self.lineShading
view.shadowed = self.shadowed
view.zeroLineVisible = self.showZeroLine
view.setZeroLineDefinition(self.zeroLineDefinition)
view.centeredAtZeroLine = self.centeredAtZeroLine
view.lowerGraphColor = self.lowerGraphColor
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
if let gridLines = self.gridLines {
view.setGridLineDefinition(gridLines)
}
else {
view.gridLinesVisible = false
}
view.markerSize = self.markerSize
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineLineGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineLineGraphView {
return self.makeLineGraph(context)
}
func updateUIView(_ view: DSFSparklineLineGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineLineGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineLineGraphView {
return self.makeLineGraph(context)
}
func updateNSView(_ view: DSFSparklineLineGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineLineGraphView.SwiftUI {
func updateView(_ view: DSFSparklineLineGraphView) {
UpdateIfNotEqual(result: &view.graphColor, val: self.graphColor)
UpdateIfNotEqual(result: &view.zeroLineVisible, val: self.showZeroLine)
view.setZeroLineDefinition(self.zeroLineDefinition)
UpdateIfNotEqual(result: &view.centeredAtZeroLine, val: self.centeredAtZeroLine)
UpdateIfNotEqual(result: &view.lowerGraphColor, val: self.lowerGraphColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.interpolated, val: self.interpolated)
UpdateIfNotEqual(result: &view.lineShading, val: self.lineShading)
UpdateIfNotEqual(result: &view.shadowed, val: self.shadowed)
UpdateIfNotEqual(result: &view.markerSize, val: self.markerSize)
view.markerDrawingBlock = self.markerDrawingBlock
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
else {
view.highlightRangeVisible = false
view.highlightRangeDefinition = []
}
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineLineGraphView.swift
================================================
//
// DSFSparklineLineGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import Cocoa
#else
import UIKit
#endif
/// A sparkline that draws a line graph
@objc public class DSFSparklineLineGraphView: DSFSparklineZeroLineGraphView {
let overlay = DSFSparklineOverlay.Line()
/// The width for the line drawn on the graph
@objc public dynamic var lineWidth: CGFloat = 1.5 {
didSet {
self.overlay.strokeWidth = self.lineWidth
}
}
/// Interpolate a curve between the points
@objc public dynamic var interpolated: Bool = false {
didSet {
self.overlay.interpolated = self.interpolated
}
}
/// Shade the area under the line
@objc public dynamic var lineShading: Bool = true {
didSet {
if lineShading == true {
self.overlay.primaryFill = DSFSparkline.Fill.Color(self.graphColor.withAlphaComponent(0.4).cgColor)
}
else {
self.overlay.primaryFill = nil
}
}
}
/// Draw a shadow under the line
@objc public dynamic var shadowed: Bool = false {
didSet {
self.overlay.shadow = self.shadowed ? NSShadow.sparklineDefault : nil
}
}
/// The size of the markers to draw. If the markerSize is less than 0, markers will not draw
@objc public dynamic var markerSize: CGFloat = -1 {
didSet {
self.overlay.markerSize = self.markerSize
}
}
/// An optional drawing function for custom drawing markers. When nil, uses the standard circle for each marker
///
/// The `markerSize` value is used to determine the frameSize of each marker.
/// If `markerSize` is less than 1, this block will not be called.
///
/// Note that this function is called very frequently so make sure its performant!
@objc public var markerDrawingBlock: DSFSparklineOverlay.Line.MarkerDrawingBlock? = nil {
didSet {
self.overlay.markerDrawingBlock = self.markerDrawingBlock
}
}
/// Should the graph be centered at the zero line?
@objc public dynamic var centeredAtZeroLine: Bool = false {
didSet {
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
}
}
/// The primary fill (for the area of the graph ABOVE the zero line)
@objc public dynamic var primaryFill: (any DSFSparklineFillable)? {
get { self.overlay.primaryFill }
set { self.overlay.primaryFill = newValue }
}
/// The secondary fill (for the area of the graph UNDER the zero line)
@objc public dynamic var secondaryFill: (any DSFSparklineFillable)? {
get { self.overlay.secondaryFill }
set { self.overlay.secondaryFill = newValue }
}
// Optional gradient colors
internal var gradient: CGGradient?
internal var lowerGradient: CGGradient?
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.colorDidChange()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.strokeWidth = self.lineWidth
self.overlay.interpolated = self.interpolated
self.overlay.shadow = self.shadowed ? NSShadow.sparklineDefault : nil
self.overlay.markerSize = self.markerSize
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
self.overlay.primaryStrokeColor = self.graphColor.cgColor
self.overlay.secondaryStrokeColor = self.lowerColor.cgColor
// Backwards compatibility
let color = self.graphColor
let fill = DSFSparkline.Fill.Gradient(colors: [
color.withAlphaComponent(0.4).cgColor,
color.withAlphaComponent(0.2).cgColor
])
self.overlay.primaryFill = fill
if let lowerColor = self.lowerGraphColor {
let fill = DSFSparkline.Fill.Gradient(colors: [
lowerColor.withAlphaComponent(0.4).cgColor,
lowerColor.withAlphaComponent(0.2).cgColor
])
self.overlay.secondaryFill = fill
}
else {
// Fallback - if secondary fill not defined the compatibility view is to use the primary fill view
self.overlay.secondaryFill = self.overlay.primaryFill
}
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklinePercentBarGraphView+SwiftUI.swift
================================================
//
// DSFSparklinePercentBarGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklinePercentBarGraphView {
/// The SwiftUI percent bar graph
struct SwiftUI {
/// Datasource for the graph
let style: DSFSparkline.PercentBar.Style
/// Palette to use when coloring the chart
let value: Double
/// The animation style to apply to changes
let animationStyle: DSFSparkline.AnimationStyle?
/// Create a sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
/// - Parameters:
public init(
style: DSFSparkline.PercentBar.Style = DSFSparkline.PercentBar.Style(),
value: Double,
animationStyle: DSFSparkline.AnimationStyle? = nil
) {
self.style = style
self.value = value
self.animationStyle = animationStyle
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklinePercentBarGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklinePercentBarGraphView
#else
public typealias UIViewType = DSFSparklinePercentBarGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklinePercentBarGraphView.SwiftUI
init(_ sparkline: DSFSparklinePercentBarGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makePercentBar(_: Context) -> DSFSparklinePercentBarGraphView {
let view = DSFSparklinePercentBarGraphView(frame: .zero)
view.displayStyle = self.style
view.value = CGFloat(self.value)
view.animationStyle = self.animationStyle
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklinePercentBarGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklinePercentBarGraphView {
return self.makePercentBar(context)
}
func updateUIView(_ view: DSFSparklinePercentBarGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklinePercentBarGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklinePercentBarGraphView {
return self.makePercentBar(context)
}
func updateNSView(_ view: DSFSparklinePercentBarGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklinePercentBarGraphView.SwiftUI {
func updateView(_ view: DSFSparklinePercentBarGraphView) {
view.displayStyle = self.style
view.value = CGFloat(self.value)
view.animationStyle = self.animationStyle
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklinePercentBarGraphView.swift
================================================
//
// DSFSparklinePercentBarGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 16/3/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline that draws a percent bar
@objc public class DSFSparklinePercentBarGraphView: DSFSparklineSurfaceView {
// MARK: - Value
/// The initial value to display in the percent bar
@objc public dynamic var value: CGFloat = 0.2 {
didSet {
self.overlay.value = self.value
self.displayUpdate()
}
}
/// The style for presenting the percent bar
@objc public var displayStyle = DSFSparkline.PercentBar.Style() {
didSet {
self.overlay.displayStyle = displayStyle
self.updateDisplay()
}
}
/// The corner radius for the bar
@objc public dynamic var cornerRadius: CGFloat = 5 {
didSet {
self.displayStyle.cornerRadius = self.cornerRadius
self.displayUpdate()
}
}
/// Should the control display a text label for the percent bar
@objc public dynamic var showLabel: Bool = true {
didSet {
self.displayStyle.showLabel = self.showLabel
self.displayUpdate()
}
}
/// The background of the bar color for the percent bar chart
@objc public dynamic var underBarColor: DSFColor = .clear {
didSet {
self.displayStyle.underBarColor = self.underBarColor.cgColor
self.displayUpdate()
}
}
/// The color for text displayed on the background
@objc public dynamic var underBarTextColor: DSFColor = .white {
didSet {
self.displayStyle.underBarTextColor = self.underBarTextColor.cgColor
self.displayUpdate()
}
}
// MARK: - Bar Color
/// The bar color for the percent bar chart
@objc public dynamic var barColor: DSFColor = .black {
didSet {
self.displayStyle.barColor = self.barColor.cgColor
self.displayUpdate()
}
}
// MARK: - Background Color
/// The color for text displayed on the bar
@objc public dynamic var barTextColor: DSFColor = .white {
didSet {
self.displayStyle.barTextColor = self.barTextColor.cgColor
self.displayUpdate()
}
}
// MARK: - Font
/// The name of the font to use when drawing the percent bar label
@objc public dynamic var fontName: String? = nil {
didSet {
if let f = fontName,
let font = DSFFont(name: f, size: self.fontSize) {
self.displayStyle.font = font
}
else {
self.displayStyle.font = DSFFont.systemFont(ofSize: self.fontSize)
}
self.displayUpdate()
}
}
/// The size (in points) of the font to use when drawing the percent bar label
@objc public dynamic var fontSize: CGFloat = 12 {
didSet {
let font = self.displayStyle.font
#if os(macOS)
if let newFont = DSFFont(descriptor: font.fontDescriptor, size: fontSize) {
self.displayStyle.font = newFont
}
#else
self.displayStyle.font = DSFFont(descriptor: font.fontDescriptor, size: fontSize)
#endif
self.overlay.displayStyle = self.displayStyle
self.displayUpdate()
}
}
/// The left inset for the bar
@objc public dynamic var leftInset: CGFloat = 0 {
didSet {
self.displayStyle.barEdgeInsets.left = self.leftInset
self.displayUpdate()
}
}
/// The top inset for the bar
@objc public dynamic var topInset: CGFloat = 0 {
didSet {
self.displayStyle.barEdgeInsets.top = self.topInset
self.displayUpdate()
}
}
/// The right inset for the bar
@objc public dynamic var rightInset: CGFloat = 0 {
didSet {
self.displayStyle.barEdgeInsets.right = self.rightInset
self.displayUpdate()
}
}
/// The bottom inset for the bar
@objc public dynamic var bottomInset: CGFloat = 0 {
didSet {
self.displayStyle.barEdgeInsets.bottom = self.bottomInset
self.displayUpdate()
}
}
// MARK: - Animation
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil {
didSet {
self.overlay.animationStyle = self.animationStyle
self.displayUpdate()
}
}
// MARK: - Control
/// Initializer
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
/// Initializer
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
// The overlay
private let overlay = DSFSparklineOverlay.PercentBar(value: 0)
}
private extension DSFSparklinePercentBarGraphView {
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
self.displayStyle.underBarColor = self.underBarColor.cgColor
self.displayStyle.underBarTextColor = self.underBarTextColor.cgColor
self.displayStyle.barColor = self.barColor.cgColor
self.displayStyle.barTextColor = self.barTextColor.cgColor
if let f = fontName,
let font = DSFFont(name: f, size: self.fontSize) {
self.displayStyle.font = font
}
else {
self.displayStyle.font = DSFFont.systemFont(ofSize: self.fontSize)
}
self.overlay.displayStyle = self.displayStyle
self.overlay.value = self.value
self.overlay.setNeedsDisplay()
self.updateDisplay()
}
func displayUpdate() {
self.overlay.displayStyle = self.displayStyle
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklinePieGraphView+SwiftUI.swift
================================================
//
// DSFSparklinePieGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklinePieGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.StaticDataSource
/// Palette to use when coloring the chart
let palette: DSFSparkline.Palette
/// Stroke Color
let strokeColor: DSFColor?
/// Stroke Width
let lineWidth: CGFloat
/// Should we animate the dataSource changes
let animationStyle: DSFSparkline.AnimationStyle?
/// Create a sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
/// - Parameters:
/// - dataSource: The data source for the graph
public init(
dataSource: DSFSparkline.StaticDataSource,
palette: DSFSparkline.Palette = .shared,
strokeColor: DSFColor? = nil,
lineWidth: CGFloat = 1.0,
animationStyle: DSFSparkline.AnimationStyle? = nil
) {
self.dataSource = dataSource
self.strokeColor = strokeColor
self.lineWidth = lineWidth
self.palette = palette
self.animationStyle = animationStyle
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklinePieGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklinePieGraphView
#else
public typealias UIViewType = DSFSparklinePieGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklinePieGraphView.SwiftUI
init(_ sparkline: DSFSparklinePieGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makePieGraph(_: Context) -> DSFSparklinePieGraphView {
let view = DSFSparklinePieGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.strokeColor = self.strokeColor
view.lineWidth = self.lineWidth
view.palette = self.palette
view.animationStyle = self.animationStyle
view.dataSource = self.dataSource
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklinePieGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklinePieGraphView {
return self.makePieGraph(context)
}
func updateUIView(_ view: DSFSparklinePieGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklinePieGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklinePieGraphView {
return self.makePieGraph(context)
}
func updateNSView(_ view: DSFSparklinePieGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklinePieGraphView.SwiftUI {
func updateView(_ view: DSFSparklinePieGraphView) {
UpdateIfNotEqual(result: &view.strokeColor, val: self.strokeColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.palette, val: self.palette)
UpdateIfNotEqual(result: &view.animationStyle, val: self.animationStyle)
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklinePieGraphView.swift
================================================
//
// DSFSparklinePieGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline that draws a simple pie chart
@objc public class DSFSparklinePieGraphView: DSFSparklineSurfaceView {
let pieOverlay = DSFSparklineOverlay.Pie()
/// The data to be displayed in the pie.
///
/// The values become a percentage of the total value stored within the
/// dataStore, and as such each value ends up being drawn as a fraction of the total.
/// So for example, if you want the pie chart to represent the number of red cars vs. number of
/// blue cars, you just set the values directly.
@objc public var dataSource = DSFSparkline.StaticDataSource() {
didSet {
self.pieOverlay.dataSource = self.dataSource
}
}
/// The stroke color for the pie chart
@objc public dynamic var strokeColor: DSFColor? {
didSet {
self.pieOverlay.strokeColor = self.strokeColor?.cgColor
}
}
/// The width of the stroke line
@objc public dynamic var lineWidth: CGFloat = 0.5 {
didSet {
self.pieOverlay.lineWidth = self.lineWidth
}
}
/// Should the pie chart animate in?
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil {
didSet {
self.pieOverlay.animationStyle = self.animationStyle
}
}
/// The palette to use when drawing the pie chart
@objc public var palette = DSFSparkline.Palette.shared {
didSet {
self.pieOverlay.palette = self.palette
}
}
// MARK: - Initializers
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.pieOverlay)
self.pieOverlay.setNeedsDisplay()
self.pieOverlay.strokeColor = self.strokeColor?.cgColor
self.pieOverlay.lineWidth = self.lineWidth
self.pieOverlay.animationStyle = self.animationStyle
self.pieOverlay.dataSource = self.dataSource
}
// MARK: - Privates
internal var animator = ArbitraryAnimator()
internal var fractionComplete: CGFloat = 0
internal var total: CGFloat = 0.0
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineStackLineGraphView+SwiftUI.swift
================================================
//
// DSFSparklineLineGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 7/12/20.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineStackLineGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The primary color for the sparkline
let graphColor: DSFColor
/// Draw a dotted line at the zero point on the y-axis
let showZeroLine: Bool
/// The drawing definition for the zero line point
let zeroLineDefinition: DSFSparkline.ZeroLineDefinition
/// The width for the line drawn on the graph
let lineWidth: CGFloat
/// Should the area below the line be shaded?
let lineShading: Bool
/// Draw a shadow under the line
let shadowed: Bool
/// Should the line graph be centered around the zero-line?
let centeredAtZeroLine: Bool
/// The color used to draw values lower than the zero-line, or nil for the same as the graph color
let lowerGraphColor: DSFColor?
/// Highlight y-ranges within the graph
let highlightDefinitions: [DSFSparkline.HighlightRangeDefinition]
/// The grid lines to be drawn on the graph
let gridLines: DSFSparkline.GridLinesDefinition?
/// Primary fill
let primaryFill: DSFSparklineFillable?
/// Secondary fill
let secondaryFill: DSFSparklineFillable?
/// Create a sparkline graph that displays dots (like the CPU history graph in Activity Monitor)
/// - Parameters:
/// - dataSource: The data source for the graph
/// - graphColor: The color of the dots that are set
/// - lineWidth: The width of the line
/// - lineShading: If true, shades the underside of the drawn line.
/// - shadowed: If true, draws a shadow under the line part of the graph.
/// - showZeroLine: Show or hide a 'zero line' horizontal line
/// - zeroLineDefinition: the settings for drawing the zero line
/// - centeredAtZeroLine: Should the line graph be centered around the zero-line?
/// - lowerGraphColor: The color used to draw values lower than the zero-line, or nil for the same as the graph color
/// - highlightDefinitions: The style of the y-range highlight
/// - gridLines: DSFSparkline.GridLinesDefinition? = nil
public init(
dataSource: DSFSparkline.DataSource,
graphColor: DSFColor,
lineWidth: CGFloat = 1.5,
lineShading: Bool = true,
shadowed: Bool = false,
showZeroLine: Bool = false,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition = .shared,
centeredAtZeroLine: Bool = false,
lowerGraphColor: DSFColor? = nil,
highlightDefinitions: [DSFSparkline.HighlightRangeDefinition] = [],
gridLines: DSFSparkline.GridLinesDefinition? = nil,
primaryFill: (any DSFSparklineFillable)? = nil,
secondaryFill: (any DSFSparklineFillable)? = nil
) {
self.dataSource = dataSource
self.graphColor = graphColor
self.showZeroLine = showZeroLine
self.zeroLineDefinition = zeroLineDefinition
self.centeredAtZeroLine = centeredAtZeroLine
self.lowerGraphColor = lowerGraphColor
self.lineWidth = lineWidth
self.lineShading = lineShading
self.shadowed = shadowed
self.highlightDefinitions = highlightDefinitions
self.gridLines = gridLines
self.primaryFill = primaryFill
self.secondaryFill = secondaryFill
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineStackLineGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineStackLineGraphView
#else
public typealias UIViewType = DSFSparklineStackLineGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineStackLineGraphView.SwiftUI
init(_ sparkline: DSFSparklineStackLineGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeLineGraph(_: Context) -> DSFSparklineStackLineGraphView {
let view = DSFSparklineStackLineGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.graphColor = self.graphColor
view.lineWidth = self.lineWidth
view.lineShading = self.lineShading
view.shadowed = self.shadowed
view.zeroLineVisible = self.showZeroLine
view.setZeroLineDefinition(self.zeroLineDefinition)
view.centeredAtZeroLine = self.centeredAtZeroLine
view.lowerGraphColor = self.lowerGraphColor
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
if let gridLines = self.gridLines {
view.setGridLineDefinition(gridLines)
}
else {
view.gridLinesVisible = false
}
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineStackLineGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineStackLineGraphView {
return self.makeLineGraph(context)
}
func updateUIView(_ view: DSFSparklineStackLineGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineStackLineGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineStackLineGraphView {
return self.makeLineGraph(context)
}
func updateNSView(_ view: DSFSparklineStackLineGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineStackLineGraphView.SwiftUI {
func updateView(_ view: DSFSparklineStackLineGraphView) {
UpdateIfNotEqual(result: &view.graphColor, val: self.graphColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.lineShading, val: self.lineShading)
UpdateIfNotEqual(result: &view.shadowed, val: self.shadowed)
UpdateIfNotEqual(result: &view.lineShading, val: self.lineShading)
UpdateIfNotEqual(result: &view.zeroLineVisible, val: self.showZeroLine)
view.setZeroLineDefinition(self.zeroLineDefinition)
UpdateIfNotEqual(result: &view.centeredAtZeroLine, val: self.centeredAtZeroLine)
UpdateIfNotEqual(result: &view.lowerGraphColor, val: self.lowerGraphColor)
if self.highlightDefinitions.count > 0 {
view.highlightRangeVisible = true
view.highlightRangeDefinition = self.highlightDefinitions
}
else {
view.highlightRangeVisible = false
view.highlightRangeDefinition = []
}
if let pf = self.primaryFill {
view.primaryFill = pf
}
if let sf = self.secondaryFill {
view.secondaryFill = sf
}
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineStackLineGraphView.swift
================================================
//
// DSFSparklineStackLineGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A stack line sparkline type
@objc public class DSFSparklineStackLineGraphView: DSFSparklineZeroLineGraphView {
let overlay = DSFSparklineOverlay.Stackline()
/// The width for the line drawn on the graph
@objc public dynamic var lineWidth: CGFloat = 1 {
didSet {
self.updateDisplay()
}
}
/// Shade the area under the line
@objc public dynamic var lineShading: Bool = true {
didSet {
self.overlay.primaryFill = DSFSparkline.Fill.Color(self.graphColor.withAlphaComponent(0.3).cgColor)
self.updateDisplay()
}
}
/// Draw a shadow under the line
@objc public dynamic var shadowed: Bool = false {
didSet {
self.overlay.shadow = self.shadowed ? NSShadow.sparklineDefault : nil
}
}
/// Should the graph be centered at the zero line?
@objc public dynamic var centeredAtZeroLine: Bool = false {
didSet {
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
}
}
/// The primary fill (for the area of the graph ABOVE the zero line)
@objc public dynamic var primaryFill: (any DSFSparklineFillable)? {
get { self.overlay.primaryFill }
set { self.overlay.primaryFill = newValue }
}
/// The secondary fill (for the area of the graph UNDER the zero line)
@objc public dynamic var secondaryFill: (any DSFSparklineFillable)? {
get { self.overlay.secondaryFill }
set { self.overlay.secondaryFill = newValue }
}
internal var gradient: CGGradient?
internal var lowerGradient: CGGradient?
override public init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
}
override public func colorDidChange() {
super.colorDidChange()
// Update the gradients to match the color change
self.gradient = CGGradient(
colorsSpace: nil,
colors: [self.graphColor.withAlphaComponent(0.4).cgColor,
self.graphColor.withAlphaComponent(0.2).cgColor] as CFArray,
locations: [1.0, 0.0]
)!
let lc = self.lowerColor
self.lowerGradient = CGGradient(
colorsSpace: nil,
colors: [lc.withAlphaComponent(0.4).cgColor,
lc.withAlphaComponent(0.2).cgColor] as CFArray,
locations: [1.0, 0.0]
)!
self.overlay.strokeWidth = self.lineWidth
self.overlay.primaryStrokeColor = self.graphColor.cgColor
self.overlay.secondaryStrokeColor = self.lowerColor.cgColor
self.overlay.centeredAtZeroLine = self.centeredAtZeroLine
// Backwards compatibility
let color = self.graphColor
let fill = DSFSparkline.Fill.Gradient(colors: [
color.withAlphaComponent(0.4).cgColor,
color.withAlphaComponent(0.2).cgColor,
])
self.overlay.primaryFill = fill
if let lowerColor = self.lowerGraphColor {
let fill = DSFSparkline.Fill.Gradient(colors: [
lowerColor.withAlphaComponent(0.4).cgColor,
lowerColor.withAlphaComponent(0.2).cgColor,
])
self.overlay.secondaryFill = fill
}
else {
// Fallback - if secondary fill not defined the compatibility view is to use the primary fill view
self.overlay.secondaryFill = self.overlay.primaryFill
}
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineStripesGraphView+SwiftUI.swift
================================================
//
// DSFSparklineDataBarGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 12/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 11, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineStripesGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
let barSpacing: UInt
let integral: Bool
let gradient: DSFSparkline.GradientBucket
static public let shared = DSFSparkline.GradientBucket(posts: [
DSFSparkline.GradientBucket.Post(color: CGColor(red: 1, green: 0, blue: 0, alpha: 1), location: 0),
DSFSparkline.GradientBucket.Post(color: CGColor(red: 0, green: 0, blue: 1, alpha: 1), location: 1)
])
/// Create a databar graph
/// - Parameters:
/// - dataSource: The data source for the graph
/// - maximumTotalValue: The maximum _total_ value. If the datasource values total is greater than this value, it clips the display
/// - palette: The color palette to use when drawing the graph
/// - unsetColor: (optional) the color to use when drawing the background (useful when the maximumValue is also set)
/// - strokeColor: The color to draw the separator lines between data points
/// - lineWidth: The width of the separator lines
/// - animated: If set, animates any datasource value changes
/// - animationDuration: The duration for the animate-in animation
public init(dataSource: DSFSparkline.DataSource,
integral: Bool = false,
barSpacing: UInt = 0,
gradient: DSFSparkline.GradientBucket = Self.shared)
{
self.dataSource = dataSource
self.integral = integral
self.barSpacing = barSpacing
self.gradient = gradient
}
}
}
@available(macOS 11, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineStripesGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineStripesGraphView
#else
public typealias UIViewType = DSFSparklineStripesGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineStripesGraphView.SwiftUI
init(_ sparkline: DSFSparklineStripesGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeStripesGraph(_: Context) -> DSFSparklineStripesGraphView {
let view = DSFSparklineStripesGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.barSpacing = self.barSpacing
view.integral = self.integral
view.gradient = self.gradient
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineStripesGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineStripesGraphView {
return self.makeStripesGraph(context)
}
func updateUIView(_ view: DSFSparklineStripesGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 11, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineStripesGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineStripesGraphView {
return self.makeStripesGraph(context)
}
func updateNSView(_ view: DSFSparklineStripesGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 11, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineStripesGraphView.SwiftUI {
func updateView(_ view: DSFSparklineStripesGraphView) {
UpdateIfNotEqual(result: &view.barSpacing, val: self.barSpacing)
UpdateIfNotEqual(result: &view.integral, val: self.integral)
view.gradient = self.gradient
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineStripesGraphView.swift
================================================
//
// DSFSparklineStripesGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 15/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline graph that displays solid color bars with a gradient (like the climate graph)
public class DSFSparklineStripesGraphView: DSFSparklineDataSourceView {
let overlay = DSFSparklineOverlay.Stripes()
@objc public dynamic var integral: Bool = true {
didSet {
self.colorDidChange()
self.updateDisplay()
}
}
/// The spacing (in pixels) between each bar
@objc public dynamic var barSpacing: UInt = 0 {
didSet {
self.colorDidChange()
self.updateDisplay()
}
}
/// The color gradient to use when rendering.
///
/// Note that transparent gradients display strangely and not as I would expect them to.
/// Stick with solid colors in your gradient for the current time.
@objc public var gradient: DSFSparkline.GradientBucket? {
didSet {
self.colorDidChange()
self.updateDisplay()
}
}
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.colorDidChange()
self.overlay.setNeedsDisplay()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.integral = self.integral
self.overlay.barSpacing = self.barSpacing
self.overlay.gradient = self.gradient?.copyGradientBucket() ?? DSFSparklineOverlay.Stripes.defaultGradient
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineTabletGraphView+SwiftUI.swift
================================================
//
// DSFSparklineZeroLineDefinition.swift
// DSFSparklines
//
// Created by Darren Ford on 25/01/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
/// A win-loss sparkline. DataSource values > 0 represent a 'win', whereas <= 0 represent a loss.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineTabletGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The 'win' color for the sparkline
let winColor: DSFColor
/// The 'loss' color for the sparkline
let lossColor: DSFColor
/// The line width (in pixels) to use when drawing the border of each tablet
let lineWidth: CGFloat
/// The spacing (in pixels) between each tablet
let barSpacing: CGFloat
/// Create a bar graph sparkline
/// - Parameters:
/// - dataSource: The data source for the graph
/// - winColor: The 'win' color for the sparkline
/// - lossColor: The 'loss' color for the sparkline
/// - lineWidth: The width of the line around each tablet
/// - barSpacing: The spacing between the tablets
public init(dataSource: DSFSparkline.DataSource,
winColor: DSFColor = .systemGreen,
lossColor: DSFColor = .systemRed,
lineWidth: CGFloat = 1,
barSpacing: CGFloat = 1)
{
self.dataSource = dataSource
self.winColor = winColor
self.lossColor = lossColor
self.lineWidth = lineWidth
self.barSpacing = barSpacing
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineTabletGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineTabletGraphView
#else
public typealias UIViewType = DSFSparklineTabletGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineTabletGraphView.SwiftUI
init(_ sparkline: DSFSparklineTabletGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
private func makeTabletGraph(_: Context) -> DSFSparklineTabletGraphView {
let view = DSFSparklineTabletGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.winColor = self.winColor
view.lossColor = self.lossColor
view.barSpacing = self.barSpacing
view.lineWidth = self.lineWidth
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineTabletGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineTabletGraphView {
return self.makeTabletGraph(context)
}
func updateUIView(_ view: DSFSparklineTabletGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineTabletGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineTabletGraphView {
return self.makeTabletGraph(context)
}
func updateNSView(_ view: DSFSparklineTabletGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineTabletGraphView.SwiftUI {
func updateView(_ view: DSFSparklineTabletGraphView) {
UpdateIfNotEqual(result: &view.winColor, val: self.winColor)
UpdateIfNotEqual(result: &view.lossColor, val: self.lossColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.barSpacing, val: self.barSpacing)
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineTabletGraphView.swift
================================================
//
// DSFSparklineTabletGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 25/01/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A tablet-style sparkline. Similar to win/loss except rendering as a row of filled/unfilled circles
@objc public class DSFSparklineTabletGraphView: DSFSparklineDataSourceView {
let overlay = DSFSparklineOverlay.Tablet()
static let clear: CGColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 0])!
/// The width of the stroke for the tablet
@objc public dynamic var lineWidth: CGFloat = 1.0 {
didSet {
self.updateDisplay()
}
}
/// The spacing (in pixels) between each bar
@objc public dynamic var barSpacing: CGFloat = 1.0 {
didSet {
self.updateDisplay()
}
}
#if os(macOS)
/// The color to draw the 'win' boxes
@objc public dynamic var winColor: NSColor = NSColor.systemGreen {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'loss' boxes
@objc public dynamic var lossColor: NSColor = NSColor.systemRed {
didSet {
self.colorDidChange()
}
}
#else
/// The color to draw the 'win' boxes
@objc public dynamic var winColor: UIColor = UIColor.systemGreen {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'loss' boxes
@objc public dynamic var lossColor: UIColor = UIColor.systemRed {
didSet {
self.colorDidChange()
}
}
#endif
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.lineWidth = self.lineWidth
self.overlay.tabletSpacing = self.barSpacing // backwards compatible naming here
self.overlay.winFill = DSFSparkline.Fill.Color(self.winColor.withAlphaComponent(0.3).cgColor)
self.overlay.winStrokeColor = self.winColor.cgColor
self.overlay.lossStrokeColor = self.lossColor.cgColor
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineWinLossGraphView+SwiftUI.swift
================================================
//
// DSFSparklineWinLossGraphView+SwiftUI.swift
// DSFSparklines
//
// Created by Darren Ford on 7/12/20.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
/// A win-loss sparkline. DataSource values > 0 represent a 'win', whereas <= 0 represent a loss.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineWinLossGraphView {
struct SwiftUI {
/// Datasource for the graph
let dataSource: DSFSparkline.DataSource
/// The 'win' color for the sparkline
let winColor: DSFColor
/// The 'loss' color for the sparkline
let lossColor: DSFColor
/// The 'tie' color for the sparkline. If nil, tie values are not drawn
let tieColor: DSFColor?
/// The line width (in pixels) to use when drawing the border of each bar
let lineWidth: UInt
/// The spacing (in pixels) between each bar
let barSpacing: UInt
/// Draw a dotted line at the zero point on the y-axis
let showZeroLine: Bool
/// The drawing definition for the zero line point
let zeroLineDefinition: DSFSparkline.ZeroLineDefinition
/// Create a bar graph sparkline
/// - Parameters:
/// - dataSource: The data source for the graph
/// - winColor: The 'win' color for the sparkline
/// - lossColor: The 'loss' color for the sparkline
/// - tieColor: The 'tie' color for the sparkline
/// - lineWidth: The width of the line around each bar
/// - barSpacing: The spacing between the bars
/// - showZeroLine: Show or hide a 'zero line' horizontal line
/// - zeroLineDefinition: the settings for drawing the zero line
public init(dataSource: DSFSparkline.DataSource,
winColor: DSFColor = .systemGreen,
lossColor: DSFColor = .systemRed,
tieColor: DSFColor? = nil,
lineWidth: UInt = 1,
barSpacing: UInt = 1,
showZeroLine: Bool = false,
zeroLineDefinition: DSFSparkline.ZeroLineDefinition = .shared)
{
self.dataSource = dataSource
self.winColor = winColor
self.lossColor = lossColor
self.tieColor = tieColor
self.lineWidth = lineWidth
self.barSpacing = barSpacing
self.showZeroLine = showZeroLine
self.zeroLineDefinition = zeroLineDefinition
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineWinLossGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineWinLossGraphView
#else
public typealias UIViewType = DSFSparklineWinLossGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineWinLossGraphView.SwiftUI
init(_ sparkline: DSFSparklineWinLossGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
private func makeWinLossGraph(_: Context) -> DSFSparklineWinLossGraphView {
let view = DSFSparklineWinLossGraphView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.dataSource = self.dataSource
view.winColor = self.winColor
view.lossColor = self.lossColor
view.tieColor = self.tieColor
view.barSpacing = self.barSpacing
view.lineWidth = self.lineWidth
view.centerlineColor = self.showZeroLine ? self.zeroLineDefinition.color : nil
view.centerlineWidth = self.zeroLineDefinition.lineWidth
view.centerlineDashStyle = self.zeroLineDefinition.lineDashStyle
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineWinLossGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineWinLossGraphView {
return self.makeWinLossGraph(context)
}
func updateUIView(_ view: DSFSparklineWinLossGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineWinLossGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineWinLossGraphView {
return self.makeWinLossGraph(context)
}
func updateNSView(_ view: DSFSparklineWinLossGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineWinLossGraphView.SwiftUI {
func updateView(_ view: DSFSparklineWinLossGraphView) {
UpdateIfNotEqual(result: &view.winColor, val: self.winColor)
UpdateIfNotEqual(result: &view.lossColor, val: self.lossColor)
UpdateIfNotEqual(result: &view.tieColor, val: self.tieColor)
UpdateIfNotEqual(result: &view.lineWidth, val: self.lineWidth)
UpdateIfNotEqual(result: &view.barSpacing, val: self.barSpacing)
UpdateIfNotEqual(result: &view.centerlineColor, val: self.showZeroLine ? self.zeroLineDefinition.color : nil)
UpdateIfNotEqual(result: &view.centerlineWidth, val: self.zeroLineDefinition.lineWidth)
UpdateIfNotEqual(result: &view.centerlineDashStyle, val: self.zeroLineDefinition.lineDashStyle)
UpdateIfNotEqual(result: &view.dataSource, val: self.dataSource)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineWinLossGraphView.swift
================================================
//
// DSFSparklineWinLossGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
@objc public class DSFSparklineWinLossGraphView: DSFSparklineDataSourceView {
let overlay = DSFSparklineOverlay.WinLossTie()
/// The line width (in pixels) to use when drawing the border of each bar
@objc public dynamic var lineWidth: UInt = 1 {
didSet {
self.updateDisplay()
}
}
/// The spacing (in pixels) between each bar
@objc public dynamic var barSpacing: UInt = 1 {
didSet {
self.updateDisplay()
}
}
#if os(macOS)
/// The color to draw the 'win' boxes
@objc public dynamic var winColor: NSColor = NSColor.systemGreen {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'loss' boxes
@objc public dynamic var lossColor: NSColor = NSColor.systemRed {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'tie' boxes
@objc public dynamic var tieColor: NSColor? = nil {
didSet {
self.colorDidChange()
}
}
#else
/// The color to draw the 'win' boxes
@objc public dynamic var winColor: UIColor = UIColor.systemGreen {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'loss' boxes
@objc public dynamic var lossColor: UIColor = UIColor.systemRed {
didSet {
self.colorDidChange()
}
}
/// The color to draw the 'tie' boxes
@objc public dynamic var tieColor: UIColor? = nil {
didSet {
self.colorDidChange()
}
}
#endif
// MARK: - Centerline Definitions
/// The centerline color. If nil, then no centerline is drawn
#if os(macOS)
@objc public dynamic var centerlineColor: NSColor? = nil {
didSet {
self.colorDidChange()
}
}
#else
@objc public dynamic var centerlineColor: UIColor? = nil {
didSet {
self.colorDidChange()
}
}
#endif
// The width of the zero line
@objc public dynamic var centerlineWidth: CGFloat = 1 {
didSet {
self.colorDidChange()
}
}
/// The pattern for drawing the line
var centerlineDashStyle: [CGFloat] = [1,1]
/// A string representation of the line dash lengths for the center line, eg. "1,3,4,2".
/// If you want a solid line, specify "-"
///
/// Primarily used for Interface Builder integration
@objc public dynamic var centerlineDashStyleString: String = "1,1" {
didSet {
if self.centerlineDashStyleString == "-" {
// Solid line
self.centerlineDashStyle = []
}
else {
let components = self.centerlineDashStyleString.extractCGFloats()
if components.count >= 2 {
self.centerlineDashStyle = components
}
else {
Swift.print("ERROR: Zero Line Style string format is incompatible (\(self.centerlineDashStyleString) -> \(components))")
self.centerlineDashStyle = []
}
}
self.colorDidChange()
}
}
// MARK: - Initializers
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
func configure() {
self.addOverlay(self.overlay)
self.overlay.setNeedsDisplay()
}
override func colorDidChange() {
super.colorDidChange()
self.overlay.lineWidth = self.lineWidth
self.overlay.barSpacing = self.barSpacing
self.overlay.winFill = DSFSparkline.Fill.Color(self.winColor.withAlphaComponent(0.3).cgColor)
self.overlay.winStroke = self.winColor.cgColor
self.overlay.lossFill = DSFSparkline.Fill.Color(self.lossColor.withAlphaComponent(0.3).cgColor)
self.overlay.lossStroke = self.lossColor.cgColor
self.overlay.tieStroke = self.tieColor?.cgColor
if let tc = self.tieColor {
self.overlay.tieFill = DSFSparkline.Fill.Color(tc.withAlphaComponent(0.3).cgColor)
}
if let color = self.centerlineColor {
self.overlay.centerLine = .init(color: color,
lineWidth: self.centerlineWidth,
lineDashStyle: self.centerlineDashStyle)
}
else {
self.overlay.centerLine = nil
}
self.updateDisplay()
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineWiperGaugeGraphView+SwiftUI.swift
================================================
//
// DSFSparklineWiperGaugeGraphView+SwiftUI.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if canImport(SwiftUI)
import SwiftUI
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineWiperGaugeGraphView {
/// The SwiftUI percent bar graph
struct SwiftUI {
/// Palette to use when coloring the chart
let valueColor: DSFSparkline.ValueBasedFill
/// The 'background' color for the value
let valueBackgroundColor: DSFColor?
/// The color of the upper arc for the control
let upperArcColor: DSFColor?
/// The color of the pointer
let pointerColor: DSFColor?
/// The background color for the gauge
let backgroundColor: DSFColor?
/// The value to display in the chart
let value: Double
/// Should changes to value be animated?
let animationStyle: DSFSparkline.AnimationStyle?
/// Create a sparkline graph that displays a 0 ... 1 value as a gauge
public init(
valueColor: DSFSparkline.ValueBasedFill,
value: Double,
valueBackgroundColor: DSFColor? = nil,
upperArcColor: DSFColor? = nil,
pointerColor: DSFColor? = nil,
backgroundColor: DSFColor? = nil,
animationStyle: DSFSparkline.AnimationStyle? = nil
) {
self.valueColor = valueColor
self.valueBackgroundColor = valueBackgroundColor
self.upperArcColor = upperArcColor
self.pointerColor = pointerColor
self.backgroundColor = backgroundColor
self.value = value
self.animationStyle = animationStyle
}
/// Create a sparkline graph that displays a 0 ... 1 value as a gauge
public init(
valueColor: DSFColor,
value: Double,
valueBackgroundColor: DSFColor? = nil,
upperArcColor: DSFColor? = nil,
pointerColor: DSFColor? = nil,
backgroundColor: DSFColor? = nil,
animationStyle: DSFSparkline.AnimationStyle? = nil
) {
self.valueColor = DSFSparkline.ValueBasedFill(flatColor: valueColor.cgColor)
self.valueBackgroundColor = valueBackgroundColor
self.upperArcColor = upperArcColor
self.pointerColor = pointerColor
self.backgroundColor = backgroundColor
self.value = value
self.animationStyle = animationStyle
}
}
}
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
extension DSFSparklineWiperGaugeGraphView.SwiftUI: DSFViewRepresentable {
#if os(macOS)
public typealias NSViewType = DSFSparklineWiperGaugeGraphView
#else
public typealias UIViewType = DSFSparklineWiperGaugeGraphView
#endif
public class Coordinator: NSObject {
let parent: DSFSparklineWiperGaugeGraphView.SwiftUI
init(_ sparkline: DSFSparklineWiperGaugeGraphView.SwiftUI) {
self.parent = sparkline
}
}
public func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeWiperGauge(_: Context) -> DSFSparklineWiperGaugeGraphView {
let view = DSFSparklineWiperGaugeGraphView(frame: .zero)
self.updateView(view)
return view
}
}
// MARK: - iOS/tvOS Specific
@available(iOS 13.0, tvOS 13.0, macOS 9999.0, *)
public extension DSFSparklineWiperGaugeGraphView.SwiftUI {
func makeUIView(context: Context) -> DSFSparklineWiperGaugeGraphView {
return self.makeWiperGauge(context)
}
func updateUIView(_ view: DSFSparklineWiperGaugeGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - macOS Specific
@available(macOS 10.15, iOS 9999.0, tvOS 9999.0, *)
public extension DSFSparklineWiperGaugeGraphView.SwiftUI {
func makeNSView(context: Context) -> DSFSparklineWiperGaugeGraphView {
return self.makeWiperGauge(context)
}
func updateNSView(_ view: DSFSparklineWiperGaugeGraphView, context _: Context) {
self.updateView(view)
}
}
// MARK: - Common updates
@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)
public extension DSFSparklineWiperGaugeGraphView.SwiftUI {
func updateView(_ view: DSFSparklineWiperGaugeGraphView) {
view.valueColor = self.valueColor
if let c = self.valueBackgroundColor {
view.valueBackgroundColor = c
}
if let c = self.upperArcColor {
view.gaugeUpperArcColor = c
}
if let c = self.pointerColor {
view.gaugePointerColor = c
}
if view.animationStyle != self.animationStyle {
view.animationStyle = self.animationStyle
}
view.value = CGFloat(self.value)
}
}
#endif
================================================
FILE: Sources/DSFSparkline/prebuilt/DSFSparklineWiperGaugeGraphView.swift
================================================
//
// DSFSparklineWiperGaugeGraphView.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A sparkline that draws a percent bar
@objc public class DSFSparklineWiperGaugeGraphView: DSFSparklineSurfaceView {
// MARK: - Value
/// The initial value to display in the percent bar
@objc public dynamic var value: CGFloat = 0.2 {
didSet {
self.overlay.value = self.value
}
}
@objc public var valueColor = DSFSparkline.ValueBasedFill.sharedPalette {
didSet {
self.overlay.valueColor = valueColor
}
}
@objc public var animationStyle: DSFSparkline.AnimationStyle? = nil {
didSet {
self.overlay.animationStyle = animationStyle
}
}
#if os(macOS)
@objc public dynamic var gaugeUpperArcColor: NSColor = .textColor {
didSet {
self.overlay.gaugeUpperArcColor = gaugeUpperArcColor.cgColor
}
}
@objc public dynamic var valueBackgroundColor: NSColor = .quaternaryLabelColor {
didSet {
self.overlay.valueBackgroundColor = valueBackgroundColor.cgColor
}
}
/// The color of the pointer component of the gauge
@objc public dynamic var gaugePointerColor: NSColor = .textColor {
didSet {
self.overlay.gaugePointerColor = gaugePointerColor.cgColor
}
}
/// The color of the pointer component of the gauge
@objc public dynamic var gaugeBackgroundColor: NSColor? = nil {
didSet {
self.overlay.gaugeBackgroundColor = gaugeBackgroundColor?.cgColor
}
}
#else
@objc public dynamic var gaugeUpperArcColor: UIColor = .label {
didSet {
self.overlay.gaugeUpperArcColor = gaugeUpperArcColor.cgColor
}
}
@objc public dynamic var valueBackgroundColor: UIColor = .quaternaryLabel {
didSet {
self.overlay.valueBackgroundColor = valueBackgroundColor.cgColor
}
}
/// The color of the pointer component of the gauge
@objc public dynamic var gaugePointerColor: UIColor = .label {
didSet {
self.overlay.gaugePointerColor = gaugePointerColor.cgColor
}
}
/// The color of the pointer component of the gauge
@objc public dynamic var gaugeBackgroundColor: UIColor? = nil {
didSet {
self.overlay.gaugeBackgroundColor = gaugeBackgroundColor?.cgColor
}
}
#endif
// MARK: - Control
/// Initializer
public override init(frame: CGRect) {
super.init(frame: frame)
self.configure()
}
/// Initializer
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.configure()
}
// The overlay
private let overlay = DSFSparklineOverlay.WiperGauge()
}
extension DSFSparklineWiperGaugeGraphView {
#if os(macOS)
public override func updateLayer() {
// Captured to handle dark/light mode changes
super.updateLayer()
self.updateColors()
}
#endif
}
private extension DSFSparklineWiperGaugeGraphView {
func configure() {
self.addOverlay(self.overlay)
self.overlay.value = self.value
self.overlay.animationStyle = animationStyle
self.updateColors()
}
func updateColors() {
self.overlay.valueColor = self.valueColor
self.overlay.valueBackgroundColor = valueBackgroundColor.cgColor
self.overlay.gaugePointerColor = gaugePointerColor.cgColor
self.overlay.gaugeUpperArcColor = gaugeUpperArcColor.cgColor
self.overlay.gaugeBackgroundColor = gaugeBackgroundColor?.cgColor
}
}
================================================
FILE: Sources/DSFSparkline/prebuilt/ui/DSFSparklineDataSourceView.swift
================================================
//
// DSFSparklineDataSourceView.swift
// DSFSparklines
//
// Created by Darren Ford on 16/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
/// A Sparkline View with an attached datasource.
@objc public class DSFSparklineDataSourceView: DSFSparklineSurfaceView {
// Listen for changes in the data and update appropriately
private var dataObserver: NSObjectProtocol?
/// The source of data for the sparkline
@objc weak public var dataSource: DSFSparkline.DataSource? {
didSet {
if self.windowSizeSetInXib {
self.dataSource?.windowSize = self.graphWindowSize
}
self.updateDataObserver()
self.rootLayer.sublayers?.forEach { layer in
if let l = layer as? DSFSparklineOverlay.DataSource {
l.dataSource = self.dataSource
}
}
self.updateDisplay()
}
}
deinit {
self.dataObserver = nil
}
/// The primary color for the sparkline
#if os(macOS)
@objc public dynamic var graphColor: NSColor = NSColor.black {
didSet {
self.colorDidChange()
}
}
#else
@objc public dynamic var graphColor: UIColor = UIColor.black {
didSet {
self.colorDidChange()
}
}
#endif
#if os(macOS)
/// Force an update when the view moves to the window
public override func viewWillMove(toSuperview newSuperview: DSFView?) {
super.viewWillMove(toSuperview: newSuperview)
self.colorDidChange()
}
#endif
/// The size of the sparkline window
///
/// This member is purely IBDesignable display related
/// `windowSize` on the dataSource
@objc public dynamic var graphWindowSize: UInt = 20 {
didSet {
self.windowSizeSetInXib = true
self.dataSource?.windowSize = self.graphWindowSize
self.updateDisplay()
}
}
private var windowSizeSetInXib = false
}
extension DSFSparklineDataSourceView {
private func updateDataObserver() {
self.dataObserver = nil
if self.dataSource != nil {
self.dataObserver = NotificationCenter.default.addObserver(
forName: DSFSparkline.DataSource.DataChangedNotification,
object: self.dataSource!,
queue: nil, using: { [weak self] (notification) in
self?.updateDisplay()
})
}
}
/// Override in inherited classes to be notified when the color changes
@objc func colorDidChange() { }
#if os(macOS)
override public func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
#else
public override func draw(_ rect: CGRect) {
super.draw(rect)
}
#endif
}
================================================
FILE: Sources/DSFSparkline/prebuilt/ui/DSFSparklineZeroLinedGraphView.swift
================================================
//
// DSFSparklineZeroLinedGraphView.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import SwiftUI
#if os(macOS)
import Cocoa
#else
import UIKit
#endif
/// A view that can draw a zero-point line. Should never be used directly, just to inherit from for other graph types
@objc public class DSFSparklineZeroLineGraphView: DSFSparklineDataSourceView {
// MARK: Zero-line display
// The zero-line overlay
let zerolineOverlay = DSFSparklineOverlay.ZeroLine()
/// Draw a dotted line at the zero point on the y-axis
@objc public dynamic var zeroLineVisible: Bool = false {
didSet {
self.updateZeroLineSettings()
}
}
/// The color of the dotted line at the zero point on the y-axis
#if os(macOS)
@objc public dynamic var zeroLineColor = NSColor.gray {
didSet {
self.updateZeroLineSettings()
}
}
#else
@objc public dynamic var zeroLineColor: UIColor = .systemGray {
didSet {
self.updateZeroLineSettings()
}
}
#endif
/// The width of the dotted line at the zero point on the y-axis
@objc public dynamic var zeroLineWidth: CGFloat = 1.0 {
didSet {
self.updateZeroLineSettings()
}
}
/// The line style for the dotted line. Use [] to specify a solid line.
@objc public var zeroLineDashStyle: [CGFloat] = [1.0, 1.0] {
didSet {
self.updateZeroLineSettings()
}
}
/// A string representation of the line dash lengths for the zero line, eg. "1,3,4,2". If you want a solid line, specify "-"
///
/// Primarily used for Interface Builder integration
@objc public dynamic var zeroLineDashStyleString: String = "1,1" {
didSet {
self.handleZeroLineString()
}
}
// MARK: Zero-line centering
/// The color used to draw values below the zero line. If nil, is the same as the graph color
@objc public dynamic var lowerGraphColor: DSFColor? {
didSet {
self.colorDidChange()
}
}
/// The 'lowerColor' represents the 'negativeColor' if it is set, otherwise its the same as the graphColor
internal var lowerColor: DSFColor {
return self.lowerGraphColor ?? self.graphColor
}
// MARK: - Highlight ranges
// A fixed interface-builder defined highlight
let ibHighlightOverlay = DSFSparklineOverlay.RangeHighlight()
// An array of additional highlights
var highlightOverlay: [DSFSparklineOverlay.RangeHighlight] = []
/// Draw a highlight for a range on the graph
@objc public dynamic var highlightRangeVisible: Bool = false {
didSet {
self.updateHighlightSettings()
}
}
/// The color of the highlight to be used
#if os(macOS)
@objc public dynamic var highlightRangeColor = NSColor.gray {
didSet {
self.updateHighlightSettings()
}
}
#else
@objc public dynamic var highlightRangeColor: UIColor = .systemGray {
didSet {
self.updateHighlightSettings()
}
}
#endif
/// A string of the format "0.1,0.7"
@objc public dynamic var highlightRangeString: String? = nil {
didSet {
self.updateHighlightSettings()
}
}
// MARK: - Grid lines support
// A fixed interface-builder defined highlight
let ibGridLinesOverlay = DSFSparklineOverlay.GridLines()
/// Draw a dotted line at the zero point on the y-axis
@objc public dynamic var gridLinesVisible: Bool = false {
didSet {
self.updateGridLinesSettings()
}
}
/// The y-values on the graph with a grid line
@objc public var gridLinesValues: [CGFloat] = [] {
didSet {
self.updateGridLinesSettings()
}
}
/// A string of the format "0.1,0.7"
@objc public dynamic var gridLinesValuesString: String? = nil {
didSet {
// Dash style
let floats = self.gridLinesValuesString?.extractCGFloats() ?? []
self.gridLinesValues = floats
}
}
#if os(macOS)
@objc public dynamic var gridLinesColor = NSColor.systemGray.withAlphaComponent(0.5) {
didSet {
self.updateGridLinesSettings()
}
}
#else
@objc public dynamic var gridLinesColor: UIColor = .systemGray.withAlphaComponent(0.5) {
didSet {
self.updateGridLinesSettings()
}
}
#endif
/// The width of the dotted line at the zero point on the y-axis
@objc public dynamic var gridLinesWidth: CGFloat = 1.0 {
didSet {
self.updateGridLinesSettings()
}
}
/// The line style for the dotted line. Use [] to specify a solid line.
@objc public dynamic var gridLinesDashStyle: [CGFloat] = [1.0, 1.0] {
didSet {
self.updateGridLinesSettings()
}
}
/// A string representation of the line dash lengths for grid lines, eg. "1,3,4,2". If you want a solid line, specify "-"
///
/// Primarily used for Interface Builder integration
@objc public dynamic var gridLinesDashStyleString: String = "1.0,1.0" {
didSet {
self.updateGridLinesSettings()
}
}
/// Set the grid line definition for the graph
@objc public func setGridLineDefinition(_ gridLineDefinition: DSFSparkline.GridLinesDefinition) {
self.gridLinesVisible = true
self.gridLinesColor = gridLineDefinition.color
self.gridLinesWidth = gridLineDefinition.width
self.gridLinesDashStyle = gridLineDefinition.dashStyle
self.gridLinesValues = gridLineDefinition.values
}
// MARK: - Init
public override init(frame: CGRect) {
super.init(frame: frame)
self.setup()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.setup()
}
private func setup() {
// The Zero line overlay
self.addOverlay(self.zerolineOverlay)
self.zerolineOverlay.zPosition = -5
self.updateZeroLineSettings()
// Highlight
self.addOverlay(self.ibHighlightOverlay)
self.ibHighlightOverlay.zPosition = -10
self.ibHighlightOverlay.dataSource = self.dataSource
self.updateHighlightSettings()
// Grid lines
self.addOverlay(self.ibGridLinesOverlay)
self.ibGridLinesOverlay.isHidden = self.gridLinesVisible
self.ibGridLinesOverlay.zPosition = -10
self.ibGridLinesOverlay.dataSource = self.dataSource
self.updateGridLinesSettings()
}
@objc public var highlightRangeDefinition: [DSFSparkline.HighlightRangeDefinition] = [] {
willSet {
self.highlightOverlay.forEach { $0.removeFromSuperlayer() }
self.highlightOverlay = []
}
didSet {
self.highlightRangeDefinition.forEach { r in
let item = DSFSparklineOverlay.RangeHighlight()
item.highlightRange = r.range
item.dataSource = self.dataSource
item.fill = r.fill
item.zPosition = -10
self.addOverlay(item)
self.highlightOverlay.append(item)
}
self.updateDisplay()
}
}
}
public extension DSFSparklineZeroLineGraphView {
/// Configure the zero line using the ZeroLineDefinition
func setZeroLineDefinition(_ definition: DSFSparkline.ZeroLineDefinition) {
self.zeroLineWidth = definition.lineWidth
self.zeroLineColor = definition.color
self.zeroLineDashStyle = definition.lineDashStyle
}
}
private extension DSFSparklineZeroLineGraphView {
func updateGridLinesSettings() {
self.ibGridLinesOverlay.isHidden = !self.gridLinesVisible
self.ibGridLinesOverlay.strokeWidth = self.gridLinesWidth
self.ibGridLinesOverlay.strokeColor = self.gridLinesColor.cgColor
self.ibGridLinesOverlay.dashStyle = self.gridLinesDashStyle
self.ibGridLinesOverlay.floatValues = self.gridLinesValues
self.updateDisplay()
}
///
private func handleZeroLineString() {
if self.zeroLineDashStyleString == "-" {
// Solid line
self.zeroLineDashStyle = []
}
else {
let components = self.zeroLineDashStyleString.extractCGFloats()
if components.count >= 2 {
self.zeroLineDashStyle = components
}
else {
Swift.print("ERROR: Zero Line Style string format is incompatible (\(self.zeroLineDashStyleString) -> \(components))")
}
}
}
private func updateZeroLineSettings() {
self.zerolineOverlay.isHidden = !self.zeroLineVisible
self.zerolineOverlay.strokeColor = self.zeroLineColor.cgColor
self.zerolineOverlay.strokeWidth = self.zeroLineWidth
self.zerolineOverlay.dashStyle = self.zeroLineDashStyle
self.updateDisplay()
}
private func updateHighlightSettings() {
self.ibHighlightOverlay.isHidden = !self.highlightRangeVisible
self.ibHighlightOverlay.fill = DSFSparkline.Fill.Color(self.highlightRangeColor.cgColor)
let floats = self.highlightRangeString?.extractCGFloats() ?? []
if floats.count == 2, floats[0] < floats[1] {
self.ibHighlightOverlay.highlightRange = floats[0] ..< floats[1]
}
else if floats.count == 0 {
// No ranges. This is fine
self.highlightRangeDefinition = []
}
else {
self.highlightRangeDefinition = []
Swift.print("ERROR: Highlight range string format is incompatible (\(self.zeroLineDashStyleString) -> \(floats))")
}
self.updateDisplay()
}
}
================================================
FILE: Sources/DSFSparkline/util/Angle.swift
================================================
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
/// An angle
///
/// An angle value represents either a radians or a degrees angle, with functions to easily convert between the two
public enum Angle {
/// Radians angle value
case radians(T)
/// Degrees angle value
case degrees(T)
/// The radians value for the angle
@inlinable public var radians: T {
switch self {
case let .radians(value): return value
case let .degrees(value): return value * T.pi / 180.0
}
}
/// The degrees value for the angle
@inlinable public var degrees: T {
switch self {
case let .radians(value): return value * 180.0 / T.pi
case let .degrees(value): return value
}
}
/// Return a guaranteed radians angle representation
@inlinable public var asRadians: Angle { .radians(self.radians) }
/// Return a guaranteed degrees angle representation
@inlinable public var asDegrees: Angle { .degrees(self.degrees) }
/// Add two angle values
@inlinable public static func +(_ left: Angle, _ right: Angle) -> Angle { .radians(left.radians + right.radians) }
/// Subtract two angle values
@inlinable public static func -(_ left: Angle, _ right: Angle) -> Angle { .radians(left.radians - right.radians) }
}
================================================
FILE: Sources/DSFSparkline/util/ArbitraryAnimator.swift
================================================
//
// ArbitraryAnimator.swift
// DSFSparklines
//
// Created by Darren Ford on 17/2/21.
// Copyright © 2021 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import QuartzCore
extension DSFSparkline {
/// Animation function types
@objc public enum AnimatorFunctionType: Int {
case linear
case easeInEaseOut
internal var function: ArbitraryAnimatorFunction {
switch self {
case .linear: return ArbitraryAnimator.Function.Linear()
case .easeInEaseOut: return ArbitraryAnimator.Function.EaseInEaseOut()
default:
fatalError()
}
}
}
/// An animation style
@objc public class AnimationStyle: NSObject {
@objc public let duration: CGFloat
@objc public let function: AnimatorFunctionType
@objc public init(duration: CGFloat = 0.25, function: AnimatorFunctionType = .easeInEaseOut) {
self.duration = duration
self.function = function
}
}
struct AnimationTransition {
let start: CGFloat
let stop: CGFloat
var distance: CGFloat { self.stop - self.start }
}
}
func ==(_ left: DSFSparkline.AnimationStyle, _ right: DSFSparkline.AnimationStyle) -> Bool {
left.duration == right.duration && left.function == right.function
}
func !=(_ left: DSFSparkline.AnimationStyle, _ right: DSFSparkline.AnimationStyle) -> Bool {
left.duration != right.duration || left.function != right.function
}
//////
protocol ArbitraryAnimatorFunction {
func evaluate(linearPosition: Double) -> Double
}
class ArbitraryAnimator {
class Function {}
#if os(macOS)
var displayLink: CVDisplayLink?
#else
var displayLink: CADisplayLink?
#endif
var progress: Double = 0.0
var duration: CFTimeInterval = 1
var startTime: CFTimeInterval = 0
var endTime: CFTimeInterval = 0
var animationFunction: ArbitraryAnimatorFunction = Function.Linear()
var isActive: Bool = false
var progressBlock: ((Double) -> Void)?
init() {
#if os(macOS)
CVDisplayLinkCreateWithActiveCGDisplays(&self.displayLink)
let displayLinkOutputCallback: CVDisplayLinkOutputCallback = {
(_: CVDisplayLink,
_: UnsafePointer,
_: UnsafePointer,
_: CVOptionFlags,
_: UnsafeMutablePointer,
displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn in
let view = unsafeBitCast(displayLinkContext, to: ArbitraryAnimator.self)
view.perform()
return CVReturn()
}
CVDisplayLinkSetOutputCallback(
displayLink!,
displayLinkOutputCallback,
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
)
#else
self.displayLink = CADisplayLink(target: self, selector: #selector(self.callback(displaylink:)))
#endif
}
func start() {
self.startTime = CACurrentMediaTime()
self.endTime = self.startTime + self.duration
self.isActive = true
#if os(macOS)
guard let displayLink = self.displayLink else { fatalError() }
CVDisplayLinkStart(displayLink)
#else
guard let displayLink = self.displayLink else { fatalError() }
displayLink.add(to: RunLoop.main, forMode: .default)
#endif
}
func stop() {
if !self.isActive {
return
}
self.isActive = false
#if os(macOS)
guard let link = self.displayLink else { fatalError() }
CVDisplayLinkStop(link)
#else
guard let displayLink = self.displayLink else { fatalError() }
displayLink.remove(from: RunLoop.main, forMode: .default)
#endif
// Force the animation to complete
self.progress = 1.0
self.progressBlock?(self.progress)
}
#if !os(macOS)
@objc func callback(displaylink _: CADisplayLink) {
self.perform()
}
#endif
func perform() {
DispatchQueue.main.async { [weak self] in
guard let `self` = self else { return }
let current = CACurrentMediaTime()
if current >= self.endTime {
self.progress = 1.0
self.stop()
}
else {
let linearPosition = (current - self.startTime) / self.duration
let evaluatedPosition = self.animationFunction.evaluate(linearPosition: linearPosition)
self.progress = evaluatedPosition
}
self.progressBlock?(self.progress)
}
}
}
extension ArbitraryAnimator.Function {
class EaseInEaseOut: ArbitraryAnimatorFunction {
let firstControlPoint: Double
let secondControlPoint: Double
init(firstControlPoint: Double = 0, secondControlPoint: Double = 1) {
self.firstControlPoint = firstControlPoint
self.secondControlPoint = secondControlPoint
}
func evaluate(linearPosition: Double) -> Double {
return
3 * linearPosition * (1 - linearPosition) * (1 - linearPosition) * self.firstControlPoint +
3 * linearPosition * linearPosition * (1 - linearPosition) * self.secondControlPoint +
linearPosition * linearPosition * linearPosition * 1.0
}
}
class Linear: ArbitraryAnimatorFunction {
func evaluate(linearPosition: Double) -> Double {
return linearPosition
}
}
}
================================================
FILE: Sources/DSFSparkline/util/CGColor+BackwardsCompatibility.swift
================================================
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Some basic support routines to abstract away differences between OS versions
import CoreGraphics
import Foundation
extension CGColor {
/// Creates a color in the Generic gray color space
/// - Parameters:
/// - gray: A grayscale value (0.0 - 1.0)
/// - alpha: An alpha value (0.0 - 1.0)
/// - Returns: A color object
@inlinable static func gray(_ gray: CGFloat, _ a: CGFloat = 1.0) -> CGColor {
if #available(iOS 13, tvOS 13, watchOS 6, *) {
return CGColor(gray: gray, alpha: a)
}
return CGColor(colorSpace: _grayColorSpace, components: [gray, a]) ?? .standard.black
}
/// Creates a color in the Generic RGB color space
/// - Parameters:
/// - red: A red component value (0.0 - 1.0)
/// - green: A green component value (0.0 - 1.0)
/// - blue: A blue component value (0.0 - 1.0)
/// - alpha: An alpha value (0.0 - 1.0)
/// - Returns: A color object
@inlinable static func RGBA(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat = 1.0) -> CGColor {
if #available(iOS 13, tvOS 13, watchOS 6, *) {
return CGColor(red: red, green: green, blue: blue, alpha: alpha)
}
return CGColor(colorSpace: _rgbaColorSpace, components: [red, green, blue, alpha]) ?? .standard.black
}
/// Creates a color in the sRGB color space.
/// - Parameters:
/// - red: A red component value (0.0 - 1.0)
/// - green: A green component value (0.0 - 1.0)
/// - blue: A blue component value (0.0 - 1.0)
/// - alpha: An alpha value (0.0 - 1.0)
/// - Returns: A color object
@inlinable static func sRGBA(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, _ alpha: CGFloat = 1.0) -> CGColor {
if #available(macOS 15, iOS 13, tvOS 13, watchOS 6, *) {
return CGColor(srgbRed: red, green: green, blue: blue, alpha: alpha)
}
return CGColor(colorSpace: _sRGBAColorSpace, components: [red, green, blue, alpha]) ?? .standard.black
}
}
// cached colorspaces
@usableFromInline internal let _sRGBAColorSpace = CGColorSpace(name: CGColorSpace.sRGB)!
@usableFromInline internal let _rgbaColorSpace = CGColorSpace(name: CGColorSpace.genericRGBLinear)!
@usableFromInline internal let _grayColorSpace = CGColorSpace(name: CGColorSpace.genericGrayGamma2_2)!
// MARK: - Standard CGColors
extension CGColor {
@usableFromInline internal struct Colors {
@usableFromInline internal let white = CGColor.gray(1)
@usableFromInline internal let black = CGColor.gray(0)
@usableFromInline internal let clear = CGColor.gray(0, 0)
@usableFromInline internal let red = CGColor.sRGBA(1, 0, 0)
@usableFromInline internal let green = CGColor.sRGBA(0, 1, 0)
@usableFromInline internal let blue = CGColor.sRGBA(0, 0, 1)
@usableFromInline internal let cyan = CGColor.sRGBA(0, 1, 1)
@usableFromInline internal let yellow = CGColor.sRGBA(1, 1, 0)
@usableFromInline internal let magenta = CGColor.sRGBA(1, 0, 1)
}
@usableFromInline internal static var standard: Colors { Colors() }
}
================================================
FILE: Sources/DSFSparkline/util/CGContext+extensions.swift
================================================
//
// CGContext+extensions.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics.CGContext
extension CGContext {
/// Execute the supplied block within a `saveGState() / restoreGState()` pair, providing a context
/// to draw in during the execution of the block
///
/// - Parameter stateBlock: The block to execute within the new graphics state
/// - Parameter context: The context to draw into within the block
///
/// Example usage:
/// ```
/// context.usingGState { (ctx) in
/// ctx.addPath(unsetBackground)
/// ctx.setFillColor(bgc1.cgColor)
/// ctx.fillPath(using: .evenOdd)
/// }
/// ```
@inlinable func usingGState(stateBlock: (_ context: CGContext) throws -> Void) rethrows {
self.saveGState()
defer {
self.restoreGState()
}
try stateBlock(self)
}
/// Wrap the drawing commands in `block` within a transparency layer
@inlinable func usingTransparencyLayer(auxiliaryInfo: CFDictionary? = nil, _ block: () -> Void) {
self.beginTransparencyLayer(auxiliaryInfo: auxiliaryInfo)
defer { self.endTransparencyLayer() }
block()
}
}
================================================
FILE: Sources/DSFSparkline/util/CGPath+Hermite.swift
================================================
//
// CGPath+Hermite.swift
// DSFSparklines
//
// Created by Darren Ford on 20/6/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
extension CGMutablePath {
@discardableResult
func addPathWithPoints(_ points: [CGPoint], smoothed: Bool = false) -> CGPath {
if smoothed {
return self.addPathByExtrapolatingPoints(points)
}
else {
return self.addPathWithPoints(points)
}
}
private func addPathByExtrapolatingPoints(_ interpolationPoints: [CGPoint]) -> CGPath {
self.interpolatePointsWithHermite(interpolationPoints: interpolationPoints)
return self
}
private func addPathWithPoints(_ points: [CGPoint]) -> CGPath {
assert(points.count > 1)
self.move(to: points.first!)
for point in points.dropFirst() {
self.addLine(to: point)
}
return self
}
}
extension CGPath {
func fit(verticallyIn rect: CGRect) -> CGPath? {
let boundingBox = self.boundingBox
let dy = rect.height / boundingBox.height
let scaleTransform = CGAffineTransform.identity
var tr = scaleTransform.scaledBy(x: 1.0, y: dy)
return self.copy(using: &tr)
}
static func pathWithPoints(_ points: [CGPoint], smoothed: Bool = false) -> CGPath {
if smoothed {
return CGPath.byExtrapolatingPoints(points)
}
else {
return CGPath.byPathWithPoints(points)
}
}
private static func byExtrapolatingPoints(_ interpolationPoints: [CGPoint]) -> CGPath {
let newPath = CGMutablePath()
newPath.interpolatePointsWithHermite(interpolationPoints: interpolationPoints)
return newPath
}
private static func byPathWithPoints(_ points: [CGPoint]) -> CGPath {
assert(points.count > 1)
let path = CGMutablePath()
path.move(to: points.first!)
for point in points.dropFirst() {
path.addLine(to: point)
}
return path
}
}
extension CGMutablePath {
func interpolatePointsWithHermite(interpolationPoints: [CGPoint]) {
let n = interpolationPoints.count - 1
for ii in 0 ..< n {
var currentPoint = interpolationPoints[ii]
if ii == 0 {
self.move(to: interpolationPoints[0])
}
var nextii = (ii + 1) % interpolationPoints.count
var previi = (ii - 1 < 0 ? interpolationPoints.count - 1 : ii - 1)
var previousPoint = interpolationPoints[previi]
var nextPoint = interpolationPoints[nextii]
let endPoint = nextPoint
var mx: CGFloat = 0.0
var my: CGFloat = 0.0
if ii > 0 {
mx = (nextPoint.x - currentPoint.x) * 0.5 + (currentPoint.x - previousPoint.x) * 0.5
my = (nextPoint.y - currentPoint.y) * 0.5 + (currentPoint.y - previousPoint.y) * 0.5
}
else {
mx = (nextPoint.x - currentPoint.x) * 0.5
my = (nextPoint.y - currentPoint.y) * 0.5
}
let controlPoint1 = CGPoint(x: currentPoint.x + mx / 3.0, y: currentPoint.y + my / 3.0)
currentPoint = interpolationPoints[nextii]
nextii = (nextii + 1) % interpolationPoints.count
previi = ii
previousPoint = interpolationPoints[previi]
nextPoint = interpolationPoints[nextii]
if ii < n - 1 {
mx = (nextPoint.x - currentPoint.x) * 0.5 + (currentPoint.x - previousPoint.x) * 0.5
my = (nextPoint.y - currentPoint.y) * 0.5 + (currentPoint.y - previousPoint.y) * 0.5
}
else {
mx = (currentPoint.x - previousPoint.x) * 0.5
my = (currentPoint.y - previousPoint.y) * 0.5
}
let controlPoint2 = CGPoint(x: currentPoint.x - mx / 3.0, y: currentPoint.y - my / 3.0)
self.addCurve(to: endPoint, control1: controlPoint1, control2: controlPoint2)
}
}
}
extension CGPath {
/// Fit a bezier path through all of the points.
static func InterpolatePointsUsingBezier(points: [CGPoint], f: CGFloat = 0.3, t: CGFloat = 0.6) -> CGPath? {
// See: https://www.geeksforgeeks.org/how-to-draw-smooth-curve-through-multiple-points-using-javascript/
let path = CGMutablePath()
guard let first: CGPoint = points.first,
points.count > 1 else
{
return nil
}
path.move(to: first)
let remaining = [CGPoint](points.dropFirst())
let pairs: [(CGPoint, CGPoint?)] = remaining.enumerated().map { point in
let next: CGPoint? =
point.offset < remaining.count - 1 ? remaining[point.offset + 1] : nil
return (point.element, next)
}
var m: CGFloat = 0
var dx1: CGFloat = 0
var dy1: CGFloat = 0
var preP = first
for pair in pairs {
let curP = pair.0
let nexP = pair.1 // This is nil for the last point in the graph
let dx2: CGFloat
let dy2: CGFloat
if let next = nexP {
m = gradient(preP, next)
dx2 = (next.x - curP.x) * -f
dy2 = dx2 * m * t
}
else {
dx2 = 0
dy2 = 0
}
path.addCurve(to: curP,
control1: CGPoint(x: preP.x - dx1, y: preP.y - dy1),
control2: CGPoint(x: curP.x + dx2, y: curP.y + dy2))
dx1 = dx2
dy1 = dy2
preP = curP
}
return path.copy()
}
}
================================================
FILE: Sources/DSFSparkline/util/CGPath+innerShadow.swift
================================================
//
// CGPath+innerShadow.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import CoreGraphics
import Foundation
public extension CGContext {
/// Draw an inner shadow in the path
/// - Parameters:
/// - path: The path to apply the inner shadow to
/// - shadowColor: Specifies the color of the shadow, which may contain a non-opaque alpha value. If NULL, then shadowing is disabled.
/// - offset: Specifies a translation in base-space.
/// - blurRadius: A non-negative number specifying the amount of blur.
///
/// **Inner Shadows in Quartz: Helftone**
/// [Blog Article](https://blog.helftone.com/demystifying-inner-shadows-in-quartz/)
/// [(Archived)](https://web.archive.org/web/20221206132428/https://blog.helftone.com/demystifying-inner-shadows-in-quartz/)
func drawInnerShadow(in path: CGPath, shadowColor: CGColor?, offset: CGSize, blurRadius: CGFloat) {
guard
let shadowColor = shadowColor,
let opaqueShadowColor = shadowColor.copy(alpha: 1.0)
else {
return
}
self.usingGState { ctx in
ctx.addPath(path)
ctx.clip()
ctx.setAlpha(shadowColor.alpha)
ctx.usingTransparencyLayer {
ctx.setShadow(offset: offset, blur: blurRadius, color: opaqueShadowColor)
ctx.setBlendMode(.sourceOut)
ctx.setFillColor(opaqueShadowColor)
ctx.addPath(path)
ctx.fillPath()
}
}
}
}
================================================
FILE: Sources/DSFSparkline/util/DSFSparkline+Shadow.swift
================================================
//
// CGContext+extensions.swift
//
// Copyright © 2024 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension DSFSparkline {
/// A shadow object
@objc class Shadow: NSObject {
/// The shadow
public let shadow: NSShadow
/// Is the shadow an inner shadow?
public let isInner: Bool
/// Create a new shadow object
@objc public init(_ shadow: NSShadow, isInner: Bool = false) {
self.shadow = shadow
self.isInner = isInner
super.init()
}
/// Create a new shadow object
@objc convenience public init(blurRadius: CGFloat, offset: CGSize, color: CGColor, isInner: Bool = false) {
#if os(macOS)
let color: DSFColor = DSFColor(cgColor: color) ?? .black
#else
let color: DSFColor = DSFColor(cgColor: color)
#endif
self.init(
NSShadow(blurRadius: blurRadius, offset: offset, color: color),
isInner: isInner
)
}
/// Shadow offset
@inlinable @objc public var offset: CGSize {
get { shadow.shadowOffset }
set { shadow.shadowOffset = newValue }
}
#if os(macOS)
/// Shadow color
@inlinable @objc public var color: CGColor? {
get { shadow.shadowColor?.cgColor }
set { shadow.shadowColor = newValue != nil ? NSColor(cgColor: newValue!) : nil }
}
#else
/// Shadow color
@inlinable @objc public var color: CGColor? {
get { (shadow.shadowColor as? DSFColor)?.cgColor }
set { shadow.shadowColor = (newValue != nil) ? UIColor(cgColor: newValue!) : nil }
}
#endif
/// Shadow blur radius
@inlinable @objc public var blurRadius: CGFloat {
get { shadow.shadowBlurRadius}
set { shadow.shadowBlurRadius = newValue }
}
/// Calculate the amount of inset required to cater for drawing a shadow
@inlinable internal var requiredShadowInset: CGFloat {
if self.isInner { return 0 }
let dx = (abs(self.offset.width) + self.blurRadius) * 2
let dy = (abs(self.offset.height) + self.blurRadius) * 2
return max(dx, dy)
}
}
}
================================================
FILE: Sources/DSFSparkline/util/LayerInvalidating.swift
================================================
//
// LayerInvalidating.swift
//
// Copyright © 2023 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public struct LayerInvalidatingType: OptionSet {
public let rawValue: UInt
/// Call 'setNeedsDisplay()' on the layer whenever the property value changes
public static let display = LayerInvalidatingType(rawValue: 1 << 0)
/// Call 'setNeedsLayout()' on the layer whenever the property value changes
public static let layout = LayerInvalidatingType(rawValue: 1 << 1)
public static let all: LayerInvalidatingType = [.display, .layout]
public init(rawValue: UInt) {
self.rawValue = rawValue
}
}
/// A property wrapper for CAlayer member properties to force a `setNeedsDisplay()` on the layer
/// whenever the property changes
@propertyWrapper
public struct LayerInvalidating {
// Stored value
private var valueType: Value
// The invalidation types for the property
private let invalidationType: LayerInvalidatingType
/// Wrapped value
public var wrappedValue: Value {
get { self.valueType }
set { self.valueType = newValue }
}
/// Initialize with a built-in invalidating type(s).
public init(wrappedValue: Value, _ invalidationType: LayerInvalidatingType) {
self.valueType = wrappedValue
self.invalidationType = invalidationType
}
public static subscript(
_enclosingInstance object: EnclosingSelf,
wrapped _: ReferenceWritableKeyPath,
storage storageKeyPath: ReferenceWritableKeyPath>
) -> Value {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
object[keyPath: storageKeyPath].updateLayerInvalidatingPropertyWrapper(newValue, object)
}
}
mutating func updateLayerInvalidatingPropertyWrapper(_ value: Value, _ layer: CALayer) {
guard self.wrappedValue != value else { return }
// Update the wrapped value
self.wrappedValue = value
// And trigger the invalidations associated with the propertywrapper
if self.invalidationType.contains(.display) { layer.setNeedsDisplay() }
if self.invalidationType.contains(.layout) { layer.setNeedsLayout() }
}
}
================================================
FILE: Sources/DSFSparkline/util/NSShadow+extensions.swift
================================================
//
// NSShadow+extensions.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics
#if os(macOS)
import AppKit
#else
import UIKit
#endif
public extension NSShadow {
@objc convenience init(blurRadius: CGFloat, offset: CGSize, color: DSFColor) {
self.init()
self.shadowBlurRadius = blurRadius
self.shadowOffset = offset
self.shadowColor = color
}
}
extension CGContext {
@inlinable func setShadow(_ shadow: NSShadow) {
#if os(macOS)
let color = shadow.shadowColor
#else
let color = shadow.shadowColor as? UIColor
#endif
self.setShadow(
offset: shadow.shadowOffset,
blur: shadow.shadowBlurRadius,
color: color?.cgColor)
}
}
// Static definition of the 'default' shadow.
private let _NSShadowDefaultValue = NSShadow(
blurRadius: 1.0,
offset: CGSize(width: 0.5, height: 0.5),
color: DSFColor.black.withAlphaComponent(0.3)
)
internal extension NSShadow {
/// The default shadow
@objc static let sparklineDefault = _NSShadowDefaultValue
}
================================================
FILE: Sources/DSFSparkline/util/Utilities.swift
================================================
//
// Utilities.swift
// DSFSparklines
//
// Created by Darren Ford on 20/12/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import Foundation
import CoreGraphics.CGContext
#if os(macOS)
import AppKit
public typealias DSFColor = NSColor
public typealias DSFView = NSView
public typealias DSFFont = NSFont
public typealias DSFEdgeInsets = NSEdgeInsets
public typealias DSFImage = NSImage
#else
import UIKit
public typealias DSFColor = UIColor
public typealias DSFView = UIView
public typealias DSFFont = UIFont
public typealias DSFEdgeInsets = UIEdgeInsets
public typealias DSFImage = UIImage
#endif
#if canImport(SwiftUI)
import SwiftUI
#if os(macOS)
@available(macOS 10.15, *)
typealias DSFViewRepresentable = NSViewRepresentable
#else
@available(iOS 13.0, tvOS 13.0, *)
typealias DSFViewRepresentable = UIViewRepresentable
#endif
#endif
extension ExpressibleByIntegerLiteral where Self: Comparable {
/// Clamp a value to a closed range
/// - Parameter range: the range to clamp to
@inlinable func clamped(to range: ClosedRange) -> Self {
return min(max(self, range.lowerBound), range.upperBound)
}
}
public extension CGGradient {
static func Create(_ definition: [(position: CGFloat, color: CGColor)],
colorSpace: CGColorSpace? = nil) -> CGGradient {
return CGGradient(
colorsSpace: colorSpace,
colors: definition.map { $0.1 } as CFArray, // [c1, c2] as CFArray,
locations: definition.map { $0.0 } //[1.0, 0.0]
)!
}
}
extension DSFView {
#if os(macOS)
@objc func retinaScale() -> CGFloat {
return self.window?.screen?.backingScaleFactor ?? 1.0
}
@inlinable func setNeedsDisplay() { self.needsDisplay = true }
#else
@objc func retinaScale() -> CGFloat {
return self.window?.screen.scale ?? 1.0
}
#endif
}
extension CGPoint {
@inlinable func offsettingX(by value: CGFloat) -> CGPoint {
return CGPoint(x: self.x + value, y: self.y)
}
@inlinable func offsettingY(by value: CGFloat) -> CGPoint {
return CGPoint(x: self.x, y: self.y + value)
}
}
/// Return the slope of the line joining points a and b
@inlinable func gradient(_ a: CGPoint, _ b: CGPoint) -> CGFloat {
return (b.y - a.y) / (b.x - a.x)
}
// MARK: - NSView/UIView snapshot
#if os(macOS)
public extension NSView {
@objc func snapshot() -> NSImage? {
guard let bitmapRep = self.bitmapImageRepForCachingDisplay(in: self.bounds) else { return nil }
self.cacheDisplay(in: self.bounds, to: bitmapRep)
let image = NSImage()
image.addRepresentation(bitmapRep)
return image
}
}
#else
public extension UIView {
@objc func snapshot() -> UIImage {
if #available(iOS 10.0, tvOS 10.0, *) {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
} else {
UIGraphicsBeginImageContext(self.frame.size)
self.layer.render(in:UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return UIImage(cgImage: image!.cgImage!)
}
}
}
#endif
// MARK: -
@discardableResult
@inlinable internal func UpdateIfNotEqual(result: inout T, val: T) -> Bool where T: Equatable {
if result != val {
result = val
return true
}
return false
}
// MARK: - Debugging utils
// Simple method to draw a rectangle in a context (usually for testing)
func DrawRect(primary: CGContext, rect: CGRect, color: CGColor = DSFColor.systemRed.cgColor) {
primary.usingGState { (outer) in
outer.addRect(rect)
outer.setLineWidth(0.5)
outer.setStrokeColor(color)
outer.strokePath()
}
}
#if !os(macOS)
extension CGColor {
static var black: CGColor { CGColor(gray: 0, alpha: 1) }
static var white: CGColor { CGColor(gray: 1, alpha: 1) }
static var clear: CGColor { CGColor(gray: 0, alpha: 0) }
}
#endif
extension String {
func extractCGFloats() -> [CGFloat] {
let components = self.split(separator: ",")
let floats: [CGFloat] = components
.map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } // Convert to string array
.compactMap { Float($0) } // Convert to float array if possible
.compactMap { CGFloat($0) } // Convert to CGFloat array
return floats
}
}
// Returns the first item in the specified array which is non-nil. If all elements are nil, returns nil
@inlinable internal func firstNotNil(_ items: [T?]) -> T? {
if let first = items.first(where: { $0 != nil }) {
return first
}
return nil
}
// A CATextLayer that vertically centers its content
class LCTextLayer : CATextLayer {
// REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
// CREDIT: David Hoerl - https://github.com/dhoerl
// USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation.
override func draw(in context: CGContext) {
let height = self.bounds.size.height
let fontSize = self.fontSize
let yDiff = (height-fontSize)/2 - fontSize/10
context.saveGState()
context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
super.draw(in: context)
context.restoreGState()
}
}
extension CGColor {
/// Returns a black or white contrasting color for this color
/// - Parameter defaultColor: If the color cannot be converted to the genericRGB colorspace, or the input color is .clear, the fallback color
/// - Returns: black or white depending on which provides the greatest contrast to this color
func flatContrastColor(defaultColor: CGColor = CGColor.DefaultBlack) -> CGColor {
guard self != CGColor.clear,
let rgbColor = self.converted(to: CGColorSpace(name: CGColorSpace.sRGB)!,
intent: .perceptual,
options: nil) else {
return defaultColor
}
let r = 0.299 * rgbColor.components![0]
let g = 0.587 * rgbColor.components![1]
let b = 0.114 * rgbColor.components![2]
let avgGray: CGFloat = 1 - (r + g + b)
return (avgGray >= 0.45) ? CGColor.DefaultWhite : CGColor.DefaultBlack
}
}
extension CGColor {
static let DefaultWhite = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 1])!
static let DefaultBlack = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0, 0, 0, 1])!
static let DefaultClear = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1, 1, 1, 0])!
}
// MARK: - CATextLayer extensions
extension CATextLayer {
func font() -> DSFFont? {
if let f = self.font as? DSFFont {
return f //return DSFFont(name: f.fontName, size: self.fontSize)
}
else if let s = self.font as? String {
return DSFFont(name: s, size: self.fontSize)
}
else {
return nil
}
}
func attributedString() -> NSAttributedString {
if let asv = self.string as? NSAttributedString {
return asv
}
else if let s = self.string as? String {
let attrs = [NSAttributedString.Key.font: self.font() as Any]
return NSAttributedString(string: s, attributes: attrs)
}
else {
fatalError()
}
}
/// Get the required bounds for the text layer content given `size` constraints
func textBounds(for size: CGSize) -> CGSize {
#if os(macOS)
let options: NSString.DrawingOptions = [NSString.DrawingOptions.usesFontLeading, NSString.DrawingOptions.usesLineFragmentOrigin]
#else
let options: NSStringDrawingOptions = [NSStringDrawingOptions.usesFontLeading, NSStringDrawingOptions.usesLineFragmentOrigin]
#endif
let ttt = self.attributedString()
let br = ttt.boundingRect(
with: CGSize(width: size.width, height: size.height),
options: options,
context: nil)
return br.size
}
}
extension CATransaction {
/// Perform 'body' without implicit animations
///
/// - Parameters:
/// - body: The block to execute without implicit animations
///
/// Example :-
/// ```
/// CATransaction.withDisabledActions {
/// self.checkerboardLayer.image.frame = self.bounds
/// ...
/// }
/// ```
class func withDisabledActions(_ body: () throws -> T) rethrows -> T {
CATransaction.begin()
CATransaction.setDisableActions(true)
defer {
CATransaction.commit()
}
return try body()
}
}
#if os(macOS)
extension CGRect {
/// Inset this rect by the amount specified in `insets`.
@inlinable func inset(by insets: NSEdgeInsets) -> CGRect {
var result = self
result.origin.x += insets.left
result.origin.y += insets.top
result.size.width -= (insets.left + insets.right)
result.size.height -= (insets.top + insets.bottom)
return result
}
}
#endif
extension DSFEdgeInsets {
/// A default zero inset
static let zero = DSFEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
/// Calculate the maximum insets between the two edgeinset objects
@inlinable func combineMaximum(using other: DSFEdgeInsets) -> DSFEdgeInsets {
return DSFEdgeInsets(top: max(self.top, other.top),
left: max(self.left, other.left),
bottom: max(self.bottom, other.bottom),
right: max(self.right, other.right))
}
}
extension Array {
/// Return a copy of this array with 'element' appended
@inlinable func appending(_ element: Element) -> Self {
var result = self
result.append(element)
return result
}
/// Return a copy of this array with an array of 'element' appended
@inlinable func appending(contentsOf elements: [Element]) -> Self {
var result = self
result.append(contentsOf: elements)
return result
}
}
extension CGImage {
func flipped() -> CGImage? {
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
let flipped = CGContext(
data: nil,
width: self.width,
height: self.height,
bitsPerComponent: 8,
bytesPerRow: 0,
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: bitmapInfo.rawValue
)!
// Flip the context
flipped.translateBy(x: 0, y: CGFloat(height))
flipped.scaleBy(x: 1.0, y: -1.0)
// Draw the image into the context
flipped.draw(self, in: CGRect(x: 0, y: 0, width: self.width, height: self.height))
// Create a new CGImage from the context
return flipped.makeImage()
}
}
================================================
FILE: Tests/DSFSparklineTests/CircularGaugeTests.swift
================================================
//
// CircularGaugeTests.swift
//
//
// Created by Darren Ford on 12/3/2024.
//
import XCTest
@testable import DSFSparkline
import SwiftImageReadWrite
private let outputFolder = try! testResultsContainer.subfolder(with: "circular-gauge")
private let imagesFolder = try! outputFolder.subfolder(with: "images")
private let imageStore = ImageOutput(imagesFolder)
private var markdownText = "# Circular Gauge\n\n"
final class CircularGaugeTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
try! outputFolder.write(markdownText, to: "circular-gauge.md", encoding: .utf8)
}
func testSizing() throws {
markdownText += "## Sizing\n\n"
try [16, 32, 48].forEach { sz in
try stride(from: 0, through: 1, by: 0.1).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = value
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: sz, height: sz), scale: 2))
let filename = "circular-gauge-ex-\(sz)-\(value).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " \n"
}
markdownText += "\n"
}
}
func testTrackInset() throws {
markdownText += "## Track inset\n\n"
let tfc = DSFSparklineOverlay.CircularGauge.TrackStyle(
width: 10,
fillColor: DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 0.1)
)
let vfc = DSFSparklineOverlay.CircularGauge.TrackStyle(
width: 6,
fillColor: DSFSparkline.Fill.Color(srgbRed: 0.2, green: 0.2, blue: 0.9),
strokeColor: CGColor.sRGBA(0, 0, 1)
)
try [false, true].forEach { hasShadow in
if hasShadow {
markdownText += "### Shadowed\n\n"
}
else {
markdownText += "### No Shadow\n\n"
}
try [10, 8, 6, 4].forEach { tw in
try stride(from: 0, through: 1, by: 0.1).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = value
a1.trackStyle = tfc
vfc.width = CGFloat(tw)
a1.lineStyle = vfc
if hasShadow {
vfc.shadow = .init(blurRadius: 3, offset: CGSize(width: 2, height: -3), color: .black)
}
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-gauge-inset-\(hasShadow)-\(tw)-\(value).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " \n"
}
markdownText += "\n"
}
}
}
func testTrackShadow() throws {
markdownText += "## Shadowing\n\n"
markdownText += "\n### Track/Value shadows\n\n"
markdownText += "| none | track only | value only | both |\n"
markdownText += "|---------|--------------|--------------|---------|\n"
markdownText += "|"
try [false, true].forEach { (lineShadow: Bool) in
try [false, true].forEach { (trackShadow: Bool) in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.65
a1.trackStyle.width = 10
a1.lineStyle.width = 4
if trackShadow {
a1.trackStyle.shadow = .init(blurRadius: 2, offset: CGSize(width: 1, height: -1), color: CGColor.sRGBA(0, 0, 1))
}
if lineShadow {
a1.lineStyle.shadow = .init(blurRadius: 2, offset: CGSize(width: 1, height: -1), color: CGColor.sRGBA(1, 0, 0))
}
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-gauge-inset-\(trackShadow)-\(lineShadow).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |"
}
}
markdownText += "|\n\n"
markdownText += "### Shadow Offset\n\n"
markdownText += "| -2, -2 | -2, 2 | 2, -2 | 2, 2 | 4, 0 | 0, 4 |\n"
markdownText += "|---------|---------|---------|---------|--------|--------|\n"
markdownText += "|"
try [(-2, -2), (-2, 2), (2, -2), (2, 2), (4, 0), (0, 4)].forEach { offset in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
let sh = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: offset.0, height: offset.1), color: .black)
a1.value = 0.65
a1.trackStyle.width = 20
a1.trackStyle.shadow = sh
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.shadow = sh
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-offset-standardshadow-\(offset).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " "
markdownText += "|"
}
markdownText += "|\n\n"
}
func testTrackShadowInset() throws {
markdownText += "## Inner Shadows\n\n"
let innerShadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: true)
let outerShadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: false)
markdownText += "\n### Track/Value shadows\n\n"
markdownText += "| none | track only | value only | both | in/out |\n"
markdownText += "|---------|---------|---------|---------|---------|\n"
markdownText += "|"
enum OTS: CaseIterable {
case none
case track
case value
case all
}
try OTS.allCases.forEach { useTrackInner in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.65
a1.trackStyle.width = 20
if useTrackInner == .track || useTrackInner == .all {
a1.trackStyle.shadow = innerShadow
}
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.strokeColor = CGColor.standard.yellow
a1.lineStyle.strokeWidth = 0.1
if useTrackInner == .value || useTrackInner == .all {
a1.lineStyle.shadow = innerShadow
}
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-inner-\(useTrackInner).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |"
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.65
a1.trackStyle.width = 20
a1.trackStyle.shadow = innerShadow
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.shadow = outerShadow
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-inner-inout.png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |\n\n"
}
markdownText += "\n### Shadow Offset\n\n"
markdownText += "| -2, -2 | -2, 2 | 2, -2 | 2, 2 |\n"
markdownText += "|---------|---------|---------|---------|\n"
markdownText += "|"
try [(-2, -2), (-2, 2), (2, -2), (2, 2)].forEach { offset in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
let sh = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: offset.0, height: offset.1), color: .black, isInner: true)
a1.value = 0.65
a1.trackStyle.width = 20
a1.trackStyle.shadow = sh
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.shadow = sh
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-offset-\(offset).png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " "
markdownText += "|"
}
markdownText += "|\n\n"
}
func testTrackShadowInset2() throws {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
let innerShadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: true)
a1.value = 0.65
a1.trackStyle.width = 20
a1.lineStyle.width = 15
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.shadow = innerShadow
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
Swift.print(image)
}
}
================================================
FILE: Tests/DSFSparklineTests/CircularProgressTests.swift
================================================
//
// CircularProgressTests.swift
//
//
// Created by Darren Ford on 1/3/2024.
//
import XCTest
@testable import DSFSparkline
import SwiftImageReadWrite
private let outputFolder = try! testResultsContainer.subfolder(with: "circular-progress")
private let imagesFolder = try! outputFolder.subfolder(with: "images")
private let imageStore = ImageOutput(imagesFolder)
private var markdownText = "# Circular Progress\n\n"
final class CircularProgressTests: XCTestCase {
override class func tearDown() {
try! outputFolder.write(markdownText, to: "circular-progress.md", encoding: .utf8)
}
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testBasic() throws {
do {
markdownText += "### Inline documentation\n\n"
try stride(from: 0, through: 1, by: 0.333).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = primaryPoke
a1.trackColor = primaryPoke.color.copy(alpha: 0.3)!
a1.trackWidth = 3
a1.value = value
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 16, height: 16), scale: 2))
let filename = "circular-poke-\(value).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
markdownText += "\n"
do {
markdownText += "### Flat\n\n"
var count = 1
try stride(from: 0, through: 2, by: 0.2).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = DSFSparkline.Fill.Color(srgbRed: 0.5, green: 0, blue: 1, alpha: 1)
a1.trackColor = CGColor(srgbRed: 0.5, green: 0, blue: 1, alpha: 0.2)
a1.value = value
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-\(count).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
count += 1
}
}
markdownText += "\n"
do {
markdownText += "### Gradient\n\n"
let gradient = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 1.0),
CGColor(srgbRed: 0.891, green: 0.000, blue: 0.090, alpha: 1.0),
]
)
var count = 1
try stride(from: 0, through: 2, by: 0.2).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = gradient
a1.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a1.value = value
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-radial-\(count).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
count += 1
}
}
markdownText += "\n"
do {
markdownText += "### Nested\n\n"
try [48, 64, 80].forEach { dimension in
var count = 1
try stride(from: 0, through: 1, by: 0.2).forEach { value in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1)
a1.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a1.value = value
a1.trackWidth = 5
b1.addOverlay(a1)
let a2 = DSFSparklineOverlay.CircularProgress()
a2.fillStyle = DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 1)
a2.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a2.trackWidth = 5
a2.padding = 7
a2.value = min(value + 0.1, 1.0)
b1.addOverlay(a2)
let a3 = DSFSparklineOverlay.CircularProgress()
a3.fillStyle = DSFSparkline.Fill.Color(srgbRed: 0, green: 1, blue: 0, alpha: 1)
a3.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a3.trackWidth = 5
a3.padding = 14
a3.value = min(value + 0.05, 1.0)
b1.addOverlay(a3)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: dimension, height: dimension), scale: 2))
let filename = "circular-radial-2overlay\(count)-\(dimension).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
count += 1
}
markdownText += "\n"
}
}
markdownText += "\n"
do {
markdownText += "### Track width\n\n"
try stride(from: 2, through: 12, by: 2).forEach { width in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1)
a1.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a1.value = 0.65
a1.trackWidth = CGFloat(width)
b1.addOverlay(a1)
let a2 = DSFSparklineOverlay.CircularProgress()
a2.fillStyle = DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 1)
a2.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a2.trackWidth = width
a2.padding = 7
a2.value = 0.25
b1.addOverlay(a2)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-trackwidth-\(width).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
}
markdownText += "\n"
do {
markdownText += "### Track padding\n\n"
try stride(from: 0, through: 10, by: 2).forEach { padding in
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularProgress()
a1.fillStyle = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1)
a1.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a1.value = 0.65
a1.trackWidth = 4
b1.addOverlay(a1)
let a2 = DSFSparklineOverlay.CircularProgress()
a2.fillStyle = DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 1)
a2.trackColor = CGColor(gray: 0.5, alpha: 0.2)
a2.trackWidth = 4
a2.padding = padding
a2.value = 0.25
b1.addOverlay(a2)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 48, height: 48), scale: 2))
let filename = "circular-trackpadding-\(padding).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
}
markdownText += "\n"
do {
markdownText += "### Health rings\n\n"
let g = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 1.0),
CGColor(srgbRed: 0.891, green: 0.000, blue: 0.090, alpha: 1.0),
]
)
let g1 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 1.0),
CGColor(srgbRed: 0.601, green: 1.000, blue: 0.009, alpha: 1.0),
]
)
let g2 = DSFSparkline.Fill.Gradient(
colors: [
CGColor(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 1.0),
CGColor(srgbRed: 0.015, green: 0.847, blue: 1.000, alpha: 1.0),
]
)
try [0.0, 0.33, 0.66, 1.0].forEach { index in
let b1 = DSFSparklineSurface.Bitmap()
do {
let a1 = DSFSparklineOverlay.CircularProgress()
a1.value = index
a1.fillStyle = g
a1.trackWidth = 10
a1.trackColor = .init(srgbRed: 0.977, green: 0.221, blue: 0.520, alpha: 0.1)
b1.addOverlay(a1)
}
do {
let a1 = DSFSparklineOverlay.CircularProgress()
a1.value = index * 0.8
a1.fillStyle = g1
a1.trackWidth = 10
a1.padding = 12
a1.trackColor = .init(srgbRed: 0.849, green: 1.000, blue: 0.000, alpha: 0.1)
b1.addOverlay(a1)
}
do {
let a1 = DSFSparklineOverlay.CircularProgress()
a1.value = index * 0.6
a1.fillStyle = g2
a1.trackWidth = 10
a1.padding = 24
a1.trackColor = .init(srgbRed: 0.000, green: 1.000, blue: 0.663, alpha: 0.1)
b1.addOverlay(a1)
}
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 96, height: 96), scale: 2))
let filename = "circular-health-\(index).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
}
markdownText += "\n"
}
}
}
================================================
FILE: Tests/DSFSparklineTests/DSFSparklineTests.swift
================================================
//
// DSFSparklinesTests.swift
// DSFSparklinesTests
//
// Created by Darren Ford on 20/6/19.
// Copyright © 2019 Darren Ford. All rights reserved.
//
// MIT license
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
import XCTest
@testable import DSFSparkline
// A temporary file container to hold results
internal let testResultsContainer = try! TestFilesContainer(named: "DSFSparkline_Tests")
class DSFSparklineTests: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testFixForWindowSizeSmallerThanInitialDataIssues() {
// Testing for https://github.com/dagronf/DSFSparkline/issues/3
let ds = SparklineWindow(windowSize: 5, dataRange: (-10 ... 10))
ds.push(values: [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
XCTAssertEqual(ds.raw.count, 5)
}
func testBlah() {
let dd = SparklineWindow(windowSize: 10, dataRange: (-100 ... 100))
// Default values for the initializer
XCTAssertEqual(dd.raw.count, 10)
XCTAssertEqual(dd.normalized.count, 10)
XCTAssertEqual(dd.raw, [-100, -100, -100, -100, -100, -100, -100, -100, -100, -100])
XCTAssertEqual(dd.normalized, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
XCTAssertTrue(dd.push(value: 0))
XCTAssertTrue(dd.push(value: 1))
XCTAssertEqual(dd.raw.count, 10)
XCTAssertEqual(dd.normalized.count, 10)
XCTAssertEqual(dd.normalized, [0, 0, 0, 0, 0, 0, 0, 0, 0.5, 0.505])
XCTAssertTrue(dd.push(value: 100))
}
func testDynamicallyRanged() {
let dd = SparklineWindow(windowSize: 10)
// Default values for the initializer
XCTAssertEqual(dd.raw.count, 10)
XCTAssertEqual(dd.normalized.count, 10)
XCTAssertEqual(dd.raw, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
XCTAssertEqual(dd.normalized, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
XCTAssertTrue(dd.push(value: 10))
XCTAssertEqual(dd.normalized, [0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
XCTAssertTrue(dd.push(value: 20))
XCTAssertEqual(dd.normalized, [0, 0, 0, 0, 0, 0, 0, 0, 0.5, 1])
XCTAssertTrue(dd.push(value: -20))
XCTAssertEqual(dd.normalized, [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.75, 1.0, 0.0])
XCTAssertTrue(dd.push(value: -10))
XCTAssertEqual(dd.normalized, [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.75, 1.0, 0.0, 0.25])
}
func testResizing() {
let dd = SparklineWindow(windowSize: 10)
XCTAssertEqual(dd.raw.count, 10)
dd.set(values: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
XCTAssertEqual(dd.normalized, [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
dd.set(values: [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5])
XCTAssertEqual(dd.normalized, [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
/// Resizing
dd.set(values: [-5, -4, -3, -2, -1, 0])
XCTAssertEqual(dd.normalized, [0.0, 0.2, 0.4, 0.6, 0.8, 1.0])
dd.set(values: [-5, -4, -3, -2, -1, 0, 1, 2])
XCTAssertEqual(dd.normalized, [0.0, 0.14285714285714285, 0.2857142857142857, 0.42857142857142855,
0.5714285714285714, 0.7142857142857143, 0.8571428571428571, 1.0])
dd.reset()
dd.windowSize = 3
XCTAssertEqual(0, dd.counter)
XCTAssertTrue(dd.push(value: 1))
XCTAssertEqual(dd.raw, [0.0, 0.0, 1.0])
XCTAssertEqual(dd.normalized, [0.0, 0.0, 1.0])
XCTAssertEqual(1, dd.counter)
XCTAssertTrue(dd.push(value: 2))
XCTAssertEqual(dd.raw, [0.0, 1.0, 2.0])
XCTAssertEqual(dd.normalized, [0.0, 0.5, 1.0])
XCTAssertEqual(2, dd.counter)
XCTAssertTrue(dd.push(value: 3))
XCTAssertEqual(dd.raw, [1.0, 2.0, 3.0])
XCTAssertEqual(dd.normalized, [0.0, 0.5, 1.0])
XCTAssertEqual(3, dd.counter)
XCTAssertTrue(dd.push(value: 4))
XCTAssertEqual(dd.raw, [2.0, 3.0, 4.0])
XCTAssertEqual(dd.normalized, [0.0, 0.5, 1.0])
XCTAssertEqual(4, dd.counter)
}
func testWindowSizeChanging() {
let dd = SparklineWindow(windowSize: 20)
dd.set(values: [1.0, 2.0, 3.0, 4.0])
dd.windowSize = 7
XCTAssertEqual([0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0], dd.raw)
dd.windowSize = 3
XCTAssertEqual([2.0, 3.0, 4.0], dd.raw)
}
func testDataSource() {
// Check that truncating to range works
let ds = DSFSparkline.DataSource(windowSize: 10, range: -10 ... 10)
XCTAssertTrue(ds.push(value: 5))
XCTAssertTrue(ds.push(value: 50))
XCTAssertEqual(ds.data, [0, 0, 0, 0, 0, 0, 0, 0, 5, 10])
XCTAssertEqual(ds.normalized, [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.75, 1.0])
// With no range, adding 5 here makes the implicit range to 0 ... 5
let ds2 = DSFSparkline.DataSource(windowSize: 5)
XCTAssertTrue(ds2.push(value: 5))
XCTAssertEqual(ds2.data, [0, 0, 0, 0, 5])
XCTAssertEqual(ds2.normalized, [0, 0, 0, 0, 1])
// With no range, adding -5 here makes the implicit range to -5 ... 5
XCTAssertTrue(ds2.push(value: -5))
XCTAssertEqual(ds2.data, [0, 0, 0, 5, -5])
XCTAssertEqual(ds2.normalized, [0.5, 0.5, 0.5, 1, 0])
// With no range, adding -5 here makes the implicit range to -5 ... 5
XCTAssertTrue(ds2.push(value: 10))
XCTAssertTrue(ds2.push(value: -3))
XCTAssertEqual(ds2.data, [0, 5, -5, 10, -3])
XCTAssertEqual(ds2.normalized, [0.3333333333333333, 0.6666666666666666, 0.0, 1.0, 0.13333333333333333])
ds2.windowSize = 3
XCTAssertEqual(ds2.data, [-5, 10, -3])
XCTAssertEqual(ds2.normalized, [0.0, 1.0, 0.13333333333333333])
ds2.windowSize = 10
XCTAssertEqual(ds2.data, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -5.0, 10.0, -3.0])
XCTAssertEqual(ds2.normalized,
[0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.3333333333333333,
0.3333333333333333, 0.3333333333333333, 0.0, 1.0, 0.13333333333333333])
ds2.reset()
XCTAssertEqual(ds2.data, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])
ds2.set(values: [0, 1, 2, 3, 4, 5, 6])
XCTAssertEqual(7, ds2.windowSize)
XCTAssertEqual(ds2.data, [0, 1, 2, 3, 4, 5, 6])
}
func testAddValues() {
let ds = DSFSparkline.DataSource(windowSize: 5)
ds.push(values: [1.1, 2.2, 3.3])
XCTAssertEqual(ds.data, [0, 0, 1.1, 2.2, 3.3])
XCTAssertTrue(ds.push(value: 1.2))
XCTAssertEqual(ds.data, [0, 1.1, 2.2, 3.3, 1.2])
ds.push(values: [10, 11, 12])
XCTAssertEqual(ds.data, [3.3, 1.2, 10.0, 11.0, 12.0])
// Check what happens if greater than window. Should truncate to the last 5 values in the array
// Equivalent to push 1, push 2, push 3, push 4 etc.
ds.push(values: [1, 2, 3, 4, 5, 6, 7, 8])
XCTAssertEqual(ds.data, [4, 5, 6, 7, 8])
}
func testCircularProgress() throws {
let l1 = DSFSparklineOverlay.CircularProgress()
l1.value = 1.8
l1.trackWidth = 10
let l2 = DSFSparklineOverlay.CircularProgress()
l2.value = 1.4
l2.trackWidth = 10
l2.padding = 12
l2.fillStyle = DSFSparkline.Fill.Gradient(colors: [
CGColor.init(red: 1, green: 0, blue: 0, alpha: 1),
CGColor.init(red: 0, green: 1, blue: 0, alpha: 1),
CGColor.init(red: 0, green: 0, blue: 1, alpha: 1),
])
// Solid white
let l3 = DSFSparklineOverlay.CircularProgress()
l3.value = 0.35
l3.trackWidth = 10
l3.padding = 24
l3.fillStyle = DSFSparkline.Fill.Gradient(colors: [
CGColor.init(red: 1, green: 0, blue: 0, alpha: 1),
CGColor.init(red: 0, green: 1, blue: 0, alpha: 1),
CGColor.init(red: 0, green: 0, blue: 1, alpha: 1),
])
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
bitmap.addOverlay(l1) // And add the overlay to the surface.
bitmap.addOverlay(l2) // And add the overlay to the surface.
bitmap.addOverlay(l3) // And add the overlay to the surface.
let image = bitmap.image(width: 100, height: 100, scale: 2)
Swift.print(image)
}
func testGradientPeek() throws {
let gr = CGGradient(
colorsSpace: nil,
colors: [
CGColor(red: 1, green: 0, blue: 0, alpha: 1),
CGColor(red: 0, green: 0, blue: 1, alpha: 1),
] as CFArray,
locations: [0.0, 1.0]
)!
let f1 = DSFSparkline.Fill.Gradient(gradient: gr)
let c1 = f1.color(at: 0)
let c2 = f1.color(at: 0.5)
let c3 = f1.color(at: 1)
Swift.print([c1, c2, c3])
}
}
================================================
FILE: Tests/DSFSparklineTests/LineGraphTests.swift
================================================
//
// LineGraphTests.swift
//
//
// Created by Darren Ford on 1/3/2024.
//
import XCTest
@testable import DSFSparkline
import SwiftImageReadWrite
private let outputFolder = try! testResultsContainer.subfolder(with: "line-graph")
private let imagesFolder = try! outputFolder.subfolder(with: "images")
private let imageStore = ImageOutput(imagesFolder)
private var markdownText = "# Line Graph\n\n"
final class LineGraphTests: XCTestCase {
override class func tearDown() {
try! outputFolder.write(markdownText, to: "line-graph.md", encoding: .utf8)
}
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
func testBasic() throws {
markdownText += "## Inline documentation\n\n"
let source = DSFSparkline.DataSource(values: [4, 1, 8, 7, 5, 9, 3], range: 0 ... 10)
try [0, 5].forEach { zeroline in
source.zeroLineValue = zeroline
markdownText += "| line | interpolated | line with markers | line with markers interpolated |\n"
markdownText += "|------|------|------|------|\n"
try [false, true].forEach { interpolated in
markdownText += "|"
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.interpolated = interpolated
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.dataSource = source
bitmap.addOverlay(line)
if zeroline != 0 {
bitmap.addOverlay(DSFSparklineOverlay.ZeroLine(dataSource: source))
}
// Generate an image with retina scale
let image = try XCTUnwrap(bitmap.cgImage(size: CGSize(width: 50, height: 25), scale: 2))
let filename = "line-simple-small-interpolated(\(interpolated))-zeroline(\(zeroline)).png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " "
}
try [false, true].forEach { interpolated in
markdownText += "|"
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let line = DSFSparklineOverlay.Line() // Create a line overlay
line.strokeWidth = 1
line.primaryStrokeColor = primaryStroke
line.primaryFill = primaryFill
line.markerSize = 3
line.interpolated = interpolated
line.dataSource = source // Assign the datasource to the overlay
bitmap.addOverlay(line) // And add the overlay to the surface.
if zeroline != 0 {
bitmap.addOverlay(DSFSparklineOverlay.ZeroLine(dataSource: source))
}
// Generate an image with retina scale
let image3 = try XCTUnwrap(bitmap.cgImage(size: CGSize(width: 50, height: 25), scale: 2))
let filename3 = "line-simple-attributed-string-inline-interpolated(\(interpolated))-zeroline(\(zeroline)).png"
let link3 = try imageStore.store(image3.representation.png(scale: 2), filename: filename3)
markdownText += " "
}
markdownText += "|\n\n"
}
}
}
================================================
FILE: Tests/DSFSparklineTests/PresentationTests.swift
================================================
//
// PresentationTests.swift
//
//
// Created by Darren Ford on 27/2/2024.
//
import XCTest
@testable import DSFSparkline
import SwiftImageReadWrite
class ImageOutput {
let _imagesFolder: TestFilesContainer.Subfolder
init(_ folder: TestFilesContainer.Subfolder) {
_imagesFolder = folder
}
func store(_ data: Data, filename: String) throws -> String {
try _imagesFolder.write(data, to: filename)
return "./images/\(filename)"
}
func store(_ string: String, filename: String) throws -> String {
try _imagesFolder.write(string, to: filename)
return "./images/\(filename)"
}
}
private let outputFolder = try! testResultsContainer.subfolder(with: "generation")
private let imagesFolder = try! outputFolder.subfolder(with: "images")
private let imageStore = ImageOutput(imagesFolder)
private var markdownText = "# Sparklines\n\n"
final class PresentationTests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
override class func tearDown() {
try! outputFolder.write(markdownText, to: "sparklines.md", encoding: .utf8)
}
}
let primaryPoke = DSFSparkline.Fill.Color(srgbRed: 0.934, green: 0.000, blue: 0.000, alpha: 1.0)
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryStroke = baseColor // (gray: 0.0, alpha: 0.3))
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
extension PresentationTests {
func testCircularGraph() throws {
markdownText += "## Circular Grid\n\n"
do {
try [0, 0.33, 0.66, 1.0].forEach { value in
let surface = DSFSparklineSurface.Bitmap()
let a = DSFSparklineOverlay.CircularGauge()
let baseColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [0.033, 0.277, 0.650, 1.000])!
let primaryFill = DSFSparkline.Fill.Color(baseColor.copy(alpha: 0.3)!)
a.trackStyle = DSFSparklineOverlay.CircularGauge.TrackStyle(width: 10, fillColor: primaryFill)
a.lineStyle = DSFSparklineOverlay.CircularGauge.TrackStyle(width: 5, fillColor: DSFSparkline.Fill.Color(baseColor))
a.value = value
surface.addOverlay(a)
let filename = "circular-gauge-small-\(value).png"
let bitmap = surface.image(width: 40, height: 40, scale: 2)!
let link = try imageStore.store(bitmap.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
let innerShadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: true)
let outerShadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: false)
a1.value = 0.65
a1.trackStyle.width = 20
a1.trackStyle.shadow = innerShadow
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 1, blue: 0)
a1.lineStyle.shadow = outerShadow
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-inner-inout.png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |\n\n"
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.35
a1.trackStyle.width = 20
a1.trackStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 0.1)
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1)
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-basic.png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |\n\n"
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.35
a1.trackStyle.width = 20
a1.trackStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 0.1)
a1.lineStyle.width = 10
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 0, alpha: 1)
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-basic.png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |\n\n"
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
a1.value = 0.55
a1.trackStyle.width = 10
a1.trackStyle.shadow = DSFSparkline.Shadow(blurRadius: 3, offset: CGSize(width: 2, height: -2), color: .black, isInner: true)
a1.trackStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 1, alpha: 0.1)
a1.lineStyle.width = 8
a1.lineStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 1, green: 0, blue: 1, alpha: 1)
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 30, height: 30), scale: 2))
let filename = "circular-gauge-slightly.png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " |\n\n"
}
do {
let b1 = DSFSparklineSurface.Bitmap()
let a1 = DSFSparklineOverlay.CircularGauge()
let gr = DSFSparkline.Fill.Gradient(colors: [
CGColor.sRGBA(0, 0, 1),
CGColor.sRGBA(0, 1, 1),
])
a1.value = 0.87
a1.trackStyle.width = 15
a1.trackStyle.fillColor = DSFSparkline.Fill.Color(srgbRed: 0, green: 0, blue: 1, alpha: 0.1)
a1.lineStyle.width = 7
a1.lineStyle.fillColor = gr
a1.lineStyle.shadow = .init(
blurRadius: 3,
offset: .init(width: 2, height: -2),
color: CGColor(gray: 0, alpha: 0.8)
)
b1.addOverlay(a1)
let image = try XCTUnwrap(b1.cgImage(size: CGSize(width: 64, height: 64), scale: 2))
let filename = "circular-gauge-gradient.png"
let link = try imageStore.store(image.representation.png(), filename: filename)
markdownText += " |\n\n"
}
}
func testActivityGrid() throws {
markdownText += "## Activity Grid\n\n"
do {
markdownText += "### Inline documentation\n\n"
let surface = DSFSparklineSurface.Bitmap()
let grid = DSFSparklineOverlay.ActivityGrid()
grid.dataSource = DSFSparkline.StaticDataSource((0 ... 1000).map { _ in CGFloat.random(in: 0 ... 1) }, range: 0 ... 1)
grid.verticalCellCount = 3
grid.cellStyle = .init(
fillScheme: DSFSparkline.ActivityGrid.CellStyle.DefaultLight,
borderColor: .black,
borderWidth: 0.5,
cellDimension: 5,
cellSpacing: 0.5,
cornerRadius: 1
)
surface.addOverlay(grid)
let bitmap = surface.image(width: 150, height: 17, scale: 2)!
// Generate an image with retina scale
let filename = "activity-grid-mini.png"
let link = try imageStore.store(bitmap.representation.png(scale: 2), filename: filename)
markdownText += " \n"
}
markdownText += "\n"
do {
let bitmap = DSFSparklineSurface.Bitmap() // Create a bitmap surface
let activity = DSFSparklineOverlay.ActivityGrid()
let data: [CGFloat] = (0 ... 100).map { _ in CGFloat.random(in: 0...100) }
activity.dataSource = .init(data)
bitmap.addOverlay(activity) // And add the overlay to the surface.
// Generate an image with retina scale
let image = bitmap.image(width: 300, height: 100, scale: 2)!
let filename = "activity-basic-1.png"
let link = try imageStore.store(image.representation.png(scale: 2), filename: filename)
markdownText += " \n"
activity.verticalCellCount = 10
activity.cellDimension = 6
activity.cellSpacing = 1
activity.cellFillScheme = DSFSparkline.ActivityGrid.CellStyle.DefaultLight
let image2 = bitmap.image(width: 300, height: 100, scale: 2)!
let filename2 = "activity-basic-2.png"
let link2 = try imageStore.store(image2.representation.png(scale: 2), filename: filename2)
markdownText += " \n"
}
}
}
================================================
FILE: Tests/DSFSparklineTests/utils/TestFilesContainer.swift
================================================
//
// File.swift
//
//
// Created by Darren Ford on 12/4/2023.
//
import Foundation
class TestFilesContainer {
// Note: DateFormatter is thread safe
// See https://developer.apple.com/documentation/foundation/dateformatter#1680059
private static let iso8601Formatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // set locale to reliable US_POSIX ISO8601
dateFormatter.dateFormat = "yyyy-MM-dd'T'HHmmssZ"
return dateFormatter
}()
private let root: Subfolder
var rootFolder: URL { self.root.folder }
init(named name: String) throws {
let baseURL = FileManager.default.temporaryDirectory.appendingPathComponent(name)
let url = baseURL.appendingPathComponent(Self.iso8601Formatter.string(from: Date()))
try? FileManager.default.removeItem(at: url)
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
self.root = Subfolder(url)
Swift.print("TestContainer(\(name) - Generated files at: \(url)")
let latest = baseURL.appendingPathComponent("_latest")
try? FileManager.default.removeItem(at: latest)
try! FileManager.default.createSymbolicLink(at: latest, withDestinationURL: url)
}
func subfolder(with components: String...) throws -> Subfolder {
var subfolder = self.rootFolder
components.forEach { subfolder.appendPathComponent($0) }
try FileManager.default.createDirectory(at: subfolder, withIntermediateDirectories: true)
return Subfolder(subfolder)
}
class Subfolder {
let folder: URL
init(_ parent: URL) {
self.folder = parent
}
init(named name: String, parent: URL) throws {
let subf = parent.appendingPathComponent(name)
try FileManager.default.createDirectory(at: subf, withIntermediateDirectories: true)
self.folder = subf
}
func subfolder(with components: String...) throws -> Subfolder {
var subfolder = self.folder
components.forEach { subfolder.appendPathComponent($0) }
try FileManager.default.createDirectory(at: subfolder, withIntermediateDirectories: true)
return Subfolder(subfolder)
}
@discardableResult func write(
_ data: Data,
to file: String
) throws -> URL {
let tempURL = self.folder.appendingPathComponent(file)
try data.write(to: tempURL)
return tempURL
}
@discardableResult func write(
_ string: String,
to file: String,
encoding: String.Encoding = .utf8
) throws -> URL {
let tempURL = self.folder.appendingPathComponent(file)
try string.write(to: tempURL, atomically: true, encoding: encoding)
return tempURL
}
}
}
================================================
FILE: Tools/sanity-check.sh
================================================
#!/bin/sh
##
## sanity-check.sh
## DSFSparklines
##
## Created by Darren Ford on 26/2/21.
## Copyright © 2021 Darren Ford. All rights reserved.
##
## MIT license
##
## Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
## documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
## rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
## permit persons to whom the Software is furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included in all copies or substantial
## portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
## WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
## OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
## OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
##
# A simple script to build all the supported platforms to make sure I havent overlooked an issue on one of them.
pushd .
cd ..
echo "Cleaning builds..."
swift package clean
##
## iOS versions
##
echo "Building iOS targets..."
# Simulator
xcrun swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios11-simulator"
# Latest iOS arm64
xcrun swift build -Xswiftc "-sdk" -Xswiftc $(xcrun --sdk iphoneos --show-sdk-path) -Xswiftc "-target" -Xswiftc "arm64-apple-ios`xcrun --sdk iphoneos --show-sdk-version`"
# Earliest iOS
xcrun swift build -Xswiftc "-sdk" -Xswiftc $(xcrun --sdk iphoneos --show-sdk-path) -Xswiftc "-target" -Xswiftc "arm64-apple-ios11"
##
## macOS versions
##
echo "Building mac targets..."
# Current macOS
xcrun swift build -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx`xcrun --sdk macosx --show-sdk-version`"
# Earliest macOS
xcrun swift build -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.11"
# Arm64 macOS
xcrun swift build -Xswiftc "-target" -Xswiftc "arm64-apple-macosx`xcrun --sdk macosx --show-sdk-version`"
##
## tvOS versions
##
echo "Building tvOS targets..."
# Simulator
xcrun swift build -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk appletvsimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-tvos`xcrun --sdk iphoneos --show-sdk-version`-simulator"
# Latest
xcrun swift build -Xswiftc "-sdk" -Xswiftc $(xcrun --sdk appletvos --show-sdk-path) -Xswiftc -target -Xswiftc "arm64-apple-tvos`xcrun --sdk iphoneos --show-sdk-version`"
# Earliest iOS (v11)
xcrun swift build -Xswiftc "-sdk" -Xswiftc $(xcrun --sdk appletvos --show-sdk-path) -Xswiftc -target -Xswiftc "arm64-apple-tvos11"
# xcrun --sdk iphoneos --show-sdk-version
# xcrun --sdk macosx --show-sdk-version
# xcrun --sdk appletvos --show-sdk-version
popd