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

Plotting refactor #2940

Draft
wants to merge 52 commits into
base: main
Choose a base branch
from
Draft

Plotting refactor #2940

wants to merge 52 commits into from

Conversation

juliuskarliczek
Copy link
Member

Description

This is the draft pull request for the branch that my plotting refactoring progress is saved on. subfolder 'plotting_refactor' contains all the elements that are used for the demo right now.
Starting the demo can be simply done by executing MainWindow.py with a sasview python environment (package dependencies should be the same)

Fixes # (issue/issues)

How Has This Been Tested?

--

Review Checklist:

Documentation (check at least one)

  • There is nothing that needs documenting
  • Documentation changes are in this PR
  • There is an issue open for the documentation (link?)
    This defenitely needs some documenting, although the code is not documented so well in the current state. I will add this in the future when more comfortable with the state of the demo.

Licencing (untick if necessary)

  • The introduced changes comply with SasView license (BSD 3-Clause)

…t has to be modified afterwards beyond the if statements, because 2d plotting is different from 1d plotting, as a 2d plot cannot show multiple curves, cannot accept linestyles
… accept the modifier or the data will indicate a drop action
@juliuskarliczek juliuskarliczek self-assigned this Jul 4, 2024
@juliuskarliczek
Copy link
Member Author

mockup 1
mockup1 2
mockup2

These mockups are what was discussed for displaying 2d data

Copy link
Contributor

@lucas-wilkins lucas-wilkins left a comment

Choose a reason for hiding this comment

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

Mostly pretty good, could do with a few more docstrings and using some of the newer python capabilities (newer typing, generics), put types on parameters, don't commit generated code, look at iteration over collections using for loops and at enumerate and zip.

James is quite impressed given you're not a CS student

#class to keep track of all datasets of fitpages and more
import RandomDatasetCreator
from Dataset import Dataset
from typing import List
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't need to import List anymore in newer pythons


class DataCollector:
def __init__(self):
self.datasets: List[Dataset] = []
Copy link
Contributor

Choose a reason for hiding this comment

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

list[Dataset]

self.datasets: List[Dataset] = []
self.datasetcreator = RandomDatasetCreator.DatasetCreator()

def update_dataset(self, main_window, fitpage_index, create_fit, checked_2d):
Copy link
Contributor

Choose a reason for hiding this comment

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

what are the types of these?

def update_dataset(self, main_window, fitpage_index, create_fit, checked_2d):
# search for an existing dataset with the right fitpage_index
existing_dataset_index = -1
for i in range(len(self.datasets)):
Copy link
Contributor

Choose a reason for hiding this comment

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

i, dataset in enumerate(self.datasets)

for i in range(len(self.datasets)):
if self.datasets[i].get_fitpage_index() == fitpage_index:
existing_dataset_index = i
if existing_dataset_index == -1:
Copy link
Contributor

Choose a reason for hiding this comment

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

add new line between blocks, if you like

def get_fitpage_index(self):
return self.fitpage_index

def get_x_data(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

else:
print("no integer")

def set_x_data(self, x_data):
Copy link
Contributor

Choose a reason for hiding this comment

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

@x_data.setter

@@ -0,0 +1,67 @@
# Form implementation generated from reading ui file 'FitPageUI.ui'
Copy link
Contributor

Choose a reason for hiding this comment

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

don't commit this

from scipy import special


class DatasetCreator:
Copy link
Contributor

Choose a reason for hiding this comment

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

docstring

print(a.shape)
print(b.shape)
print(c.shape)
'''
Copy link
Contributor

Choose a reason for hiding this comment

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

remove, unless you're still using it.

Copy link
Contributor

@krzywon krzywon left a comment

Choose a reason for hiding this comment

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

I've looked at DataCollector.py through MainWindow.py in my first round of code review. I will take a look at the other half next week.

def get_datasets(self) -> List:
return self.datasets

def get_data_fp(self, fitpage_index) -> Dataset:
Copy link
Contributor

Choose a reason for hiding this comment

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

This and the next 6 methods are almost exactly the same and could probably be abstracted a bit. Also, the name for your first method suggests you are getting the fitpage, but you are getting the data. get_data_by_fp is more syntactically correct.

An untested catch-all that could be a function:

def get_prop_from_object(obj, prop):
    for k, v for enumerate(obj):
        if hasattr(v, prop) and v.prop == prop:
            return v


return x_data, y_data, y_fit

def get_datasets(self) -> List:
Copy link
Contributor

Choose a reason for hiding this comment

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

Getters and setters are very Java-esque. Invoke self.datasets instead, but this gives full access to the outside.

_datasets could be a private class variable instead, and then, optionally, datasets could be a property of the class to allow other logic to happen when getting/setting the value.

Comment on lines 14 to 16
for i in range(len(self.datasets)):
if self.datasets[i].get_fitpage_index() == fitpage_index:
existing_dataset_index = i
Copy link
Contributor

Choose a reason for hiding this comment

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

Another place where you are iterating through all datasets looking for a particular item. Something as simple as holding all data in a dictionary that is generated as data is added would simplify and likely speed up the processing of all of these.

self.datasets[existing_dataset_index].set_y_fit(y_fit)
self.datasets[existing_dataset_index].set_2d(checked_2d)

def simulate_data(self, main_window, fitpage_index, create_fit, checked_2d):
Copy link
Contributor

Choose a reason for hiding this comment

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

This collector looks more like a controller than a view, but you are treating it as both in this case. I would suggest you pass in the parameters (scale, radius, height, etc.) to keep a clean separation from data calculations and GUI interactions.

def get_data_id(self):
return self.data_id

class DataItem(PlotPageItem):
Copy link
Contributor

Choose a reason for hiding this comment

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

These do not need to inherit from one another. The DataItem instances should be held within a PlotPageItem instead.

Comment on lines 75 to 76
if isinstance(self.plotTreeWidget.topLevelItem(i), TabItem):
if fitpage_index == self.plotTreeWidget.topLevelItem(i).data(0, 1).get_fitpage_index():
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be all on one line. Python evaluates each part of an if statement individually and from left to right, so there will be no cost or increase in errors by combining these.

if isinstance(self.plotTreeWidget.topLevelItem(i), TabItem) and fitpage_index == self.plotTreeWidget.topLevelItem(i).data(0, 1).get_fitpage_index():

Copy link
Contributor

Choose a reason for hiding this comment

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

Our .gitignore usually manages the auto-generated Python files, but that is only when they are subpackages into UI directories.

Note - keep these in the repo for now, because they help people demo the package without any extra work, but the auto-gen files should not be included in the repo once this has been integrated into src/sas.

self.x_data = x_data
self.y_data = y_data
self.y_fit = y_fit
self.plotpage_index = plotpage_index
Copy link
Contributor

Choose a reason for hiding this comment

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

As discussed at the call today and tangentially mentioned in other comments, the data should have no knowledge of the plots, and the plots should be a view of the data. plotpage_index would be better served elsewhere.

Comment on lines 45 to 48
if isinstance(plotpage_index, int):
self.plotpage_index = plotpage_index
else:
print("no integer")
Copy link
Contributor

Choose a reason for hiding this comment

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

Your check will reject floats and string representations of integers. Values that cannot be coerced to ints will throw a ValueError.

Suggested change
if isinstance(plotpage_index, int):
self.plotpage_index = plotpage_index
else:
print("no integer")
try:
self.plotpage_index = int(plotpage_index)
except ValueError:
print("no integer")

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it should be accessible as a string. If you use a type annotation, it would be then safe to assume that the input would be an int (obviously, someone could pass it an int, but if things go wrong, that's their fault)

Comment on lines 59 to 65
sys.excepthook = excepthook
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()

ret = app.exec()
sys.exit(ret)
Copy link
Contributor

Choose a reason for hiding this comment

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

Your implementation will always run if MainWindow is imported in any way, as well as when the file it is directly called from python. Wrapping this in a function will eliminate running on import but still give you the ability to access the function from outside, if desired.

Suggested change
sys.excepthook = excepthook
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
ret = app.exec()
sys.exit(ret)
def main():
sys.excepthook = excepthook
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
ret = app.exec()
sys.exit(ret)
if __name__ == '__main__':
main()

return self.checkBox2dData.isChecked()

def index_changed(self, selected_item):
if selected_item == 0:
Copy link
Contributor

Choose a reason for hiding this comment

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

My only other comment: Python uses 'truthiness' of objects. 0 is false and 1 is true, so, unless there is a chance set_item could be something other than 0 or 1, this if/else could simplify to self.doubleSpinBox_height.setDisabled(not selected_item).


return None

def get_data_by_fp(self, fitpage_index: int) -> Dataset:
Copy link
Contributor

Choose a reason for hiding this comment

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

A better name would just be data_by_id

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