@@ -18,9 +18,17 @@ public struct GraphLayout {
18
18
19
19
var enablePrimaryAxisGrid = true
20
20
var enableSecondaryAxisGrid = true
21
+ var drawsGridOverForeground = false
21
22
var markerTextSize : Float = 12
23
+ var markerThickness : Float = 2
22
24
/// The amount of (horizontal) space to reserve for markers on the Y-axis.
23
25
var yMarkerMaxWidth : Float = 40
26
+
27
+ enum MarkerLabelAlignment {
28
+ case atMarker
29
+ case betweenMarkers
30
+ }
31
+ var markerLabelAlignment = MarkerLabelAlignment . atMarker
24
32
25
33
struct Results : CoordinateResolver {
26
34
/// The size these results have been calculated for; the entire size of the plot.
@@ -91,9 +99,11 @@ public struct GraphLayout {
91
99
// 5. Round the markers to integer values.
92
100
roundMarkers ( & markers)
93
101
results. plotMarkers = markers
94
- }
102
+ }
95
103
legendInfo. map { results. legendLabels = $0 }
96
- // 6. Lay out remaining chrome.
104
+ // 6. Adjust the plot size if the graph requests less space.
105
+ ( drawingData as? AdjustsPlotSize ) . map { adjustPlotSize ( info: $0, results: & results) }
106
+ // 7. Lay out remaining chrome.
97
107
calcMarkerTextLocations ( renderer: renderer, results: & results)
98
108
calcLegend ( results. legendLabels, renderer: renderer, results: & results)
99
109
return ( drawingData, results)
@@ -142,23 +152,28 @@ public struct GraphLayout {
142
152
// Add a space to the top when there is no title.
143
153
borderRect. size. height -= Self . titleLabelPadding
144
154
}
145
- borderRect. contract ( by: plotBorder. thickness)
146
155
// Give space for the markers.
147
156
borderRect. clampingShift ( dy: ( 2 * markerTextSize) + 10 ) // X markers
148
157
// TODO: Better space calculation for Y/Y2 markers.
149
158
borderRect. clampingShift ( dx: yMarkerMaxWidth + 10 ) // Y markers
150
159
borderRect. size. width -= yMarkerMaxWidth + 10 // Y2 markers
160
+ // Space for border thickness.
161
+ borderRect. contract ( by: plotBorder. thickness)
151
162
152
163
// Sanitize the resulting rectangle.
153
164
borderRect. size. width = max ( borderRect. size. width, 0 )
154
165
borderRect. size. height = max ( borderRect. size. height, 0 )
155
- borderRect. origin. y. round ( . up)
156
- borderRect. origin. x. round ( . up)
157
- borderRect. size. width. round ( . down)
158
- borderRect. size. height. round ( . down)
166
+ borderRect. roundInwards ( )
159
167
return borderRect
160
168
}
161
169
170
+ /// Makes adjustments to the layout as requested by the plot.
171
+ private func adjustPlotSize( info: AdjustsPlotSize , results: inout Results ) {
172
+ if info. desiredPlotSize != . zero {
173
+ results. plotBorderRect. size = info. desiredPlotSize
174
+ }
175
+ }
176
+
162
177
/// Rounds the given markers to integer pixel locations, for sharper gridlines.
163
178
private func roundMarkers( _ markers: inout PlotMarkers ) {
164
179
for i in markers. xMarkers. indices {
@@ -196,20 +211,58 @@ public struct GraphLayout {
196
211
197
212
for i in 0 ..< results. plotMarkers. xMarkers. count {
198
213
let textWidth = renderer. getTextWidth ( text: results. plotMarkers. xMarkersText [ i] , textSize: markerTextSize)
199
- let text_p = Point ( results. plotMarkers. xMarkers [ i] - ( textWidth/ 2 ) , - 2.0 * markerTextSize)
200
- results. xMarkersTextLocation. append ( text_p)
214
+ let markerLocation = results. plotMarkers. xMarkers [ i]
215
+ var textLocation = Point ( 0 , - 2.0 * markerTextSize)
216
+ switch markerLabelAlignment {
217
+ case . atMarker:
218
+ textLocation. x = markerLocation - ( textWidth/ 2 )
219
+ case . betweenMarkers:
220
+ let nextMarkerLocation : Float
221
+ if i < results. plotMarkers. xMarkers. endIndex - 1 {
222
+ nextMarkerLocation = results. plotMarkers. xMarkers [ i + 1 ]
223
+ } else {
224
+ nextMarkerLocation = results. plotBorderRect. width
225
+ }
226
+ let midpoint = markerLocation + ( nextMarkerLocation - markerLocation) / 2
227
+ textLocation. x = midpoint - ( textWidth/ 2 )
228
+ }
229
+ results. xMarkersTextLocation. append ( textLocation)
230
+ }
231
+
232
+ /// Vertically aligns the label and returns the optimal Y coordinate to draw at.
233
+ func alignYLabel( markers: [ Float ] , index: Int , textSize: Size ) -> Float {
234
+ let markerLocation = markers [ index]
235
+ switch markerLabelAlignment {
236
+ case . atMarker:
237
+ return markerLocation - ( textSize. height/ 2 )
238
+ case . betweenMarkers:
239
+ let nextMarkerLocation : Float
240
+ if index < markers. endIndex - 1 {
241
+ nextMarkerLocation = markers [ index + 1 ]
242
+ } else {
243
+ nextMarkerLocation = results. plotBorderRect. height
244
+ }
245
+ let midpoint = markerLocation + ( nextMarkerLocation - markerLocation) / 2
246
+ return midpoint - ( textSize. height/ 2 )
247
+ }
201
248
}
202
-
249
+
203
250
for i in 0 ..< results. plotMarkers. yMarkers. count {
204
- var textWidth = renderer. getTextWidth ( text: results. plotMarkers. yMarkersText [ i] , textSize: markerTextSize)
205
- textWidth = min ( textWidth, yMarkerMaxWidth)
206
- let text_p = Point ( - textWidth - 8 , results. plotMarkers. yMarkers [ i] - 4 )
207
- results. yMarkersTextLocation. append ( text_p)
251
+ var textSize = renderer. getTextLayoutSize ( text: results. plotMarkers. yMarkersText [ i] ,
252
+ textSize: markerTextSize)
253
+ textSize. width = min ( textSize. width, yMarkerMaxWidth)
254
+ var textLocation = Point ( - textSize. width - 8 , 0 )
255
+ textLocation. y = alignYLabel ( markers: results. plotMarkers. yMarkers, index: i, textSize: textSize)
256
+ results. yMarkersTextLocation. append ( textLocation)
208
257
}
209
258
210
259
for i in 0 ..< results. plotMarkers. y2Markers. count {
211
- let text_p = Point ( results. plotBorderRect. width + 8 , results. plotMarkers. y2Markers [ i] - 4 )
212
- results. y2MarkersTextLocation. append ( text_p)
260
+ var textSize = renderer. getTextLayoutSize ( text: results. plotMarkers. y2MarkersText [ i] ,
261
+ textSize: markerTextSize)
262
+ textSize. width = min ( textSize. width, yMarkerMaxWidth)
263
+ var textLocation = Point ( results. plotBorderRect. width + 8 , 0 )
264
+ textLocation. y = alignYLabel ( markers: results. plotMarkers. y2Markers, index: i, textSize: textSize)
265
+ results. y2MarkersTextLocation. append ( textLocation)
213
266
}
214
267
}
215
268
@@ -238,12 +291,17 @@ public struct GraphLayout {
238
291
if let plotBackgroundColor = plotBackgroundColor {
239
292
renderer. drawSolidRect ( results. plotBorderRect, fillColor: plotBackgroundColor, hatchPattern: . none)
240
293
}
241
- drawGrid ( results: results, renderer: renderer)
294
+ if !drawsGridOverForeground {
295
+ drawGrid ( results: results, renderer: renderer)
296
+ }
242
297
drawBorder ( results: results, renderer: renderer)
243
298
drawMarkers ( results: results, renderer: renderer)
244
299
}
245
300
246
301
func drawForeground( results: Results , renderer: Renderer ) {
302
+ if drawsGridOverForeground {
303
+ drawGrid ( results: results, renderer: renderer)
304
+ }
247
305
drawTitle ( results: results, renderer: renderer)
248
306
drawLabels ( results: results, renderer: renderer)
249
307
drawLegend ( results. legendLabels, results: results, renderer: renderer)
@@ -289,7 +347,9 @@ public struct GraphLayout {
289
347
}
290
348
291
349
private func drawBorder( results: Results , renderer: Renderer ) {
292
- renderer. drawRect ( results. plotBorderRect,
350
+ var rect = results. plotBorderRect
351
+ rect. contract ( by: - plotBorder. thickness/ 2 )
352
+ renderer. drawRect ( rect,
293
353
strokeWidth: plotBorder. thickness,
294
354
strokeColor: plotBorder. color)
295
355
}
@@ -298,67 +358,74 @@ public struct GraphLayout {
298
358
guard enablePrimaryAxisGrid || enablePrimaryAxisGrid else { return }
299
359
let rect = results. plotBorderRect
300
360
for index in 0 ..< results. plotMarkers. xMarkers. count {
301
- let p1 = Point ( results. plotMarkers. xMarkers [ index] + rect. minX, rect. minY)
302
- let p2 = Point ( results. plotMarkers. xMarkers [ index] + rect. minX, rect. maxY)
303
- renderer. drawLine ( startPoint: p1,
304
- endPoint: p2,
305
- strokeWidth: grid. thickness,
306
- strokeColor: grid. color,
307
- isDashed: false )
361
+ let p1 = Point ( results. plotMarkers. xMarkers [ index] + rect. minX, rect. minY)
362
+ let p2 = Point ( results. plotMarkers. xMarkers [ index] + rect. minX, rect. maxY)
363
+ guard rect. internalXCoordinates. contains ( p1. x) ,
364
+ rect. internalXCoordinates. contains ( p2. x) else { continue }
365
+ renderer. drawLine ( startPoint: p1,
366
+ endPoint: p2,
367
+ strokeWidth: grid. thickness,
368
+ strokeColor: grid. color,
369
+ isDashed: false )
308
370
}
309
371
310
372
if ( enablePrimaryAxisGrid) {
311
373
for index in 0 ..< results. plotMarkers. yMarkers. count {
312
- let p1 = Point ( rect. minX, results. plotMarkers. yMarkers [ index] + rect. minY)
313
- let p2 = Point ( rect. maxX, results. plotMarkers. yMarkers [ index] + rect. minY)
314
- renderer. drawLine ( startPoint: p1,
315
- endPoint: p2,
316
- strokeWidth: grid. thickness,
317
- strokeColor: grid. color,
318
- isDashed: false )
374
+ let p1 = Point ( rect. minX, results. plotMarkers. yMarkers [ index] + rect. minY)
375
+ let p2 = Point ( rect. maxX, results. plotMarkers. yMarkers [ index] + rect. minY)
376
+ guard rect. internalYCoordinates. contains ( p1. y) ,
377
+ rect. internalYCoordinates. contains ( p2. y) else { continue }
378
+ renderer. drawLine ( startPoint: p1,
379
+ endPoint: p2,
380
+ strokeWidth: grid. thickness,
381
+ strokeColor: grid. color,
382
+ isDashed: false )
319
383
}
320
384
}
321
385
if ( enableSecondaryAxisGrid) {
322
386
for index in 0 ..< results. plotMarkers. y2Markers. count {
323
- let p1 = Point ( rect. minX, results. plotMarkers. y2Markers [ index] + rect. minY)
324
- let p2 = Point ( rect. maxX, results. plotMarkers. y2Markers [ index] + rect. minY)
325
- renderer. drawLine ( startPoint: p1,
326
- endPoint: p2,
327
- strokeWidth: grid. thickness,
328
- strokeColor: grid. color,
329
- isDashed: false )
387
+ let p1 = Point ( rect. minX, results. plotMarkers. y2Markers [ index] + rect. minY)
388
+ let p2 = Point ( rect. maxX, results. plotMarkers. y2Markers [ index] + rect. minY)
389
+ guard rect. internalYCoordinates. contains ( p1. y) ,
390
+ rect. internalYCoordinates. contains ( p2. y) else { continue }
391
+ renderer. drawLine ( startPoint: p1,
392
+ endPoint: p2,
393
+ strokeWidth: grid. thickness,
394
+ strokeColor: grid. color,
395
+ isDashed: false )
330
396
}
331
397
}
332
398
}
333
399
334
400
private func drawMarkers( results: Results , renderer: Renderer ) {
335
401
let rect = results. plotBorderRect
402
+ let border = plotBorder. thickness
336
403
for index in 0 ..< results. plotMarkers. xMarkers. count {
337
- let p1 = Point ( results. plotMarkers. xMarkers [ index] , - 6 ) + rect. origin
338
- let p2 = Point ( results. plotMarkers. xMarkers [ index] , 0 ) + rect. origin
404
+ let p1 = Point ( results. plotMarkers. xMarkers [ index] , - border - 6 ) + rect. origin
405
+ let p2 = Point ( results. plotMarkers. xMarkers [ index] , - border ) + rect. origin
339
406
renderer. drawLine ( startPoint: p1,
340
407
endPoint: p2,
341
- strokeWidth: plotBorder . thickness ,
408
+ strokeWidth: markerThickness ,
342
409
strokeColor: plotBorder. color,
343
410
isDashed: false )
344
411
renderer. drawText ( text: results. plotMarkers. xMarkersText [ index] ,
345
- location: results. xMarkersTextLocation [ index] + rect. origin,
412
+ location: results. xMarkersTextLocation [ index] + rect. origin + Pair ( 0 , - border ) ,
346
413
textSize: markerTextSize,
347
414
color: plotBorder. color,
348
415
strokeWidth: 0.7 ,
349
416
angle: 0 )
350
417
}
351
418
352
419
for index in 0 ..< results. plotMarkers. yMarkers. count {
353
- let p1 = Point ( - 6 , results. plotMarkers. yMarkers [ index] ) + rect. origin
354
- let p2 = Point ( 0 , results. plotMarkers. yMarkers [ index] ) + rect. origin
420
+ let p1 = Point ( - border - 6 , results. plotMarkers. yMarkers [ index] ) + rect. origin
421
+ let p2 = Point ( - border , results. plotMarkers. yMarkers [ index] ) + rect. origin
355
422
renderer. drawLine ( startPoint: p1,
356
423
endPoint: p2,
357
- strokeWidth: plotBorder . thickness ,
424
+ strokeWidth: markerThickness ,
358
425
strokeColor: plotBorder. color,
359
426
isDashed: false )
360
427
renderer. drawText ( text: results. plotMarkers. yMarkersText [ index] ,
361
- location: results. yMarkersTextLocation [ index] + rect. origin,
428
+ location: results. yMarkersTextLocation [ index] + rect. origin + Pair ( - border , 0 ) ,
362
429
textSize: markerTextSize,
363
430
color: plotBorder. color,
364
431
strokeWidth: 0.7 ,
@@ -367,13 +434,13 @@ public struct GraphLayout {
367
434
368
435
if !results. plotMarkers. y2Markers. isEmpty {
369
436
for index in 0 ..< results. plotMarkers. y2Markers. count {
370
- let p1 = Point ( results. plotBorderRect. width,
437
+ let p1 = Point ( results. plotBorderRect. width + border ,
371
438
( results. plotMarkers. y2Markers [ index] ) ) + rect. origin
372
- let p2 = Point ( results. plotBorderRect. width + 6 ,
439
+ let p2 = Point ( results. plotBorderRect. width + border + 6 ,
373
440
( results. plotMarkers. y2Markers [ index] ) ) + rect. origin
374
441
renderer. drawLine ( startPoint: p1,
375
442
endPoint: p2,
376
- strokeWidth: plotBorder . thickness ,
443
+ strokeWidth: markerThickness ,
377
444
strokeColor: plotBorder. color,
378
445
isDashed: false )
379
446
renderer. drawText ( text: results. plotMarkers. y2MarkersText [ index] ,
@@ -427,6 +494,9 @@ public struct GraphLayout {
427
494
}
428
495
}
429
496
497
+ protocol AdjustsPlotSize {
498
+ var desiredPlotSize : Size { get }
499
+ }
430
500
public protocol HasGraphLayout {
431
501
432
502
var layout : GraphLayout { get set }
@@ -500,6 +570,11 @@ extension HasGraphLayout {
500
570
get { layout. markerTextSize }
501
571
set { layout. markerTextSize = newValue }
502
572
}
573
+
574
+ public var markerThickness : Float {
575
+ get { layout. markerThickness }
576
+ set { layout. markerThickness = newValue }
577
+ }
503
578
}
504
579
505
580
extension Plot where Self: HasGraphLayout {
0 commit comments