@@ -276,111 +276,82 @@ extension Histogram: HasGraphLayout {
276
276
}
277
277
}
278
278
}
279
-
280
- //functions to draw the plot
279
+
280
+ /// Draw with rectangles if `histogramType` is `.bar` or with lines if `histogramType` is `.step`
281
281
public func drawData( markers: PlotMarkers , size: Size , renderer: Renderer ) {
282
- var xM = Float ( xMargin)
282
+ let binCount = histogramSeries. bins
283
+ let allSeries = [ histogramSeries] + histogramStackSeries
283
284
switch histogramSeries. histogramSeriesOptions. histogramType {
284
285
case . bar:
285
- for i in 0 ..< histogramSeries. bins {
286
- var currentHeight : Float = histogramSeries. scaledBinFrequency [ i]
287
- var rect = Rect (
288
- origin: Point ( xM, 0 ) ,
289
- size: Size ( width: barWidth, height: currentHeight)
290
- )
291
- renderer. drawSolidRect ( rect,
292
- fillColor: histogramSeries. color,
293
- hatchPattern: . none)
294
-
295
- for series in histogramStackSeries {
296
- rect. origin. y = currentHeight
297
- rect. size. height = series. scaledBinFrequency [ i]
298
- renderer. drawSolidRect ( rect,
299
- fillColor: series. color,
300
- hatchPattern: . none)
301
- currentHeight += series. scaledBinFrequency [ i]
286
+ let xStart = Float ( xMargin)
287
+ let xValues = stride ( from: xStart, to: xStart + Float( binCount) * barWidth, by: barWidth)
288
+
289
+ // Get a `Slice` of frequencies for each series so we can take one element from each series for each x value
290
+ var frequencySlices = allSeries. map { $0. scaledBinFrequency [ ... ] }
291
+ for x in xValues {
292
+ var currentHeight : Float = 0.0
293
+ for (series, index) in zip ( allSeries, frequencySlices. indices) {
294
+ let height = frequencySlices [ index] . removeFirst ( )
295
+ let rect = Rect ( origin: Point ( x, currentHeight) , size: Size ( width: barWidth, height:
296
+ height) )
297
+ renderer. drawSolidRect ( rect, fillColor: series. color, hatchPattern: . none)
298
+ currentHeight += height
302
299
}
303
- xM += barWidth
300
+ currentHeight = 0.0
304
301
}
305
302
case . step:
306
- var firstHeight : Float = histogramSeries. scaledBinFrequency [ 0 ]
307
- var firstBottomLeft = Point ( xM, 0.0 )
308
- var firstTopLeft = Point ( xM, firstHeight)
309
- renderer. drawLine ( startPoint: firstBottomLeft,
310
- endPoint: firstTopLeft,
311
- strokeWidth: strokeWidth,
312
- strokeColor: histogramSeries. color,
313
- isDashed: false )
314
- for series in histogramStackSeries {
315
- firstBottomLeft = Point ( firstBottomLeft. x, firstHeight)
316
- firstTopLeft = Point ( firstTopLeft. x, firstHeight + series. scaledBinFrequency [ 0 ] )
317
- renderer. drawLine ( startPoint: firstBottomLeft,
318
- endPoint: firstTopLeft,
319
- strokeWidth: strokeWidth,
320
- strokeColor: series. color,
321
- isDashed: false )
322
- firstHeight += series. scaledBinFrequency [ 0 ]
323
- }
324
- for i in 0 ..< histogramSeries. bins {
325
- var currentHeight : Float = histogramSeries. scaledBinFrequency [ i]
326
- var topLeft = Point ( xM, currentHeight)
327
- var topRight = Point ( xM+ barWidth, currentHeight)
328
- renderer. drawLine ( startPoint: topLeft,
329
- endPoint: topRight,
330
- strokeWidth: strokeWidth,
331
- strokeColor: histogramSeries. color,
332
- isDashed: false )
333
- if ( i != histogramSeries. bins- 1 ) {
334
- let nextTopLeft = Point ( topRight. x, histogramSeries. scaledBinFrequency [ i+ 1 ] )
335
- renderer. drawLine ( startPoint: topRight,
336
- endPoint: nextTopLeft,
337
- strokeWidth: strokeWidth,
338
- strokeColor: histogramSeries. color,
339
- isDashed: false )
303
+ let xStart = Float ( xMargin)
304
+ let xValues = stride ( from: xStart, through: xStart + Float( binCount) * barWidth, by: barWidth)
305
+
306
+ // One heights array for each series
307
+ var seriesHeights : [ [ Float ] ] = [ [ Float] ( repeating: 0.0 , count: binCount + 2 ) ]
308
+
309
+ // Update `currentHeights` with the height from the series and add ìt to `heights`
310
+ var currentHeights = seriesHeights [ seriesHeights. startIndex]
311
+ for series in allSeries {
312
+ for (newHeight, index) in zip ( series. scaledBinFrequency, currentHeights. indices. dropFirst ( ) . dropLast ( ) ) {
313
+ currentHeights [ index] += newHeight
340
314
}
341
- for series in histogramStackSeries {
342
- topLeft = Point ( topLeft. x, currentHeight + series. scaledBinFrequency [ i] )
343
- topRight = Point ( topRight. x, currentHeight + series. scaledBinFrequency [ i] )
344
- if ( series. scaledBinFrequency [ i] > 0 ) {
345
- renderer. drawLine ( startPoint: topLeft,
346
- endPoint: topRight,
347
- strokeWidth: strokeWidth,
348
- strokeColor: series. color,
349
- isDashed: false )
350
- if ( i != histogramSeries. bins- 1 ) {
351
- var nextHeight = histogramSeries. scaledBinFrequency [ i+ 1 ]
352
- for k in histogramStackSeries {
353
- nextHeight += k. scaledBinFrequency [ i+ 1 ]
354
- }
355
- let nextTopLeft = Point ( topRight. x, nextHeight)
356
- renderer. drawLine ( startPoint: topRight,
357
- endPoint: nextTopLeft,
358
- strokeWidth: strokeWidth,
359
- strokeColor: series. color,
360
- isDashed: false )
361
- }
315
+ seriesHeights. append ( currentHeights)
316
+ }
317
+
318
+ // Draw only the line segments that will actually be visible, unobstructed from other lines that will be on top
319
+ // We iterate over the series in reverse to draw them from back to front
320
+ var seriesHeightsSlice = seriesHeights. reversed ( ) [ ... ]
321
+ var backHeightsSlice = seriesHeightsSlice. removeFirst ( ) [ ... ]
322
+ for (frontHeights, series) in zip ( seriesHeightsSlice, allSeries. reversed ( ) ) {
323
+ var frontHeightsSlice = frontHeights [ ... ]
324
+
325
+ // Iterate over bin edges focusing on the height of the left and right bins of the series on the back and in front
326
+ var backLeftBinHeight = backHeightsSlice. removeFirst ( )
327
+ var frontLeftBinHeight = frontHeightsSlice. removeFirst ( )
328
+ var line = [ Point] ( )
329
+ for (( backRightBinHeight, frontRightBinHeight) , x) in zip ( zip ( backHeightsSlice, frontHeightsSlice) , xValues) {
330
+
331
+ func endLine( ) {
332
+ renderer. drawPlotLines ( points: line, strokeWidth: strokeWidth,
333
+ strokeColor: series. color, isDashed: false )
334
+ line. removeAll ( keepingCapacity: true )
362
335
}
363
- currentHeight += series. scaledBinFrequency [ i]
336
+
337
+ // Conditions for appending specific points or ending the line at different places based on the relative heights (4 measures)
338
+ let c1 = backLeftBinHeight > frontLeftBinHeight
339
+ let c2 = backRightBinHeight > frontRightBinHeight
340
+ let c3 = backLeftBinHeight > frontRightBinHeight
341
+ let c4 = backRightBinHeight > frontLeftBinHeight
342
+
343
+ if c1 || c3 && c4 { line. append ( Point ( x, backLeftBinHeight) ) }
344
+ if !c3 { endLine ( ) }
345
+ if c1 && !c4 { line. append ( Point ( x, frontLeftBinHeight) ) }
346
+ if !c4 { endLine ( ) }
347
+ if c2 && !c3 { line. append ( Point ( x, frontRightBinHeight) ) }
348
+ if c2 || c3 && c4 { line. append ( Point ( x, backRightBinHeight) ) }
349
+ if !c2 { endLine ( ) }
350
+
351
+ backLeftBinHeight = backRightBinHeight
352
+ frontLeftBinHeight = frontRightBinHeight
364
353
}
365
- xM+= barWidth
366
- }
367
- var lastHeight : Float = histogramSeries. scaledBinFrequency [ histogramSeries. scaledBinFrequency. count- 1 ]
368
- var lastBottomRight = Point ( xM, 0.0 )
369
- var lastTopRight = Point ( xM, lastHeight)
370
- renderer. drawLine ( startPoint: lastBottomRight,
371
- endPoint: lastTopRight,
372
- strokeWidth: strokeWidth,
373
- strokeColor: histogramSeries. color,
374
- isDashed: false )
375
- for series in histogramStackSeries {
376
- lastBottomRight = Point ( lastBottomRight. x, lastHeight)
377
- lastTopRight = Point ( lastTopRight. x, lastHeight + series. scaledBinFrequency [ series. scaledBinFrequency. count- 1 ] )
378
- renderer. drawLine ( startPoint: lastBottomRight,
379
- endPoint: lastTopRight,
380
- strokeWidth: strokeWidth,
381
- strokeColor: series. color,
382
- isDashed: false )
383
- lastHeight += series. scaledBinFrequency [ series. scaledBinFrequency. count- 1 ]
354
+ backHeightsSlice = frontHeights [ ... ]
384
355
}
385
356
}
386
357
}
0 commit comments