brunomnsilva / JavaFXSmartGraph

Generic (Java FX) Graph Visualization Library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Determine vertex / edge label in data layer

Bertie2011 opened this issue · comments

We're working with complex objects and it would be nice if we could set the label during vertex/edge creation. If I'm not mistaken, we would now have to first update the graph, then call update(), then use .attachLabel() on SmartGraphVertexNode (or something like that) and then call update() again.

Currently we're just overriding toString() of the generic vertex value, but for others there might be multiple string representations one would like to see. The method toString() will probably be a technical representation for debugging purposes in most cases. Another solution could be wrapping the object into another class on which the toString() is set for the visualisation, but that's sub-optimal.

No, you just have to update you underlying model (the graph) and the update() will also update the labels. Hence, you may call update() only once, I believe.

image

This is some code that is called during the panel's update(). It updates the attached labels.

Yeah I saw that, but vertexNodes map is not filled until the first .update(). (Assuming you start with an empty graph). And then (which I spent way too much time on) you overwrite the text of the attached label anyways with .toString(). At first I thought the code only used .toString() if no label was defined, because I glanced too fast over it.

(Also attached label comes from a view class generated in updateNodes call, there is nothing referencing labels in the domain/data Vertex class)

So there is actually no way to custom set the label now, excluding toString. Because I didn't like to set toString in my model, I ended up writing a small GraphNode class that wraps the model and calls getName() in its toString().

It would be nice if the Vertex constructor had an optional parameter for string. The default action of picking .toString would have to stay here, to preserve updates of the toString result (instead of caching it inside the Vertex object)

I understand what you mean (and can see some cases in which that behavior would be useful), but I think the visualization is best to be in sync with the model after each update.

If not, one could have a setCustomLabel method that would override the default behavior. But I think that could lead to some confusion when things didn't seem to match up.

Other solutions (similar to yours) would require the model classes to implement some kind of interface, which I think is also unnecessary and adds more entropy. But, on the other hand, I see that the current code limits what one can do with .toString() for other uses, e.g., debug.

If we had function pointers in Java, it could be interesting for this situation.

Perhaps the Vertex / Edge could have a lambda parameter that the user (like me) can use to specify where to pull the string from or how to build it?
In my case it would be c -> c.getName()

I've delved into this and managed to use a lambda expression as you said. But I didn't like the end result, in the sense that now you can put whatever thing you want in the label that may not be even related to the underlying model 😞

For the cases you describe, the user can utilize its own lambda expression in the .toString method of the underlying object:

public  interface MyLabelGenerator {
    public String generate();
}

public class MyVertexObject {
   
   private MyLabelGenerator graphLabel = () -> getName() + "whatever you want"; 

   public String getName() { ... }

   @override
   public String toString() {
       return graphLabel.generate();
   }
}

And then it can add a setGenerator(MyLabelGenerator graphLabel) to MyVertexObject to change the output of .toString() on-the-fly -- and then calling panel.update().

Neither the proposed solution or directly implementing toString has tighter coupling with the model any more than an external lambda. I could pass unrelated labels from toString or the "generator" as well, if I really wanted to. The problem with the solution above is that I have to know when the update is run and make sure I collect all the vertices and set their label generator.

The code would roughly look like this (I don't fully remember the available methods)
new Thread(() -> {
for (Vertex v : graph.getVertices()) v.getElement().setGenerator(new GraphLabelGenerator())
graphPanel.updateAndWait()
for (Vertex v : graph.getVertices()) v.getElement().setGenerator(new DefaultLabelGenerator())
}).start();

It requires the new thread, because if you wait in the JavaFX thread you create a deadlock. The javafx thread would be waiting for itself to pickup the task that's on the Platform.runLater queue

Which is far from perfect. It's valid reasoning to split the visual label from the debug representation and I rather do that at creation time instead of every time I call .update. You mentioned you were a teacher before and from that point of view, I understand you want your students to be guided in the implementation. However, as I said, the location of the string generation has nothing to do with how well it represents the model.
After all, I could still implement
public String toString() { return new Random().next() }

I had something in those lines, but it would further require to encapsulate the behavior in different interfaces as to not expose the underlying data structure outside, so that you could later interact and change the generator on-the-fly.

However, the in-place solution in the model data, that I mentioned, should work the same. Only the avoidance of the direct use of the .toString() resonates with me, but if I now require the model classes to implement, say:

interface DataLabel {
   String getLabel();
} 

class SmartGraphPanel<V extends DataLabel, E extends DataLabel> { }

it could break existing code that uses the library.

But all this labelling stuff could be more flexible, I agree. I'll have another go at it in the meantime.

P.S. - Yes, you can do that, but it comes directly from the model 😉

But..... there is no added value from wanting to have the data "come from the model" if you (as developer of SmartGraph) have no say about what comes out of toString/generator anyways.
Other than that, I understand the problem of using interfaces and generic constraints and my wish to have creation-time label definition is still standing :)

Curious what you come up with in the end, I hope you can find something.

EDIT: If you know a little something about reflection, you could write an annotation that could be placed on a method/property to get a string from. I could then simply add @SmartGraphLabel to my getName() method or name property.
You avoid interfaces and toString occupance and still have the visual representation in the model.
https://dzone.com/articles/creating-custom-annotations-in-java

Thanks for the tip on the custom annotations; that seems like an interesting solution 😄

I've created a @SmartLabelSource annotation; thanks for the tips! :)