KarthikRIyer / swiftplot

Swift library for Data Visualization :bar_chart:
Apache License 2.0
401 stars 39 forks source link

Unify graph plot layout #46

Closed karwa closed 4 years ago

karwa commented 4 years ago

I noticed that all the plots we have currently do a lot of work to set up their plot chrome. By unifying them, we can achieve greater consistency, fewer bugs, and it will be easier to make improvements. For example, I'm currently looking at a way to tweak the layout so that axis labels will not overlap the nearby markers. By doing this on GraphLayout, all charts automatically benefit.

The way it works is to move the layout calculation/drawing logic and the data which affects it to a separate struct. The data properties are exposed on the object via the HasGraphLayout protocol, so code like barChart.plotTitle = ... still works, even though the storage has moved to the layout object.

The great thing about this is that it makes the code super-easy: the charts now only need to worry about formatting (scaling) and drawing their data in their unique way. And it's super-easy to make a new chart and get some chrome:

class MyGraph: Plot, HasGraphLayout {

            var layout: GraphLayout
            var xOffset: Float = 0
            var yOffset: Float = 0

            init(dimensions: PlotDimensions = .init(frameWidth: 1000, frameHeight: 700)) {
                layout = GraphLayout(plotDimensions: dimensions)
            }

            func calculateScaleAndMarkerLocations(markers: inout PlotMarkers, renderer: Renderer) {

                markers.xMarkers = Array(stride(from: 0, to: 500, by: 100))
                markers.xMarkersText = markers.xMarkers.map { String($0) }

                markers.yMarkers = Array(stride(from: 0, to: 500, by: 100))
                markers.yMarkersText = markers.yMarkers.map { String($0) }

                markers.y2Markers = Array(stride(from: 0, to: 500, by: 150))
                markers.y2MarkersText = markers.y2Markers.map { String($0) }
            }

            func drawData(markers: PlotMarkers, renderer: Renderer) {
            }

            var legendLabels: [(String, LegendIcon)] { [
                ("Something", .square(.green)),
                ("Another thing", .shape(.star, .lightBlue)),
            ]}
        }

Also added support for text colours. I would have liked to separate that out to keep the PR as easy to read as possible, but it ended up being too tricky to cut it out.

KarthikRIyer commented 4 years ago

Thanks! Merging.