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

Basic DataView Implementation #543

Merged
merged 52 commits into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
6fca300
First cut a data_view that can display successfully.
corranwebster Jun 1, 2020
70ba5f0
Add a column-based data model and corresponding example.
corranwebster Jun 4, 2020
fa3c892
Add missing file.
corranwebster Jun 4, 2020
3bfaaaa
Target code for basic data view implementation.
corranwebster Jun 16, 2020
02c7af7
Update abstract data model docstrings.
corranwebster Jun 16, 2020
5f1de11
Lots more docstrings, some additional clean-up and API clarification.
corranwebster Jun 16, 2020
5b4b43a
More and better tests.
corranwebster Jun 16, 2020
15554b2
Fix up wx widget scaffolding.
corranwebster Jun 17, 2020
f06bd98
Add documentation for the DataModel with an example.
corranwebster Jun 17, 2020
bc36909
Fix references.
corranwebster Jun 17, 2020
cce14b1
Move the column data model to the examples, clean up example.
corranwebster Jun 18, 2020
a561645
Add copyright headers.
corranwebster Jun 18, 2020
45fd7ed
Add tests for remaining value type classes.
corranwebster Jun 18, 2020
9208713
Apply suggestions from code review
corranwebster Jun 22, 2020
597f869
Updates and fixes from PR review.
corranwebster Jun 23, 2020
23b852d
Add some more tests, improve a comment.
corranwebster Jun 23, 2020
8a9f49e
Get a smoke test working for the DataViewWidget class.
corranwebster Jun 23, 2020
8e70092
More clean-up of the toolkit implementations.
corranwebster Jun 23, 2020
d50d7ef
Apply suggestions from code review
corranwebster Jun 28, 2020
4792f66
Fixes based in PR suggestions.
corranwebster Jun 30, 2020
6f2d37f
Fix flake8 issue.
corranwebster Jun 30, 2020
eabf7f5
Convert from lists for indices to tuples.
corranwebster Jul 1, 2020
483db31
Fix bug in set method.
corranwebster Jul 1, 2020
7e8ba1e
Make column count constant, plus some small fixes.
corranwebster Jul 3, 2020
a96ac32
Release model when destroying widgets.
corranwebster Jul 7, 2020
1cc2caf
Fix docstrings.
corranwebster Jul 7, 2020
3defcee
Rename "editable" -> "editor_value".
corranwebster Jul 7, 2020
e9d680d
And "editable" -> "editro_value" in other files.
corranwebster Jul 7, 2020
9be4d98
Improve TextValue and NumericValues.
corranwebster Jul 7, 2020
8341a2d
Fix spelling.
corranwebster Jul 7, 2020
277a966
Instantiate exceptions.
corranwebster Jul 7, 2020
a02a48b
Move to using exceptions to handle failure to set values.
corranwebster Jul 7, 2020
4929afe
Fix iteration over columns.
corranwebster Jul 7, 2020
4b0efee
Use more descriptive metadata for updating value types.
corranwebster Jul 7, 2020
7d12b2a
Don't try to guess value type for array model, ensure 2d arrays.
corranwebster Jul 7, 2020
262bc5b
Add a test for setting 1d data.
corranwebster Jul 7, 2020
fde28c2
And test default data is 2d.
corranwebster Jul 7, 2020
62a38c7
Improve tests of empty data.
corranwebster Jul 7, 2020
2aa4c11
Assorted fixes from PR review.
corranwebster Jul 7, 2020
25009ef
Dispatch model changes on the ui thread.
corranwebster Jul 7, 2020
e3d625f
Cleanup of confusing parentheses.
corranwebster Jul 7, 2020
bb5c380
Improve documentation for data view.
corranwebster Jul 8, 2020
aab3854
Minro fixes and improvements to documentation.
corranwebster Jul 8, 2020
b4ccec7
Remove ui dispatch from pure data models.
corranwebster Jul 8, 2020
f382d71
Docstring fixes.
corranwebster Jul 8, 2020
65a1ebe
More fixes from PR review.
corranwebster Jul 8, 2020
4b0f75a
Change the way that we handle low-dimension arrays.
corranwebster Jul 10, 2020
89748c7
Handle unusual life-cycle issues.
corranwebster Jul 10, 2020
b559ac5
Remove extraneous file commited.
corranwebster Jul 13, 2020
b92e062
Apply suggestions from code review
corranwebster Jul 14, 2020
83a80ca
various fixes and improvements from review.
corranwebster Jul 14, 2020
516cc22
Add missing import.
corranwebster Jul 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions docs/source/data_view.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
Pyface DataViews
=================

The Pyface DataView API allows visualization of hierarchical and
non-hierarchical tabular data.

Data Models
-----------

Data to be viewed needs to be exposed to the DataView infrastructure by
creating a data model for it. This is a class that implements the
interface of |AbstractDataModel|.

A data model for a dictionary could be implemented like this::

class DictDataModel(AbstractDataModel):

data = Dict

index_manager = Instance(IntIndexManager, ())

The index manager is an ``IntIndexManager`` because the data is
non-hierarchical and this is more memory-efficient than the alternative
``TupleIndexManager``.

Data Structure
~~~~~~~~~~~~~~

The keys are displayed in the row headers, so each row has one column
displaying the value::

def get_column_count(self):
return 1

The data is non-hierarchical, so the root has children and no other
rows have children::

def can_have_children(self, row):
if len(row) == 0:
return True
return False

The number of child rows of the root is the length of the dictionary::

def get_row_count(self, row):
if len(row) == 0:
return len(self.data)
return 0
kitchoi marked this conversation as resolved.
Show resolved Hide resolved

Data Values
~~~~~~~~~~~

To get the values of the data model, we need to find the apprpriate value
from the dictionary::

keys_header = Str("Keys")
values_header = Str("Values")

def get_value(self, row, column):
if len(row) == 0:
# this is a column header
if len(column) == 0:
# title of the row headers
return self.keys_header
else:
return self.values_header
else:
row_index = row[0]
key, value = list(self.data.items())[row_index]
if len(column) == 0:
# the is a row header, so get the key
return key
else:
return value

In this case, all of the values are text, and read-only, so we can have a
trait holding the value type, and return that for every item::

header_value_type = Instance(AbstractValueType)
key_value_type = Instance(AbstractValueType)
value_type = Instance(AbstractValueType)

def _default_header_value_type(self):
return TextValue(is_editable=False)

def _default_key_value_type(self):
return TextValue(is_editable=False)

def _default_value_type(self):
return TextValue(is_editable=False)

def get_value_type(self, row, column):
if len(row) == 0:
return self.header_value_type
elif len(column) == 0:
return self.key_value_type
else:
return self.value_type

The default assumes that values representable as text, but if the values were
ints, for example then the class could be instantiated with::

model = DictDataModel(value_type=IntValue(is_editable=False))

The ``is_editable`` arguments are not strictly needed, as the default
implementation of |can_set_value| returns False, but if we wanted to make the
data model read-write we would need to write an implementation of
|can_set_value| which returns True for editable items, and an implementation
of |set_value| that updates the data in-place. This would look something like::

def can_set_value(self, row, column):
return len(row) != 0 and len(column) != 0:

def set_value(self, row, column, value):
if self.can_set_value(row, column):
row_index = row[0]
key = list(self.data)[row_index]
self.data[key] = value
return True
return False

Update Events
-------------

Finally, when the underlying data changes, the DataView infrastructure expects
certain event traits to be fired. If a value is changed, or the value type is
updated, but the number of rows and columns is unaffected, then the
``values_changed`` trait should be fired with a tuple of ``(start_row_index,
start_column_index, end_row_index, end_column_index)``. If a major change has
occurred, or if the size, shape or layout of the data has changed, then
the ``structure_changed`` event should be fired with a simple ``True`` value.

So for example, if the value types change, only the displayed values need to be
updated::

@observe('header_value_type.updated')
def header_values_updated(self, event):
self.values_changed = ([], [], [], [0])

@observe('key_value_type.updated')
def key_values_updated(self, event):
self.values_changed = ([0], [], [len(self.data) - 1], [])

@observe('value_type.updated')
def values_updated(self, event):
self.values_changed = ([0], [0], [len(self.data) - 1], [0])

On the other hand, if the dictionary or its items change, then it is simplest
to just indicate that the entire view needs updating::

@observe('data.items')
Copy link
Contributor

Choose a reason for hiding this comment

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

Related to another comment as for whether dispatch="ui" is needed. The above @observe might require dispatch="ui".

def data_updated(self, event):
self.structure_changed = True



.. |AbstractDataModel| replace:: :py:class:`~pyface.data_view.abstract_data_model.AbstractDataModel`
.. |can_set_value| replace:: :py:class:`~pyface.data_view.abstract_data_model.AbstractDataModel.can_set_value`
.. |set_value| replace:: :py:class:`~pyface.data_view.abstract_data_model.AbstractDataModel.set_value`
59 changes: 59 additions & 0 deletions examples/data_view/array_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# (C) Copyright 2005-2020 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

from traits.api import Array, Instance

from pyface.api import ApplicationWindow, GUI
from pyface.data_view.data_models.array_data_model import ArrayDataModel
from pyface.data_view.i_data_view_widget import IDataViewWidget
from pyface.data_view.data_view_widget import DataViewWidget
from pyface.data_view.value_types.api import FloatValue


class MainWindow(ApplicationWindow):
""" The main application window. """

data = Array

data_view = Instance(IDataViewWidget)

def _create_contents(self, parent):
""" Creates the left hand side or top depending on the style. """

self.data_view = DataViewWidget(
parent=parent,
data_model=ArrayDataModel(
data=self.data,
value_type=FloatValue(),
),
)
self.data_view._create()
return self.data_view.control

def _data_default(self):
import numpy
return numpy.random.uniform(size=(10000, 10, 10))*1000000

def destroy(self):
self.data_view.destroy()
super().destroy()


# Application entry point.
if __name__ == "__main__":
# Create the GUI (this does NOT start the GUI event loop).
gui = GUI()

# Create and open the main window.
window = MainWindow()
window.open()

# Start the GUI event loop!
gui.start_event_loop()
Loading