Visualising Xtext Models with Picto¶
This article shows how Picto can be used to produce graphical views from Xtext-based models. To demonstrate the Picto/Xtext integration, we use the Entity DSL showcased in the 15' Xtext tutorial.

Setup¶
- Import the projects below from the
examplesfolder of the Epsilon Git repositoryorg.eclipse.epsilon.examples.picto.xtext.domainmodelorg.eclipse.epsilon.examples.picto.xtext.domainmodel.ideorg.eclipse.epsilon.examples.picto.xtext.domainmodel.uiorg.eclipse.epsilon.examples.picto.xtext.domainmodel.picto
- Right-click on
Domainmodel.xtextand selectRun As→Generate Xtext Artefacts - Run a nested Eclipse instance.
- In the nested Eclipse instance workspace, create a new file named
blog.dmodelwith the content below:
datatype String
entity Blog {
title: String
many posts: Post
}
entity HasAuthor {
author: String
}
entity Post extends HasAuthor {
title: String
content: String
many comments: Comment
}
entity Comment extends HasAuthor {
content: String
many responses: Comment
}
- Open the Picto view from the Window → Show View menu.
- Go through the produced graphical views as shown in the image above.
The Picto-Xtext Integration Plugin Project¶
We now dive into the org.eclipse.epsilon.examples.picto.xtext.domainmodel.picto project which contains the Picto-Xtext integration code (the rest of the projects are standard Xtext projects).
The DmodelPictoSource class¶
This class extends Picto's built-in EglPictoSource class and produces graphical views in Picto from *.dmodel files edited in an Xtext-based editor. In particular:
- The
supportsEditorTypemethod specifies that this class contributes visualisation capabilities to Xtext-based editors, the title of which ends with.dmodel - The
getRenderingMetadatamethod specifies the EGL transformation that produces the graphical views every time an editor of interest is opened, activated or saved. - The
getFileandgetResourcemethods extract anIFileand an EMFResourcefrom the editor of interest and should be reusable without changes for other Xtext-based languages too. - The
showElementmethod reveals and highlights the element with the specifiedidin the Xtext editor, enabling navigation back to the source model of the view.
package org.eclipse.epsilon.examples.picto.xtext.domainmodel.picto;
import org.eclipse.core.resources.IFile;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.epsilon.picto.dom.Picto;
import org.eclipse.epsilon.picto.dom.PictoFactory;
import org.eclipse.epsilon.picto.source.EglPictoSource;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PlatformUI;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.workspace.WorkspaceLockAccess.Result;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
public class DmodelPictoSource extends EglPictoSource {
@Override
protected Picto getRenderingMetadata(IEditorPart editorPart) {
Picto metadata = PictoFactory.eINSTANCE.createPicto();
metadata.setTransformation("platform:/plugin/org.eclipse.epsilon.examples.picto.xtext.domainmodel.picto/dmodel.egx");
return metadata;
}
@Override
protected Resource getResource(IEditorPart editorPart) {
XtextEditor editor = (XtextEditor) editorPart;
final XtextResourceHolder holder = new XtextResourceHolder();
editor.getDocument().readOnly(new IUnitOfWork<Result, XtextResource>() {
public Result exec(XtextResource state) throws Exception {
holder.setResource(state);
return null;
};
});
return holder.getResource();
}
@Override
protected IFile getFile(IEditorPart editorPart) {
IEditorInput editorInput = ((XtextEditor) editorPart).getEditorInput();
if (editorInput instanceof IFileEditorInput) {
return ((IFileEditorInput) editorInput).getFile();
}
return null;
}
@Override
protected boolean supportsEditorType(IEditorPart editorPart) {
return editorPart instanceof XtextEditor &&
editorPart.getTitle().endsWith(".dmodel");
}
@Override
public void showElement(String id, String uri, IEditorPart editor) {
ICompositeNode node = NodeModelUtils.getNode(getResource(editor).getEObject(id));
if (node != null) {
ISourceViewer textViewer = ((XtextEditor) editor).getInternalSourceViewer();
int offset = node.getOffset();
int length = node.getLength();
textViewer.setRangeIndication(offset, length, true);
textViewer.revealRange(offset, length);
textViewer.setSelectedRange(offset, length);
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().activate(editor);
}
}
}
Picto is made aware of this class through the org.eclipse.epsilon.picto.pictoSource extension in the project's plugin.xml.
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
<extension
point="org.eclipse.epsilon.picto.pictoSource">
<pictoSource
class="org.eclipse.epsilon.examples.picto.xtext.domainmodel.picto.DmodelPictoSource">
</pictoSource>
</extension>
</plugin>
The Visualisation Transformation¶
The visualisation transformation has been implemented using Epsilon's EGL model-to-text transformation language and produces Graphviz-based class diagrams from the active entity model. The transformation consists of the dmodel.egx coordination rules, and the entity2graphviz.egl template shown below. The transformation produces one view for each entity in the model, which consists of the entity itself, any super/sub-types it may have, as well as other entities that it is related to.
rule Entity2Graphviz
transform e : Entity {
template : "entity2graphviz.egl"
parameters : Map{
"path" = Sequence{"Model", e.name},
"icon" = "diagram-ffffff",
"format" = "graphviz-dot"
}
}
digraph G {
graph[splines=ortho]
node[fontname=Arial, fontsize=10, shape=record]
edge[fontname=Arial, fontsize=10]
[%var entities = getVisibleEntities();%]
[%for (e in entities){%]
[%=e.getNodeName()%][shape=none, margin=0, label=<[%=e.getLabel()%]>]
[%}%]
[%for (f in e.features.flatten().select(f|f.type.isTypeOf(Entity) and f.type <> e)){%]
[%=f.eContainer.getNodeName()%]->[%=f.type.getNodeName()%][arrowhead=[%=f.getArrow()%],arrowtail=none,tooltip="[%=f.name%]"];
[%}%]
[%if (e.superType.isDefined()){%]
[%=e.superType.getNodeName()%]->[%=e.getNodeName()%][arrowhead=none,arrowtail=empty,dir=back];
[%}%]
[%for (s in Entity.all.select(en|en.superType = e)){%]
[%=e.getNodeName()%]->[%=s.getNodeName()%][arrowhead=none,arrowtail=empty,dir=back];
[%}%]
}
[%
operation Entity getLabel() {
var onClick = "top.showView(['Model','" + self.name + "'])";
if (self == e) {
onClick = "top.showElement('" + self.id + "', '" + self.eResource.uri + "')";
}
var label = "<table cellspacing='0' cellborder='0' cellpadding='1' bgcolor='" + self.getColour() + "'>";
label += "<tr><td sides='B' colspan='2' border='1' cellpadding='0'>" +
"<table border='0' cellspacing='0' cellborder='0'>" +
"<tr><td align='right' valign='middle'><img src='" + self.getIcon()+ "'></img></td>" +
"<td align='left' valign='middle' href=\"javascript:" + onClick + "\" tooltip='Go'>" + self.name + " </td></tr></table></td></tr>";
label += "<tr><td></td><td></td></tr>";
for (f in self.features.sortBy(a|a.name.toLowerCase())) {
label += "<tr>";
label += "<td><img src='" + f.getIcon() + "'></img></td><td align='left'>" + f.getLabel() + "</td>";
label += "</tr>";
}
if (self.features.isEmpty()){
label += "<tr>";
label += "<td> </td><td> </td>";
label += "</tr>";
}
label += "</table>";
return label;
}
operation Entity getIcon() {
return getImage("icons/entity.gif");
}
operation Feature getLabel() {
return self.name + " : " + self.type?.name + (self.many ? "["+"*"+"]" : "") + " " ;
}
operation Feature getIcon() {
return getImage("icons/attribute.gif");
}
operation Entity getNodeName() {
return "_Entity" + Entity.all.indexOf(self);
}
operation Feature getArrow() {
if (self.many) {
return "crow";
}
else {
return "open";
}
}
operation getVisibleEntities() {
var visibleEntities : Set;
visibleEntities.add(e);
visibleEntities.addAll(e.features.select(f|f.type.isTypeOf(Entity)).collect(f|f.type));
if (e.superType.isDefined()) visibleEntities.add(e.superType);
visibleEntities.addAll(Entity.all.select(en|en.superType = e));
return visibleEntities;
}
operation Entity getColour() {
if (self == e) return "#fff2d2";
else return "#fffcdc";
}
%]
Interactive Diagrams¶
As shown below, you can navigate between diagrams and back to the Xtext editor using Picto's built-in showElement and showView JavaScript functions.

Lazy Execution¶
Since Picto executes EGL transformations lazily when the entity model is saved, only the view that is currently visible is regenerated immediately, which is useful when working with large models.