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

New: Add state setters with options #95

Merged

Conversation

AlexIchenskiy
Copy link

Resolves #68

As an enhancement to the freshly implemented getters and setters, this PR introduces a new way to set the state of nodes and edges using objects that specify state with additional parameters such as isSingle and isToggle. Now, it is possible to use both the regular setter:

node.setState(state)

and the new setter:

node.setState({ state: GraphObjectState.SELECTED, options: { isSingle: true } })
edge.setState({ state: GraphObjectState.SELECTED, options: { isSingle: false, isToggle: true } })

and so on. This provides more customization flexibility to the user, with default values for these parameters set to false. It should be easily extendable for adding even more options.

@AlexIchenskiy AlexIchenskiy self-assigned this Mar 11, 2024
if (data.type === 'node' && data.options.isSingle) {
const nodes = this._nodes.getAll();

for (let i = 0; i < nodes.length; i++) {
Copy link
Author

Choose a reason for hiding this comment

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

I feel like these iterations through all nodes/edges can be slow. Any ideas? And do we want to clear the state of all nodes/edges or only the adjusting ones?

Copy link
Contributor

@cizl cizl Mar 12, 2024

Choose a reason for hiding this comment

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

How about using hash maps? Obviously, this would require a lot of code change. Just starting a discussion, I don't think it's doable right now.

For the second question, I would say only the ones that are being adjusted.

Copy link
Contributor

@cizl cizl Mar 12, 2024

Choose a reason for hiding this comment

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

Or maybe to supplement this nodes array we can keep an index where nodeIndex[node.id] === nodes.findIndex((el) => el.id === node.id)

Copy link
Author

Choose a reason for hiding this comment

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

Are you suggesting storing nodes in a hashmap? As far as I know, retrieving elements from an array by a known index is O(1) complexity, so iterating through an array vs a hashmap (which also has O(1) complexity for retrieving element by key) may not make a big difference here. I might have misunderstood your point, or perhaps I missed something, so feel free to clarify it further :)

Copy link
Contributor

Choose a reason for hiding this comment

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

If all nodes are in state X, and you want to clear them up if they are not node with ID 1, you still need to to N - 1 updates. So hash maps or any other structure won't help there, you already have the fastest structure, an array.

But, you can do this and increase the memory footprint: I am not sure if it makes sense to go into this right now, but this would be the plan:

  • Graph keeps a structure of nodes/edges by state, so each state has a structure of nodes and edges (probably just id) - a set of a map.
  • States in nodes and edges are just views, readonly
  • When state is updated in node, edge it is just updated in the graph

This way, if you have isSingle: true, graph, as an owner of the data can then literally take all selected nodes from a set or a map, clear those states and set the new node as single selected. This would improve the speed, but it would worse the memory footprint and code simplitiy because owner of the state becomes the graph, not graph objects (node/edge).

I would keep this as is and fix only if we see that it hurts performance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I misunderstood the functionality. I saw === instead of !==. I thought you were trying to clear the state of anode with a specific id.

Comment on lines 428 to 432
if (options && options.isToggle) {
this._toggleState(result.state);
} else {
this._state = result.state;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Either have this._state = <function that handles options in general>(results.state) where function doesn't mutate the state, but returns what the new state should be, or keep this as is, but still change the toggleState to not mutate the this._state because I don't see a reason why it should. The best functions are the clean ones, which do not mutate state (if it is possible to make them).

Comment on lines 423 to 426
if (typeof result === 'number') {
this._state = result;
} else if (typeof result === 'object') {
const options = result.options;
Copy link
Contributor

Choose a reason for hiding this comment

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

For these checks, please use utils/type.utils.ts because of:

  • cleaner code, isNumber vs `typeof x === 'number'
  • handles edge cases, isPlainObject vs typeof result === 'object' (edge case is: typeof null will return 'object').

result = arg;
}

if (typeof result === 'number') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comments as for edges.

Comment on lines 10 to 13
export interface IGraphObjectStateOptions {
isToggle?: boolean;
isSingle?: boolean;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In these cases, when it is options, it would be best not to have optionals here, but where you use it, line 17: Partial<IGraphObjectStateOptions> where options can also be optional, e.g. options?: Partial<...>.

If you move external logic (having optional params) too deep into the type, it is harder to handle that in some other cases. E.g. if you wish to create default params, you could then just say: const default: IGraphObjectStateOptions = { ... } and you would be forced to define both. If you would use options from Parameters you would see that this object is optional here with all its children. The context of when is something optional, when it isn't is being kept.

if (data.type === 'node' && data.options.isSingle) {
const nodes = this._nodes.getAll();

for (let i = 0; i < nodes.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If all nodes are in state X, and you want to clear them up if they are not node with ID 1, you still need to to N - 1 updates. So hash maps or any other structure won't help there, you already have the fastest structure, an array.

But, you can do this and increase the memory footprint: I am not sure if it makes sense to go into this right now, but this would be the plan:

  • Graph keeps a structure of nodes/edges by state, so each state has a structure of nodes and edges (probably just id) - a set of a map.
  • States in nodes and edges are just views, readonly
  • When state is updated in node, edge it is just updated in the graph

This way, if you have isSingle: true, graph, as an owner of the data can then literally take all selected nodes from a set or a map, clear those states and set the new node as single selected. This would improve the speed, but it would worse the memory footprint and code simplitiy because owner of the state becomes the graph, not graph objects (node/edge).

I would keep this as is and fix only if we see that it hurts performance.

Comment on lines +381 to +382
if (data && 'type' in data && 'options' in data && 'isSingle' in data.options) {
if (data.type === 'node' && data.options.isSingle) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you maybe simplify this to if (data && data.options?.isSingle && data.type === 'node')?

Copy link
Author

Choose a reason for hiding this comment

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

I believe it could be possible, but only after reworking IObserverDataPayload into a more generic solution as you mentioned here. Currently, I am using some sort of custom type guard because of the interfaces used in that payload

@AlexIchenskiy AlexIchenskiy added this to the v1.0.0 milestone Mar 18, 2024
this.notifyListeners();
}

private _handleState(state: number, options?: Partial<IGraphObjectStateOptions>): number {
Copy link
Contributor

Choose a reason for hiding this comment

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

It is nitpicking really, but maybe getNextState would be a better name.

@AlexIchenskiy AlexIchenskiy merged commit c9de1cf into new/add-setters-and-getters Mar 20, 2024
1 check passed
@AlexIchenskiy AlexIchenskiy deleted the new/add-state-setters-with-options branch March 20, 2024 14:24
AlexIchenskiy added a commit that referenced this pull request Mar 27, 2024
* New: Add node properties setters

* New: Add edge properties setters

* New: Add properties getters

* New: Add patch for nodes and edges

* Fix: Make getters return copies

* Fix: Edge factory listeners copying

* Fix: Jest outdated tests

* Fix: Github actions node version

* Chore: Refactor observer interface

* Chore: Refactor node/edge constructor settings

* Chore: Refactor node/edge function grouping

* Chore: Refactor node/edge function grouping

* Fix: Listeners behaviour

* Chore: Refactor property copying

* Chore: Refactor subject implementation

* Fix: Set position behaviour on node drag

* Chore: Upgrade node version

* Chore: Refactor function type check

* Fix: Remove listener behaviour

* Chore: Refactor util naming

* Chore: Remove unused type assertion

* Chore: Refactor position setter options

* Chore: Refactor property patch function

* Fix: Set map node position behaviour

* Chore: Refactor simulator data patching

* Chore: Change observers to callbacks

* New: Add state setters with options (#95)

* New: Add state setters with options

* Chore: Remove leftover comments

* Chore: Refactor state setter logic

* Chore: Refactor state types

* Fix: Rename merged function usage

* Fix: Merged variable naming

* Chore: Fix tests
AlexIchenskiy added a commit that referenced this pull request Mar 27, 2024
* New: Add new simulator (#56)

* New: Add simulator scenarios for manual testing
* New: Refactor simulator (WIP)
* New: Add progress overlay, Update descriptions
* Fix: Introduce new simulator event, Fix main-thread behavior
* Fix: Rearrange class methods based on visibility
* Fix: Improve naming
* Chore(release): 0.2.0
* Fix: Tweak simulator, adjust API slightly
* Fix: Temporarily patch some physics behavior
* Fix: Adjust re-heating parameters
* Fix: tweak physics behavior -> immediately stop sim when disabling
* Chore: Remove the beta from release branches

---------

Co-authored-by: dlozic <davidlozic@gmail.com>

* New: Add zoom and recenter functions (#74)

* New: Add zoom and recenter functions

* Fix: Reduce excessive recentering (#75)

* Fix: Remove excessive recenterings
* Fix: Remove unused code

* Fix: New simulator (#92)

* Chore: Refactor naming

* New: Add new events

* Chore: Refactor code styling

* Docs: Remove unused flags

* Chore: Remove unused simulation functions

* Chore: Refactor view render function calls

* Chore: Add missing tests

* New: Add removal functions (#96)

* New: Add removal functions

* Fix: Add missing callback data

* Chore: Refactor remove return values

* Chore: Refactor remove function type usage

* Fix: Default settings for node placement (#98)

* New: Add properties setters and getters (#93)

* New: Add node properties setters

* New: Add edge properties setters

* New: Add properties getters

* New: Add patch for nodes and edges

* Fix: Make getters return copies

* Fix: Edge factory listeners copying

* Fix: Jest outdated tests

* Fix: Github actions node version

* Chore: Refactor observer interface

* Chore: Refactor node/edge constructor settings

* Chore: Refactor node/edge function grouping

* Chore: Refactor node/edge function grouping

* Fix: Listeners behaviour

* Chore: Refactor property copying

* Chore: Refactor subject implementation

* Fix: Set position behaviour on node drag

* Chore: Upgrade node version

* Chore: Refactor function type check

* Fix: Remove listener behaviour

* Chore: Refactor util naming

* Chore: Remove unused type assertion

* Chore: Refactor position setter options

* Chore: Refactor property patch function

* Fix: Set map node position behaviour

* Chore: Refactor simulator data patching

* Chore: Change observers to callbacks

* New: Add state setters with options (#95)

* New: Add state setters with options

* Chore: Remove leftover comments

* Chore: Refactor state setter logic

* Chore: Refactor state types

* Fix: Rename merged function usage

* Fix: Merged variable naming

* Chore: Fix tests

---------

Co-authored-by: dlozic <davidlozic@gmail.com>
Co-authored-by: Oleksandr Ichenskyi <55350107+AlexIchenskiy@users.noreply.github.com>
Co-authored-by: AlexIchenskiy <aichenskiy@gmail.com>
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