Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make marker size absolute in scatterplot #3535

Open
Ben-Epstein opened this issue Dec 29, 2021 · 26 comments
Open

Make marker size absolute in scatterplot #3535

Ben-Epstein opened this issue Dec 29, 2021 · 26 comments
Labels
feature something new P3 backlog

Comments

@Ben-Epstein
Copy link

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.

Screen.Recording.2021-12-29.at.5.58.42.PM.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
Copy link

nikita-galileo commented Dec 29, 2021

+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
Copy link

+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
Copy link

luggie commented Mar 20, 2022

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

@PaulRivaud
Copy link

+1, also looking for a solution

@Alpha-Rome0
Copy link

+1 d3 does this out of the box.

@JeyDi
Copy link

JeyDi commented Jun 27, 2022

  • 1 we also didn't found a solution yet

@luggie
Copy link

luggie commented Jun 27, 2022

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
Copy link

+1 this would be very nice to have

@benocd
Copy link

benocd commented Nov 14, 2022

I currently need this feature for work :)

@WhoTHU
Copy link

WhoTHU commented Feb 27, 2023

+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
Copy link

+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
Copy link

This would be very useful!

@stefansjs
Copy link

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
Copy link

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
Copy link

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

@umarbutler
Copy link

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

@archmoj
Copy link
Contributor

archmoj commented Dec 14, 2023

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.

@Coding-with-Adam Coding-with-Adam added feature something new P3 backlog sev-4 cosmetic labels Dec 14, 2023
@alexcjohnson
Copy link
Collaborator

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:

  • Give two sizes, one for x and one for y. This means in general as you zoom the markers will change aspect ratio, to become ellipses, rectangles, etc, but if the marker size really is supposed to mean something related to each axis scale, that might be the right solution. (note: this solution doesn't scale to 3D, because the markers maintain their orientation as the scene is rotated).
  • Size the markers based on one of the axis scales, but maintain aspect ratio regardless of the scale of the other axis.
  • Size the markers based on the data-scale area they cover, meaning when converted to linear size, markers grow with the geometric mean of the x and y (and z, this flavor would scale to 3D) axis scales. (@archmoj I guess this is what you meant with your variant 2? I like it!) This feels to me like it may be the best if the goal is "make the markers bigger when you've zoomed in so there are only a few of them." I worry though that it'll be hard to use, as you'll need to figure out a reasonable scale on both axes and multiply these together, giving a number that might not be very meaningful to you, so you'd likely end up just trying a few things until something looks good.
  • I wonder if there's a calculation we could do as an "auto" input to the above variant - something like the std deviation along each axis of all the traces with markers, divided by the total number of markers?

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
Copy link

stefansjs commented Dec 18, 2023

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
Copy link

alferan commented Jan 5, 2024

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.

@Coding-with-Adam Coding-with-Adam added p4 and removed P3 backlog sev-4 cosmetic labels Jan 18, 2024
@umarbutler
Copy link

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
Copy link

fzyzcjy commented Jul 7, 2024

+1 Is there any updates? Thanks!

@gvwilson gvwilson self-assigned this Jul 11, 2024
@mikerrr
Copy link

mikerrr commented Jul 30, 2024

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)

@gvwilson gvwilson removed their assignment Aug 2, 2024
@gvwilson gvwilson added P3 backlog and removed p4 labels Aug 12, 2024
@vidarsko
Copy link

vidarsko commented Sep 3, 2024

+1 This feature would be very useful.

@nve-sak
Copy link

nve-sak commented Sep 16, 2024

Is there a way to get the user-modified zoom level out of a Plotly figure (specifically a map), and to access this server-side via a callback? Then it would be easy to make a custom scale dependency, even other than linear, and simply update_traces with a new marker size. NB Looking for a widget based solution, not Dash-only.

@MattMcmullanQumulo
Copy link

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:

  • Give two sizes, one for x and one for y. This means in general as you zoom the markers will change aspect ratio, to become ellipses, rectangles, etc, but if the marker size really is supposed to mean something related to each axis scale, that might be the right solution. (note: this solution doesn't scale to 3D, because the markers maintain their orientation as the scene is rotated).

I view this as the most useful option. Here's a real world example that I've been trying to visualize for the last few hours.
I have data from a test doing doing storage I/O.

  • On the x axis, I have time. I want the marker to cover the time-span from the beginning to end of that operation.
  • On the y axis, I have file offsets. I want the marker to cover the offset-span that the I/O covers.

This would give me a graph intuitively showing me the I/O pattern over time. Zooming into sections would show me useful information like...

  • Is this section of I/O fully contiguous, or sparse?
  • How significant is the delay between completion and getting the next operation?
  • How many requests were concurrently processing at each point in time?

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.

I agree wholeheartedly. If my graph is covering a large timespan, I still want to see the trend of the requests, even though the duration of an individual request might make it be less than a pixel without capping the minimum size. For example, I might have an hours worth of data covering requests which individually span milliseconds. I think that it's important to keep this cap in display-space, not axis-space for that reason. I don't personally understand why you would ever want a maximum size in this kind of setup, but maybe someone else has a use-case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature something new P3 backlog
Projects
None yet
Development

No branches or pull requests