vojtamolda / Plotly.swift

Interactive data visualization library for Swift
https://vojtamolda.github.io/Plotly.swift/
MIT License
82 stars 8 forks source link

Allow individual subplot axis customization #8

Closed vojtamolda closed 4 years ago

vojtamolda commented 4 years ago

Currently, the Layout struct doesn't allow customization of subplot axes. When one creates a Figure with multiple traces that are displayed as sub-plots, there's no way to set properties of the second and all the following axes of any type.

Here's the full list of axis types that exist in the Layout object and can't be customized and shared between subplots:

vojtamolda commented 4 years ago

The issue happens because association between axes and traces is done via string matching in JavaScript. For example, two scatter subplots with customized x-axes are created in the following way:

var scatter1 = { "xaxis": "x1", "type": "scatter" ... }
var scatter2 = { "xaxis": "x2", "type": "scatter" ... }
var layout = {
  "xaxis1": { /* x1 axis customization goes here */ },
  "xaxis2": { /* x2 axis customization goes here */ }
}

Or two polar subplots to be displayed side-by-side can be constructed like this:

var barpolar1 = { "subplot": "polar1", "type": "barpolar", ... }
var barpolar2 = { "subplot": "polar2", "type": "barpolar", ... }
var layout = {
  "polar1": { /* polar subplot customization goes here */ },
  "polar2": { /* polar subplot customization goes here */ }
}

While it's possible to create the same figure in Swift by setting the .subplot or .xAxis trace properties to corresponding strings (i.e. "polar1/2" or "x1/2"), it's not possible to customize the "x2", or "polar2" axes. Layout object is strictly typed and it doesn't allow addition of Layout.x1, .x2, .polar1 or .polar2 members ad-hoc. These members when encoded to JSON would be translated by Plotly.js to customization of the corresponding sub-plot axes.

In fact, setting the trace subplot axis correspondence via string matching as described above, is not consistent with the API Design Guidelines. Allowing it is therefore not desirable because it breaks the conventions and unwritten rules Swift developers are used to.

vojtamolda commented 4 years ago

One idea for improvement of the Plotly.swift *Axis API would have all the *Axis types adopt reference semantics. Or in other words it would become a class. The .xAxis/.yAxis and .subplot properties of different traces would become references to a class instance that could be shared as needed and re-used in other traces to obtain desired subplot axis association.

There are two possible ways how this could behave in code:

Option A: Layout.create*SubPlot Factories for all *Axis Types

var scatter1 = Scatter(...)
var scatter2 = Scatter(...)

var layout = Layout(...)
scatter1.xAxis = layout.createXAxis( /* x1 axis customization goes here */ )
scatter2.xAxis = layout.createXAxis( /* x2 axis customization goes here */ )
var barPolar1 = BarPolar(...)
var barPolar2 = BarPolar(...)

var layout = Layout(...)
barPolar1.subPlot = layout.createPolarSubplot( /* polar axis customization goes here */ )
barPolar1.subPlot = layout.createPolarSubplot( /* polar axis customization goes here */ )

Option B: Layout Parameter for All *Axis Constructors

var scatter1 = Scatter(...)
var scatter2 = Scatter(...)

var layout = Layout(...)
scatter1.xAxis = .init(layout: layout, /* x1 axis customization goes here */ )
//             = Layout.XAxis(layout: layout, /* x1 axis customization goes here */ )
scatter2.xAxis = .init(layout: layout, /* x2 axis customization goes here */ )
//             = Layout.XAxis(layout: layout, /* x2 axis customization goes here */ )
var barPolar1 = BarPolar(...)
var barPolar2 = BarPolar(...)

var layout = Layout(...)
barPolar1.subplot = .init(layout: layout, /* polar subplot customization goes here */ )
//                = Layout.Polar(layout: layout, /* polar subplot customization goes here */ )
barPolar1.subplot = .init(layout: layout, /* polar subplot customization goes here */ )
//                = Layout.Polar(layout: layout, /* polar subplot customization goes here */ )

Both proposals imply that all properties of Layout struct that currently store the *Axis type would have to privately store a and manage a list of references [*Axis] instead.

vojtamolda commented 4 years ago

Done!