/* =====================================================================
* Ocelotl Visualization Tool
* =====================================================================
*
* Ocelotl is a Framesoc plug in that enables to visualize a trace
* overview by using aggregation techniques
*
* (C) Copyright 2013 INRIA
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Damien Dosimont <damien.dosimont@imag.fr>
* Generoso Pagano <generoso.pagano@inria.fr>
*/
package fr.inria.soctrace.tools.ocelotl.visualizations.spatiotemporal.views;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.PolylineConnection;
import org.eclipse.draw2d.RectangleFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.swt.SWT;
import org.eclipse.wb.swt.SWTResourceManager;
import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopic;
import fr.inria.soctrace.framesoc.core.bus.FramesocBusTopicList;
import fr.inria.soctrace.framesoc.core.bus.IFramesocBusListener;
import fr.inria.soctrace.framesoc.ui.colors.FramesocColorManager;
import fr.inria.soctrace.tools.ocelotl.core.constants.OcelotlConstants;
import fr.inria.soctrace.tools.ocelotl.core.dataaggregmanager.spacetime.EventProducerHierarchy.EventProducerNode;
import fr.inria.soctrace.tools.ocelotl.core.dataaggregmanager.spacetime.EventProducerHierarchy;
import fr.inria.soctrace.tools.ocelotl.ui.views.unitAxisView.HierarchyAxisMouseListener;
import fr.inria.soctrace.tools.ocelotl.ui.views.unitAxisView.UnitAxisView;
import fr.inria.soctrace.tools.ocelotl.core.ivisuop.VisuSTOperator;
import fr.inria.soctrace.tools.ocelotl.core.ivisuop.IVisuOperator;
/**
* Hierarchy Axis View : Show the hierarchy of the event producer nodes as
* colored rectangles
*
* @author "Damien Dosimont <damien.dosimont@imag.fr>"
*/
public class HierarchyAxisView extends UnitAxisView implements IFramesocBusListener {
protected EventProducerHierarchy hierarchy;
// Level of hierarchy to display
protected int hierarchyLevel;
protected int smallestDisplayableHierarchyLevel;
HashMap<Integer, Integer> sameHierarchy = new HashMap<Integer, Integer>();
HashMap<Integer, Integer> newHierarchy = new HashMap<Integer, Integer>();
protected int spaceDirty = 10;
protected int spaceDirty2 = 1;
protected int iterationDirty = 3;
protected int levelWidth = 15;
final protected int MinLevelWidth = 10;
protected int areaWidth = 0;
protected int verticalBorder = 10;
protected int horizontalBorder = 5;
protected double rootHeight;
protected double height;
protected double logicHeight;
protected List<Integer> yendlist;
protected List<Integer> xendlist;
protected final int verticalSpace = 3;
protected final int horizontalSpace = 5;
protected int minLogicWeight = OcelotlConstants.MinimalHeightDrawingThreshold;
protected SelectFigure highLightAggregateFigure;
protected FramesocBusTopicList topics = null;
public HierarchyAxisView() {
super();
mouse = new HierarchyAxisMouseListener(this);
xendlist = new ArrayList<Integer>();
yendlist = new ArrayList<Integer>();
// Register update to synchronize traces
topics = new FramesocBusTopicList(this);
topics.addTopic(FramesocBusTopic.TOPIC_UI_COLORS_CHANGED);
topics.registerAll();
}
public void createDiagram(EventProducerHierarchy hierarchy,
boolean activeSelection) {
root.removeAll();
this.hierarchy = hierarchy;
if (hierarchy != null && !hierarchy.getEventProducerNodes().isEmpty()) {
drawHierarchy();
if (originY != -1 && cornerY != -1)
selectFigure.draw(originY, cornerY, activeSelection);
if (currentlySelectedEpn != null)
highLightSelectedProducer.draw(currentlySelectedEpn);
}
root.validate();
}
@Override
public void createDiagram(IVisuOperator manager) {
root.removeAll();
VisuSTOperator propOperator = (VisuSTOperator) manager;
hierarchy = propOperator.getHierarchy();
if (!hierarchy.getEventProducerNodes().isEmpty()) {
drawHierarchy();
}
root.validate();
}
public void createDiagram() {
root.removeAll();
if (hierarchy != null && !hierarchy.getEventProducerNodes().isEmpty()) {
drawHierarchy();
if (originY != -1 && cornerY != -1)
selectFigure.draw(originY, cornerY, true);
if (currentlySelectedEpn != null)
highLightSelectedProducer.draw(currentlySelectedEpn);
}
root.validate();
}
// When receiving a notification, redraw the overview with the new color
@Override
public void handle(FramesocBusTopic topic, Object data) {
if (topic.equals(FramesocBusTopic.TOPIC_UI_COLORS_CHANGED) && hierarchy != null) {
createDiagram();
}
}
/**
* Print the hierarchy for a given eventproducerNode
*
* @param epn
* eventProducerNode
*/
protected void print(EventProducerNode epn) {
if (epn.getHierarchyLevel() > smallestDisplayableHierarchyLevel)
return;
if (newHierarchy.containsKey(epn.getHierarchyLevel()))
drawProd(epn, newHierarchy.get(epn.getHierarchyLevel()), false);
boolean tooSmall = false;
if (ocelotlView.getOcelotlParameters().getOcelotlSettings()
.isUseVisualAggregate()) {
// Check for each child that we have enough vertical space
// to display them
for (EventProducerNode ep : epn.getChildrenNodes()) {
if ((ep.getWeight() * logicHeight - verticalSpace) < minLogicWeight) {
// Too small
tooSmall = true;
break;
}
}
}
// If enough space
// recursively call print() on the children node
if (!tooSmall) {
printChildren(epn);
} else if (epn.getHierarchyLevel() + 1 <= smallestDisplayableHierarchyLevel) {
// draw with dirty texture
drawProd(epn, newHierarchy.get(epn.getHierarchyLevel() + 1), true);
}
}
/**
* Draw an event producer node
*
* @param epn
* the drawn epn
* @param currentHierarchyLevel
* the current hierarchy level
* @param dirty
* is it an aggregate of epn ?
*/
public void drawProd(EventProducerNode epn, int currentHierarchyLevel, boolean dirty) {
String text = epn.getMe().getName();
// If it is similar to another hierarchy
if(sameHierarchy.containsValue(epn.getHierarchyLevel()))
for(Integer level: sameHierarchy.keySet())
if(sameHierarchy.get(level) == epn.getHierarchyLevel())
// Add the name of the similar level to the label text
text = text + " / " + hierarchy.getEventProducerNodesFromHierarchyLevel(level).get(0).getMe().getName();
final Label label = new Label(" " + text + " ");
label.setFont(SWTResourceManager.getFont("Cantarell", 8, SWT.NORMAL));
RectangleFigure rectangle = new RectangleFigure();
rectangle.setBackgroundColor(FramesocColorManager.getInstance()
.getEventProducerColor(epn.getMe().getName()).getSwtColor());
rectangle.setForegroundColor(FramesocColorManager.getInstance()
.getEventProducerColor(epn.getMe().getName()).getSwtColor());
rectangle.setToolTip(label);
int xa = (int) (areaWidth - horizontalBorder - levelWidth * (hierarchyLevel - currentHierarchyLevel + 1));
int ya = (int) (rootHeight - height + epn.getIndex() * logicHeight - verticalBorder);
int xb = xendlist.get(currentHierarchyLevel);
int yb = yendlist.get(epn.getIndex() + epn.getWeight());
// If it is a false leaf (i.e. a non-leaf producer producing event)
if(epn.getChildrenNodes().isEmpty() && !epn.isRealLeaf())
xa = xa - horizontalSpace;
Rectangle boundrect = new Rectangle(new Point(xa, ya), new Point(xb, yb));
root.add(rectangle, boundrect);
if (dirty)
drawTextureDirty(xa, xb, ya, yb, label.getText());
// Save the rectangle in a map
eventProdToFigures.put(epn, boundrect);
figuresToEventProd.put(boundrect, epn);
}
/**
* Draw the graduation and the name of the event producers
*/
public void drawHierarchy() {
eventProdToFigures.clear();
figuresToEventProd.clear();
hierarchyLevel = 0;
smallestDisplayableHierarchyLevel = 0;
areaWidth = root.getClientArea().width();
computeGradMeasure();
print(hierarchy.getRoot());
}
/**
* If all the event producer node of a hierarchy level are too small to be
* represented, then do not show them
*
* @param epn
* the tested event producer node
*/
public void findSmallestDisplayableHierarchy(EventProducerNode epn) {
// Check for each child that we have enough vertical space
// to display them
for (EventProducerNode ep : epn.getChildrenNodes()) {
if ((ep.getWeight() * logicHeight - verticalSpace) >= minLogicWeight) {
// Too small
if (ep.getHierarchyLevel() > smallestDisplayableHierarchyLevel)
smallestDisplayableHierarchyLevel = ep.getHierarchyLevel();
}
findSmallestDisplayableHierarchy(ep);
}
}
/**
* Compute the number and size of the graduations to draw
*/
public void computeGradMeasure() {
rootHeight = root.getClientArea().height;
height = rootHeight - (2 * verticalBorder);
logicHeight = height / hierarchy.getRoot().getWeight();
findSmallestDisplayableHierarchy(hierarchy.getRoot());
findSameHierarchy();
hierarchyLevel = smallestDisplayableHierarchyLevel - sameHierarchy.keySet().size();
rebuildHierarchy();
initX(hierarchy.getRoot());
initY(hierarchy.getRoot());
}
/**
* Build an index with contiguous values to access the displayed hierarchy
* levels
*/
private void rebuildHierarchy() {
newHierarchy = new HashMap<Integer, Integer>();
// Shift the hierarchy level in order for the displayed ones to be
// contiguous
int cpt = 0;
for (int i = 0; i <= smallestDisplayableHierarchyLevel; i++) {
if (!sameHierarchy.containsKey(i)) {
newHierarchy.put(i, cpt);
cpt++;
}
}
// Compute the width of a hierarchy level
levelWidth = (areaWidth - horizontalBorder * 2)
/ newHierarchy.keySet().size();
// If smaller than the min
if (levelWidth < MinLevelWidth && newHierarchy.keySet().size() > 1) {
// Remove root
newHierarchy.remove(0);
hierarchyLevel--;
// Update hierarchies
cpt = 0;
for (int i = 0; i <= smallestDisplayableHierarchyLevel; i++) {
if (newHierarchy.containsKey(i)) {
newHierarchy.put(i, cpt);
cpt++;
}
}
// Recompute the width
levelWidth = (areaWidth - horizontalBorder * 2)
/ newHierarchy.keySet().size();
if (levelWidth < MinLevelWidth && newHierarchy.keySet().size() > 1) {
// If the leaves are displayed
if (smallestDisplayableHierarchyLevel == hierarchy
.getMaxHierarchyLevel()) {
// Remove leaves
newHierarchy.remove(smallestDisplayableHierarchyLevel);
hierarchyLevel--;
// Update hierarchies
cpt = 0;
for (int i = 0; i <= smallestDisplayableHierarchyLevel; i++) {
if (newHierarchy.containsKey(i)) {
newHierarchy.put(i, cpt);
cpt++;
}
}
}
}
}
// Recompute width
levelWidth = (areaWidth - horizontalBorder * 2)
/ newHierarchy.keySet().size();
//Still too small ?
if (levelWidth < MinLevelWidth) {
// Put min width
levelWidth = MinLevelWidth;
}
}
/**
* Recursively print all the children of the event producer with the
* given id
*
* @param epn
* the event producer node
*/
protected void printChildren(EventProducerNode epn) {
for (EventProducerNode epnChild : epn.getChildrenNodes())
print(epnChild);
}
/**
* Compute the list of Y values according to the weight of the node
*/
protected void initY(EventProducerNode epn) {
yendlist.clear();
for (int i = 0; i <= epn.getWeight(); i++)
yendlist.add((int) (rootHeight - height + i * logicHeight
- verticalSpace - verticalBorder));
}
/**
* Compute the list of X values according to the hierarchy level
*/
protected void initX(EventProducerNode epn) {
xendlist.clear();
for (int i = 0; i <= hierarchyLevel; i++)
xendlist.add((int) (areaWidth - horizontalBorder - horizontalSpace - levelWidth
* (hierarchyLevel - i)));
}
/**
* Sort event producer based on their id
*/
public void sortProducers(List<EventProducerNode> producers) {
Collections.sort(producers,
new Comparator<EventProducerHierarchy.EventProducerNode>() {
@Override
public int compare(final EventProducerNode arg0,
final EventProducerNode arg1) {
int diff = arg1.getMe().getId() - arg0.getMe().getId();
return diff;
}
});
}
/**
* Search the hierarchy level that are similar
*/
public void findSameHierarchy() {
sameHierarchy = new HashMap<Integer, Integer>();
int i;
for (i = 0; i < smallestDisplayableHierarchyLevel; i++) {
List<EventProducerNode> producers = hierarchy
.getEventProducerNodesFromHierarchyLevel(i);
boolean different = false;
// If one of the node has more than one child (or none), then it is
// different
for (EventProducerNode epn : producers) {
if (epn.getChildrenNodes().isEmpty()
|| epn.getChildrenNodes().size() != 1) {
different = true;
break;
}
}
if (!different) {
// If similar, associate it with the similar level
if (sameHierarchy.containsKey(i)) {
sameHierarchy.put(i + 1, sameHierarchy.get(i));
} else {
sameHierarchy.put(i + 1, i);
}
}
}
}
/**
* Draw vertical stripes to signal that event producers were aggregated
* @param xa
* @param xb
* @param ya
* @param yb
* @param label
*/
protected void drawTextureDirty(int xa, int xb, int ya, int yb, String label) {
int i = 0;
for (int x = xa + spaceDirty2; x < (xb + yb - ya); x = x + spaceDirty2
+ 1) {
i++;
if (i > iterationDirty - 1) {
i = 0;
x += spaceDirty;
}
if (x >= (xb + yb - ya)) {
break;
}
final PolylineConnection line = new PolylineConnection();
int xinit = x;
int yinit = ya;
int xfinal = Math.max(xa, (xinit - (yb - ya)));
int yfinal = Math.min(yb, ya + xinit - xfinal);
if (xb < xinit) {
yinit = Math.min(yb, ya + xinit - xb);
xinit = xb;
}
line.setBackgroundColor(ColorConstants.white);
line.setForegroundColor(ColorConstants.white);
line.setEndpoints(new Point(xinit, yinit),
new Point(xfinal, yfinal));
line.setLineWidth(1);
line.setAntialias(SWT.ON);
line.setToolTip(new Label(label));
root.add(line);
}
}
public void initDiagram() {
if (ocelotlView.getMainViewTopSashform().getWeights()[0] == 0) {
ocelotlView.getMainViewTopSashform().setWeights(
OcelotlConstants.yAxisDefaultWeight);
ocelotlView.getMainViewTopSashform().layout();
}
}
public void resizeDiagram() {
root.removeAll();
createDiagram(hierarchy, true);
root.repaint();
}
public void resizeDiagram(boolean activeSelection) {
root.removeAll();
createDiagram(hierarchy, activeSelection);
root.repaint();
}
@Override
public void unselect() {
originY = -1;
cornerY = -1;
currentlySelectedEpn = null;
if (highLightSelectedProducer != null) {
highLightSelectedProducer.delete();
}
resizeDiagram();
}
@Override
public void select(int y0, int y1, boolean active) {
originY = y0;
cornerY = y1;
resizeDiagram(active);
}
}