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

WIP: Graph display #53

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 91 additions & 0 deletions RFC/0000-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# [RFC Template] Title

**Authors:**

- @nickname
- @nickname

## 1 Executive Summary

*A short paragraph or bullet list that quickly explains what you're trying to do.*

## 2 Motivation

*What motivates this proposal, and why is it important?*

*Here, we aim to get comfortable articulating the value of our actions.*

## 3 Proposed Implementation

*This is the core of your proposal, and its purpose is to help you think through the problem because [writing is thinking](https://medium.learningbyshipping.com/writing-is-thinking-an-annotated-twitter-thread-2a75fe07fade).*

*Consider:*

- *using diagrams to help illustrate your ideas.*
- *including code examples if you're proposing an interface or system contract.*
- *linking to project briefs or wireframes that are relevant.*

## 4 Metrics & Dashboards
Copy link
Member

Choose a reason for hiding this comment

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

"Dashboards"? I think measurable metrics is fine, and all we need to capture the product success / scope.


*What are the main metrics we should be measuring? For example, when interacting with an external system, it might be the external system latency. When adding a new table, how fast would it fill up?*

## 5 Drawbacks

*Are there any reasons why we should not do this? Here we aim to evaluate risk and check ourselves.*

## 6 Alternatives

*What are other ways of achieving the same outcome?*

## 7 Potential Impact and Dependencies

*Here, we aim to be mindful of our environment and generate empathy towards others who may be impacted by our decisions.*

- *What other systems or teams are affected by this proposal?*
- *How could this be exploited by malicious attackers?*

## 8 Unresolved questions

*What parts of the proposal are still being defined or not covered by this proposal?*

## 9 Conclusion

*Here, we briefly outline why this is the right decision to make at this time and move forward!*

## 10 RFC Process Guide, remove this section when done

*By writing an RFC, you're giving insight to your team on the direction you're taking. There may not be a right or better decision in many cases, but we will likely learn from it. By authoring, you're making a decision on where you want us to go and are looking for feedback on this direction from your team members, but ultimately the decision is yours.*

This document is a:

- thinking exercise, prototype with words.
- historical record, its value may decrease over time.
- way to broadcast information.
- mechanism to build trust.
- tool to empower.
- communication channel.

This document is not:

- a request for permission.
- the most up to date representation of any process or system

**Checklist:**

- [ ] Copy template
- [ ] Draft RFC (think of it as a wireframe)
- [ ] Share as WIP with folks you trust to gut-check
- [ ] Send pull request when comfortable
- [ ] Label accordingly
- [ ] Assign reviewers (ask your manager if in doubt)
- [ ] Merge yourself with two approved reviews

**Recommendations**

- Tag RFC title with [WIP] if you're still ironing out details.
- Tag RFC title with [newbie] if you're trying out something experimental or you're not entirely convinced of what you're proposing.
- Tag RFC title with [SWARCH] if you'd like to schedule a SWARCH review to discuss the RFC.
- If there are areas that you're not convinced on, tag people who you consider may know about this and ask for their input.
- If you have doubts, ask your manager for help moving something forward.
- As the author/s, this is _your decision_. You are empowered to choose to move forward despite dissenting comments. We're not looking for consensus-driven decision-making.
- The success of the implementation of your proposal depends on how this decision relates to our company's objectives and priorities.
241 changes: 241 additions & 0 deletions RFC/0001-graph-display.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
# WIP: Graph Display Architecture for QML

**Authors:**

- Adam Washington

## 1 Executive Summary

This document specifies an architecture for a graph display library
for the Dissolve QML GUI and requests discussion and suggestions for
improvement.

## 2 Motivation

Our current system for creating layers and generators has multiple issues with the user experience:

- Modules are presented in a linear list for what might be a highly
parallel process.
- It is only possible to examine one module at a time.
- Values shared between nodes are contained in a keyword system that
can be both opaque (hard to determine keyword values) and brittle
(long but similar keyword names that must be typed exactly).
- It is difficult for users to create complicated layers without
outside help.
Comment on lines +23 to +24
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- It is difficult for users to create complicated layers without
outside help.
- It is impossible to create complex workflows in the current linear layout, hindering the development and use of Dissolve for advanced scientific analysis.

- There is no method of gaining a high level overview of the
activities of a layer. Each module much be read in sequence and a
mental modal of the entire, possibly non-linear, process must be
constructed from this.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
- Usage relationships between data / objects are "weak", leading to significant data-management within the code in order to maintain a valid state.

A graphical display of a connected nodes provides an alternative that
ameliorates most of these issues.

- All modules can be seen at once with the relations between modules
explicitly declared by the connection.
- All modules and their values are present on the workspace at the same time.
- Values are shared by drawing connections between modules instead of
variables from a global namespace.
- The graphical interface provides an intuitive framework for treating
the modules as individual building blocks.
- One glance at the workflow gives an overview of all of the modules
and their relations

Copy link
Member

Choose a reason for hiding this comment

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

  • Links between data / objects is well-defined and strong, removing data management issues almost completely


## 3 Proposed Implementation

The base of the implementation is an `AbstractGraphModel` class.

```mermaid
classDiagram
class AbstractGraphModel {
+nodes() : AbstractListModel~NodeInterface~
+edges() : AbstractListModel~EdgeInterface~
#createNode(string)
#deleteNode(int)
#createEdge(int, int, int, int)
#deleteEdge(int)
}
<<Abstract>> AbstractGraphModel
class NodeInterface {
+ x : float
+ y : float
+ name: String
+ icon: Icon
+ type: String
}
<<Interface>> NodeInterface
class EdgeInterface {
+ startHandle : Point2D
+ start : Point2D
+ end : Point2D
+ endHandle : Point2D
+ style : int
}
<<Interface>> EdgeInterface
AbstractGraphModel *-- NodeInterface
AbstractGraphModel *-- EdgeInterface
```

### 3.1 AbstractGraphModel

The `AbstractGraphModel` defines the interface to our graph. The
internal representation of the graph is both beyond the scope of this
document[^1] and independent of the behaviour of the library. All
that matters is that the class which represents the graph contains two
Qt properties. The first property is `nodes`, which returns an object
that is a subclass of `AbstractListModel` and each node returned
implements the `NodeInterface` in the Qt properties. The other
property, `edges`, is similar, except that the mandatory interface is
the `EdgeInterface`.

The `AbstractGraphModel` also requires four slots for accepting
signals from the interface. The `createNode` slot takes a string that
corresponds to the `type` property of the Node Interface and appends a
new Node of the correct type. Correspondingly, `deleteNode` takes the
index of a node and removes that node from the model. In the same
Copy link
Member

Choose a reason for hiding this comment

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

I think it would be wise to continue demanding unique names for nodes / modules, so we don't need to access by index and could use the object name instead.

manner, `deleteEdge` takes an edge index and removes the corresponding
edge. Finally, the `createEdge` slot requires four indices,
Copy link
Member

Choose a reason for hiding this comment

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

Again, since we will still use named keywords we could use named connections to data instead of indexing which feels more robust.

corresponding to the index of the source node, the index of the output
within that source node, the index of the destination node, and the
index of the input within that destination.

[^1]: Although the author does have some strong, possibly
contraversial opinions.

### 3.2 NodeInterface

Each node must implement five properties. The first two, `x` and `y`,
define the position of the node on the screen. The `name` and `icon`
provide the label and image for the top of the node. Finally, the
`type` describe what kind of information is contained in the node.
The repeater delegate can use a
[DelegateChooser](https://doc.qt.io/qt-6/qml-qt-labs-qmlmodels-delegatechooser.html)
to select the correct QML file to display the node after dispatching
on the `type`.

### 3.3 EdgeInterface

Each edge implements five properties to do describe the spline
connecting the nodes. The `start` and `end` properties describe the
exact starting and ending positions of the spline. The `startHandle`
and `endHandle` are the positions of the spline handels for the
corresponding endpoints. Finally, the `style` gives an index of the
type of line being drawn. For example, curves containing `int` values
might have an index of 1 and `string` values an index of 2. This
provides the View with the information to display different edges
while still deferring to the View about the exact display
(e.g. allowing different colours to be chosen in a dark mode).

### 3.4 Implementation

In the actual QML, we will create a `GraphNodeView` that takes two
parameters. First first parameter is the `AbstractGraphModel` that
contains the graph information. The second is a delegate that accepts
the `NodeInterface` and returns a group of widgets. This delegate
will *not* be responsible for adding the title bar to the node or
drawing the node border.

The implementation of this QML will be two `Repeaters`. The first
will iterate over the `nodes`, create the header and border, and use
the provided delegate to populate the nodes. The second will iterate
over the `edges` and draw the splines.


## 4 Metrics & Dashboards

The final implementation will need to provide the following abilities:

- Add new nodes of any type
- Drag nodes to a new position
- Draw connections between nodes with the mouse
- Delete connections
- Auto-arrange nodes
- Display the nodes at multiple zoom levels
- Scroll around the node environment

None of these actions should leave the graph in an invalid state.

## 5 Drawbacks

- This project provides a significant time sink for at least one developer.

- Even once the general library is written, more developer time will
be needed to write the Dissolve specific uses of the library


## 6 Alternatives

[Qt Node Editor](https://github.com/paceholder/nodeeditor) does not
natively support QML and thus will require significant work to provide
a consistent experience with the rest of the application.

[QuickQanava](https://github.com/cneben/QuickQanava?tab=readme-ov-file)
requires the construction and maintenance of an explicit graph object,
as opposed to providing an abstract interface that we can build our
own model again. Additionally, at the time of this writing,
QuickQanava does not build on Mac or Linux

## 7 Potential Impact and Dependencies

This could have a significant impact on the structure of the input
file, since it represents a major change in the state of the data.
Furthermore, this may be a significant break change, as it would not
be a trivial matter to convert existing, linear code into the new
Copy link
Member

Choose a reason for hiding this comment

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

This work would really represent a huge change - Dissolve 2 - and one which would break basically every existing input file. It might be possible to write some kind of importer for 1.X TOML input, but it may be difficult and perhaps not worth the effort. I would be happy to accept the fact that Dissolve 2 is a new starting point for everyone's simulations. One might argue there is little value to the user in importing an old simulation, since they will need to get used to the new workflows we'll be presenting.

graph based setup.

## 8 Unresolved questions

### 8.1 Invalid Graphs

Users might attempt to connect nodes in incompatible way
(e.g. connecting a string to a value expecting an int) or fail to
provide a node with all of the information that it requires. More
generally, the system needs to deal with the possibility of invalid
graphs.

This first method is to simply forbid invalid graphs. The model
should reject any attempt to create invalid edges or delete nodes that
are required. This had advantages in design and implementation, but
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
are required. This had advantages in design and implementation, but
are required. This has advantages in design and implementation, but

places limits on what can provide a good user experience. For
example, every possible node input must have a **valid** default
value, so that the connection to that input can be deleted.
Comment on lines +199 to +201
Copy link
Member

Choose a reason for hiding this comment

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

I think that strictly disallowing connections between dissimilar input / output types is the best route to go - something like Blender will allow you to connect related type inputs/outputs and still yield sensible results, but I don't think we need that softness here. Regarding the need for default values, this is what we currently have with keywords in modules and nodes, so not a problem moving forward.


The second method is to allow invalid graphs, but refuse to run them.
This provides more flexibility in the user interface, but also
provides the challenge of informing the user about invalid graphs. A
user might edit a graph, then switch to a different tab, come back ten
hours later and be very confused why their simulation will not run.
This becomes especially perilous in the case of saving files. Should
it possible to save an input file with an invalid graph? If it is,
then the user has been handed a tool to create files that break the
CLI version of Dissolve. If saving invalid graphs is forbidden, then
a user is stuck unable to save hours of work until they have fixed the
problem with their graph.

### 8.2 Underlying iteration model


Do we truly want an `AbstractListModel` for the `edges` and `nodes`?
Copy link
Member

Choose a reason for hiding this comment

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

Probably we do, at least based on how I'm understanding your proposal here as it stands. Creating this as a light framework feels like the best option, with the specific functionality we need only.

It would also be possible to use `AbtractTableModel` or
`AbstractItemModel`. I chose `AbstractListModel` since it is the
easier to subclass, but all of these models have an element of
ordering that we currently do not care about. If we do care in the
future, it might be better to have used `AbstractListModel`, but that
Copy link
Member

Choose a reason for hiding this comment

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

I guess you meant either AbstractTableModel or AbstractItemModel here?

also might be adding a bunch of complication now for functionality
that we will never use.

### 8.3 Raw spline coordinates

Should the `EdgeInterface` truly provide raw coordinates? An
alternative implementation would be to simply provide a source and
destination and leave the drawing up to the view. It would also be a
more accurate representation of the data that we have. The
disadvantage is finding a valid representation of the source and
destination that can be passed in the interface. This is especially
relevant since we are not merely connecting nodes together, but
individual components of nodes, which might each have multiple inputs
and outputs.

## 9 Conclusion

N/A