plotly / plotly.py

The interactive graphing library for Python :sparkles: This project now includes Plotly Express!
https://plotly.com/python/
MIT License
15.96k stars 2.54k forks source link

Make marker size absolute in scatterplot #3535

Open Ben-Epstein opened 2 years ago

Ben-Epstein commented 2 years ago

It seems that when creating a scatterplot, the marker sizes are always in pixels. The result is that when zooming into the plot, the points remain small. Is there a way to have them dynamically resize? Or set the size to be absolute instead of pixel?

A code snippet to showcase the point

import pandas as pd
import numpy as np
import plotly.express as px

some_data = pd.DataFrame({"id":list(range(50_000)), "x":np.random.normal(size=50_000), "y":np.random.normal(size=50_000)})
some_data["c"] = pd.Categorical(np.random.randint(low=0,high=2,size=50_000))

fig = px.scatter(some_data, x="x", y="y", color="c")

fig.update_traces(marker=dict(size=3, opacity=0.5),
                  selector=dict(mode='markers'))

fig.update_layout({
    'plot_bgcolor': 'rgba(0, 0, 0, 0)',
    'paper_bgcolor': 'rgba(0, 0, 0, 0)',
})

fig.update_yaxes(matches=None, showticklabels=False, visible=False)
fig.update_xaxes(matches=None, showticklabels=False, visible=False)

config = dict({'scrollZoom': True})

fig.show(config=config)

Zoomed out, this is a nice graph that shows the distribution clearly, but when zooming in, the points are so small it's nearly impossible to interact with any individual one.

https://user-images.githubusercontent.com/22605641/147709359-34eb5036-d96e-4feb-a32f-f9076a7f3b4d.mov

Is there a way for those points to have absolute sizes instead of fixed ones? Such that zooming in would cause them to increase (as would be intuitive naturally)?

Thanks!

nikita-galileo commented 2 years ago

+1 would love to see this as well. Would love a way to set their absolute size without having to dig too deep into plotly internals or plotly.js callbacks.

jonathangomesselman commented 2 years ago

+1 I looked into this for some time a little while back with no success :(. It would be amazing to finally find a solution to this!

luggie commented 2 years ago

+2 tried to forge a hand-made solution for this but failed

PaulRivaud commented 2 years ago

+1, also looking for a solution

Alpha-Rome0 commented 2 years ago

+1 d3 does this out of the box.

JeyDi commented 2 years ago
luggie commented 2 years ago

kiiiiind of a 'solution' would be to draw shapes instead of scatter points for each scatter point with add_shape() here, zooming includes an increase of objects size when coming closer. However there are drawbacks because shapes are not meant to be plot objects but rather drawings/annotations. Therefore things like hover info need to be implemented with invisible scatter points etc. Feels very dodgy but for my purposes it does the job.

Paximilianus commented 1 year ago

+1 this would be very nice to have

benocd commented 1 year ago

I currently need this feature for work :)

WhoTHU commented 1 year ago

+1 Drawing spheres do provide a solution. However, it can be too heavy when I have to draw over tens of thousands of points in the 3D plot. Waiting for an upgrade.

AdamColligan commented 1 year ago

+1 This would be a big help in a dataset I'm working with where the size-generating data spans orders of magnitude and where there are dense regions of smaller-size markers.

tsitsimis commented 1 year ago

This would be very useful!

stefansjs commented 10 months ago

Came here to see a solution. Looks like they've added documentation based on @luggie's solution https://plotly.com/python/shapes/#circles-positioned-relative-to-the-axis-data

duchengyao commented 10 months ago

Came here to see a solution. Looks like they've added documentation based on @luggie's solution https://plotly.com/python/shapes/#circles-positioned-relative-to-the-axis-data

Useful when there is little data.

dboeckenhoff commented 10 months ago

This would be also useful in order to be able to become independent of rescaling the figure dpi after plotting

umarbutler commented 8 months ago

+1 I have been searching for a solution to this problem and am yet to find one. This should be standard behaviour.

archmoj commented 8 months ago

I could imagine 2 different APIs for this.

  1. To add sizex and sizey to marker. In this case the a circle marker become an ellipse when scaled in on direction.
  2. To add a constant sizemode. In this case a circle marker remains circle when scaled in one direction as it is adjusted in other direction to maintain the area.
alexcjohnson commented 8 months ago

For those of you interested in this feature: the key next step is to design the API, and for that we need to know a bit more about the use cases you have in mind. The crux of the issue is that zooming in and out does not generally preserve aspect ratio, so if we're trying to set the marker size in reference to the axis scaling rather than pixels, the question is which axis? I guess the options could be:

And for all variants, I bet it'll also be important to constrain the max and min resulting sizes. For array-sized markers we already have a marker.sizemin attribute, we should be able to reuse that here, and perhaps add marker.sizemax as well.

stefansjs commented 8 months ago

I can't imagine any use case where somebody wants an absolute scale, but not absolute in both axes.

What I would use is a fixed radius, which means, r = xscale = yscale. That means it would be an ellipse when zoomed in.

What's the situation when you might want a fixed scale in one axis but not the other?

That said, if you want an ellipse API and a circle API, just make two different shapes?

alferan commented 8 months ago

I would also like to have this feature. If we have same units on each axis like a coordinates in meter for each axis and radius or diameter in meter for each marker we could associate this data to sizing each marker. As proposed by @archmoj I think it would be a good approach for circle data, the markers will appears as circle if axis x and y are orthonormal, as an ellipsis if not.

umarbutler commented 6 months ago

kiiiiind of a 'solution' would be to draw shapes instead of scatter points for each scatter point with add_shape() here, zooming includes an increase of objects size when coming closer. However there are drawbacks because shapes are not meant to be plot objects but rather drawings/annotations. Therefore things like hover info need to be implemented with invisible scatter points etc. Feels very dodgy but for my purposes it does the job.

Unfortunately, this only really works for small datasets. I managed to plot >100k datapoints as circles using this stack overflow answer but there are so many shapes in the figure that it is essentially impossible to render.

fzyzcjy commented 1 month ago

+1 Is there any updates? Thanks!

mikerrr commented 1 month ago

I make it with GeoJSON and it works!

# https://community.plotly.com/t/problem-of-adding-shape-to-maps/60047/2
import geopy

test = centermost_points.iloc[0]
test_df = pd.DataFrame([test])

fig = px.scatter_mapbox(test_df, lat="latitude", lon="longitude", 
                        color = "cluster",zoom=5, height=500, size_max=2)
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.update_layout(autosize=True)

coords = list()
for deg in range(360):
    lat = geopy.distance.distance(kilometers=2).destination((test['latitude'], test['longitude']), bearing=deg).latitude
    lon = geopy.distance.distance(kilometers=2).destination((test['latitude'], test['longitude']), bearing=deg).longitude
    coords.append([lon,lat])
shape = {"type": "FeatureCollection"}
shape['features'] = [{ "type": "Feature",
                                     "geometry": {"type": "Polygon",
                                      "coordinates":  [coords] }}]

layers=[dict(sourcetype = 'geojson',
             source = shape,
             below=' ', 
             type = 'fill',   
             color = 'PaleTurquoise',
             opacity=0.5
)]
fig.update_mapboxes(layers=layers)