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

Fix in-cell slots #126

Merged
merged 10 commits into from
Sep 23, 2023
Merged

Fix in-cell slots #126

merged 10 commits into from
Sep 23, 2023

Conversation

hanjinliu
Copy link
Owner

@hanjinliu hanjinliu commented Sep 18, 2023

This PR allow using slice(None) as the in-cell slots.

Summary by CodeRabbit

  • New Feature: Added AsyncImporter class for asynchronous imports, improving application responsiveness.
  • New Feature: Introduced SignalArray and InCellRangedSlot classes to enhance signal handling and cell evaluation in tables.
  • New Feature: Added pickable parameter to QtMplPlotCanvas constructor, allowing control over plot element interactivity.
  • Refactor: Removed redundant methods as_iloc and as_iloc_slices, simplifying codebase.
  • Refactor: Replaced direct thread usage with ThreadPoolExecutor in AsyncImporter, enhancing performance and maintainability.
  • Refactor: Updated FigureCanvas import logic to support different backends, improving compatibility.
  • Bug Fix: Fixed zooming functionality in plot canvas, providing smoother user experience.
  • Test: Added new test cases in test_cell_ref_eval.py for scalar and vector outputs and the use of the N variable.

@coderabbitai
Copy link

coderabbitai bot commented Sep 21, 2023

Walkthrough

This pull request introduces significant changes across the codebase, focusing on enhancing functionality, improving modularity, and optimizing performance. Key updates include modifications to the AsyncImporter class, addition of new classes and methods for handling signals and cell evaluation in tables, and adjustments to test cases.

Changes

File(s) Summary
tabulous/__main__.py, tabulous/_async_importer.py Simplified imports and refactored threading logic in AsyncImporter.
tabulous/_magicgui/..., tabulous/_qt/... Added new parameters and methods to handle pickability and mouse events in plot canvas.
tabulous/_psygnal/... Introduced new classes and functions for signal handling and cell evaluation in tables.
tabulous/_range.py, tabulous/_slice_op.py, tabulous/commands/... Implemented utility functions for working with slices and updated related usage across the codebase.
tabulous/widgets/... Enhanced widget functionality by adding new parameters and methods.
tests/test_cell_ref_eval.py, tests/test_proxy.py Updated test cases to reflect changes in slicing syntax and added new tests for scalar and vector outputs.

🐇 "In the land of code, where logic reigns,

Changes abound, as the rabbit maintains.

With each pull request, a new tale unfolds,

Of improved functions and tales yet untold.

So hop along, there's much to explore,

In this world of code, forevermore!" 🎉


Tips

Chat with CodeRabbit Bot (@coderabbitai)

  • Mention @coderabbitai in any review comment for bot assistance.
  • Note: Review comments are made on code diffs or files, not on the PR overview.

Pause Incremental Reviews

  • Insert @coderabbitai: ignore in the PR description to halt the bot's ongoing reviews. Remove the line to resume.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 16

Commits Files that changed from the base of the PR and between bd83d53 and a0dd108.
Files selected for processing (29)
  • tabulous/main.py (1 hunks)
  • tabulous/_async_importer.py (2 hunks)
  • tabulous/_magicgui/_dialog.py (1 hunks)
  • tabulous/_magicgui/_selection.py (1 hunks)
  • tabulous/_psygnal/init.py (1 hunks)
  • tabulous/_psygnal/_array.py (1 hunks)
  • tabulous/_psygnal/_psygnal_compat.py (1 hunks)
  • tabulous/_psygnal/_slots.py (1 hunks)
  • tabulous/_psygnal/_special_objects.py (1 hunks)
  • tabulous/_qt/_plot/_mpl_canvas.py (7 hunks)
  • tabulous/_qt/_plot/_widget.py (8 hunks)
  • tabulous/_qt/_table/_base/_enhanced_table.py (4 hunks)
  • tabulous/_qt/_table/_base/_line_edit.py (2 hunks)
  • tabulous/_qt/_table/_base/_table_base.py (2 hunks)
  • tabulous/_qt/_titlebar.py (1 hunks)
  • tabulous/_qt/_traceback.py (3 hunks)
  • tabulous/_range.py (7 hunks)
  • tabulous/_selection_op.py (9 hunks)
  • tabulous/_slice_op.py (1 hunks)
  • tabulous/_text_formatter.py (1 hunks)
  • tabulous/commands/_arange.py (2 hunks)
  • tabulous/commands/_dialogs.py (6 hunks)
  • tabulous/commands/_optimizer.py (3 hunks)
  • tabulous/commands/_plot_models.py (16 hunks)
  • tabulous/commands/_sklearn/_widget.py (2 hunks)
  • tabulous/widgets/_component/_plot.py (1 hunks)
  • tabulous/widgets/_component/_ranges.py (3 hunks)
  • tabulous/widgets/_table.py (5 hunks)
  • tests/test_cell_ref_eval.py (2 hunks)
Files skipped from review due to trivial changes (7)
  • tabulous/main.py
  • tabulous/_selection_op.py
  • tabulous/_text_formatter.py
  • tabulous/commands/_dialogs.py
  • tabulous/commands/_optimizer.py
  • tabulous/_psygnal/_psygnal_compat.py (diff too large)
  • tabulous/_psygnal/_slots.py (diff too large)
Additional comments (Suppressed): 60
tabulous/_psygnal/__init__.py (1)
  • 1-4: The new hunk introduces SignalArray and InCellRangedSlot from the _array and _slots modules respectively. Ensure that these classes are correctly implemented in their respective modules and that they are used appropriately throughout the codebase.
tabulous/_magicgui/_dialog.py (1)
  • 107-110: The QtMplPlotCanvas constructor now includes a new argument pickable. Ensure that all calls to this constructor throughout the codebase have been updated to include this argument. Also, verify if setting pickable=False here is intended and does not affect any functionality where pickability of the plot canvas is required.
tabulous/_qt/_table/_base/_table_base.py (1)
  • 24-30: The import statement for _slice_op as _sl is new. Ensure that the functions from this module are correctly used in the rest of the code.
tabulous/_qt/_table/_base/_line_edit.py (2)
  • 12-18: The import of MultiRectRange has been removed. Ensure that this class is not used elsewhere in the codebase or that it has been properly replaced with an equivalent functionality.

  • 422-433: The method as_iloc_slices() now includes a new argument fit_shape=False. Verify that this change does not affect the expected behavior of the function and that all calls to this function have been updated accordingly. Also, the assignment of new_range has changed from MultiRectRange.from_slices(ranges) to just ranges. Make sure that this change doesn't break any existing functionality where new_range is used.

tabulous/_qt/_titlebar.py (1)
  • 56-60: The new method setBold() is introduced to allow the title text to be set as bold. This change enhances the visual customization of the title bar. However, ensure that this change does not affect the readability or layout of the title bar in different contexts where it is used.
tabulous/_qt/_table/_base/_enhanced_table.py (4)
  • 123-124: The _current_drawing_slot_ranges and _additional_ranges are now initialized as empty lists instead of None. This change could potentially affect other parts of the code that check for None to determine if these variables have been set. Please verify that all such checks have been updated accordingly.

  • 210-214: The _on_moving method has been modified to reset _additional_ranges at the start of each call, and to update _need_update_all based on whether _current_drawing_slot_ranges is non-empty. This change could potentially affect the behavior of the table when moving between cells. Please ensure that this new behavior is intended and does not introduce any unexpected side effects.

  • 222-228: The _on_moving method now sets _current_drawing_slot_ranges to an empty list when there is no slot at the destination index, or when the parent table is not a QTableWidget. Previously, it was set to None in these cases. As mentioned earlier, this change could potentially affect other parts of the code that check for None to determine if _current_drawing_slot_ranges has been set. Please verify that all such checks have been updated accordingly.

  • 628-640: The paintEvent method now draws rectangles for both _current_drawing_slot_ranges and _additional_ranges, whereas previously it only drew rectangles for _current_drawing_slot_ranges. This change could potentially affect the appearance of the table, especially if _additional_ranges contains many ranges. Please ensure that this new behavior is intended and does not negatively impact the user experience.

tabulous/_async_importer.py (2)
  • 1-7: The threading module is no longer imported, and the concurrent.futures module is now imported instead. This change suggests a shift from using individual threads to using a thread pool for asynchronous tasks.

  • 22-24: The ignore_error parameter has been removed from the get() method. This change means that any exceptions raised during the import will be propagated to the caller, which may need to handle them. Ensure that all calls to get() are updated to handle potential exceptions.

tabulous/_qt/_traceback.py (4)
  • 4-8: The import of QtCore is new in this hunk. Ensure that it's used in the code sections not included in these hunks.

  • 50-50: The _old_focus attribute is newly introduced and stores the widget that currently has focus. This is likely to restore focus after the message box is closed. Make sure that this doesn't interfere with other parts of the UI that might also manipulate focus.

  • 63-64: Shortcut "T" is added for the traceback button. Verify if this shortcut conflicts with any existing shortcuts in the application.

  • 67-72: The exec_() method now checks if _old_focus is not None before calling setFocus(). This is a good practice as it prevents potential errors when _old_focus is None. However, ensure that the focus restoration works correctly in all scenarios where the error message box is used.

tabulous/commands/_plot_models.py (5)
  • 53-56: The add_data method now requires a table parameter. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 88-98: The add_data method now requires a table parameter. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 186-201: > Note: This review was outside of the patch, so it was mapped to the patch with the greatest overlap. Original lines [157-252]

The add_data method now requires a table parameter. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 241-252: The add_data method now requires a table parameter. Ensure that all calls to this function throughout the codebase have been updated to match the new signature.

  • 445-451: The as_iloc_slices method is now called with an additional argument fit_shape=False. This seems to be a significant change in how slices are created from the data. Please ensure that this change does not introduce any unexpected behavior or bugs in the rest of the codebase.

tabulous/_psygnal/_array.py (9)
  • 40-63: The SignalArray class is a 2D-parametric signal for a table widget that extends the psygnal.Signal class. It allows partial slot connection, which means you can connect a slot to the whole table or to a specific range of the table. This is a significant enhancement in terms of flexibility and control over slot connections.

  • 65-89: The __get__ method has been overloaded to handle different types of instances. The method returns the instance of the SignalArray if the instance is None, otherwise it creates a new SignalArrayInstance and sets it as an attribute of the instance before returning it. This ensures that each instance has its own SignalArrayInstance.

  • 95-113: The SignalArrayInstance class is a parametric version of SignalInstance. It inherits from both SignalInstance and TableAnchorBase. The __getitem__ method is overridden to return a sub-array reference, which is useful for accessing specific parts of the array.

  • 115-198: The connect method is overloaded to handle different parameters and scenarios. It checks whether the slot is callable and whether it's already connected. If the slot is not callable, a TypeError is raised. If the slot is already connected and the unique parameter is set to "raise", a ValueError is raised. Otherwise, the slot is appended to the _slots list. There's also a connect_cell_slot method specifically for connecting cell slots.

  • 200-253: The emit method is overloaded to handle different parameters and scenarios. It checks whether the signal is blocked or paused. If the signal is blocked, the method simply returns None. If the signal is paused, the arguments are appended to the _args_queue and the method returns None. Otherwise, the _run_emit_loop method is called to run the emit loop.

  • 255-293: The insert_rows, insert_columns, remove_rows, and remove_columns methods are used to modify the rows and columns of the signal array. They update the slot ranges in-place, which means they directly modify the original data structure instead of creating a new one. This can improve performance when dealing with large data structures.

  • 295-340: The _run_emit_loop method runs the emit loop for the signal array. It iterates over all slots and calls each slot with the provided arguments. If a slot is not callable or has been garbage collected, it's removed from the _slots list. If a slot doesn't overlap with the provided range, it's skipped. Any exceptions raised during the execution of a slot are caught and re-raised as an EmitLoopError.

  • 342-401: The _SignalSubArrayRef class is a reference to a subarray of a signal. It provides connect and emit methods that delegate to the parent SignalArrayInstance. This allows you to connect to and emit signals from a specific part of the signal array.

  • 403-426: The _parse_a_key and _parse_key functions are utility functions for parsing keys. They handle different types of keys, including slices, lists, numpy arrays, and integers. These functions are used to convert keys into a format that can be used to index into the signal array.

tabulous/commands/_sklearn/_widget.py (2)
  • 1-7: The import of pandas has been removed from the top-level imports and moved to a conditional import block under if TYPE_CHECKING:. This change is likely made to improve the startup time of the script by delaying the import of pandas until it's actually needed for type checking. However, if pandas is used in the runtime code (not just for type annotations), this could lead to NameError exceptions. Please verify that pandas is not used outside of type annotations.

  • 17-25: The pandas module is now imported inside an if TYPE_CHECKING: block. This approach is typically used to avoid circular imports or to reduce the startup time by importing heavy modules only when type checking. Ensure that pandas is not used in the runtime code, as this will cause a NameError.

tabulous/_range.py (6)
  • 5-5: The import statement for _slice_op has been changed. Ensure that the _slice_op module exists and contains the necessary functions used in this file.

  • 39-41: A new method as_keys() is added to return a list of (row, column) keys. This could be useful for iterating over the range in a more convenient way.

  • 56-56: The _fmt_slice function call has been replaced with _sl.fmt. Make sure that the _slice_op module (_sl) contains the fmt function and it behaves as expected.

  • 187-192: Two new methods as_keys() and __iter__() are added to the NoRange class. These methods seem to provide a consistent interface across different range classes, which can improve code readability and maintainability.

  • 226-227: The as_keys() method is added to the MultiRectRange class. It uses a generator expression and the sum function with start=[] to concatenate the results from calling as_keys() on each range. This is an efficient way to flatten a list of lists.

  • 293-295: The _parse_slice function is unchanged but the _fmt_slice function above it has been removed. If _fmt_slice is no longer needed, this is fine. Otherwise, make sure that its functionality is covered elsewhere.

tabulous/widgets/_component/_plot.py (2)
  • 62-68: The QtMplPlotCanvas constructor now requires a table parameter. Ensure that all calls to this constructor throughout the codebase have been updated to match the new signature.

  • 66-66: The set_background_color() method is called with the name of the background color. Make sure that the set_background_color() method can handle color names and not just RGB values or other color formats.

tabulous/commands/_arange.py (1)
  • 12-18: The import statement for _slice_op as _sl is added. This module provides utility functions for working with slices, which are used in the get_value() method of the _RangeDialog class.
tabulous/_qt/_plot/_widget.py (7)
  • 25-37: The pickable and table parameters have been added to the QtMplPlotCanvas constructor. Ensure that all calls to this constructor throughout the codebase have been updated to match the new signature.

  • 56-82: The connection of signals to slots in the QtMplPlotCanvas constructor is now conditional on the pickable parameter. This change could potentially affect the behavior of instances where pickable is set to False. Verify that this change does not introduce unexpected behavior in such cases.

  • 90-110: The _item_clicked and _repaint_ranges methods have been added, and the axes property has been moved. The addition of these methods and the movement of the axes property do not appear to introduce any issues.

  • 130-161: The _edit_artist method has been modified to call _repaint_ranges and possibly align_to_table, depending on whether a table is associated with the canvas. Ensure that these changes do not introduce unexpected behavior when editing an artist.

  • 173-179: No significant changes are made in this hunk. The import statement for matplotlib has been moved inside the function, which should not cause any issues.

  • 193-200: The QMplEditor constructor now sets the window flags to Qt.WindowType.Dialog, which makes the editor behave like a dialog window. Ensure that this change does not introduce unexpected behavior in the UI.

  • 205-236: Several methods related to mouse events and alignment have been added to the QMplEditor class. These additions do not appear to introduce any issues.

tests/test_cell_ref_eval.py (2)
  • 308-308: The status message has changed from "df.iloc[0:3, 0]" to "df.iloc[:, 0]". This change implies that the sum operation now includes all rows of the first column instead of just the first three. Please verify if this is the intended behavior.

  • 327-327: Similar to the previous comment, the status message has changed from "df.iloc[0:5, 0]" to "df.iloc[:, 0]". This change implies that the sum operation now includes all rows of the first column instead of just the first five. Please verify if this is the intended behavior.

tabulous/widgets/_component/_ranges.py (3)
  • 13-19: The import statement for _slice_op has been added. Ensure that this module exists and is accessible from the current file's location.

  • 40-44: The function _fmt_slice() has been moved to a different module (_slice_op). This change should not affect functionality as long as the function in the new module behaves identically to the old one.

  • 168-170: The _fmt_slice() function has been removed from this file. If it is used elsewhere in this file, those usages need to be updated to use the function from its new location in the _slice_op module.

tabulous/_qt/_plot/_mpl_canvas.py (2)
  • 12-31: The FigureCanvas class is now conditionally imported based on the TYPE_CHECKING flag. This change seems to be for type hinting purposes and should not affect runtime behavior. However, the itemPicked signal has been replaced with two new signals: itemClicked and itemDoubleClicked. Ensure that all connections to the old itemPicked signal have been updated to connect to the appropriate new signal(s).

  • 94-98: The mouseMoveEvent method now returns None instead of just returning when certain conditions are met. This change makes the code more explicit and improves readability.

tabulous/widgets/_table.py (5)
  • 91-94: The function signature of __get__ in the DataProperty class has been changed. The owner parameter is now optional and defaults to None. Ensure that this change does not affect any existing calls to this method.

  • 106-109: Similar to the previous comment, the function signature of __get__ in the MetadataProperty class has also been changed. The owner parameter is now optional and defaults to None. Verify that this change does not impact any existing calls to this method.

  • 267-271: The metadata property has been moved from the TableBase class to the MetadataProperty class. This could potentially break code that relies on accessing metadata directly from an instance of TableBase. Please ensure all such instances have been updated accordingly.

  • 284-287: The docstring for the table_shape property has been modified to indicate that the shape of the table considers filters. If there are any methods or functions that rely on the old behavior (i.e., not considering filters), they may need to be updated.

  • 736-739: The way data is appended to the DataFrame has been changed. Instead of directly setting the data attribute, the setDataFrame method of _qwidget is now used. This could potentially have implications for how the data is updated and displayed in the UI. Please verify that this change does not introduce any unexpected behavior.

Comment on lines 81 to 82
table = find_current_table(self)
if table is None:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _find_table method is missing error handling for when table is None. This could lead to a NoneType error if the method is called elsewhere and the return value is used without checking for None. Consider raising an exception or returning a default value when table is None.

     def _find_table(self) -> TableBase:
         table = find_current_table(self)
         if table is None:
+               raise ValueError("No current table found.")

Comment on lines 1582 to 1598
return pd.Index(di_list)


def _fmt_slice(sl: slice) -> str:
return f"{sl.start}:{sl.stop}"


def _selection_to_literal(sel: tuple[slice, slice]) -> str:
rsel, csel = sel
rsize = rsel.stop - rsel.start
csize = csel.stop - csel.start
if rsize == 1 and csize == 1:
txt = f"[{rsel.start}, {csel.start}]"
elif rsize == 1:
txt = f"[{rsel.start}, {_fmt_slice(csel)}]"
txt = f"[{rsel.start}, {_sl.fmt(csel)}]"
elif csize == 1:
txt = f"[{_fmt_slice(rsel)}, {csel.start}]"
txt = f"[{_sl.fmt(rsel)}, {csel.start}]"
else:
txt = f"[{_fmt_slice(rsel)}, {_fmt_slice(csel)}]"
txt = f"[{_sl.fmt(rsel)}, {_sl.fmt(csel)}]"
return txt

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function _selection_to_literal has been refactored to use the fmt function from the _slice_op module instead of the previously defined _fmt_slice function. This change should not affect the functionality if the fmt function from _slice_op behaves identically to _fmt_slice.

-        txt = f"[{rsel.start}, {_fmt_slice(csel)}]"
+        txt = f"[{rsel.start}, {_sl.fmt(csel)}]"

self._qtable = weakref.ref(qtable)

def __int__(self) -> int:
return len(self._qtable().getDataFrame())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __int__ method is directly accessing the _qtable weak reference without checking if it's still alive. This could lead to a TypeError if the referenced object has been garbage collected. It would be better to first check if the weak reference is still valid before trying to access its methods.

-        return len(self._qtable().getDataFrame())
+        qtable = self._qtable()
+        if qtable is None:
+            raise ReferenceError("Referenced QMutableTable object has been destroyed")
+        return len(qtable.getDataFrame())

def __repr__(self) -> str:
return str(int(self))

def __index__(self) -> int:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The __index__ method is also directly accessing the _qtable weak reference without checking if it's still alive. This could lead to a TypeError if the referenced object has been garbage collected. It would be better to first check if the weak reference is still valid before trying to access its methods.

-        return self.__int__()
+        qtable = self._qtable()
+        if qtable is None:
+            raise ReferenceError("Referenced QMutableTable object has been destroyed")
+        return len(qtable.getDataFrame())

Comment on lines +6 to +12
def len_1(sl: slice) -> bool:
"""True if the slice is of length 1."""
if sl.start is None:
return sl.stop == 1
elif sl.stop is None:
return False
return sl.stop - sl.start == 1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function len_1 checks if a slice is of length 1. However, it does not consider the step attribute of the slice object. If the step is not None and not equal to 1, the result might be incorrect.

Comment on lines 130 to +135
"""True if the range is empty."""
r0_s, r1_s = self._rsl.start, self._rsl.stop
c0_s, c1_s = self._csl.start, self._csl.stop
if r0_s is None or r1_s is None or c0_s is None or c1_s is None:
return False
return self._rsl.start >= self._rsl.stop or self._csl.start >= self._csl.stop
r0, r1 = self._rsl.start, self._rsl.stop
c0, c1 = self._csl.start, self._csl.stop
r_empty = r0 is not None and r1 is not None and r0 >= r1
c_empty = c0 is not None and c1 is not None and c0 >= c1
return r_empty or c_empty
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for checking if the range is empty has been modified. The new implementation seems to be more efficient as it directly checks if the start is greater than or equal to the stop for both rows and columns. However, ensure that this change doesn't introduce any unexpected behavior when dealing with ranges where the start or stop is None.

Comment on lines 105 to 111

def get_value(self, df: pd.DataFrame) -> tuple[slice, slice, pd.Index]:
rsl, csl = self._selection.value.as_iloc_slices(df)
if csl.start != csl.stop - 1:
if not _sl.len_1(csl):
raise ValueError("Selection must be a single column")
periods = rsl.stop - rsl.start
start, end, freq = self._get_params()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition to check if the column slice (csl) represents a single column has been changed from csl.start != csl.stop - 1 to not _sl.len_1(csl). The new condition uses the len_1() function from the _slice_op module, which checks if the length of the slice is 1. This change makes the code more readable and maintainable by encapsulating the logic for checking the length of a slice into a separate function.

-        if csl.start != csl.stop - 1:
+        if not _sl.len_1(csl):

Comment on lines +340 to +359
sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]])
sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])"
assert sheet.data.iloc[1, 2] == 6
sheet.cell[3, 1] = "4"
assert sheet.data.iloc[1, 2] == 10

# check vector output
sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]])
sheet.cell[1, 2] = "&=np.cumsum(df.iloc[:, 1])"
assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6])
sheet.cell[3, 1] = "4"
assert_equal(sheet.data.iloc[:, 2].values, [1, 3, 6, 10])

def test_N(make_tabulous_viewer):
viewer: TableViewer = make_tabulous_viewer()
sheet = viewer.add_spreadsheet([[0, 1], [0, 2], [0, 3]])
sheet.cell[1, 2] = "&=np.sum(df.iloc[:, 1])/N"
assert sheet.data.iloc[1, 2] == 2
sheet.cell[3, 1] = "4"
assert sheet.data.iloc[1, 2] == 2.5
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test case checks the behavior of scalar and vector outputs when the spreadsheet data is updated. It also tests the use of a special variable N in the expression. The tests seem comprehensive and cover different scenarios. However, it would be beneficial to add comments explaining what each part of the test is checking for better maintainability.

Comment on lines 48 to 57
for container in ax.containers:
# if an artist is in a container, emit the container instead
if artist in container:
self.itemPicked.emit(container)
artist = container
break
if event.mouseevent.dblclick:
self.itemDoubleClicked.emit(artist)
else:
self.itemPicked.emit(artist)
self.itemClicked.emit(artist)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for emitting signals when an artist is picked has changed. Previously, if an artist was in a container, the itemPicked signal would emit the container. Now, the artist itself is emitted instead of the container. This could potentially break any code that expects the container to be emitted when an artist is picked.

Comment on lines 146 to +149

self.figure.tight_layout()
self.figure.canvas.draw()
return None
return super().mouseDoubleClickEvent(event)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The mousePressEvent, mouseReleaseEvent, and mouseDoubleClickEvent methods no longer return None explicitly. Instead, they return the result of calling the superclass's corresponding method. This change should not cause any issues as long as the superclass's methods do not return any unexpected values.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 1

Commits Files that changed from the base of the PR and between a0dd108 and 68f90c6.
Files selected for processing (1)
  • tabulous/_qt/_plot/_mpl_canvas.py (9 hunks)
Additional comments (Suppressed): 5
tabulous/_qt/_plot/_mpl_canvas.py (5)
  • 12-31: The InteractiveFigureCanvas class has been modified to emit different signals based on the type of mouse click event. The itemPicked signal has been replaced with itemClicked and itemDoubleClicked. Ensure that all parts of the codebase that rely on these signals have been updated accordingly.

  • 48-56: The logic for emitting signals when an artist is picked has been changed. Previously, the itemPicked signal was emitted with the container as the argument if the artist was in a container. Now, the artist itself is emitted instead. This could potentially affect how the signal is handled elsewhere in the codebase.

  • 239-246: The calculation for setting the x limits when the x scale is linear has been simplified. The new calculation directly adds and subtracts the change in x (dx) from the old limits. This change should not affect the functionality of the code, but it does make the calculation easier to understand.

  • 252-259: Similar to the changes made for the x limits, the calculation for setting the y limits when the y scale is linear has been simplified. The new calculation directly adds and subtracts the change in y (dy) from the old limits. This change should not affect the functionality of the code, but it does make the calculation easier to understand.

  • 294-305: A new helper function _get_xy_data() has been introduced. This function attempts to transform the mouse event coordinates into data coordinates using the Axes object's transformation. If this fails, it falls back to using the mouse event's data coordinates. This function improves code modularity by encapsulating this logic into a separate function.

Comment on lines 80 to 112
if mouse_event.inaxes:
self.pressed = mouse_event.button
self.last_axis = mouse_event.inaxes
# self.clicked.emit(mouse_event)
return None
return super().mousePressEvent(event)

def mouseMoveEvent(self, event):
"""
Translate axes focus while dragging. If there are subplots, only the subplot in which
cursor exists will be translated.
Translate axes focus while dragging. If there are subplots, only
the subplot in which cursor exists will be translated.
"""
ax = self.last_axis
if (
self.pressed not in (MouseButton.LEFT, MouseButton.RIGHT)
or self.lastx_pressed is None
or not self._interactive
or not ax
):
if self.lastx_pressed is None or not self._interactive or not ax:
return

event = self.get_mouse_event(event)
x, y = event.xdata, event.ydata
xdata, ydata = _get_xy_data(ax, event)

if x is None or y is None:
if xdata is None or ydata is None:
return None

if self.pressed == MouseButton.LEFT:
_translate_x(ax, self.lastx_pressed, x)
_translate_y(ax, self.lasty_pressed, y)
_translate_x(ax, self.lastx_pressed, xdata)
_translate_y(ax, self.lasty_pressed, ydata)
elif self.pressed == MouseButton.RIGHT:
_zoom_x(ax, self.lastx, x)
_zoom_y(ax, self.lasty, y)
_zoom_x(ax, self.lastx, xdata)
_zoom_y(ax, self.lasty, ydata)
xdata, ydata = _get_xy_data(ax, event) # ticks changed!
else:
return None

self.lastx, self.lasty = x, y
self.lastx, self.lasty = xdata, ydata
self.figure.canvas.draw()
return None

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditions for handling mouse move events have been simplified. Previously, the function would return early if the pressed button was not left or right, or if other conditions were not met. Now, it only returns early if self.lastx_pressed is None, self._interactive is False, or ax is None. This change could potentially allow more mouse move events to be processed than before.

-        if (
-            self.pressed not in (MouseButton.LEFT, MouseButton.RIGHT)
-            or self.lastx_pressed is None
-            or not self._interactive
-            or not ax
-        ):
+        if self.lastx_pressed is None or not self._interactive or not ax:
             return

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Status

Actionable comments generated: 0

Commits Files that changed from the base of the PR and between 68f90c6 and c3bec90.
Files selected for processing (5)
  • tabulous/main.py (2 hunks)
  • tabulous/_psygnal/_slots.py (1 hunks)
  • tabulous/_qt/_console/_widget.py (1 hunks)
  • tests/test_cell_ref_eval.py (2 hunks)
  • tests/test_proxy.py (1 hunks)
Files skipped from review due to trivial changes (4)
  • tabulous/main.py
  • tests/test_cell_ref_eval.py
  • tests/test_proxy.py
  • tabulous/_psygnal/_slots.py (diff too large)
Additional comments (Suppressed): 1
tabulous/_qt/_console/_widget.py (1)
  • 142-143: The addition of the line magic %matplotlib inline is a significant change. This magic command configures the integration of Matplotlib with Jupyter (or IPython) such that the output of plotting commands is displayed inline within the notebook, directly below the code cell that produced it. The resulting plots are stored in the notebook document. Ensure that this change doesn't affect any existing functionality or user experience, especially if there are other parts of the codebase that rely on a different matplotlib backend.

@hanjinliu hanjinliu merged commit d05bfa1 into main Sep 23, 2023
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.

1 participant