New: Add properties setters and getters - #93
Conversation
|
Tests are failing because of the usage of the |
| | INodeCoordinates | ||
| | INodeMapCoordinates | ||
| | INodePosition | ||
| | ((node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition), |
There was a problem hiding this comment.
Should I merge these interfaces somehow? I was considering assigning a type to nodes (for map view and regular view), but I'm unsure if it's necessary since users can use any value from the node data for lng and lat. Is there a better approach to setting the position for map view nodes maybe?
There was a problem hiding this comment.
Check the orb usage for the map, you will see that the user needs to say how lat and long are extracted for each node. That is where user has ability to remove positions (not returning anything, etc.).
https://github.com/memgraph/orb/blob/main/examples/example-graph-on-map.html#L49
So LAT and LNG should be removed from these, first because it changes the data - we should never change the data because that is owner by the user, and second, user can have abc and def for his latitude and longitude information - in the map callback he would just do this:
orb.setView((context) => new Orb.MapView(context, {
getGeoPosition: (node) => ({ lat: node.data.abc, lng: node.data.def, }),
}));
To recap, map is not using these positions.
| readonly offset: number; | ||
| readonly start: any; | ||
| readonly startNode: INode<N, E>; | ||
| readonly end: any; |
There was a problem hiding this comment.
I was considering rewriting the values to use getters for consistency, but some are required in the IEdgeBase interface (such as id, start, end), which is also required in topology.ts. While I believe it's a good idea to use getters for consistency, it seems unnatural given the existing interface requirements. Is it fine to have a combination of TypeScript getters (e.g., get x) and regular getters (e.g., getX), or should I consider modifying the API further?
There was a problem hiding this comment.
Hmm, I am not sure. These are already readonly so user can just read them and there is no way to set those except creating a new edge or node. I am leaving the decision to you, if you wish and you think it will be more consistent, feel free to have it consistently. But Base class will still need to have those simple fields because that's how user creates nodes and edges (as JS objects).
Yep, we should update Node to some newer version for sure! |
| update(): void { | ||
| this.render(); | ||
| } |
There was a problem hiding this comment.
Hmm, this is maybe a bit tricky, because on external API (orb view), there is an .update() call because of an observer. Is there a way to handle this better? Would an observer with a callback do the trick or?
There was a problem hiding this comment.
Is there anything we can do there? Do you have any ideas?
There was a problem hiding this comment.
Do you Toni maybe have an idea how to implement this?
There was a problem hiding this comment.
Currently I am seeing two ideas, but there can be more for sure:
- Have a proxy class that is an actual listener, but also gives you a callback way to respond to updates. Then view can initiate that proxy class where
updatewill be called, and update calls a callback that is registered privately from the view. Thenupdateis not exposed - easier - Add callback to the observer pattern, so listener can either have a callback or extending the
updateto get calls for notifications - harder
| update(): void { | ||
| this.render(); | ||
| } |
There was a problem hiding this comment.
Is there anything we can do there? Do you have any ideas?
| | INodeMapCoordinates | ||
| | INodePosition | ||
| | ((node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition), | ||
| isInner?: boolean, |
There was a problem hiding this comment.
I have to use the second argument here because sometimes we don't need to notify listeners on set position (e.g., on SIMULATION_PROGRESS in orb-view.ts because position handling is done through the simulation). However, this approach does not seem to be very clean. Do you have any ideas on how I could make it better?
There was a problem hiding this comment.
If we decide to leave it here, perhaps a better naming option would be isInnerCall? I will also update the interface accordingly when we define it :)
There was a problem hiding this comment.
Aha. Angular uses dispatch: false for those kind of events. isInnerCall doesn't really say what you described so I would go into the direction such as: isNotifySkipped or isDispatchSkipped, isNotifyIgnored. In general, use the name that describes what you described to me, you don't want to notify listeners. And the default should be false or not existing object, that's why I used skipped or Ignored. Feel free to come up with a name with such constraints.
There was a problem hiding this comment.
Also, this should be an object, optional options?.
| } | ||
| } | ||
|
|
||
| patchData(data: Partial<ISimulationGraph>) { |
There was a problem hiding this comment.
Is it somehow possible to reduce the code redundancy between the patchData and _initializeNewData functions? They are essentially performing patch and set operations, but I'm not sure if it's possible to reduce the code repetition here without overcomplicating things.
| fx: data.x, | ||
| fy: data.y, | ||
| id: data.id, | ||
| }, |
There was a problem hiding this comment.
Should I create and use some sort of getStickyNode function here instead of directly writing these properties?
There was a problem hiding this comment.
I don't see a problem writing it directly, but maybe @cizl has more to say here?
There was a problem hiding this comment.
I think this is fine. I would write a utility function if this starts to get repeated.
But on the other hand, when can this be called? Will it interfere with the physics functionality that depends on fx and fy? Is the nodes in the data always guaranteed to be sticky?
| import { INodeCoordinates, INodeMapCoordinates, INodePosition } from '../models/node'; | ||
|
|
||
| export type IObserverDataPayload = INodePosition | INodeCoordinates | INodeMapCoordinates; | ||
|
|
||
| export interface IObserver { | ||
| update(data?: IObserverDataPayload): void; | ||
| } |
There was a problem hiding this comment.
Uh oh, this is not generic anymore because you introduced local models (Position, Coordinates, NodeMapCoordinates) to the util ones. :D
There was a problem hiding this comment.
I am considering adding parameterization for data by including parameters that represent the data that can be sent (similar to this payload but specified in the parameters of each implementation). Do you maybe have any better ideas?
There was a problem hiding this comment.
I have created an issue #97 for this where we can discuss it further :)
| const position = this._settings.getPosition(nodes[i]); | ||
| if (position) { | ||
| nodes[i].position = { id: nodes[i].id, ...position }; | ||
| nodes[i].setPosition({ id: nodes[i].getId(), ...position }, true); |
There was a problem hiding this comment.
Similar to previous comments, seeing this true as second argument makes code harder to read. Having an object instead will work better because it will be more clear enough what true is all about.
There was a problem hiding this comment.
@tonilastre When passing objects as inputs to functions, how do we handle sane defaults?
When using function arguments we can put some defaults, but when using objects, we need to specify each and every property.
On a call earlier this week we discussed having a default object in that case, and passing a Partial<IInputParams> as an input to override the "defaults". Does this make sense? How is this usually handled in practice?
There was a problem hiding this comment.
It depends on the options. In some other comment, I suggested what the options should be for this one where empty options would mean normal mail flow. Only in exception, the options can be added.
If that is not the case, then yea, the function should define defaults internally and merge it with the input options.
| fx: data.x, | ||
| fy: data.y, | ||
| id: data.id, | ||
| }, |
There was a problem hiding this comment.
I don't see a problem writing it directly, but maybe @cizl has more to say here?
| }; | ||
|
|
||
| export const patchProperties = <T>(target: T, source: T): void => { | ||
| const keys = Object.keys(source as Object); |
There was a problem hiding this comment.
I am not sure, but if you say here that these keys are (keyof T)[] or something similar, you won't need this monster of as X on line 128.
| data.nodes = this._fixDefinedNodes(data.nodes); | ||
| const nodeIds = this._nodes.map((node) => node.id); | ||
| for (let i = 0; i < data.nodes.length; i += 1) { | ||
| if (nodeIds.includes(data.nodes[i].id)) { |
There was a problem hiding this comment.
Save indents. Have reverse if here and continue. The main code will have one less indent - it is easier to read code then.
| this.setData({ ...data, lng: undefined }); | ||
| } | ||
|
|
||
| if ('lat' in this._data) { | ||
| this.setData({ ...data, lat: undefined }); |
There was a problem hiding this comment.
This is a bit tricky because you are updating user's data. I understand that you wanted to handle both map and regular view, but user should handle the map positions with the callback that is on the map view. These x, y positions are not even used by the simulator on the map.
To recap, I would remove this because it changes the user's data.
| setPosition(position: INodeCoordinates | INodeMapCoordinates | INodePosition, isInner?: boolean): void; | ||
| setPosition( | ||
| callback: (node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition, | ||
| isInner?: boolean, |
There was a problem hiding this comment.
Btw, these functions in the interface have name call instead of isInner. It would be great to have them consistent.
| | INodeCoordinates | ||
| | INodeMapCoordinates | ||
| | INodePosition | ||
| | ((node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition), |
There was a problem hiding this comment.
Check the orb usage for the map, you will see that the user needs to say how lat and long are extracted for each node. That is where user has ability to remove positions (not returning anything, etc.).
https://github.com/memgraph/orb/blob/main/examples/example-graph-on-map.html#L49
So LAT and LNG should be removed from these, first because it changes the data - we should never change the data because that is owner by the user, and second, user can have abc and def for his latitude and longitude information - in the map callback he would just do this:
orb.setView((context) => new Orb.MapView(context, {
getGeoPosition: (node) => ({ lat: node.data.abc, lng: node.data.def, }),
}));
To recap, map is not using these positions.
| | INodeMapCoordinates | ||
| | INodePosition | ||
| | ((node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition), | ||
| isInner?: boolean, |
There was a problem hiding this comment.
Aha. Angular uses dispatch: false for those kind of events. isInnerCall doesn't really say what you described so I would go into the direction such as: isNotifySkipped or isDispatchSkipped, isNotifyIgnored. In general, use the name that describes what you described to me, you don't want to notify listeners. And the default should be false or not existing object, that's why I used skipped or Ignored. Feel free to come up with a name with such constraints.
| | INodeMapCoordinates | ||
| | INodePosition | ||
| | ((node: INode<N, E>) => INodeCoordinates | INodeMapCoordinates | INodePosition), | ||
| isInner?: boolean, |
There was a problem hiding this comment.
Also, this should be an object, optional options?.
| this.render(); | ||
| } | ||
| }, | ||
| listeners: [this._update], |
There was a problem hiding this comment.
It would be great to add a comment why this line and not this. Same for map view.
There was a problem hiding this comment.
I've added this comment in observer.utils.ts so we don't have to add it everywhere. Is it fine, or should I add it to the view files as well?
* New: Add state setters with options * Chore: Remove leftover comments * Chore: Refactor state setter logic * Chore: Refactor state types
…raph/orb into new/add-setters-and-getters
* 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>
… tooling * Fix: Fix multiple maps issue (#33) * Chore: Update package.json version * New: Change the API to handle OrbView and OrbMapView (#34) * New: Change the API to handle OrbView and OrbMapView * New: Change the API for select/hover strategies * Chore: Release/1.0.0 * New: Add support to get selected/hovered nodes and edges (#61) * New: Added support to get selected nodes and edges * New: Added support to get hovered nodes and edges --------- Co-authored-by: Abhinav Singh Parmar <abhinavparmar147@gmail.com> * New: Add support for enabling and disabling dragging of nodes (fixes #62) (#69) * New: Add feature to enable/disable node dragging (fixes #62) * New: Added support to modify interaction from setSettings * New: Updated documentation for interaction property * New: Add feature to enable/disable zoom (fixes #62) * NEW: Updated documentation for interaction property * NEW: Updated documentation to include isDragEnabled * New: Add support for custom edge line style (#77) * New Added support for custom edges * Refactor: Streamline edge rendering code and optimize line style handling * New: Add support for handling device pixel ratio (#45) * Chore: Move container and canvas creation from the view to the renderer * New: Add devicePixelRatio render property and handler * Fix: Add default DPR for older browsers * Chore: Remove useless check for automatic DPR * New: Add new simulator (#56) (#57) * 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> * New: Add zoom in and out functions (#100) * Fix: Skip unnecessary listener notify on set style * Fix: remove unnecessary rerender on state change * Chore: Update documentation * Fix: Node/edge getter performance issue * Chore: Add data change docs example * Fix: Docs typos * Fix: Disable source map generation (#105) * New: Add tree layout (#107) * New: Add new layouts * New: Add layout options * Chore: Make layout dynamically changeable * Chore: Update docs * Fix: Naming typo * Chore: Refactor layouts * New: Enable layout node add/remove * Chore: Improve behavior for recurrent nodes * Chore: Move some simulator settings to layout * Chore: Refactor code quality and performance * Fix: Layout behavior on change * Chore: Add recenter on layout change * Fix: Layout change behavior * Fix: Simulation behavior on data deletion * Chore: Add recenter on layout change * Fix: Change layout engine logic (#108) * Fix: Change layout engine logic * New: Add simulation cancellation logic * Chore: Remove leftover code * Fix: Hierarchical layout recenter logic * Chore: Refactor package versions and code logic * Fix: Refactor function types * New: Add multiselect (#110) * New: Add multiselect * Chore: Simplify logic * Chore: Update package.json * New: Add SVG export (#111) * New: Add SVG export * Chore: Refactor code quality * New: Add WebGL renderer (#109) * New: Add WebGL renderer * New: Add naive WebGL force layout computation * New: Add WebGL improved node/shape geometry options * New: Add labels and node images * Fix: GPU drag behavior * Chore: Remove leftover comments * Fix: WebGL renderer style invalidating * Chore: Refactor code quality * Chore: Add WebGL example and docs * New: Add docs page (#112) * New: Add docs page * New: Add more docs examples * Update .github/workflows/docs.yml --------- Co-authored-by: Toni <toni.lastre@memgraph.io> * Chore: Finish up the release process * Chore: Fix sync between package.json files * Chore: Add new package-lock.json --------- Co-authored-by: David <davidlozic@gmail.com> Co-authored-by: Abhinv Singh Parmar <abhi171b010@gmail.com> Co-authored-by: Abhinav Singh Parmar <abhinavparmar147@gmail.com> Co-authored-by: Abhinv Singh Parmar <abhinav.parmar@infosys.com> Co-authored-by: Oleksandr Ichenskyi <55350107+AlexIchenskiy@users.noreply.github.com> Co-authored-by: AlexIchenskiy <aichenskiy@gmail.com>
Resolves #88
This PR introduces setters, patchers, and getters for node/edge properties, along with implementing the Observer pattern to invoke view rerender upon property changes. The API has been significantly changed. Instead of writing unsafe code like
we now use
and
Accessing properties is also modified, from
to
Documentation needs to be updated accordingly. However, I believe it's more appropriate to do a complete rewrite upon the 1.0.0 release. Additionally, I want to implement tests after discussing all the details.
This PR is marked as a draft because I feel there are important aspects to double-check and review. However, it's open for all sorts of comments and advice!