@@ -5,6 +5,11 @@ public enum LegendIcon {
5
5
case shape( ScatterPlotSeriesOptions . ScatterPattern , Color )
6
6
}
7
7
8
+ // TODO: Add layout tests for:
9
+ // - No axes labels
10
+ // - No plot markers
11
+ // - No axes labels or plot markers
12
+
8
13
/// A component for laying-out and rendering rectangular graphs.
9
14
///
10
15
/// The principle 3 components of a `GraphLayout` are:
@@ -44,8 +49,8 @@ public struct GraphLayout {
44
49
/// The region of the plot which will actually be filled with chart data.
45
50
let plotBorderRect : Rect
46
51
47
- // TODO: Try to move this up to GraphLayout itself .
48
- let components : EdgeComponents < [ LayoutComponent ] >
52
+ /// All of the `LayoutComponent`s on this graph, including built-in components .
53
+ let allComponents : EdgeComponents < [ LayoutComponent ] >
49
54
50
55
/// The measured sizes of the plot elements.
51
56
let sizes : EdgeComponents < [ Size ] >
@@ -101,29 +106,38 @@ extension GraphLayout {
101
106
let plotRect = layoutPlotRect ( plotSize: plotSize, componentSizes: sizes)
102
107
let roundedMarkers = markers. map { var m = $0; roundMarkers ( & m) ; return m } ?? PlotMarkers ( )
103
108
104
- var results = LayoutPlan ( totalSize: size,
105
- plotBorderRect: plotRect,
106
- components : components,
107
- sizes: sizes,
108
- plotMarkers: roundedMarkers,
109
- legendLabels: legendInfo ?? [ ] )
110
- calcMarkerTextLocations ( renderer: renderer, plan: & results )
111
- calcLegend ( results . legendLabels, renderer: renderer, plan: & results )
112
- return ( drawingData, results )
109
+ var plan = LayoutPlan ( totalSize: size,
110
+ plotBorderRect: plotRect,
111
+ allComponents : components,
112
+ sizes: sizes,
113
+ plotMarkers: roundedMarkers,
114
+ legendLabels: legendInfo ?? [ ] )
115
+ calcMarkerTextLocations ( renderer: renderer, plan: & plan )
116
+ calcLegend ( plan . legendLabels, renderer: renderer, plan: & plan )
117
+ return ( drawingData, plan )
113
118
}
114
119
115
120
static let xLabelPadding : Float = 12
116
121
static let yLabelPadding : Float = 12
117
122
static let titleLabelPadding : Float = 16
118
123
119
- // FIXME: To be removed. These items should already be LayoutComponents.
124
+ static let markerStemLength : Float = 6
125
+ /// Padding around y marker-labels.
126
+ static let yMarkerSpace : Float = 4
127
+ /// Padding around x marker-labels.
128
+ static let xMarkerSpace : Float = 6
129
+
130
+ // FIXME: To be removed. These items should already be `LayoutComponent`s.
120
131
private func makeLayoutComponents( ) -> EdgeComponents < [ LayoutComponent ] > {
121
132
var elements = EdgeComponents < [ LayoutComponent ] > ( left: [ ] , top: [ ] , right: [ ] , bottom: [ ] )
122
- // TODO: Currently, only labels are "LayoutComponent"s.
123
133
if !plotLabel. xLabel. isEmpty {
124
134
let label = Label ( text: plotLabel. xLabel, size: plotLabel. size)
125
135
. padding ( . all( Self . xLabelPadding) )
126
136
elements. bottom. append ( label)
137
+ // Add a space, otherwise the label looks misaligned.
138
+ elements. bottom. append ( FixedSpace ( size: Self . titleLabelPadding) )
139
+ } else {
140
+ elements. bottom. append ( FixedSpace ( size: Self . xLabelPadding) )
127
141
}
128
142
if !plotLabel. yLabel. isEmpty {
129
143
let label = Label ( text: plotLabel. yLabel, size: plotLabel. size)
@@ -139,6 +153,8 @@ extension GraphLayout {
139
153
let label = Label ( text: plotTitle. title, size: plotTitle. size)
140
154
. padding ( . all( Self . titleLabelPadding) )
141
155
elements. top. append ( label)
156
+ } else {
157
+ elements. top. append ( FixedSpace ( size: Self . titleLabelPadding) )
142
158
}
143
159
return elements
144
160
}
@@ -155,9 +171,9 @@ extension GraphLayout {
155
171
156
172
// Subtract space for the markers.
157
173
// TODO: Make this more accurate.
158
- plotSize. height -= ( 2 * markerTextSize ) + 10 // X markers
159
- plotSize. width -= yMarkerMaxWidth + 10 // Y markers
160
- plotSize. width -= yMarkerMaxWidth + 10 // Y2 markers
174
+ plotSize. height -= ( Self . markerStemLength + ( 2 * Self . xMarkerSpace ) + markerTextSize ) // X markers
175
+ plotSize. width -= ( Self . markerStemLength + ( 2 * Self . yMarkerSpace ) + yMarkerMaxWidth ) // Y markers
176
+ plotSize. width -= ( Self . markerStemLength + ( 2 * Self . yMarkerSpace ) + yMarkerMaxWidth ) // Y2 markers
161
177
// Subtract space for border thickness.
162
178
plotSize. height -= 2 * plotBorder. thickness
163
179
plotSize. width -= 2 * plotBorder. thickness
@@ -180,16 +196,16 @@ extension GraphLayout {
180
196
plotOrigin. x += componentSizes. left. reduce ( into: 0 ) { $0 += $1. width }
181
197
plotOrigin. y += componentSizes. bottom. reduce ( into: 0 ) { $0 += $1. height }
182
198
// Offset by marker sizes (TODO: they are not PlotElements yet, so not handled above).
183
- let xMarkerHeight = ( 2 * markerTextSize ) + 10 // X markers
184
- let yMarkerWidth = yMarkerMaxWidth + 10 // Y markers
199
+ let xMarkerHeight = ( Self . markerStemLength + ( 2 * Self . xMarkerSpace ) + markerTextSize ) // X markers
200
+ let yMarkerWidth = ( Self . markerStemLength + ( 2 * Self . yMarkerSpace ) + yMarkerMaxWidth ) // Y markers
185
201
plotOrigin. y += xMarkerHeight
186
202
plotOrigin. x += yMarkerWidth
187
203
// Offset by plot thickness.
188
204
plotOrigin. x += plotBorder. thickness
189
205
plotOrigin. y += plotBorder. thickness
190
206
191
207
// These are the final coordinates of the plot's internal space, so update `results`.
192
- return Rect ( origin: plotOrigin, size: plotSize)
208
+ return Rect ( origin: plotOrigin, size: plotSize) . roundedInwards
193
209
}
194
210
195
211
/// Makes adjustments to the layout as requested by the plot.
@@ -220,14 +236,15 @@ extension GraphLayout {
220
236
}
221
237
222
238
private func calcMarkerTextLocations( renderer: Renderer , plan: inout LayoutPlan ) {
223
-
239
+ let xLabelOffset = plotBorder. thickness + Self. markerStemLength + Self. xMarkerSpace
240
+ let yLabelOffset = plotBorder. thickness + Self. markerStemLength + Self. yMarkerSpace
224
241
for i in 0 ..< plan. plotMarkers. xMarkers. count {
225
- let textWidth = renderer. getTextWidth ( text: plan. plotMarkers. xMarkersText [ i] , textSize: markerTextSize)
242
+ let textSize = renderer. getTextLayoutSize ( text: plan. plotMarkers. xMarkersText [ i] , textSize: markerTextSize)
226
243
let markerLocation = plan. plotMarkers. xMarkers [ i]
227
- var textLocation = Point ( 0 , - 2.0 * markerTextSize )
244
+ var textLocation = Point ( 0 , - xLabelOffset - textSize . height )
228
245
switch markerLabelAlignment {
229
246
case . atMarker:
230
- textLocation. x = markerLocation - ( textWidth / 2 )
247
+ textLocation. x = markerLocation - ( textSize . width / 2 )
231
248
case . betweenMarkers:
232
249
let nextMarkerLocation : Float
233
250
if i < plan. plotMarkers. xMarkers. endIndex - 1 {
@@ -236,7 +253,7 @@ extension GraphLayout {
236
253
nextMarkerLocation = plan. plotBorderRect. width
237
254
}
238
255
let midpoint = markerLocation + ( nextMarkerLocation - markerLocation) / 2
239
- textLocation. x = midpoint - ( textWidth / 2 )
256
+ textLocation. x = midpoint - ( textSize . width / 2 )
240
257
}
241
258
plan. xMarkersTextLocation. append ( textLocation)
242
259
}
@@ -263,7 +280,7 @@ extension GraphLayout {
263
280
var textSize = renderer. getTextLayoutSize ( text: plan. plotMarkers. yMarkersText [ i] ,
264
281
textSize: markerTextSize)
265
282
textSize. width = min ( textSize. width, yMarkerMaxWidth)
266
- var textLocation = Point ( - textSize . width - 8 , 0 )
283
+ var textLocation = Point ( - yLabelOffset - textSize . width , 0 )
267
284
textLocation. y = alignYLabel ( markers: plan. plotMarkers. yMarkers, index: i, textSize: textSize)
268
285
plan. yMarkersTextLocation. append ( textLocation)
269
286
}
@@ -272,7 +289,7 @@ extension GraphLayout {
272
289
var textSize = renderer. getTextLayoutSize ( text: plan. plotMarkers. y2MarkersText [ i] ,
273
290
textSize: markerTextSize)
274
291
textSize. width = min ( textSize. width, yMarkerMaxWidth)
275
- var textLocation = Point ( plan. plotBorderRect. width + 8 , 0 )
292
+ var textLocation = Point ( yLabelOffset + plan. plotBorderRect. width, 0 )
276
293
textLocation. y = alignYLabel ( markers: plan. plotMarkers. y2Markers, index: i, textSize: textSize)
277
294
plan. y2MarkersTextLocation. append ( textLocation)
278
295
}
@@ -324,7 +341,7 @@ extension GraphLayout {
324
341
if drawsGridOverForeground {
325
342
drawGrid ( plan, renderer: renderer)
326
343
}
327
- drawLayoutComponents ( plan. components , plotRect: plan. plotBorderRect,
344
+ drawLayoutComponents ( plan. allComponents , plotRect: plan. plotBorderRect,
328
345
measuredSizes: plan. sizes, renderer: renderer)
329
346
drawLegend ( plan. legendLabels, plan: plan, renderer: renderer)
330
347
drawAnnotations ( resolver: plan, renderer: renderer)
@@ -438,31 +455,32 @@ extension GraphLayout {
438
455
let rect = plan. plotBorderRect
439
456
let border = plotBorder. thickness
440
457
for index in 0 ..< plan. plotMarkers. xMarkers. count {
441
- let p1 = Point ( plan. plotMarkers. xMarkers [ index] , - border - 6 ) + rect. origin
442
- let p2 = Point ( plan. plotMarkers. xMarkers [ index] , - border) + rect. origin
458
+ // Draw stem.
459
+ let p1 = Point ( plan. plotMarkers. xMarkers [ index] , - border) + rect. origin
460
+ let p2 = Point ( plan. plotMarkers. xMarkers [ index] , - border - Self. markerStemLength) + rect. origin
443
461
renderer. drawLine ( startPoint: p1,
444
462
endPoint: p2,
445
463
strokeWidth: markerThickness,
446
464
strokeColor: plotBorder. color,
447
465
isDashed: false )
448
466
renderer. drawText ( text: plan. plotMarkers. xMarkersText [ index] ,
449
- location: plan. xMarkersTextLocation [ index] + rect. origin + Pair ( 0 , - border ) ,
467
+ location: plan. xMarkersTextLocation [ index] + rect. origin,
450
468
textSize: markerTextSize,
451
469
color: plotBorder. color,
452
470
strokeWidth: 0.7 ,
453
471
angle: 0 )
454
472
}
455
473
456
474
for index in 0 ..< plan. plotMarkers. yMarkers. count {
457
- let p1 = Point ( - border - 6 , plan. plotMarkers. yMarkers [ index] ) + rect. origin
475
+ let p1 = Point ( - border - Self . markerStemLength , plan. plotMarkers. yMarkers [ index] ) + rect. origin
458
476
let p2 = Point ( - border, plan. plotMarkers. yMarkers [ index] ) + rect. origin
459
477
renderer. drawLine ( startPoint: p1,
460
478
endPoint: p2,
461
479
strokeWidth: markerThickness,
462
480
strokeColor: plotBorder. color,
463
481
isDashed: false )
464
482
renderer. drawText ( text: plan. plotMarkers. yMarkersText [ index] ,
465
- location: plan. yMarkersTextLocation [ index] + rect. origin + Pair ( - border , 0 ) ,
483
+ location: plan. yMarkersTextLocation [ index] + rect. origin,
466
484
textSize: markerTextSize,
467
485
color: plotBorder. color,
468
486
strokeWidth: 0.7 ,
@@ -471,17 +489,15 @@ extension GraphLayout {
471
489
472
490
if !plan. plotMarkers. y2Markers. isEmpty {
473
491
for index in 0 ..< plan. plotMarkers. y2Markers. count {
474
- let p1 = Point ( plan. plotBorderRect. width + border,
475
- ( plan. plotMarkers. y2Markers [ index] ) ) + rect. origin
476
- let p2 = Point ( plan. plotBorderRect. width + border + 6 ,
477
- ( plan. plotMarkers. y2Markers [ index] ) ) + rect. origin
492
+ let p1 = Point ( rect. width + border, plan. plotMarkers. y2Markers [ index] ) + rect. origin
493
+ let p2 = Point ( rect. width + border + Self. markerStemLength, plan. plotMarkers. y2Markers [ index] ) + rect. origin
478
494
renderer. drawLine ( startPoint: p1,
479
495
endPoint: p2,
480
496
strokeWidth: markerThickness,
481
497
strokeColor: plotBorder. color,
482
498
isDashed: false )
483
499
renderer. drawText ( text: plan. plotMarkers. y2MarkersText [ index] ,
484
- location: plan. y2MarkersTextLocation [ index] + rect. origin + Pair ( border , 0 ) ,
500
+ location: plan. y2MarkersTextLocation [ index] + rect. origin,
485
501
textSize: markerTextSize,
486
502
color: plotBorder. color,
487
503
strokeWidth: 0.7 ,
0 commit comments