Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

LinearRegionSelector #164

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

Merged
merged 25 commits into from
Apr 18, 2023
Merged

LinearRegionSelector #164

merged 25 commits into from
Apr 18, 2023

Conversation

kushalkolar
Copy link
Member

@kushalkolar kushalkolar commented Apr 14, 2023

starts #142

  • entire selection responds to mouse events
  • edges allow resizing with mouse events, edges change color and thickness with interaction
  • LinearSelector.get_selected_data() returns a view of the data, or a list of views if from a collection such as LineStack
  • LinearBoundsFeature manages events.
  • x-axis
  • y-axis
  • LineGraphic.add_linear_region_selector(), allows adding multiple selectors onto the same graphic
  • LineCollection.add_linear_region_selector()

As we make more selectors we can think about making a more centralized implementation for Graphic.add_<selector>. This may or may not be possible due to the diveristy of selectors that apply to a given graphic. For example, you wouldn't use a LinearSelector on a ScatterGraphic, and you wouldn't use a RectangleSelector on a LineGraphic.

import fastplotlib as fpl
import numpy as np


gp = fpl.GridPlot((2, 2))

# preallocated size for zoomed data
zoomed_prealloc = 1_000

# data to plot
xs = np.linspace(0, 100, 1_000)
sine = np.sin(xs) * 20

# make sine along x axis
sine_graphic_x = gp[0, 0].add_line(sine)

# just something that looks different for line along y-axis
sine_y = sine
sine_y[sine_y > 0] = 0

# sine along y axis
sine_graphic_y = gp[0, 1].add_line(np.column_stack([sine_y, xs]))

# offset the position of the graphic to demonstrate `get_selected_data()` later
sine_graphic_y.position.set_x(50)
sine_graphic_y.position.set_y(50)

# add linear selectors
ls_x = sine_graphic_x.add_linear_region_selector()  # default axis is "x"
ls_y = sine_graphic_y.add_linear_region_selector(axis="y")

# preallocate array for storing zoomed in data
zoomed_init = np.column_stack([np.arange(zoomed_prealloc), np.random.rand(zoomed_prealloc)])

# make line graphics for displaying zoomed data
zoomed_x = gp[1, 0].add_line(zoomed_init)
zoomed_y = gp[1, 1].add_line(zoomed_init)


def interpolate(subdata: np.ndarray, axis: int):
    """1D interpolation to display within the preallocated data array"""
    x = np.arange(0, zoomed_prealloc)
    xp = np.linspace(0, zoomed_prealloc, subdata.shape[0])
    
    # interpolate to preallocated size
    return np.interp(x, xp, fp=subdata[:, axis])

def set_zoom_x(ev):
    """sets zoomed x selector data"""
    selected_data = ev.pick_info["selected_data"]
    zoomed_x.data = interpolate(selected_data, axis=1)  # use the y-values
    gp[1, 0].auto_scale()


def set_zoom_y(ev):
    """sets zoomed y selector data"""
    selected_data = ev.pick_info["selected_data"]
    zoomed_y.data = -interpolate(selected_data, axis=0)  # use the x-values
    gp[1, 1].auto_scale()


# update zoomed plots when bounds change
ls_x.bounds.add_event_handler(set_zoom_x)
ls_y.bounds.add_event_handler(set_zoom_y)

gp.show()
linear_selector_xy-2023-04-16_05.57.06.mp4

@kushalkolar kushalkolar changed the title linear selector, basic functionality linear selector Apr 14, 2023
@kushalkolar kushalkolar requested a review from clewis7 April 14, 2023 08:16
@kushalkolar
Copy link
Member Author

kushalkolar commented Apr 14, 2023

@clewis7
@EricThomson
@ArjunPutcha

Some ideas on how to make these easy to use:

Just add a method to all graphics where it makes sense, like LineGraphic and HeatmapGraphic. It automatically sets the limits, height, etc. based on the the data in that graphic and adds it to the plot area!

line = plot.add_line(...)

selector = line.add_linear_selector(resizable=True)  # by default sets initial bounds as 20% of the data size
# similar syntax for heatmap

Would be nice to have a simple API to automatically link one Graphic to another using a selector. For example a heatmap to line stack. The selector does have access to the parent Graphic DataFeature (i.e. it has acccess to the data array of the parent graphic), so maybe something like this:

selector = heatmap.add_linear_selector(...)

# sets the target's data with the sub-data from the selection, interpolates if necessary
selector.link("bounds", target=line, feature="data", interpolate=True)

# or change some other feature, like color?
selector.link("bounds", target=line, feature="colors", new_data="w")

If they want full manual control for something more complex:

from fastplotlib.graphics.selectors import LinearSelector

# do complex stuff
ls = LinearSelector(
    bounds, 
    limits, 
    height, 
    position,
    resizable=True
)

@EricThomson
Copy link
Contributor

EricThomson commented Apr 14, 2023

This looks great. I am not sure from reading just the parameter names what the difference between bounds and limits is, but it is clear from context. It is hard to name things like that I know from experience with windowing functions I've tried things like "view_range" and "selector_limits" to highlight that one is showing the range of the current viewpoint, the other is giving absolute limits of the selector, but honestly nothing will be perfect. That's what docs are for I guess. ¯\(ツ)

I agree that creating abstractions so people can just attach these to data types so it works out of the box would be pretty amazing!

@kushalkolar
Copy link
Member Author

kushalkolar commented Apr 14, 2023

More things to add to this and future selectors:

  • Put a parent_graphic attribute on selector, can be None or can be a single graphic. In the future maybe we can do multiple graphics
    • have a Selector.get_selected_<feature>() where feature can be a GraphicFeature such as data, colors, present etc. and this returns the underlying feature data for that feature for the parent_graphic.
  • Make edge lines thinner, only when mouse is hovering on it make it thicker

This looks great. I am not sure from reading just the parameter names what the difference between bounds and limits is, but it is clear from context. It is hard to name things like that I know from experience with windowing functions I've tried things like "view_range" and "selector_limits" to highlight that one is showing the range of the current viewpoint, the other is giving absolute limits of the selector, but honestly nothing will be perfect. That's what docs are for I guess. ¯_(ツ)_/¯

Yea if you have better name ideas they're welcome. Just took a look at pyqtgraph.LinearRegionItem which is what this selector is inspired by: https://pyqtgraph.readthedocs.io/en/latest/api_reference/graphicsItems/linearregionitem.html

values A list of the positions of the lines in the region. These are not limits; limits can be set by specifying bounds.
clipItem An item whose bounds will be used to limit the region bounds.
-- --
bounds Optional [min, max] bounding values for the region
-- --
span Optional [min, max] giving the range over the view to draw the region. For example, with a vertical line, use span=(0.5, 1) to draw only on the top half of the view.
-- --

😂

I agree that creating abstractions so people can just attach these to data types so it works out of the box would be pretty amazing!

Yup and with having Selector.get_selected_data(), get_selected_colors(), get_selected_present() etc. it'll make it easy to use.

@kushalkolar
Copy link
Member Author

Should allow this to be vertical as well so that it can be used with histograms for pyqtgraph-like vmin vmax setters

@kushalkolar
Copy link
Member Author

I just realized it's useful to have a "selectors" property on LineGraphic (and the collections) which returns selectors attached to them. Just iterate through plot_area.graphics and return those which have "g.parent is self"

fastplotlib/plot.py Show resolved Hide resolved
fastplotlib/graphics/selectors/linear.py Outdated Show resolved Hide resolved
fastplotlib/graphics/selectors/linear.py Outdated Show resolved Hide resolved
fastplotlib/graphics/selectors/linear.py Outdated Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Show resolved Hide resolved
fastplotlib/graphics/selectors/_linear.py Outdated Show resolved Hide resolved
@kushalkolar kushalkolar changed the title linear selector LinearRegionSelector Apr 18, 2023
@kushalkolar kushalkolar merged commit 4b0f7a9 into master Apr 18, 2023
@kushalkolar kushalkolar deleted the linear-selector branch May 23, 2023 04:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.