Minimal Example (Xtext Model)
Last modified by Niklas Rentz on 2023/09/12 13:04
These instructions should guide you if you'd like to try out using KLighD for visualizing your own models or data structures. A minimal example is shown where a Xtext model from a textual editor is visualized with KLighD.
Step-By-Step Guide
In order to get this example working, please follow the instructions below:
- Download Eclipse Kepler (Modeling Edition) and install KIELER Pragmatics 0.10.0 (according to these instructions)
- Download the following files, unzip them and import them as existing projects into your Eclipse workspace using File->Import...->General->Existing projects into Workspace (see help for details):
- You may now create a new run-configuration (according to these instructions) and run everything as an Eclipse Application.
- Create a new project (File->New project...->General->Project or right-click in the Project Explorer and choose New->Project...->General->Project)
- Once you create a new text file with the ending "*.myxtextdata" and enter some contentMinimal Example (Xtext Model) (Ctrl+Space gives you content assist), you should see the synthesized diagram as follows:
The Details
The following files are involved (found in the src folder of the imported project in your Eclipse workspace):
- MyXtextData.xtext (de.cau.cs.kieler.klighd.example.myxtextdata) : It contains a very simple Xtext grammar for the textual language
- ShowDiagramCombination.java (de.cau.cs.kieler.klighd.example.myxtextdata.klighd) : This is a KIELER combination for triggering the automatic diagram synthesis when editing the model in the textual editor.
- MyXtextDataDiagramSynthesis.xtend (de.cau.cs.kieler.klighd.example.myxtextdata.klighd) : This is the most essential part, it is the transformation from your data/model to the view model (the diagram). You can use this as a bare bone example and extend it as you like. Note that it already uses several KLighD extension libraries that are included. These libraries can be inspected also if you are searching for further features that you want to use, they contain the most common features already.
The Files
MyXtextData.xtext
grammar de.cau.cs.kieler.klighd.example.myxtextdata.MyXtextData with org.eclipse.xtext.common.Terminals
generate myXtextData "http://www.cau.de/cs/kieler/klighd/example/myxtextdata/MyXtextData"
Model:
name=ID (':' '{'
subData+=Model*
'}')?;
generate myXtextData "http://www.cau.de/cs/kieler/klighd/example/myxtextdata/MyXtextData"
Model:
name=ID (':' '{'
subData+=Model*
'}')?;
ShowDiagramCombination.java
package de.cau.cs.kieler.klighd.example.myxtextdata.klighd;
import de.cau.cs.kieler.core.kivi.AbstractCombination;
import de.cau.cs.kieler.core.kivi.triggers.PartTrigger;
import de.cau.cs.kieler.core.kivi.triggers.SelectionTrigger;
import de.cau.cs.kieler.klighd.xtext.UpdateXtextModelKLighDCombination;
//import de.cau.cs.kieler.klighd.incremental.UpdateStrategy
public class ShowDiagramCombination extends UpdateXtextModelKLighDCombination {
/**
* The 'execute()' method, see doc of {@link AbstractCombination}.
*/
public void execute(PartTrigger.PartState es, SelectionTrigger.SelectionState selectionState) {
// do not react on partStates as well as on selectionStates in case
// a view part has been deactivated recently, as an potentially out-dated selection
// is currently about to be processed
// most certainly a "part activated" event will follow and subsequently a further
// selection event if the selection of the newly active part is changed, too!
if (this.latestState() == es || es.getEventType() == PartTrigger.EventType.VIEW_DEACTIVATED) {
return;
}
}
}
import de.cau.cs.kieler.core.kivi.AbstractCombination;
import de.cau.cs.kieler.core.kivi.triggers.PartTrigger;
import de.cau.cs.kieler.core.kivi.triggers.SelectionTrigger;
import de.cau.cs.kieler.klighd.xtext.UpdateXtextModelKLighDCombination;
//import de.cau.cs.kieler.klighd.incremental.UpdateStrategy
public class ShowDiagramCombination extends UpdateXtextModelKLighDCombination {
/**
* The 'execute()' method, see doc of {@link AbstractCombination}.
*/
public void execute(PartTrigger.PartState es, SelectionTrigger.SelectionState selectionState) {
// do not react on partStates as well as on selectionStates in case
// a view part has been deactivated recently, as an potentially out-dated selection
// is currently about to be processed
// most certainly a "part activated" event will follow and subsequently a further
// selection event if the selection of the newly active part is changed, too!
if (this.latestState() == es || es.getEventType() == PartTrigger.EventType.VIEW_DEACTIVATED) {
return;
}
}
}
MyXtextDataDiagramSynthesis.xtend
class MyXtextDataDiagramSynthesis extends AbstractDiagramSynthesis<Model> {
@Inject extension KNodeExtensions
@Inject extension KEdgeExtensions
@Inject extension KPortExtensions
@Inject extension KLabelExtensions
@Inject extension KRenderingExtensions
@Inject extension KContainerRenderingExtensions
@Inject extension KPolylineExtensions
@Inject extension KColorExtensions
// Some self-defined colors
private static val KColor BLUE1 = RENDERING_FACTORY.createKColor() =>
[it.red = 248; it.green = 249; it.blue = 253];
private static val KColor BLUE2 = RENDERING_FACTORY.createKColor() =>
[it.red = 205; it.green = 220; it.blue = 243];
// Additional transformation option to hide or show a shadow
private static val SynthesisOption SHOW_SHADOW = SynthesisOption::createCheckOption("Shadow", true);
// Add all transformation options (comma separated)
override getDisplayedSynthesisOptions() {
return ImmutableList::of(
SHOW_SHADOW
);
}
override KNode transform(Model model) {
val root = model.createNode()
root.putToLookUpWith(model) => [
// Optional Layout parameters can be set
//it.addLayoutParam(LayoutOptions::ALGORITHM, "de.cau.cs.kieler.kiml.ogdf.planarization");
//it.addLayoutParam(LayoutOptions::SPACING, 75f);
//it.addLayoutParam(LayoutOptions::DIRECTION, Direction::UP);
// A rounded rectangle is created for every MyData instance
it.addRoundedRectangle(5, 5) => [
// Set linewith, foreground color, and a fading background color
it.lineWidth = 1;
it.setForeground("darkGray".color)
// We need a fresh copy of each color item, because it is contained by its element
it.setBackgroundGradient(BLUE1.copy, BLUE2.copy, 90)
// Here we see a how to use a boolean transformation/diagram option
if (SHOW_SHADOW.booleanValue) {
it.shadow = "black".color;
}
// Set a text
it.addText(" " + model.name + " ") => [
it.setFontSize(9)
it.setForeground("black".color)
]
// If this is a hierarchical MyData instance, then create horizontal splitter,
// a child area, and add its children
if (model.subData.length > 0) {
it.setGridPlacement(1);
it.addHorizontalSeperatorLine(1, 2).setForeground("darkGray".color)
it.addChildArea().setGridPlacementData() => [
from(LEFT, 3, 0, TOP, 3, 0).to(RIGHT, 3, 0, BOTTOM, 3, 0)
minCellHeight = 5;
minCellWidth = 5;
];
for (subData : model.subData) {
// To the recursive call to transform for all children
val child = subData.transform
// It is important to add all children to the root!
root.children.add(child)
}
}
]
]
return root;
}
}
@Inject extension KNodeExtensions
@Inject extension KEdgeExtensions
@Inject extension KPortExtensions
@Inject extension KLabelExtensions
@Inject extension KRenderingExtensions
@Inject extension KContainerRenderingExtensions
@Inject extension KPolylineExtensions
@Inject extension KColorExtensions
// Some self-defined colors
private static val KColor BLUE1 = RENDERING_FACTORY.createKColor() =>
[it.red = 248; it.green = 249; it.blue = 253];
private static val KColor BLUE2 = RENDERING_FACTORY.createKColor() =>
[it.red = 205; it.green = 220; it.blue = 243];
// Additional transformation option to hide or show a shadow
private static val SynthesisOption SHOW_SHADOW = SynthesisOption::createCheckOption("Shadow", true);
// Add all transformation options (comma separated)
override getDisplayedSynthesisOptions() {
return ImmutableList::of(
SHOW_SHADOW
);
}
override KNode transform(Model model) {
val root = model.createNode()
root.putToLookUpWith(model) => [
// Optional Layout parameters can be set
//it.addLayoutParam(LayoutOptions::ALGORITHM, "de.cau.cs.kieler.kiml.ogdf.planarization");
//it.addLayoutParam(LayoutOptions::SPACING, 75f);
//it.addLayoutParam(LayoutOptions::DIRECTION, Direction::UP);
// A rounded rectangle is created for every MyData instance
it.addRoundedRectangle(5, 5) => [
// Set linewith, foreground color, and a fading background color
it.lineWidth = 1;
it.setForeground("darkGray".color)
// We need a fresh copy of each color item, because it is contained by its element
it.setBackgroundGradient(BLUE1.copy, BLUE2.copy, 90)
// Here we see a how to use a boolean transformation/diagram option
if (SHOW_SHADOW.booleanValue) {
it.shadow = "black".color;
}
// Set a text
it.addText(" " + model.name + " ") => [
it.setFontSize(9)
it.setForeground("black".color)
]
// If this is a hierarchical MyData instance, then create horizontal splitter,
// a child area, and add its children
if (model.subData.length > 0) {
it.setGridPlacement(1);
it.addHorizontalSeperatorLine(1, 2).setForeground("darkGray".color)
it.addChildArea().setGridPlacementData() => [
from(LEFT, 3, 0, TOP, 3, 0).to(RIGHT, 3, 0, BOTTOM, 3, 0)
minCellHeight = 5;
minCellWidth = 5;
];
for (subData : model.subData) {
// To the recursive call to transform for all children
val child = subData.transform
// It is important to add all children to the root!
root.children.add(child)
}
}
]
]
return root;
}
}
Example contentThe following code can be used as example .myxtextdata file:
test.myxtextdata
diagram : {
outer_data : {
inner_1
inner_2 : {most_inner_6}
inner_5
}
}
outer_data : {
inner_1
inner_2 : {most_inner_6}
inner_5
}
}