JuliaPlots / PlotlyJS.jl

Julia library for plotting with plotly.js
Other
422 stars 78 forks source link

densitymapbox doesn't generate a valid visualization #484

Closed juliohm closed 6 months ago

juliohm commented 7 months ago

Describe the bug The densitymapbox trace doesn't generate a valid visualization.

Version info PlotlyJS v0.18.13

Julia Version 1.10.2
Commit bd47eca2c8a (2024-03-01 10:14 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 8 × Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, skylake)
Threads: 8 default, 0 interactive, 4 GC (on 8 virtual cores)
Environment:
  JULIA_EDITOR = code
  JULIA_NUM_THREADS = 8

MWE

using PlotlyJS

lon = rand(100)
lat = rand(100)

plot(densitymapbox(lon = lon, lat = lat))

image

empet commented 7 months ago

You did not give any values to be mapped to a colorscale in order to get a density. Here is the reference for densitymapbox https://plotly.com/julia/reference/densitymapbox/ and this is an example:

using HTTP, CSV, DataFrames, PlotlyJS
res = HTTP.get("https://raw.githubusercontent.com/plotly/datasets/master/earthquakes-23k.csv")
df = CSV.read(res.body, DataFrame);

fig = Plot(densitymapbox(lat=df[!, :Latitude], lon=df[!, :Longitude],
                         z=df[:, :Magnitude], radius=10, 
                         colorscale=reverse(colors.matter),
                        ))
relayout!(fig, mapbox_style="open-street-map", mapbox_center_lon=68, mapbox_center_lat=29)  
display(fig)

densitymapbox

LE: To display on hover all information contained in the dataframe, you should include customdata and hovertemplate in the trace definition:

fig = Plot(densitymapbox(lat=df[!, :Latitude], lon=df[!, :Longitude],
                         z=df[:, :Magnitude], radius=10, customdata = df[!, :Date],
                         hovertemplate="lon: %{lon}<br>lat: %{lat}<br>date: %{customdata}<br>magnit: %{z}<extra></extra>",
                         colorscale=reverse(colors.matter),
                        ))
juliohm commented 7 months ago

The line that fixes the issue is the relayout!, so it is certainly a bug somewhere.

empet commented 7 months ago

Your MWE contained just plot(densitymapbox(lon = lon, lat = lat)). I don't think that without adding z-values, only relayout you get a density plot that displays the right data in the dataframe.

juliohm commented 7 months ago
using PlotlyJS

lon = rand(100)
lat = rand(100)

p = plot(densitymapbox(lon = lon, lat = lat))

relayout!(p, mapbox_style = "open-street-map")

p

image

The issue has no relation with z.

empet commented 7 months ago

But without z the generated plot has NO meaning. For my example, if I define the trace without z:

fig = Plot(densitymapbox(lat=df[!, :Latitude], lon=df[!, :Longitude],
            colorscale=reverse(colors.matter) ))

I get this plot: densitymapboxnoz

What does it represent? Compare with the first densitymapbox.! Heatmaps, contour plots and here densitymapbox need z-values to be mapped to a colorscheme.

juliohm commented 7 months ago

A density map doesn't require z values. The density is computed based on the proximity of points in the map. The z values are just an option to adjust the heights of the density.

The issue I am reporting above has no relation with the specification of the z attribute. It is about the layout that is not set correctly by default.

To fix the issue one has to manually call relayout! or provide a layout in the plot command with the same mapbox options:

plot(densitymapbox(...), Layout(....))
empet commented 7 months ago

I'm sure you know what is a density. It's not the density of geographic locations, but the density of values associated to them. The mapbox layout cannot be set directly, by default, because each user wants to display a particular region of the globe, and so with different mapbox centers. I'm stopping this discussion. :)

juliohm commented 7 months ago

So you mean that the current behavior is acceptable? I believe that this is very bogus without the actual density being displayed by default.

The density of geographic locations is a particular case of density estimation with constant z value. We have tons of similar interpolation methods in our GeoStats.jl stack, and know in depth all the details that go into density estimation.

I feel that the PlotlyJS.jl behavior is buggy, and that beginners will struggle producing the simplest maps when nothing is shown by default. If the trace is called densitymapbox, then the attributes of the layout should be set for mapbox by default. Setting style = "open-street-map" is enough to fix the bug.

RoyiAvital commented 7 months ago

@empet , What do you think the role of the z parameter?

According to the link you gave:

image

The parameter z is only optional. On the documentation of the parameter:

image

It says the parameter is basically a weight of the corresponding point. So the values to be scaled does not require z, no?

It might be an issue of PlotLy itself and not PlotLyJS.jl.

empet commented 7 months ago

Yes you may use it without z-values, but with NO MEANING. Look here at a geographical region within Pacific Ocean (no island around), with lon, lat, at a distance of two units, and and two locations at a smaller distance around Paris. We get the same density. How do you interpret this similarity between the two densities?

fig1 = Plot(densitymapbox(lat=[-1, 1],  lon=[177, 179]))
relayout!(fig1, mapbox_style="open-street-map", mapbox_center_lon=160, mapbox_center_lat=0,
                  width=600, height=350)

ocean

fig2 = Plot(densitymapbox(lat=[48.25, 49],  lon=[2, 2.5]))
relayout!(fig2, mapbox_style="open-street-map", mapbox_center_lon=2.25, mapbox_center_lat=48.5,
                  width=600, height=350)  

Paris

juliohm commented 7 months ago

Any density estimation procedure depends on a bandwidth or radius of a moving window. If you set the radius = 1 you'll see more detail. If you set radius = 10 the points collapse into one as they are within the same window.

The z value only controls the height of the peaks.

Em sáb., 9 de mar. de 2024 06:55, Math, Python & Julia < @.***> escreveu:

Yes you may use it without z-values, but with NO MEANING. Look here at a geographical region within Pacific Ocean (no island around), with lon, lat, at a distance of two units, and and two locations at a smaller distance around Paris. We get the same density. How do you interpret this similarity between the two densities?

fig1 = Plot(densitymapbox(lat=[-1, 1], lon=[177, 179])) relayout!(fig1, mapbox_style="open-street-map", mapbox_center_lon=160, mapbox_center_lat=0, width=600, height=350)

ocean.png (view on web) https://github.com/JuliaPlots/PlotlyJS.jl/assets/3627253/61a5e78b-dbcf-490d-b2c4-e9f75d58b597

fig2 = Plot(densitymapbox(lat=[48.25, 49], lon=[2, 2.5])) relayout!(fig2, mapbox_style="open-street-map", mapbox_center_lon=2.25, mapbox_center_lat=48.5, width=600, height=350)

Paris.png (view on web) https://github.com/JuliaPlots/PlotlyJS.jl/assets/3627253/4c344542-c393-4ead-840d-097e4c5a014e

— Reply to this email directly, view it on GitHub https://github.com/JuliaPlots/PlotlyJS.jl/issues/484#issuecomment-1986811740, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAZQW3N3WIUR3NTVLFX6YPDYXLL7TAVCNFSM6AAAAABEM3TVCSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTSOBWHAYTCNZUGA . You are receiving this because you authored the thread.Message ID: @.***>

RoyiAvital commented 7 months ago

@empet , I have little knowledge about the geographical things. If it is a standard 2D KDE, and z is, as documented, just the weight, it should work with the same meaning. As any KDE the output is a balance of bias / variance tradeoff between the local resolution and the accuracy of the estimation. The case with no z should be equivalent to the case all z are equal and it is a valid weighing.

Yet I think the point of the issue is why the layout is required and it doesn't work without it.

Then the question is whether it is specific to PlotLyJS.jl or something in PlotLy itself.

@juliohm , You may try the same in Python. If it behaves the same as you pointed, the issue is in PlotLy, otherwise, it is in PlotLyJS.jl (Most likely).

empet commented 7 months ago

Python version works in this case (of densitymapbox) like PlotlyJS. These lines of python code:

import plotly.graph_objects as go
fig = go.Figure(go.Densitymapbox(lat=[-1, 1],  lon=[177, 179]))
fig.show()

don't display anything, because go.Densitymapbox works only when the user has set his/her favorite mapbox_style (some styles can be accessed only with the user's mapbox_token). You are interpreting the actual behaviour of densitymapbox as an issue, because the Layout isn't predefined. Plotly.py and hence its version, PlotlyJS, doesn't prescribe the Layout for any plot (no recipes!!!). Each Figure instance contains two objects: the trace and the layout. Then the user, according to his/her data, goal, inspiration and creativity, is setting the attributes leading to the final plot. plotly.py, PlotlyJS.jl, R, javascript have a detailed reference site for traces and layout (here is for Julia: https://plotly.com/julia/reference/index/), and moreover, examples (docs) for different traces: https://plotly.com/julia/. The docs for versions of PlotlyJS.jl before 0.18 are outdated.

juliohm commented 6 months ago

I will close the issue given that the bug is in the underlying Javascript library. It feels very unpolished, the typical Javascript experience.