Line plot like in Robinhood app in SwiftUI

Overview

RHLinePlot

Line plot like in Robinhood app, in SwiftUI

Demo

Looking for how to do the moving price label effect? Another repo here.

P.S. Of course this is not, in anyway, affiliated with Robinhood officially. This is just an attempt to replicate its UI and I don't own any of this design.

Demo stock API is from Alphavantage.

Table of Contents

Features

  • Support drag interaction, highlight active segment
  • Support glowing indicator, i.e. for real-time data
  • Customize animation duration, glowing size, labels etc.
  • Laser mode!

Play around with the example app to see possible customizations and the Robinhood-style view shown in the demo.

Installation

Cocoapods

pod install RHLinePlot

Or just use the source however you like. The library is in folder RHLinePlot.

APIs

Without any interaction

RHLinePlot(
    values: valuesToPlot,
    occupyingRelativeWidth: 0.8,
    showGlowingIndicator: true,
    lineSegmentStartingIndices: segments,
    activeSegment: 2,
    customLatestValueIndicator: {
      // Return a custom glowing indicator if you want
    }
)

Notes:

  • segments is the beginning indices of each segment. I.e. values = [1,2,3,4,3,2,1,2,3,4] and segments = [0,4,8] means there are three segments in this line plot: 0-3, 4-7, 8-9.
  • occupyingRelativeWidth = 0.8 is to plot 80% of the plot canvas. This is useful to simulate realtime data. I.e. compute the current hour of the day relative to the 24-hour timeframe and use that ratio. By default this is 1.0.

With interactive elements

RHInteractiveLinePlot(
    values: values,
    occupyingRelativeWidth: 0.8,
    showGlowingIndicator: true,
    lineSegmentStartingIndices: segments,
    didSelectValueAtIndex: { index in
      // Do sth useful with index...
},
    customLatestValueIndicator: {
      // Custom indicator...
},
    valueStickLabel: { value in
      // Label above the value stick...
})

Configuration via Environment

To customize:

YourView
.environment(\.rhLinePlotConfig, RHLinePlotConfig.default.custom(f: { (c) in
    c.useLaserLightLinePlotStyle = isLaserModeOn
}))

Full config:

public struct RHLinePlotConfig {

    /// Width of the rectangle holding the glowing indicator (i.e. not `radius`, but rather `glowingIndicatorWidth = 2*radius`). Default is `8.0`
    public var glowingIndicatorWidth: CGFloat = 8.0
    
    /// Line width of the line plot. Default is `1.5`
    public var plotLineWidth: CGFloat = 1.5
    
    /// If all values are equal, we will draw a straight line. Default is 0.5 which draws a line at the middle.
    public var relativeYForStraightLine: CGFloat = 0.5
    
    /// Opacity of unselected segment. Default is `0.3`.
    public var opacityOfUnselectedSegment: Double = 0.3
    
    /// Animation duration of opacity on select/unselect a segment. Default is `0.1`.
    public var segmentSelectionAnimationDuration: Double = 0.1
    
    /// Scale the fading background of glowing indicator to specified value. Default is `5` (scale to 5 times bigger before disappear)
    public var glowingIndicatorBackgroundScaleEffect: CGFloat = 5
    
    public var glowingIndicatorDelayBetweenGlow: Double = 0.5
    public var glowingIndicatorGlowAnimationDuration: Double = 0.8
    
    /// Use laser stroke mode to plot lines.
    ///
    /// Note that your plot will be automatically shrinked so that the blurry part fits inside the canvas.
    public var useLaserLightLinePlotStyle: Bool = false
    
    /// Use drawing group for laser light mode.
    ///
    /// This will increase responsiveness if there's a lot of segments.
    /// **But, the blurry parts will be clipped off the canvas bounds.**
//    public var useDrawingGroupForLaserLightLinePlotStyle: Bool = false
    
    /// The edges to fit the line strokes within canvas. This interacts with `plotLineWidth`. Default is `[]`.
    ///
    /// By default only the line skeletons (*paths*) exactly fits in the canvas,** without considering the `plotLineWidth`**.
    /// So when you increase the line width, the edge of the extreme values could go out of the canvas.
    /// You can provide a set of edges to consider to adjust to fit in canvas.
    public var adjustedEdgesToFitLineStrokeInCanvas: Edge.Set = []
    
    // MARK:- RHInteractiveLinePlot
    
    public var valueStickWidth: CGFloat = 1.2
    public var valueStickColor: Color = .gray
    
    /// Padding from the highest point of line plot to value stick. If `0`, the top of value stick will be at the same level of the highest point in plot.
    public var valueStickTopPadding: CGFloat = 28
    
    /// Padding from the lowest point of line plot to value stick. If `0`, the end of value stick will be at the same level of the lowest point in plot.
    public var valueStickBottomPadding: CGFloat = 28
    
    public var spaceBetweenValueStickAndStickLabel: CGFloat = 8

    /// Duration of long press before the value stick is activated and draggable.
    ///
    /// The more it is, the less likely the interactive part is activated accidentally on scroll view. Default is `0.1`.
    ///
    /// There's some lower-bound on this value that I guess coming from delaysContentTouches of
    /// the ScrollView. So if this is `0`, iit won't immediately activate the long press (but quickly horizontal pan will).
    public var minimumPressDurationToActivateInteraction: Double = 0.1
    
    public static let `default` = RHLinePlotConfig()
    
    public func custom(f: (inout RHLinePlotConfig) -> Void) -> RHLinePlotConfig {
        var new = self
        f(&new)
        return new
    }
}

TODO

  • Support two finger drag to compare between two values on the plot.
  • Dragging in the interactive plot consumes all the gestures. If you put it in a ScrollView, you can't scroll the scroll view in the interactive plot area, you'd be interacting with the plot instead. - Fixed by using a clear proxy view to handle gestures

Fun Solved Problems

Drag gesture consumes all the drag

Problem: So you can't put the plot in a scroll view and scroll down on the plot. I tried adding LongPressGesture like in Apple's tutorial, but looks like it too consumes gesture exclusively if put under a scroll view.

Solution: This is currently fixed by putting a proxy view that implements custom long press gesture detection.

Indicator label must stick at the edge of plot

Problem: To stick the indicator label (valueStickLabel) translation at the horizontal edge of the plot, we need to know the label width. However its content is dynamic, it could be anything a user set.

Solution: This is fixed by having two valueStickLabels. First one is used for sizing and hidden away. The second one is overlaid on the first with GeometryReader, so we know the final size of the label, ready to calculate the translation next (where we could clamp its offset with the width).

// Indicator Label
//
// HACK: Get a dynamic size of the indicator label with `overlay` + `GeometryReader`.
// Hide the bottom one (just use it for sizing), then show the overlaid one.
valueStickLabel.opacity(0)
    .overlay(
        GeometryReader { labelProxy in
            valueStickLabel
                .transformEffect(labelTranslation(labelProxy: labelProxy))
        }.opacity(stickAndLabelOpacity))

StickylabelDemo

Laser mode is unresponsive to segment highlighting

Problem: The laser mode puts 3 blur effects on each segment of the line plot, so it can be unresponsive to drag around fast and animate opacity of different parts.

Solution: Just use drawingGroup(). This helps a lot. However, this introduces the next issue:

The blurry effect is clipped off at the edge of the plot frame with drawingGroup()

Problem: Using drawingGroup() seems to apply the clipsToBounds-like effect on the blurry part, and it doesn't look nice.

BlurryProblemDemo

Solution: Inset the plot canvas relative to the plotLineWidth config (the larger the value, the larger the blurry blob) so that drawingGroup has more space to draw and cache image:

let adjustedEachBorderDueToBlur: CGFloat = {
    if rhLinePlotConfig.useLaserLightLinePlotStyle {
        return 7.5 * rhLinePlotConfig.plotLineWidth // Magic number accounts for blurring
    } else {
        return 0
    }
}()
let largerCanvas = canvasFrame.insetBy(dx: -adjustedEachBorderDueToBlur, dy: -adjustedEachBorderDueToBlur)

BlurryFixedDemo

You might also like...
Line Chart library for iOS written in Swift
Line Chart library for iOS written in Swift

Swift LineChart Usage var lineChart = LineChart() lineChart.addLine([3, 4, 9, 11, 13, 15]) Features Super simple Highly customizable Auto scaling Touc

Demonstrate a way to build your own line chart without using any third-party library
Demonstrate a way to build your own line chart without using any third-party library

LineChart This code demonstrate a way to build your own line chart without using any third-party library. It contains a simple yet effective algorithm

Simple iOS Application built using UIKit displaying the list of Cryptocurrencies and a detailed screen with a line graph.
Simple iOS Application built using UIKit displaying the list of Cryptocurrencies and a detailed screen with a line graph.

CryptoViewer Simple iOS Application built using UIKit displaying the list of Cryptocurrencies and a detailed screen with a line graph. Home Screen: Di

Flower-like chart written in Swift
Flower-like chart written in Swift

FlowerChart - custom chart written in Swift Fully vector flower-shaped chart written in Swift Flower-shaped chart written in Swift, this repo is a sam

A charting library to visualize and interact with a vector map on iOS. It's like Geochart but for iOS!
A charting library to visualize and interact with a vector map on iOS. It's like Geochart but for iOS!

FSInteractiveMap A charting library to visualize data on a map. It's like geochart but for iOS! The idea behind this library is to load a SVG file of

 SwiftUICharts - ChartView made in SwiftUI
SwiftUICharts - ChartView made in SwiftUI

SwiftUICharts Swift package for displaying charts effortlessly. V2 Beta 1 is released, if you would like to try it out you can find a demo project her

🎉 SwiftUI stock charts for iOS
🎉 SwiftUI stock charts for iOS

SwiftUI Stock Charts Display interactive stock charts easily 🎉 Instalation In Xcode go to File - Swift packages - Add package dependency Copy and p

🎉 SwiftUI stock charts for iOS
🎉 SwiftUI stock charts for iOS

🎉 SwiftUI stock charts for iOS

SwiftUI Charts with custom styles
SwiftUI Charts with custom styles

SwiftUI Charts Build custom charts with SwiftUI Styles Line Chart(data: [0.1, 0.3, 0.2, 0.5, 0.4, 0.9, 0.1]) .chartStyle( LineChartStyle(.

Comments
  • +1 to not have interactive line plot consume all gestures

    +1 to not have interactive line plot consume all gestures

    Nice work on this! Just wanted to echo your note about wanting to work on interactive line plots not consuming all gestures. I think it would be neat to support various ways to work with it: for example, require an event to trigger horizontal scrubbing such as a long-press, or if possible only listen to horizontal movements and not vertical (in the case of scrolling a view up/down). But think the former is more than sufficient with a user-customizable long-press delay.

    opened by stammy 1
Releases(0.1.0)
Owner
Wirawit Rueopas
Wirawit Rueopas
SwiftUICharts - A simple line and bar charting library that supports accessibility written using SwiftUI.

SwiftUICharts - A simple line and bar charting library that supports accessibility written using SwiftUI.

Majid Jabrayilov 1.4k Jan 9, 2023
An interactive line chart written in SwiftUI with many customizations.

LineChartView LineChartView is a Swift Package written in SwiftUI to add a line chart to your app. It has many available customizations and is interac

Jonathan Gander 59 Dec 10, 2022
Fully customizable line chart for SwiftUI 🤩

?? CheesyChart Create amazing Crypto and Stock charts ?? ?? Looking for an easy to use and fully customizable charting solution written in SwiftUI? Th

adri567 13 Dec 14, 2022
A SwiftUI Contribution Chart (GitHub-like) implementation package

ContributionChart A contribution chart (aka. heatmap, GitHub-like) library for iOS, macOS, and watchOS. 100% written in SwiftUI. It Supports Custom Bl

null 41 Dec 27, 2022
SwiftChart - A simple line and area charting library for iOS.

SwiftChart A simple line and area charting library for iOS. ?? Line and area charts ?? Multiple series ?? Partially filled series ?? Works with signed

Giampaolo Bellavite 1k Jan 2, 2023
A powerful 🚀 Android chart view / graph view library, supporting line- bar- pie- radar- bubble- and candlestick charts as well as scaling, panning and animations.

⚡ A powerful & easy to use chart library for Android ⚡ Charts is the iOS version of this library Table of Contents Quick Start Gradle Maven Documentat

Philipp Jahoda 36k Jan 5, 2023
Elegant Line Graphs for iOS. (Charting library)

BEMSimpleLineGraph BEMSimpleLineGraph makes it easy to create and customize line graphs for iOS. BEMSimpleLineGraph is a charting library that makes i

Boris Emorine 2.7k Dec 26, 2022
iOS-based charting library for both line and bar graphs.

JBChartView Introducing JBChartView - Jawbone's iOS-based charting library for both line and bar graphs. It is easy to set-up, and highly customizable

Jawbone 3.8k Jan 1, 2023
FSLineChart A line chart library for iOS.

FSLineChart A line chart library for iOS. Screenshots Installing FSLineChart Add the contents of the FSLineChart project to your directory or simply a

Arthur 856 Nov 24, 2022
ANDLineChartView is easy to use view-based class for displaying animated line chart.

ANDLineChartView for iOS ANDLineChartView is easy to use view-based class for displaying animated line chart. Usage API is simple. Just implement foll

Andrzej Naglik 421 Dec 11, 2022