Skip to content

Commit 70bedfc

Browse files
committed
Added chart markers
1 parent 2ddf05f commit 70bedfc

File tree

3 files changed

+224
-11
lines changed

3 files changed

+224
-11
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import Foundation
2+
import Charts
3+
4+
/// This class is a custom view which is displayed over a chart element (e.g. a Bar) when it is highlighted. It
5+
/// basically is a duplicate of the `BalloonMarker` class found in the Charts demo project.
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 = CGSize(width: 15, height: 11)
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(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+
}
32+
33+
open override func offsetForDrawing(atPoint point: CGPoint) -> CGPoint {
34+
var offset = self.offset
35+
var size = self.size
36+
37+
if size.width == 0.0 && image != nil {
38+
size.width = image!.size.width
39+
}
40+
41+
if size.height == 0.0 && image != nil {
42+
size.height = image!.size.height
43+
}
44+
45+
let width = size.width
46+
let height = size.height
47+
let padding: CGFloat = 8.0
48+
49+
var origin = point
50+
origin.x -= width / 2
51+
origin.y -= height
52+
53+
if origin.x + offset.x < 0.0 {
54+
offset.x = -origin.x + padding
55+
} else if let chart = chartView, origin.x + width + offset.x > chart.bounds.size.width {
56+
offset.x = chart.bounds.size.width - origin.x - width - padding
57+
}
58+
59+
if origin.y + offset.y < 0 {
60+
offset.y = height + padding
61+
} else if let chart = chartView, origin.y + height + offset.y > chart.bounds.size.height {
62+
offset.y = chart.bounds.size.height - origin.y - height - padding
63+
}
64+
65+
return offset
66+
}
67+
68+
open override func draw(context: CGContext, point: CGPoint) {
69+
guard let label = label else {
70+
return
71+
}
72+
73+
let offset = self.offsetForDrawing(atPoint: point)
74+
let size = self.size
75+
76+
var rect = CGRect(
77+
origin: CGPoint(
78+
x: point.x + offset.x,
79+
y: point.y + offset.y),
80+
size: size)
81+
rect.origin.x -= size.width / 2.0
82+
rect.origin.y -= size.height
83+
84+
context.saveGState()
85+
context.setFillColor(color.cgColor)
86+
87+
if offset.y > 0 {
88+
context.beginPath()
89+
context.move(to: CGPoint(
90+
x: rect.origin.x,
91+
y: rect.origin.y + arrowSize.height))
92+
context.addLine(to: CGPoint(
93+
x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0,
94+
y: rect.origin.y + arrowSize.height))
95+
//arrow vertex
96+
context.addLine(to: CGPoint(
97+
x: point.x,
98+
y: point.y))
99+
context.addLine(to: CGPoint(
100+
x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0,
101+
y: rect.origin.y + arrowSize.height))
102+
context.addLine(to: CGPoint(
103+
x: rect.origin.x + rect.size.width,
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 + rect.size.height))
108+
context.addLine(to: CGPoint(
109+
x: rect.origin.x,
110+
y: rect.origin.y + rect.size.height))
111+
context.addLine(to: CGPoint(
112+
x: rect.origin.x,
113+
y: rect.origin.y + arrowSize.height))
114+
context.fillPath()
115+
} else {
116+
context.beginPath()
117+
context.move(to: CGPoint(
118+
x: rect.origin.x,
119+
y: rect.origin.y))
120+
context.addLine(to: CGPoint(
121+
x: rect.origin.x + rect.size.width,
122+
y: rect.origin.y))
123+
context.addLine(to: CGPoint(
124+
x: rect.origin.x + rect.size.width,
125+
y: rect.origin.y + rect.size.height - arrowSize.height))
126+
context.addLine(to: CGPoint(
127+
x: rect.origin.x + (rect.size.width + arrowSize.width) / 2.0,
128+
y: rect.origin.y + rect.size.height - arrowSize.height))
129+
//arrow vertex
130+
context.addLine(to: CGPoint(
131+
x: point.x,
132+
y: point.y))
133+
context.addLine(to: CGPoint(
134+
x: rect.origin.x + (rect.size.width - arrowSize.width) / 2.0,
135+
y: rect.origin.y + rect.size.height - arrowSize.height))
136+
context.addLine(to: CGPoint(
137+
x: rect.origin.x,
138+
y: rect.origin.y + rect.size.height - arrowSize.height))
139+
context.addLine(to: CGPoint(
140+
x: rect.origin.x,
141+
y: rect.origin.y))
142+
context.fillPath()
143+
}
144+
145+
if offset.y > 0 {
146+
rect.origin.y += self.insets.top + arrowSize.height
147+
} else {
148+
rect.origin.y += self.insets.top
149+
}
150+
rect.size.height -= self.insets.top + self.insets.bottom
151+
UIGraphicsPushContext(context)
152+
label.draw(in: rect, withAttributes: _drawAttributes)
153+
UIGraphicsPopContext()
154+
context.restoreGState()
155+
}
156+
157+
open override func refreshContent(entry: ChartDataEntry, highlight: Highlight) {
158+
let hintString = entry.accessibilityValue ?? String(entry.y)
159+
setLabel(hintString)
160+
}
161+
162+
@objc open func setLabel(_ newLabel: String) {
163+
label = newLabel
164+
165+
_drawAttributes.removeAll()
166+
_drawAttributes[.font] = self.font
167+
_drawAttributes[.paragraphStyle] = _paragraphStyle
168+
_drawAttributes[.foregroundColor] = self.textColor
169+
_labelSize = label?.size(withAttributes: _drawAttributes) ?? CGSize.zero
170+
171+
var size = CGSize()
172+
size.width = _labelSize.width + self.insets.left + self.insets.right
173+
size.height = _labelSize.height + self.insets.top + self.insets.bottom
174+
size.width = max(minimumSize.width, size.width)
175+
size.height = max(minimumSize.height, size.height)
176+
self.size = size
177+
}
178+
}

WooCommerce/Classes/ViewRelated/Dashboard/MyStore/PeriodDataViewController.swift

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ private extension PeriodDataViewController {
127127
barChartView.noDataFont = StyleManager.chartLabelFont
128128
barChartView.noDataTextColor = StyleManager.wooSecondary
129129
barChartView.extraRightOffset = Constants.chartExtraRightOffset
130+
barChartView.delegate = self
130131

131132
let xAxis = barChartView.xAxis
132133
xAxis.labelPosition = .bottom
@@ -160,7 +161,7 @@ private extension PeriodDataViewController {
160161
}
161162

162163

163-
// MARK: - IndicatorInfoProvider Conformance
164+
// MARK: - IndicatorInfoProvider Conformance (Tab Bar)
164165
//
165166
extension PeriodDataViewController {
166167
func indicatorInfo(for pagerTabStripController: PagerTabStripViewController) -> IndicatorInfo {
@@ -169,7 +170,28 @@ extension PeriodDataViewController {
169170
}
170171

171172

172-
// MARK: - IAxisValueFormatter Conformance
173+
// MARK: - ChartViewDelegate Conformance (Charts)
174+
//
175+
extension PeriodDataViewController: ChartViewDelegate {
176+
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
177+
guard entry.y != 0.0 else {
178+
// Do not display the marker if the Y-value is zero
179+
chartView.highlightValue(nil, callDelegate: false)
180+
return
181+
}
182+
183+
let marker: ChartMarker = ChartMarker(color: StyleManager.wooSecondary,
184+
font: StyleManager.chartLabelFont,
185+
textColor: StyleManager.wooWhite,
186+
insets: Constants.chartMarkerInsets)
187+
marker.minimumSize = Constants.chartMarkerMinimumSize
188+
marker.arrowSize = Constants.chartMarkerArrowSize
189+
chartView.marker = marker
190+
}
191+
}
192+
193+
194+
// MARK: - IAxisValueFormatter Conformance (Charts)
173195
//
174196
extension PeriodDataViewController: IAxisValueFormatter {
175197
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
@@ -206,7 +228,7 @@ extension PeriodDataViewController: IAxisValueFormatter {
206228
}
207229
} else {
208230
if value == 0.0 {
209-
// Do not show the 0 label on the Y axis
231+
// Do not show the "0" label on the Y axis
210232
return ""
211233
} else {
212234
return value.friendlyString()
@@ -271,20 +293,24 @@ private extension PeriodDataViewController {
271293
}
272294

273295
func generateBarDataSet() -> BarChartData? {
274-
guard let statItems = orderStats?.items, !statItems.isEmpty else {
296+
guard let orderStats = orderStats, let statItems = orderStats.items, !statItems.isEmpty else {
275297
return nil
276298
}
277299

278300
var barCount = 0
279301
var dataEntries: [BarChartDataEntry] = []
280302
statItems.forEach { (item) in
281-
dataEntries.append(BarChartDataEntry(x: Double(barCount), y: item.totalSales))
303+
let entry = BarChartDataEntry(x: Double(barCount), y: item.totalSales)
304+
entry.accessibilityValue = "\(item.period): \(orderStats.currencySymbol)\(item.totalSales.friendlyString())"
305+
dataEntries.append(entry)
282306
barCount += 1
283307
}
284308

285309
let dataSet = BarChartDataSet(values: dataEntries, label: "Data")
286310
dataSet.setColor(StyleManager.wooCommerceBrandColor)
287-
dataSet.highlightEnabled = false
311+
dataSet.highlightEnabled = true
312+
dataSet.highlightColor = StyleManager.wooAccent
313+
dataSet.highlightAlpha = Constants.chartHighlightAlpha
288314
dataSet.drawValuesEnabled = false // Do not draw value labels on the top of the bars
289315
return BarChartData(dataSet: dataSet)
290316
}
@@ -298,7 +324,12 @@ private extension PeriodDataViewController {
298324
static let placeholderText = "-"
299325

300326
static let chartAnimationDuration: TimeInterval = 0.75
301-
static let chartExtraRightOffset: CGFloat = 20.0
327+
static let chartExtraRightOffset: CGFloat = 25.0
328+
static let chartHighlightAlpha: CGFloat = 1.0
329+
330+
static let chartMarkerInsets: UIEdgeInsets = UIEdgeInsets(top: 5.0, left: 2.0, bottom: 5.0, right: 2.0)
331+
static let chartMarkerMinimumSize: CGSize = CGSize(width: 50.0, height: 30.0)
332+
static let chartMarkerArrowSize: CGSize = CGSize(width: 8, height: 6)
302333

303334
static let chartXAxisDashLengths: [CGFloat] = [5.0, 5.0]
304335
static let chartXAxisDashPhase: CGFloat = 0.0

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
748C7780211E18A600814F2C /* OrderStats+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C777F211E18A600814F2C /* OrderStats+Woo.swift */; };
3232
748C7782211E294000814F2C /* Double+Woo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C7781211E294000814F2C /* Double+Woo.swift */; };
3333
748C7784211E2D8400814F2C /* DoubleWooTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 748C7783211E2D8400814F2C /* DoubleWooTests.swift */; };
34+
74AAF6A5212A04A900C612B0 /* ChartMarker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AAF6A4212A04A900C612B0 /* ChartMarker.swift */; };
3435
74AFF2EA211B9B1B0038153A /* StoreStatsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */; };
3536
74E0F441211C9AE600A79CCE /* PeriodDataViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */; };
3637
86DBBB0BDEA3488E2BEBB314 /* Pods_WooCommerce.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BABE5E07DD787ECA6D2A76DE /* Pods_WooCommerce.framework */; };
@@ -209,6 +210,7 @@
209210
748C777F211E18A600814F2C /* OrderStats+Woo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OrderStats+Woo.swift"; sourceTree = "<group>"; };
210211
748C7781211E294000814F2C /* Double+Woo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Woo.swift"; sourceTree = "<group>"; };
211212
748C7783211E2D8400814F2C /* DoubleWooTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoubleWooTests.swift; sourceTree = "<group>"; };
213+
74AAF6A4212A04A900C612B0 /* ChartMarker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartMarker.swift; sourceTree = "<group>"; };
212214
74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreStatsViewController.swift; sourceTree = "<group>"; };
213215
74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PeriodDataViewController.xib; sourceTree = "<group>"; };
214216
8A659E65308A3D9DD79A95F9 /* Pods-WooCommerceTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-WooCommerceTests.release.xcconfig"; path = "../Pods/Target Support Files/Pods-WooCommerceTests/Pods-WooCommerceTests.release.xcconfig"; sourceTree = "<group>"; };
@@ -375,6 +377,7 @@
375377
74036CBE211B87FD00E462C2 /* MyStore */ = {
376378
isa = PBXGroup;
377379
children = (
380+
74AAF6A4212A04A900C612B0 /* ChartMarker.swift */,
378381
74036CBF211B882100E462C2 /* PeriodDataViewController.swift */,
379382
74E0F440211C9AE600A79CCE /* PeriodDataViewController.xib */,
380383
74AFF2E9211B9B1B0038153A /* StoreStatsViewController.swift */,
@@ -1174,6 +1177,7 @@
11741177
B57B678C2107638C00AF8905 /* Order+Woo.swift in Sources */,
11751178
CE1EC8CA20B479F1009762BF /* TwoColumnLabelView.swift in Sources */,
11761179
CE21B3DD20FF9BC200A259D5 /* ProductListViewController.swift in Sources */,
1180+
74AAF6A5212A04A900C612B0 /* ChartMarker.swift in Sources */,
11771181
CE32B11520BF8779006FBCF4 /* ProductListTableViewCell.swift in Sources */,
11781182
CE263DE8206ACE3E0015A693 /* MainTabBarController.swift in Sources */,
11791183
CE1EC8EC20B8A3FF009762BF /* LeftImageTableViewCell.swift in Sources */,
@@ -1254,7 +1258,7 @@
12541258
CODE_SIGN_STYLE = Automatic;
12551259
INFOPLIST_PREFIX_HEADER = InfoPlist.h;
12561260
PRODUCT_NAME = "$(TARGET_NAME)";
1257-
SECRETS_PATH = "$HOME/.woo_app_credentials.json";
1261+
SECRETS_PATH = $HOME/.woo_app_credentials.json;
12581262
VALID_ARCHS = "$(inherited)";
12591263
};
12601264
name = Debug;
@@ -1265,7 +1269,7 @@
12651269
CODE_SIGN_STYLE = Automatic;
12661270
INFOPLIST_PREFIX_HEADER = InfoPlist.h;
12671271
PRODUCT_NAME = "$(TARGET_NAME)";
1268-
SECRETS_PATH = "$HOME/.woo_app_credentials.json";
1272+
SECRETS_PATH = $HOME/.woo_app_credentials.json;
12691273
VALID_ARCHS = "$(inherited)";
12701274
};
12711275
name = Release;
@@ -1403,7 +1407,7 @@
14031407
PRODUCT_NAME = "$(TARGET_NAME)";
14041408
PROVISIONING_PROFILE = "";
14051409
PROVISIONING_PROFILE_SPECIFIER = "WooCommerce Development";
1406-
SECRETS_PATH = "$HOME/.woo_app_credentials.json";
1410+
SECRETS_PATH = $HOME/.woo_app_credentials.json;
14071411
SWIFT_VERSION = 4.0;
14081412
TARGETED_DEVICE_FAMILY = "1,2";
14091413
USER_HEADER_SEARCH_PATHS = "";
@@ -1429,7 +1433,7 @@
14291433
PRODUCT_BUNDLE_IDENTIFIER = com.automattic.woocommerce;
14301434
PRODUCT_NAME = "$(TARGET_NAME)";
14311435
PROVISIONING_PROFILE_SPECIFIER = "WooCommerce App Store";
1432-
SECRETS_PATH = "$HOME/.woo_app_credentials.json";
1436+
SECRETS_PATH = $HOME/.woo_app_credentials.json;
14331437
SWIFT_VERSION = 4.0;
14341438
TARGETED_DEVICE_FAMILY = "1,2";
14351439
USER_HEADER_SEARCH_PATHS = "";

0 commit comments

Comments
 (0)