@@ -18,9 +18,17 @@ public struct GraphLayout {
1818
1919 var enablePrimaryAxisGrid = true
2020 var enableSecondaryAxisGrid = true
21+ var drawsGridOverForeground = false
2122 var markerTextSize : Float = 12
23+ var markerThickness : Float = 2
2224 /// The amount of (horizontal) space to reserve for markers on the Y-axis.
2325 var yMarkerMaxWidth : Float = 40
26+
27+ enum MarkerLabelAlignment {
28+ case atMarker
29+ case betweenMarkers
30+ }
31+ var markerLabelAlignment = MarkerLabelAlignment . atMarker
2432
2533 struct Results : CoordinateResolver {
2634 /// The size these results have been calculated for; the entire size of the plot.
@@ -91,9 +99,11 @@ public struct GraphLayout {
9199 // 5. Round the markers to integer values.
92100 roundMarkers ( & markers)
93101 results. plotMarkers = markers
94- }
102+ }
95103 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.
97107 calcMarkerTextLocations ( renderer: renderer, results: & results)
98108 calcLegend ( results. legendLabels, renderer: renderer, results: & results)
99109 return ( drawingData, results)
@@ -142,23 +152,28 @@ public struct GraphLayout {
142152 // Add a space to the top when there is no title.
143153 borderRect. size. height -= Self . titleLabelPadding
144154 }
145- borderRect. contract ( by: plotBorder. thickness)
146155 // Give space for the markers.
147156 borderRect. clampingShift ( dy: ( 2 * markerTextSize) + 10 ) // X markers
148157 // TODO: Better space calculation for Y/Y2 markers.
149158 borderRect. clampingShift ( dx: yMarkerMaxWidth + 10 ) // Y markers
150159 borderRect. size. width -= yMarkerMaxWidth + 10 // Y2 markers
160+ // Space for border thickness.
161+ borderRect. contract ( by: plotBorder. thickness)
151162
152163 // Sanitize the resulting rectangle.
153164 borderRect. size. width = max ( borderRect. size. width, 0 )
154165 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 ( )
159167 return borderRect
160168 }
161169
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+
162177 /// Rounds the given markers to integer pixel locations, for sharper gridlines.
163178 private func roundMarkers( _ markers: inout PlotMarkers ) {
164179 for i in markers. xMarkers. indices {
@@ -196,20 +211,58 @@ public struct GraphLayout {
196211
197212 for i in 0 ..< results. plotMarkers. xMarkers. count {
198213 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+ }
201248 }
202-
249+
203250 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)
208257 }
209258
210259 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)
213266 }
214267 }
215268
@@ -238,12 +291,17 @@ public struct GraphLayout {
238291 if let plotBackgroundColor = plotBackgroundColor {
239292 renderer. drawSolidRect ( results. plotBorderRect, fillColor: plotBackgroundColor, hatchPattern: . none)
240293 }
241- drawGrid ( results: results, renderer: renderer)
294+ if !drawsGridOverForeground {
295+ drawGrid ( results: results, renderer: renderer)
296+ }
242297 drawBorder ( results: results, renderer: renderer)
243298 drawMarkers ( results: results, renderer: renderer)
244299 }
245300
246301 func drawForeground( results: Results , renderer: Renderer ) {
302+ if drawsGridOverForeground {
303+ drawGrid ( results: results, renderer: renderer)
304+ }
247305 drawTitle ( results: results, renderer: renderer)
248306 drawLabels ( results: results, renderer: renderer)
249307 drawLegend ( results. legendLabels, results: results, renderer: renderer)
@@ -289,7 +347,9 @@ public struct GraphLayout {
289347 }
290348
291349 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,
293353 strokeWidth: plotBorder. thickness,
294354 strokeColor: plotBorder. color)
295355 }
@@ -298,67 +358,74 @@ public struct GraphLayout {
298358 guard enablePrimaryAxisGrid || enablePrimaryAxisGrid else { return }
299359 let rect = results. plotBorderRect
300360 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 )
308370 }
309371
310372 if ( enablePrimaryAxisGrid) {
311373 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 )
319383 }
320384 }
321385 if ( enableSecondaryAxisGrid) {
322386 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 )
330396 }
331397 }
332398 }
333399
334400 private func drawMarkers( results: Results , renderer: Renderer ) {
335401 let rect = results. plotBorderRect
402+ let border = plotBorder. thickness
336403 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
339406 renderer. drawLine ( startPoint: p1,
340407 endPoint: p2,
341- strokeWidth: plotBorder . thickness ,
408+ strokeWidth: markerThickness ,
342409 strokeColor: plotBorder. color,
343410 isDashed: false )
344411 renderer. drawText ( text: results. plotMarkers. xMarkersText [ index] ,
345- location: results. xMarkersTextLocation [ index] + rect. origin,
412+ location: results. xMarkersTextLocation [ index] + rect. origin + Pair ( 0 , - border ) ,
346413 textSize: markerTextSize,
347414 color: plotBorder. color,
348415 strokeWidth: 0.7 ,
349416 angle: 0 )
350417 }
351418
352419 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
355422 renderer. drawLine ( startPoint: p1,
356423 endPoint: p2,
357- strokeWidth: plotBorder . thickness ,
424+ strokeWidth: markerThickness ,
358425 strokeColor: plotBorder. color,
359426 isDashed: false )
360427 renderer. drawText ( text: results. plotMarkers. yMarkersText [ index] ,
361- location: results. yMarkersTextLocation [ index] + rect. origin,
428+ location: results. yMarkersTextLocation [ index] + rect. origin + Pair ( - border , 0 ) ,
362429 textSize: markerTextSize,
363430 color: plotBorder. color,
364431 strokeWidth: 0.7 ,
@@ -367,13 +434,13 @@ public struct GraphLayout {
367434
368435 if !results. plotMarkers. y2Markers. isEmpty {
369436 for index in 0 ..< results. plotMarkers. y2Markers. count {
370- let p1 = Point ( results. plotBorderRect. width,
437+ let p1 = Point ( results. plotBorderRect. width + border ,
371438 ( results. plotMarkers. y2Markers [ index] ) ) + rect. origin
372- let p2 = Point ( results. plotBorderRect. width + 6 ,
439+ let p2 = Point ( results. plotBorderRect. width + border + 6 ,
373440 ( results. plotMarkers. y2Markers [ index] ) ) + rect. origin
374441 renderer. drawLine ( startPoint: p1,
375442 endPoint: p2,
376- strokeWidth: plotBorder . thickness ,
443+ strokeWidth: markerThickness ,
377444 strokeColor: plotBorder. color,
378445 isDashed: false )
379446 renderer. drawText ( text: results. plotMarkers. y2MarkersText [ index] ,
@@ -427,6 +494,9 @@ public struct GraphLayout {
427494 }
428495}
429496
497+ protocol AdjustsPlotSize {
498+ var desiredPlotSize : Size { get }
499+ }
430500public protocol HasGraphLayout {
431501
432502 var layout : GraphLayout { get set }
@@ -500,6 +570,11 @@ extension HasGraphLayout {
500570 get { layout. markerTextSize }
501571 set { layout. markerTextSize = newValue }
502572 }
573+
574+ public var markerThickness : Float {
575+ get { layout. markerThickness }
576+ set { layout. markerThickness = newValue }
577+ }
503578}
504579
505580extension Plot where Self: HasGraphLayout {
0 commit comments