iCharts 1.0.0

iCharts 1.0.0

Maintained by Volodymyr Hryhoriev.



iCharts 1.0.0

iCharts

Swift version Cocoapods License

The solution for Telegram "March Coding Competition". Completely implemented using Core Animation and Auto Layout (NSLayoutAnchor).

You may take part in beta testing on TestFlight.

Demo Gif

Telegram "March Coding Competition" tasks

  • Use date from chart_data.json as input for your charts
  • Show all 5 charts on one screen
  • Implement Day/Night mode
  • Add pan gesture on chart in order to highlight points at x and watch detailed information
  • Show date labels bellow x axis
  • Show value labels before y axis
  • Create expandable slider control in order to select visible part of chart and its scale.
  • Show visible part of [Xi, Xj] in [minY, maxY] segment with vertical insets at top and bottom, where minY = {y | y <= f(x), x in [Xi, Xj]}, maxY = {y | y >= f(x), x in [Xi, Xj]}
  • Animate value labels changing
  • Animate date labels changing
  • Animate appearance/dissappearance of lines on chart

Usage

The simplest case is shown below. You should just create props with arrat of Lines and pass it into render(props:) method.

let chartView = DetailedChartView() // ChartScrollView, PannableChartView, ChartView

// Frame based layout or Auto Layout

let props = ChartView.Props(lines: [
              Line(title: "#1", xs: [1, 2, 3], ys: [3, 4, 5], color: .red),
              Line(title: "#2", xs: [1, 2, 3], ys: [5, 4, 3], color: .green)
            ])

chartView.render(props: props)

In order to change theme you should just change Colors property.

chartView.colors = makeColors() // you should write `makeColors` method, it is just example

ChartView.Props properties

  • lines - an array of Line structs
  • lineWidth - a positive real value which defines width of line on chart
  • highlithedX - a x coordinate which user highlight on PannableChartView
  • estimatedGridSpace - estimated space between 2 horizontal lines of grid
  • estimatedXLabelWidth - estimated width of label below x axis
  • inset - vertical inset when user renders chart as full-sized
  • isInFullSize - determines that should we render chart as full-sized
  • range - the range of visible part of chart in percents or points
  • didHighlightX - closure which fires when user highlight points on PannableChartView

Colors

It is not necessary to enumerate all properties of Colors struct of each component. You should just know, that you can change color of any part of chart.

See Colors struct in DetailedChartView for more information.

Instalation

TBD

Workspace structure

  • iChartsDemo is an iOS single view application project which demonstrates usage of iCharts framework
  • iCharts is a framework which contains ChartView source code
  • Utils is a static library which contains some useful structs and typealiases such as Result, Variable, etc.

Architecture

TL;DR

  • fully implemented on CALayers
  • preferred composition over inheritance
  • fully data-driven UI
  • render only visible part of a chart

Details

The implementation of iCharts framework is highly motivated by Core Animaton CALayers capabilities and classes composition instead of inheritance in order to have flexible, extendable and easy-maintainable code base with SRP principle in the head.

Note: of course in competition situation with time boundaries it is very hard to find trade offs between speed and quality, that's why some principles of SOLID are violated sometime.

Also it should be remarked that all parts of UI are data-driven. Props is used as a dumb representation of UI state at each point of time. This approach makes possible to implement time-traveling debugging feature in future.

ChartView.Props example

extension ChartView {

  public struct Props {
    public var lines: [Line]
    public var lineWidth: CGFloat
    public var highlithedX: CGFloat?
    public var estimatedGridSpace: Int?
    public var estimatedXLabelWidth: Int?
    public var inset: CGFloat?
    public var isInFullSize: Bool
    public var range: Range?
    public var didHighlightX: ClosureWith<Output>?

    // Init
  }
}

In order to implement theming nicely Colors struct is used on each component too.

ChartView.Colors example

extension ChartView {

  public struct Colors {
    public let labels: UIColor
    public let horizontalLines: UIColor
    public let lineChart: LineChartLayer.Colors
    
    // Init
    
    public static let initial = Colors(
            labels: .gray,
            horizontalLines: .gray,
            lineChart: .initial)
  }
}

Each line of chart is represented by Line struct, where Points is typealias for [CGPoint].

public struct Line {
  public let title: String
  public var points: Points
  public var highlightedPoint: CGPoint?
  public let color: UIColor
  public var isHidden: Bool

  // Init
}

Views & Layers

  • ChartView is a core view which is responsible for rendering all chart layers: ChartView Layer Hierarchy of ChartView
    • GridLayer renders horizontal lines of grid
    • LineChartLayer contains LineLayers and VerticalLineLayer
      • LineLayer renders line based on CGPoint vector (if VerticalLineLayer is also appeared, it will also render circle in order to show highlighted point)
      • VerticalLineLayer renders vertical line through highlighted points
    • YLabelsLayer renders labels above horizontal lines of GridLayer (y values of each line)
    • XLabelsLayer renders labels below LinearChartLayer or x axis in a nutshell (dates in "MMM dd" format)
  • PannableChartView is a subclass of UIControl which implements behaviour similar to UIPanGestureRecognizer. It tells ChartView to show highlighted points and shows ChartInfoView with details of the points above chart. PannableChartView
  • ChartScrollView contains instance of PannableChartView and ExpandableSliderView which allows user to choose visible part of chart and its scale. ChartScrollView
  • DetailedChartView contains instance of ChartScrollView and UITableView with names and colors of lines and capability to show/hide them DetailedChartView

Normalizer

  • Normalizer is a protocol which defines method for line normalization based on target rect size of layer.
  • SizeNormalizer is a class which normalize lines based on:
    • [minY, maxY] segment with verticalInses (depends on isInFullSize and verticalInset properties)
    • [0, maxY] segment (full-sized)

Note about minY, maxY:

  • Formal case: minY, maxY are in {y | y in Y1 || Y2 || ... || Yn}, where || means union (set operation), Yi is a set of y values of the ith line
  • Informal case: minY, maxY are selected among each y of each line in chart

License

iCharts is available under the MIT license. See the LICENSE file for more info.