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.
- 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 bellowx
axis - Show
value
labels beforey
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
The simplest case is shown below. You should just create props with array of Line
s 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
lines
- an array ofLine
structslineWidth
- a positive real value which defines width of line on charthighlithedX
- ax
coordinate which user highlight onPannableChartView
estimatedGridSpace
- estimated space between 2 horizontal lines of gridestimatedXLabelWidth
- estimated width of label belowx
axisinset
- vertical inset when user renders chart as full-sizedisInFullSize
- determines that should we render chart as full-sizedrange
- the range of visible part of chart in percents or pointsdidHighlightX
- closure which fires when user highlight points onPannableChartView
It is not necessary to enumerate all properties of Colors
struct of each component here. You should just know, that you can change color of any part of chart.
See Colors
struct in DetailedChartView
for more information.
To install iCharts via CocoaPods, add the following line to your Podfile:
target 'MyAppName' do
pod 'iCharts'
use_frameworks!
end
Enter pod try iCharts
in terminal in order to retrieve demo application sources.
TL;DR
- fully implemented on
CALayer
s - 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
CALayer
s 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
}
ChartView
is a core view which is responsible for rendering all chart layers:GridLayer
renders horizontal lines of gridLineChartLayer
containsLineLayer
s andVerticalLineLayer
- LineLayer renders line based on
CGPoint
vector (ifVerticalLineLayer
is also appeared, it will also render circle in order to show highlighted point) VerticalLineLayer
renders vertical line through highlighted points
- LineLayer renders line based on
YLabelsLayer
renders labels above horizontal lines ofGridLayer
(y
values of each line)XLabelsLayer
renders labels belowLinearChartLayer
orx
axis in a nutshell (dates in "MMM dd" format)
PannableChartView
is a subclass ofUIControl
which implements behaviour similar toUIPanGestureRecognizer
. It tellsChartView
to show highlighted points and showsChartInfoView
with details of the points above chart.ChartScrollView
contains instance ofPannableChartView
andExpandableSliderView
which allows user to choose visible part of chart and its scale.DetailedChartView
contains instance ofChartScrollView
andUITableView
with names and colors of lines and capability to show/hide them
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 onisInFullSize
andverticalInset
properties) - [0, maxY] segment (full-sized)
- [minY, maxY] segment with
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 ofy
values of thei
th line - Informal case: minY, maxY are selected among each y of each line in chart
iCharts is available under the MIT license. See the LICENSE file for more info.