vojtamolda / Plotly.swift

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

Nothing displaying in ScatterMapbox #24

Closed IndigoCurnick closed 1 year ago

IndigoCurnick commented 1 year ago

Using ScatterMapbox, show() makes a window open in the webbrowser, but it doesn't contain anything, just white. I think that I need to include a Mapbox API key in order to get the map data, but I can't figure out how to do that. Is this supported? I have used the Python and Rust versions of Plotly before, and they both have an update_layout() method which allows for injecting the API key

vojtamolda commented 1 year ago

Hello @NathanielCurnick ,

I think this is a bug or to be more precise an API that isn't yet implemented. I personally haven't used the Mapbox based charts at all. So chances are you are the first user who found out about the API key issue.

I think, the best workaround would be to try to build the HTML code directly yourself. Just use the library to generate HTML/JS code for the <div> that holds the chart. JSON.create(from: figure) is all you should need.

Here's how it is currently used: https://github.com/vojtamolda/Plotly.swift/blob/main/Sources/Plotly/IO/HTML.swift.

Then have full control of the generated HTML and you can insert the JS calls to set the Mapbox API key wherever it is needed.

vojtamolda commented 1 year ago

BTW, a PR with a fix would be welcome too ;)

IndigoCurnick commented 1 year ago

Hi @vojtamolda! Thanks for your super quick reply! If nobody has implemented this yet, I might be able to do it. I will be needing a lot of map plotting tools soon, so I will make it a priority to get a few hours of development into making this That being said, I'm new to swift so I might take a little while to figure out how to actually do it 😅

vojtamolda commented 1 year ago

Sure thing! I'll be happy to talk if you get stuck. Just ping me.

Especially the part of the codebase located in the Codegen directory is very difficult to understand. It parses Plotly JS templates and generates corresponding Codable Plotly structs.

But to implement what you need here, I don't think you have to touch it.

IndigoCurnick commented 1 year ago

After a long time I can move forward with this again. I've been diving through the code, and the Layout file does seem to support something Mapboxy here

public var mapbox: [Mapbox] = []

Plus there's an extension to Array here

extension Array where Element == Mapbox {
    public static func preset(domain: Domain? = nil, accessToken: String? = nil, style: Anything? =
            nil, center: Mapbox.Center? = nil, zoom: Double? = nil, bearing: Double? = nil, pitch: Double? =
            nil, layers: [Mapbox.Layer]? = nil, uiRevision: Anything? = nil) -> [Mapbox] {
        let axis = Mapbox(uid: 1)
        axis.domain = domain
        axis.accessToken = accessToken
        axis.style = style
        axis.center = center
        axis.zoom = zoom
        axis.bearing = bearing
        axis.pitch = pitch
        axis.layers = layers
        axis.uiRevision = uiRevision
        return [axis]
    }

So the access token system exists.

After hacking around a bit, I figured out I can do this

let trace = ScatterMapbox(name: "Trace", longitude: [1.0], latitude: [1.0]);
var layout = Layout();
layout.mapbox = [Mapbox(accessToken: "SOME TOKEN")];
let fig = Figure(data: [trace], layout: layout);
try! fig.show();

Which produces the following

Plotly.react('441E18FE-CE47-4769-BD11-111D8ECA9C5E',
    {"layout":{"mapbox1":{},"mapbox17407751477860320571":{"accesstoken":"SOME TOKEN"}},"data":[{"subplot":"mapbox1","lat":[1],"transforms":[],"type":"scattermapbox","name":"Trace","lon":[1]}],"config":{},"frames":[]}
  )

I made the equivalent Python code like this

import plotly.graph_objects as go

fig = go.Figure()

fig.add_trace(go.Scattermapbox(lat=[1.0], lon=[1.0]))

fig.update_layout(mapbox=dict(accesstoken="SOME TOKEN"))

json = fig.to_plotly_json()
print(json)

Which produces a much much bigger JSON than the Swift version (but most of it is just defining the defaults) but it does have the key

'mapbox': {'accesstoken': 'SOME TOKEN'}

Which is, I think, the only thing that needs to change for the Swift version to work perfectly? I don't know how to do this exactly, but I am willing to help out with PRs :)

vojtamolda commented 1 year ago

Let's try to work towards a minimal code example that should work but doesn't.

It seems to me that all your code is missing a Config object passed to the Figure constructor. The Config object has a mapboxAccessToken property that needs to be set for the Mapbox access traces to work.

I think something like this is a good minimal code example:

let trace = ScatterMapbox(name: "Trace", longitude: [1.0], latitude: [1.0])
let config = Config(mapboxAccessToken: "ACCESS_TOKEN_HERE")
let fig = Figure(data: [trace], config: config)
try! fig.show()

Does it work for you?

PS: I haven't actually compiled it, so take it with a grain of salt and correct whatever is needed.

IndigoCurnick commented 1 year ago

Oh, it does work, guess I just didn't understand how to use the Config the whole time -_- Thanks for the help! Sorry to take up too much time

vojtamolda commented 1 year ago

Perfect! No worries, I'm glad all works. Good luck with your project!