(Read my previous post for a rationale on graphical views)
No matter with what graphics technology you choose to implement a diagram for your Xtext-based language, there are a few things you should know in order to connect the diagram view/editor to the rest of the infrastructure.
Let us first have a look how to implement a context menu action for the Xtext editor to show the element at the current cursor position in the diagram. In Eclipse, you have to implement a handler for such an action. Xtext offers a bunch of classes making your life easier. EditorUtils detects the Xtext editor for the handler’s action and EObjectAtOffsetHelper finds the semantic model element at a given text offset.
As the document – and thereby the model parsed from it – is subject to editing, you have to make sure nobody is changing it while you traverse it to derive your diagram. You can execute code in a read transaction on the XtextDocument by wrapping it in an IUnitOfWork that you pass to the XtextDocument#readOnly method.
The following snipped shows an example handler written in Xtend.
Note that the @Inject annotation in the example requires you to use your language's executable extension factory when you register the handler class it in the plugin.xml of your language's UI plug-in. See the Xtext documentation for details.
To navigate from a diagram element back to it’s element definition in the text, you need to store some back reference. The model element itself is not suitable, as its lifecycle is bound to the one of the editor, and the parser is even free to expunge model elements whenever the document is re-parsed. As Xtext is based on EMF, each model element has a unique URI. You should always use these URIs to store references to model elements beyond the boundaries of a read/write transaction. An element’s URI is provided by EMF's EcoreUtil#getURI method.
Having the URI, the navigation to the respective model element’s definition is easiest to implement using Xtext’s IURIEditorOpener. If your diagram view supports selection listeners, the respective Xtend code could look like this:
Showing posts with label Diagram. Show all posts
Showing posts with label Diagram. Show all posts
Tuesday, August 26, 2014
Friday, August 22, 2014
Graphical Views for Xtext
Xtext provides you with a powerful IDE and a rich featured text editor for your domain-specific language with little effort. But sometimes, a picture says more than a thousand words: You want to have some additional graphical representation of your models, a set of diagrams.
Diagrams are superior to code when it comes to high-level views. But while programmers can easily cope with files that contain several hundred lines of code, the same amount of information usually blows a diagram and destroys all its suggestiveness. Diagrams with just a few nodes and edges showing on a certain aspect of the models only are best fit for human readers. Such aspects are often spread across various model files. So in order to add the best value for the language users on top of an Xtext infrastructure, we need to allow them to create multiple diagrams, each highlighting just a subset of model information, picked from multiple model files. In other words, diagrams that have a completely different structure than their associated textual models.
Traditional graphical editing frameworks focus on editing the underling model through the diagram.
But synchronizing the model changes from textual and diagram editors is very hard if their content's structures differ. In most cases, the integration will lead to major usability quirks like unexpected editor behavior, forced save operations, blocked editors or even data loss.
So rather than working around the hard challenges of integrating graphical and textual model editing, we can leave model modification to Xtext. For the graphical stuff we can concentrate on diagram editing and leave the underlying model read-only. This way we can spend our energy on the things that really matter to the user, like easy and useful ways to populate diagrams or best visual appearance.
In Eclipse, one could build such graphical views using Zest or GEF. If model and diagram do not differ too much in structure, a small code generator targeting GraphViz is a very simple solution with high quality output.
The general integration with Xtext is covered in a follow up post.
The following screencast shows another solution for an Xtext-based domain model language. The graphical editor is using FXDiagram, a diagram framework based on JavaFX. FXDiagram offers a very smooth user experience, excellent rendering, support for touch-pad gestures, animated undo/redo, diagram persistence, export to scalable vector graphics and much more. If you are interested in learning more, feel free to contact me.
Diagrams are superior to code when it comes to high-level views. But while programmers can easily cope with files that contain several hundred lines of code, the same amount of information usually blows a diagram and destroys all its suggestiveness. Diagrams with just a few nodes and edges showing on a certain aspect of the models only are best fit for human readers. Such aspects are often spread across various model files. So in order to add the best value for the language users on top of an Xtext infrastructure, we need to allow them to create multiple diagrams, each highlighting just a subset of model information, picked from multiple model files. In other words, diagrams that have a completely different structure than their associated textual models.
Traditional graphical editing frameworks focus on editing the underling model through the diagram.
But synchronizing the model changes from textual and diagram editors is very hard if their content's structures differ. In most cases, the integration will lead to major usability quirks like unexpected editor behavior, forced save operations, blocked editors or even data loss.
So rather than working around the hard challenges of integrating graphical and textual model editing, we can leave model modification to Xtext. For the graphical stuff we can concentrate on diagram editing and leave the underlying model read-only. This way we can spend our energy on the things that really matter to the user, like easy and useful ways to populate diagrams or best visual appearance.
In Eclipse, one could build such graphical views using Zest or GEF. If model and diagram do not differ too much in structure, a small code generator targeting GraphViz is a very simple solution with high quality output.
The general integration with Xtext is covered in a follow up post.
The following screencast shows another solution for an Xtext-based domain model language. The graphical editor is using FXDiagram, a diagram framework based on JavaFX. FXDiagram offers a very smooth user experience, excellent rendering, support for touch-pad gestures, animated undo/redo, diagram persistence, export to scalable vector graphics and much more. If you are interested in learning more, feel free to contact me.
Tuesday, October 22, 2013
Eclipse Diagram Editors From A User's Perspective
I believe that the Eclipse's diagram editing frameworks have a lot to catch up when it comes to usability. For illustration I've created a screencast on how a typical Eclipse graphical editor appears to a user. I have used the Ecore Tools editor as an example, but most issues presented are caused by defaults of GMF or GEF which are pretty hard to change. So don't be surprised if you re-encounter these or similar issues in Graphiti, Sirius etc. The screencast doesn't even cover the rendering errors described in my previous blog post.
So what has gone wrong?
In my opinion, diagram editor behavior and editing metaphors haven't really changed since the first commits on the GEF project in 2002. Advances in UI design and usability have had almost no priority on Eclipse's graphical frameworks since then. They were built from a pure developer's perspective. So a lot of effort has been spent on:
So what has gone wrong?
In my opinion, diagram editor behavior and editing metaphors haven't really changed since the first commits on the GEF project in 2002. Advances in UI design and usability have had almost no priority on Eclipse's graphical frameworks since then. They were built from a pure developer's perspective. So a lot of effort has been spent on:
- Facilitating the development of new editors. This usually causes a restriction to very specific use cases.
- Establishing weird processes to implement new diagram editors.
- Repeatedly putting new abstraction layers on top of the existing ones. Hide the seemingly ugly or incomprehensible concepts of a base framework. The problem is to find better abstractions that don't leak. In the end you often have to learn both (or more) frameworks in order to use them.
- Building on top of existing frameworks with serious limitations, like integer coordinates, missing alpha channel, no hardware acceleration etc.
- Supporting very large diagrams. Yes, there are very big models. But diagrams with hundreds of nodes and connections only make good wallpapers.
- Creating new compatibility APIs, e.g. to replace the rendering engine. These will limit the available functionality to the common minimum set of features of all backends.
- Changing a model graphically. Note the difference between editing a diagram – e.g. to make it look nicer – and changing the underlying model. Did you ever wonder why GEF Zest is the only Eclipse diagram framework with its own diagram layout algorithms and basic support for multi-touch gestures?
- Appealing visual design,
- Intuitive editing metaphors,
- Snappy behavior,
- Support for modern input devices, and
- The possibility for individual customization.
Saturday, September 7, 2013
Obi-Wan at the Edge
There are two hard problems in computer science: cache validation, variable substitution, and off-by-one errors. (unknown source)The uncounted latter is often referred to as OBOE or, with a geek sense of humor, "Obi-Wan error".
Obi-Wan In Eclipse Diagram Editors
In an older post, I already mentioned one major weakness of GEF (3.x): All coordinates are integers. One problem with that is that it can easily result in off-by-one rendering bugs. That unfortunately holds for many frameworks built on top of GEF. Have a close look at the gradient fill, anchor points (where connections are touching the nodes), clipping bounds and the arrow head in the following screenshots from the standard examples:Integer vs. double coordinates is comparable to raster vs. vector images. While in integer coordinates a point usually means a pixel, in double coordinates a point has no area and a line can be infinitesimally thin. In a world of double precision it is obvious that we need some rendering. We are forced to think about things like line width, stroke type, line caps, line joins, miters, etc. There are excellent explanatory pictures on these in the JavaDocs of JavaFX's Shape class.
"Smooth" Curves
You have to take round-off errors into account when approximating smooth curves (splines) with integer polylines. Otherwise your allegedly smooth curve will go zigzag. This might be the reason why GEF does not have any curves. GMF does:Arrow Heads
I'd expect an edges arrow head them to be aligned with the tangent of the edge at the anchor point. For polylines this is the line connecting the anchor with the last bendpoint. Same for Bézier curves, but apparently not for splines like these:It is also a good idea to prohibit bendpoints too close to the anchor: The inclination of the curve may become too steep:
The last screenshot also shows that the line cap style plays a role at the tip of the arrow head.
Use the Force
GEF 4 will have double coordinates and Bézier curves. But at the time of writing this blogpost, there hasn't been an official release yet. It is not clear either whether and when it will be adopted by downstream projects.I came across all this when trying to calculate edges and arrow heads for my sparetime JavaFX diagram editor project. Luckily, JavaFX uses double coordinates only. JavaFX also includes quadratic and cubic Bézier curves. These curves are really smooth and drawn fast, though I must admit I'd have expected B-Splines at least.
I am going to demonstrate this at EclipseCon Europe 2013. Stay tuned for more.
Sunday, January 6, 2013
Accumulating JavaFX Transforms With Xtend
I was a bit vague in my last post on JavaFX's transformation API, so I decided to write a separate post on this topic.
In JavaFX, each node in the scenegraph can be translated, rotated, scaled or sheared relative to its parent. In mathematical terms, each node maintains a transformation describing its own local coordinate system. This transformation can be defined with a matrix represented as a Transform object in JavaFX.
The JavaFX API offers some convenience methods to manipulate this transformation. E.g., you can scale a node in x-direction and translate it by 42 units in y-direction by calling
In my diagram editor application, I want to scroll (translate), zoom (scale) and rotate the canvas using mouse gestures. From the user's point of view it's imperative that each transformation builds on the previous state. The convenience methods don't match this usecase.
The correct solution to this problem is to multiply the transformation matrices. Unfortunately, JavaFX lacks any kind of calculation API for Transform and its subclasses.
Once again, this is where Xtend comes to our aid. An extension method in Xtend can be used to define functions for existing (closed) types, which syntactically look like being methods of the type on the caller's side. Affine is a subtype of Transform that allows its matrix entries to change. So I wrote extension methods to translate, rotate, scale and shear an existing Affine by multiplying the respective transformation matrix, e.g.
PS: I went for mutable transformation matrices, but the same mechnism will hold for immutable matrices. The extension methods then return a new Affine instead of modifying the receiver.
In JavaFX, each node in the scenegraph can be translated, rotated, scaled or sheared relative to its parent. In mathematical terms, each node maintains a transformation describing its own local coordinate system. This transformation can be defined with a matrix represented as a Transform object in JavaFX.
The JavaFX API offers some convenience methods to manipulate this transformation. E.g., you can scale a node in x-direction and translate it by 42 units in y-direction by calling
node.setScaleX(2); node.setTranslateY(42)Unfortunately, these convenience methods do not accumulate as one would expect. Instead of multiplying the matrices for subsequent transformations, the only set specific entries in the matrix. As a result, translating an object before rotating yields the same result as applying these transformations in reverse order. This is mathematically wrong and does also not match the users expectations.
In my diagram editor application, I want to scroll (translate), zoom (scale) and rotate the canvas using mouse gestures. From the user's point of view it's imperative that each transformation builds on the previous state. The convenience methods don't match this usecase.
The correct solution to this problem is to multiply the transformation matrices. Unfortunately, JavaFX lacks any kind of calculation API for Transform and its subclasses.
Once again, this is where Xtend comes to our aid. An extension method in Xtend can be used to define functions for existing (closed) types, which syntactically look like being methods of the type on the caller's side. Affine is a subtype of Transform that allows its matrix entries to change. So I wrote extension methods to translate, rotate, scale and shear an existing Affine by multiplying the respective transformation matrix, e.g.
class TransformExtensions {
def static scale(Affine it, double x, double y) {
// left multiply a scale matrix to it
// highly optimized as there are many zeros in the scale matrix
mxx = x * mxx
xy = x * mxy
mxz = x * mxz
tx = x * tx // take existing translation into account
myx = y * myx
myy = y * myy
myz = y * myz
ty = y * ty
} }Importing these as extensions
import static extension ...TransformExtensions.*now allows to accumulate the transformations, e.g.
val diagram = scene.root val diagramTransform = new Affine diagram.transforms.clear diagram.transforms += diagramTransform val EventHandlerscrollHandler = [ diagramTransform.translate(deltaX, deltaY)
] scene.onScrollStarted = scrollHandler scene.onScroll = scrollHandler scene.onScrollFinished = scrollHandler val EventHandler rotateHandler = [ diagramTransform.rotate(angle, sceneX, sceneY) ] scene.onRotationStarted = rotateHandler scene.onRotate = rotateHandler scene.onRotationFinished = rotateHandler ...
The resulting behavior looks like the following screenshot. Note that the mouse position is the pivot for rotations and zoom.
The same mechanism can be used to properly place labels along connections:
Subscribe to:
Posts (Atom)




