Skip to content

Commit 5971fc4

Browse files
authored
Merge pull request #247 from woocommerce/feature/177-dashboard-mark6
Dashboard Mark 6: Adds charts to dashboard
2 parents 6998fe8 + 02ab02f commit 5971fc4

File tree

10 files changed

+471
-14
lines changed

10 files changed

+471
-14
lines changed

Podfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ target 'WooCommerce' do
3030
pod 'KeychainAccess', '~> 3.1'
3131
pod 'CocoaLumberjack/Swift', '~> 3.4'
3232
pod 'XLPagerTabStrip', '~> 8.0'
33+
pod 'Charts', '~> 3.1'
3334

3435
# Unit Tests
3536
# ==========

Podfile.lock

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ PODS:
55
- CocoaLumberjack (~> 3.4.1)
66
- Reachability (~> 3.1)
77
- UIDeviceIdentifier (~> 0.4)
8+
- Charts (3.1.1):
9+
- Charts/Core (= 3.1.1)
10+
- Charts/Core (3.1.1)
811
- CocoaLumberjack (3.4.2):
912
- CocoaLumberjack/Default (= 3.4.2)
1013
- CocoaLumberjack/Extensions (= 3.4.2)
@@ -69,6 +72,7 @@ PODS:
6972
DEPENDENCIES:
7073
- Alamofire (~> 4.7)
7174
- Automattic-Tracks-iOS (from `https://github.com/Automattic/Automattic-Tracks-iOS.git`, tag `0.2.3`)
75+
- Charts (~> 3.1)
7276
- CocoaLumberjack/Swift (~> 3.4)
7377
- Crashlytics (~> 3.10)
7478
- Gridicons (= 0.15)
@@ -81,6 +85,7 @@ SPEC REPOS:
8185
https://github.com/cocoapods/specs.git:
8286
- 1PasswordExtension
8387
- Alamofire
88+
- Charts
8489
- CocoaLumberjack
8590
- Crashlytics
8691
- Fabric
@@ -116,6 +121,7 @@ SPEC CHECKSUMS:
116121
1PasswordExtension: 0e95bdea64ec8ff2f4f693be5467a09fac42a83d
117122
Alamofire: e4fa87002c137ba2d8d634d2c51fabcda0d5c223
118123
Automattic-Tracks-iOS: d8c6c6c1351b1905a73e45f431b15598d71963b5
124+
Charts: 90a4d61da0f6e06684c591e3bcab11940fe61736
119125
CocoaLumberjack: db7cc9e464771f12054c22ff6947c5a58d43a0fd
120126
Crashlytics: ccaac42660eb9351b9960c0d66106b0bcf99f4fa
121127
Fabric: f233c9492b3bbc1f04e3882986740f7988a58edb
@@ -137,6 +143,6 @@ SPEC CHECKSUMS:
137143
wpxmlrpc: bfc572f62ce7ee897f6f38b098d2ba08732ecef4
138144
XLPagerTabStrip: c908b17cbf42fcd2598ee1adfc49bae25444d88a
139145

140-
PODFILE CHECKSUM: f61f936dde41c2e27f9fe80a0da46c2def730421
146+
PODFILE CHECKSUM: 579bb6345aecc3f27e78d220e137a749e9ee5f08
141147

142148
COCOAPODS: 1.5.3

WooCommerce/Classes/Extensions/Date+Helpers.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,50 @@ extension DateFormatter {
1818
return formatter
1919
}()
2020
}
21+
22+
/// Chart Formatters
23+
///
24+
struct Charts {
25+
26+
/// Date formatter used for creating the date displayed on a chart axis for **day** granularity.
27+
///
28+
public static let chartsDayFormatter: DateFormatter = {
29+
let formatter = DateFormatter()
30+
formatter.locale = Locale(identifier: "en_US_POSIX")
31+
formatter.timeZone = TimeZone(identifier: "GMT")
32+
formatter.dateFormat = "MMM d"
33+
return formatter
34+
}()
35+
36+
/// Date formatter used for creating the date displayed on a chart axis for **week** granularity.
37+
///
38+
public static let chartsWeekFormatter: DateFormatter = {
39+
let formatter = DateFormatter()
40+
formatter.locale = Locale(identifier: "en_US_POSIX")
41+
formatter.timeZone = TimeZone(identifier: "GMT")
42+
formatter.dateFormat = "MMM d"
43+
return formatter
44+
}()
45+
46+
/// Date formatter used for creating the date displayed on a chart axis for **month** granularity.
47+
///
48+
public static let chartsMonthFormatter: DateFormatter = {
49+
let formatter = DateFormatter()
50+
formatter.locale = Locale(identifier: "en_US_POSIX")
51+
formatter.timeZone = TimeZone(identifier: "GMT")
52+
formatter.dateFormat = "MMM"
53+
return formatter
54+
}()
55+
56+
/// Date formatter used for creating the date displayed on a chart axis for **year** granularity.
57+
///
58+
public static let chartsYearFormatter: DateFormatter = {
59+
let formatter = DateFormatter()
60+
formatter.locale = Locale(identifier: "en_US_POSIX")
61+
formatter.timeZone = TimeZone(identifier: "GMT")
62+
formatter.dateFormat = "yyyy"
63+
return formatter
64+
}()
65+
}
66+
2167
}

WooCommerce/Classes/Model/OrderStats+Woo.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,13 @@ extension OrderStats {
1818

1919
return Locale(identifier: identifier).currencySymbol ?? currency
2020
}
21+
22+
/// Returns the sum of total sales this stats period. This value is typically used in the dashboard for revenue reporting.
23+
///
24+
/// *Note:* The value returned here is an aggregation of all the `OrderStatsItem.totalSales` values and
25+
/// _not_ `OrderStats.totalGrossSales` or `OrderStats.totalNetSales`.
26+
///
27+
var totalSales: Double {
28+
return items?.map({ $0.totalSales }).reduce(0.0, +) ?? 0.0
29+
}
2130
}

WooCommerce/Classes/Styles/Style.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ protocol Style {
1616
var buttonDisabledHighlightedColor: UIColor { get }
1717
var buttonDisabledTitleColor: UIColor { get }
1818
var cellSeparatorColor: UIColor { get }
19+
var chartLabelFont: UIFont { get }
1920
var defaultTextColor: UIColor { get }
2021
var destructiveActionColor: UIColor { get }
2122
var navBarImage: UIImage { get }
@@ -51,6 +52,7 @@ class DefaultStyle: Style {
5152
let actionButtonTitleFont = UIFont.font(forStyle: .headline, weight: .semibold)
5253
let alternativeLoginsTitleFont = UIFont.font(forStyle: .subheadline, weight: .semibold)
5354
let subheadlineFont = UIFont.font(forStyle: .subheadline, weight: .regular)
55+
let chartLabelFont = UIFont.font(forStyle: .caption2, weight: .ultraLight)
5456

5557
// Colors!
5658
//
@@ -182,6 +184,10 @@ class StyleManager {
182184
return active.cellSeparatorColor
183185
}
184186

187+
static var chartLabelFont: UIFont {
188+
return active.chartLabelFont
189+
}
190+
185191
static var defaultTextColor: UIColor {
186192
return active.defaultTextColor
187193
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import Foundation
2+
import Charts
3+
4+
5+
/// This class is a custom view which is displayed over a chart element (e.g. a Bar) when it is highlighted.
6+
///
7+
/// See: https://github.com/danielgindi/Charts/blob/master/ChartsDemo-iOS/Swift/Components/BalloonMarker.swift
8+
///
9+
class ChartMarker: MarkerImage {
10+
@objc open var color: UIColor
11+
@objc open var arrowSize = Constants.arrowSize
12+
@objc open var font: UIFont
13+
@objc open var textColor: UIColor
14+
@objc open var insets: UIEdgeInsets
15+
@objc open var minimumSize = CGSize()
16+
17+
private var label: String?
18+
private var _labelSize: CGSize = CGSize()
19+
private var _paragraphStyle: NSMutableParagraphStyle?
20+
private var _drawAttributes = [NSAttributedStringKey: AnyObject]()
21+
22+
@objc public init(chartView: ChartViewBase?, color: UIColor, font: UIFont, textColor: UIColor, insets: UIEdgeInsets) {
23+
self.color = color
24+
self.font = font
25+
self.textColor = textColor
26+
self.insets = insets
27+
28+
_paragraphStyle = NSParagraphStyle.default.mutableCopy() as? NSMutableParagraphStyle
29+
_paragraphStyle?.alignment = .center
30+
super.init()
31+
self.chartView = chartView
32+
}
33+
34+
open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint {
35+
var offset = self.offset
36+
var size = self.size
37+
38+
if let image = image, size.width == 0.0 {
39+
size.width = image.size.width
40+
}
41+
42+
if let image = image, size.height == 0.0 {
43+
size.height = image.size.height
44+
}
45+
46+
let width = size.width
47+
let height = size.height
48+
let padding = Constants.offsetPadding
49+
50+
var origin = point
51+
origin.x -= width / 2
52+
origin.y -= height
53+
54+
if (origin.x + offset.x) < 0.0 {
55+
offset.x = -origin.x + padding
56+
} else if let chart = chartView, (origin.x + width + offset.x) > chart.bounds.size.width {
57+
offset.x = chart.bounds.size.width - origin.x - width - padding
58+
}
59+
60+
if (origin.y + offset.y) < 0 {
61+
offset.y = height + padding
62+
} else if let chart = chartView, (origin.y + height + offset.y) > chart.bounds.size.height {
63+
offset.y = chart.bounds.size.height - origin.y - height - padding
64+
}
65+
66+
return CGPoint(x: round(offset.x), y: round(offset.y))
67+
}
68+
69+
open override func draw(context: CGContext, point: CGPoint) {
70+
guard let label = label else {
71+
return
72+
}
73+
74+
let offset = self.offsetForDrawing(atPoint: point)
75+
let size = self.size
76+
77+
var rect = CGRect(
78+
origin: CGPoint(
79+
x: point.x + offset.x,
80+
y: point.y + offset.y),
81+
size: size)
82+
rect.origin.x -= size.width / 2.0
83+
rect.origin.y -= size.height
84+
rect = rect.integral
85+
86+
context.saveGState()
87+
context.setFillColor(color.cgColor)
88+
89+
if offset.y > 0 {
90+
context.beginPath()
91+
context.move(to: CGPoint(
92+
x: rect.origin.x,
93+
y: rect.origin.y + arrowSize.height))
94+
context.addLine(to: CGPoint(
95+
x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0,
96+
y: rect.origin.y + arrowSize.height))
97+
98+
// Arrow vertex
99+
context.addLine(to: CGPoint(
100+
x: point.x,
101+
y: point.y))
102+
context.addLine(to: CGPoint(
103+
x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0,
104+
y: rect.origin.y + arrowSize.height))
105+
context.addLine(to: CGPoint(
106+
x: rect.origin.x + rect.size.width,
107+
y: rect.origin.y + arrowSize.height))
108+
context.addLine(to: CGPoint(
109+
x: rect.origin.x + rect.size.width,
110+
y: rect.origin.y + rect.size.height))
111+
context.addLine(to: CGPoint(
112+
x: rect.origin.x,
113+
y: rect.origin.y + rect.size.height))
114+
context.addLine(to: CGPoint(
115+
x: rect.origin.x,
116+
y: rect.origin.y + arrowSize.height))
117+
context.fillPath()
118+
} else {
119+
context.beginPath()
120+
context.move(to: CGPoint(
121+
x: rect.origin.x,
122+
y: rect.origin.y))
123+
context.addLine(to: CGPoint(
124+
x: rect.origin.x + rect.size.width,
125+
y: rect.origin.y))
126+
context.addLine(to: CGPoint(
127+
x: rect.origin.x + rect.size.width,
128+
y: rect.origin.y + rect.size.height - arrowSize.height))
129+
context.addLine(to: CGPoint(
130+
x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0,
131+
y: rect.origin.y + rect.size.height - arrowSize.height))
132+
133+
//Arrow vertex
134+
context.addLine(to: CGPoint(
135+
x: point.x,
136+
y: point.y))
137+
context.addLine(to: CGPoint(
138+
x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0,
139+
y: rect.origin.y + rect.size.height - arrowSize.height))
140+
context.addLine(to: CGPoint(
141+
x: rect.origin.x,
142+
y: rect.origin.y + rect.size.height - arrowSize.height))
143+
context.addLine(to: CGPoint(
144+
x: rect.origin.x,
145+
y: rect.origin.y))
146+
context.fillPath()
147+
}
148+
149+
if offset.y > 0 {
150+
rect.origin.y += self.insets.top + arrowSize.height
151+
} else {
152+
rect.origin.y += self.insets.top
153+
}
154+
rect.size.height -= self.insets.top + self.insets.bottom
155+
rect = rect.integral
156+
UIGraphicsPushContext(context)
157+
label.draw(in: rect, withAttributes: _drawAttributes)
158+
UIGraphicsPopContext()
159+
context.restoreGState()
160+
}
161+
162+
open override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
163+
let hintString = entry.accessibilityValue ?? String(entry.y)
164+
setLabel(hintString)
165+
}
166+
167+
@objc open func setLabel(_ newLabel: String) {
168+
label = newLabel
169+
170+
_drawAttributes.removeAll()
171+
_drawAttributes[.font] = self.font
172+
_drawAttributes[.paragraphStyle] = _paragraphStyle
173+
_drawAttributes[.foregroundColor] = self.textColor
174+
_labelSize = label?.size(withAttributes: _drawAttributes) ?? CGSize.zero
175+
176+
var size = CGSize()
177+
size.width = _labelSize.width + self.insets.left + self.insets.right
178+
size.height = _labelSize.height + self.insets.top + self.insets.bottom
179+
size.width = max(minimumSize.width, size.width)
180+
size.height = max(minimumSize.height, size.height)
181+
self.size = size
182+
}
183+
}
184+
185+
186+
// MARK: - Constants!
187+
//
188+
private extension ChartMarker {
189+
enum Constants {
190+
static let arrowSize = CGSize(width: 20, height: 14)
191+
static let offsetPadding: CGFloat = 4.0
192+
}
193+
}

0 commit comments

Comments
 (0)