I want to use Prefuse to visualise a graph. I followed their tutorial and tried their sample application. Its sourcecode can be found here
However, even if I simply copy the full code, the resulting graph does not look as displayed in the tutorial. It is only half visible, stuck in JPanel's upper left corner. Some parts of it are missing becuase they would have to be displayed outside the panel.
I tried with some graphs of my own, but I keep running into the same phenomenon.
I suppose this is not expected behaviour, but I have no idea where to hunt for the problem. I don't know if this is a problem with Swing(x) or prefuse or ... ?
Update:
This is the revised code. I did not change much from the example, only added what trashgod suggested.
package visualise;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.data.io.DataIOException;
import prefuse.data.io.GraphMLReader;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;
public class PrefuseExample {
public static void main(String[] argv) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// -- 1. load the data ------------------------------------------------
// load the socialnet.xml file. it is assumed that the file can be
// found at the root of the java classpath
Graph graph = null;
try {
graph = new GraphMLReader().readGraph("../../resources/visualisation/prefuse/Prefuse-master/data/socialnet.xml");
} catch ( DataIOException e ) {
e.printStackTrace();
System.err.println("Error loading graph. Exiting...");
System.exit(1);
}
// -- 2. the visualization --------------------------------------------
// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add("graph", graph);
vis.setInteractive("graph.edges", null, false);
// -- 3. the renderers and renderer factory ---------------------------
// draw the "name" label for NodeItems
LabelRenderer r = new LabelRenderer("name");
r.setRoundedCorner(8, 8); // round the corners
// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
vis.setRendererFactory(new DefaultRendererFactory(r));
// -- 4. the processing actions ---------------------------------------
// create our nominal color palette
// pink for females, baby blue for males
int[] palette = new int[] {
ColorLib.rgb(255,180,180), ColorLib.rgb(190,190,255)
};
// map nominal data values to colors using our provided palette
DataColorAction fill = new DataColorAction("graph.nodes", "gender",
Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
// use black for node text
ColorAction text = new ColorAction("graph.nodes",
VisualItem.TEXTCOLOR, ColorLib.gray(0));
// use light grey for edges
ColorAction edges = new ColorAction("graph.edges",
VisualItem.STROKECOLOR, ColorLib.gray(200));
// create an action list containing all color assignments
ActionList color = new ActionList();
color.add(fill);
color.add(text);
color.add(edges);
// create an action list with an animated layout
ActionList layout = new ActionList(Activity.INFINITY);
layout.add(new ForceDirectedLayout("graph"));
layout.add(new RepaintAction());
// add the actions to the visualization
vis.putAction("color", color);
vis.putAction("layout", layout);
// -- 5. the display and interactive controls -------------------------
Display d = new Display(vis);
d.setSize(720, 500); // set display size
// drag individual items around
d.addControlListener(new DragControl());
// pan with left-click drag on background
d.addControlListener(new PanControl());
// zoom with right-click drag
d.addControlListener(new ZoomControl());
// -- 6. launch the visualization -------------------------------------
// create a new window to hold the visualization
JFrame frame = new JFrame("prefuse example");
// ensure application exits when window is closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(d);
frame.pack(); // layout components in window
frame.setVisible(true); // show the window
// assign the colors
vis.run("color");
// start up the animated layout
vis.run("layout");
}
});
}
}
I'm new to Prefuse, but a number of common errors can contribute to the observed problem. Looking at the example,
As discussed here, don't use setSize() on the Display when you really mean to override getPreferredSize().
Swing GUI objects should be constructed and manipulated only on the event dispatch thread.
The initial clustering is an artifact of the graph's origin falling on the top-left corner of the Display component at the point (0, 0). Having chosen a preferred size, one can pan() to the center.
private static final int W = 640;
private static final int H = 480;
…
Display d = new Display(vis) {
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
};
d.pan(W / 2, H / 2);
Related
I have made a Snake project in which I used a grid layout
with width 20 and height 20. The problem is that when I want to access any square in the panel, it gets the square with the reversed coordinates. For example: when I use lightMeUp() method, which can change the color of the square, if I choose to color the square with coordinates (1,2) it will color instead (2,1) , and not only that but
other functions inside my project are also affected like this. Like for example when I try to color the positions which are traversed by the snake I have to reverse the coordinates in order to get the right squares colored. I will leave here some code that will show how I built the grid and the threads:
import java.awt.GridLayout;
import java.util.ArrayList;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
public class Window extends JFrame{
public static ArrayList<ArrayList<DataOfSquare>> Grid;
public static int width=20;
public static int height=20;
public Window() {
//Creates the arraylist that'll contain the threads
Grid=new ArrayList<ArrayList<DataOfSquare>>();
ArrayList<DataOfSquare> data;
//Creates Threads and its data and adds it to the arrayList
for(int i=0;i<height;i++) {
data=new ArrayList<DataOfSquare>();
for(int j=0;j<width;j++) {
DataOfSquare c=new DataOfSquare(2);
data.add(c);
}
Grid.add(data);
}
//Setting up the layout of the panel
getContentPane().setLayout(new GridLayout(20,20,5,5));
//Start and pauses all threads, then adds every square of each thread to the panel
for(int i=0;i<height;i++) {
for(int j=0;j<width;j++) {
getContentPane().add(Grid.get(i).get(j).square);
}
}
//initial position of the snake
Tuple position=new Tuple(5,5);
//passing this value to the controller
ThreadsController c=new ThreadsController(position);
//Let's start the game
c.start();
//Links the window to the keyboardListener
this.addKeyListener((KeyListener) new KeyboardListener());
}
}'
I also have other classes inside this project, but I don`t think they have anything to do with this problem so I showed just the code responsible for the grid layout.
There are no errors in my code, the code is doing well if I reverse the coordinates but I just do not understand why it is working like this.
What do I have to do in order to have the coordinates in the right place ?
I am currently trying to figure out, howjfreechart's splitting into swing(1.5) and JavaFX (1.0.1) affects the JavaFX part. As far as I (very limited knowledge on this topic) understand The jfree-fx uses fxgraphics2d to draw its original swing components(?) into an FX-canvas to add this into JavaFX nodes.
Now my question is, if that fxgraphics2d object is still interactable with? I mean things like tooltips and scrolling and similar stuff normal jfreecharts offer. Since my knowledge and time is somewhat limited, I would want to know, if it is worth digging deeper into jfree-fx (if those charts are still interactable with) or if those charts are solely pictures of the actual chart and not interactable with. Then I would need to find a better solution.
I am currently learning how to build a candlestick chart within my java application. While I managed to build a chart only using JavaFX, its performance was really bad as soon as there were drawn some hundred candlesticks.
Then I came across jfreechart, of which I read, that its performance is well above the internal JavaFX charting possibilities. So today I managed to construct my first chart with jfreechart-fx and performance is quite ok. Further I find it much more intuitive to build those charts... but I am not sure if the jfree-fx version only prints images or real chart objects to the nodes. (I read somewhere something about converting a chart into an image to increase performance of drawing...)
Thank you for any information on that topic.
For example here is my JFreeChart Class, which is drawn correctly, but I just do not get any interaction with the chart with my mouse. E.g. I'd like to zoom in/out using the mousewheel and I'd like to pan the chart to the left/right by clickhold leftmouse. That why I am concerned that I am only looking at an image right now. All viable solutions I find through google seem to address only JFreeChart and not JFreeChart-FX.
package org.ezstrats.jfreeChart;
import javafx.collections.ObservableList;
import org.ezstrats.model.chartData.Candlestick;
import org.ezstrats.model.chartData.Chart;
import org.ezstrats.model.chartData.Exchange;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.CandlestickRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.FixedMillisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.ohlc.OHLCSeries;
import org.jfree.data.time.ohlc.OHLCSeriesCollection;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
public class JFreeCandlestickChart extends JPanel {
private static final DateFormat READABLE_TIME_FORMAT = new SimpleDateFormat("kk:mm:ss");
private OHLCSeries ohlcSeries;
private TimeSeries volumeSeries;
private JFreeChart candlestickChart;
public JFreeCandlestickChart(String title) {
ObservableList<Candlestick> candlesticks = Exchange.getCandlesticks();
// Create new chart
candlestickChart = createChart(title, candlesticks);
// Create new chart panel
final ChartPanel chartPanel = new ChartPanel(candlestickChart);
chartPanel.setPreferredSize(new Dimension(832, 468));
chartPanel.getChart().getXYPlot().getDomainAxis().setAutoRange(false);
chartPanel.getChart().getXYPlot().getDomainAxis().setLowerBound(candlesticks.get(candlesticks.size() - 300).getTimestampOpen());
chartPanel.getChart().getXYPlot().getDomainAxis().setUpperBound(candlesticks.get(candlesticks.size() - 1).getTimestampOpen());
// Enable zooming - not workign?! ...
chartPanel.setMouseZoomable(true);
chartPanel.setMouseWheelEnabled(true);
chartPanel.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
// process before
super.mouseDragged(e);
chartPanel.getChart().getXYPlot().getDomainAxis().configure();
// process after
}
});
add(chartPanel, BorderLayout.CENTER);
}
public JFreeChart createChart(String title, ObservableList<Candlestick> candlesticks){
/**
* 1st:
* Creating candlestick subplot
*/
// Create OHLCSeriesCollection as a price dataset for candlestick chart
OHLCSeriesCollection candlestickDataset = new OHLCSeriesCollection();
ohlcSeries = new OHLCSeries("Price");
candlestickDataset.addSeries(ohlcSeries);
// Create candlestick chart priceAxis
NumberAxis priceAxis = new NumberAxis("Price");
priceAxis.setAutoRangeIncludesZero(false);
// Create candlestick chart renderer
CandlestickRenderer candlestickRenderer = new CandlestickRenderer(CandlestickRenderer.WIDTHMETHOD_AVERAGE,
false,
new HighLowItemLabelGenerator(new SimpleDateFormat("kk:mm"), new DecimalFormat("0.00000000")));
// Create candlestickSubplot
XYPlot candlestickSubplot = new XYPlot(candlestickDataset, null, priceAxis, candlestickRenderer);
candlestickSubplot.setBackgroundPaint(Color.white);
/**
* 2nd:
* Creating volume subplot
*/
// creates TimeSeriesCollection as a volume dataset for volume chart
TimeSeriesCollection volumeDataset = new TimeSeriesCollection();
volumeSeries = new TimeSeries("Volume");
volumeDataset.addSeries(volumeSeries);
// Create volume chart volumeAxis
NumberAxis volumeAxis = new NumberAxis("Volume");
volumeAxis.setAutoRangeIncludesZero(true);
// Set to no decimal
volumeAxis.setNumberFormatOverride(new DecimalFormat("0"));
// Create volume chart renderer
XYBarRenderer timeRenderer = new XYBarRenderer();
timeRenderer.setShadowVisible(false);
timeRenderer.setDefaultToolTipGenerator(new StandardXYToolTipGenerator("Volume--> Time={1} Size={2}",
new SimpleDateFormat("kk:mm"), new DecimalFormat("0")));
// Create volumeSubplot
XYPlot volumeSubplot = new XYPlot(volumeDataset, null, volumeAxis, timeRenderer);
volumeSubplot.setBackgroundPaint(Color.white);
/**
* 3rd:
* Adding Candles to this chart
**/
for (Candlestick candle: candlesticks){
addCandleToChart(candle.getTimestampOpen(),
candle.getPriceOpen(),
candle.getPriceHigh(),
candle.getPriceLow(),
candle.getPriceClose(),
candle.getVolumeQuote());
}
/**
* 4th:
* Create chart main plot with two subplots (candlestickSubplot,
* volumeSubplot) and one common dateAxis
*/
// Creating charts common dateAxis
DateAxis dateAxis = new DateAxis("Time");
dateAxis.setDateFormatOverride(new SimpleDateFormat("dd.mm.yy kk:mm"));
//dateAxis.setRange();
// reduce the default left/right margin from 0.05 to 0.02
dateAxis.setLowerMargin(0.02);
dateAxis.setUpperMargin(0.02);
dateAxis.setLabelAngle(0);
// Create mainPlot
CombinedDomainXYPlot mainPlot = new CombinedDomainXYPlot(dateAxis);
mainPlot.setGap(10.0);
mainPlot.add(candlestickSubplot, 4);
mainPlot.add(volumeSubplot, 1);
mainPlot.setOrientation(PlotOrientation.VERTICAL);
mainPlot.setDomainPannable(true);
JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, mainPlot, false);
//chart.removeLegend();
// Einbetten in JScrollPaenl??? um Scrollen zu ermöglichen...
// ChartPanel chartPanel = new ChartPanel(chart);
return chart;
}
/**
* Fill series with data.
*
* #param c opentime
* #param o openprice
* #param h highprice
* #param l lowprice
* #param c closeprice
* #param v volume
*/
private void addCandleToChart(long time, double o, double h, double l, double c, double v) {
// Add bar to the data. Let's repeat the same bar
FixedMillisecond t = new FixedMillisecond(time);
//READABLE_TIME_FORMAT.parse(String.valueOf(time)));
ohlcSeries.add(t, o, h, l, c);
volumeSeries.add(t, v);
}
public void setOhlcSeries(OHLCSeries ohlcSeries) {
this.ohlcSeries = ohlcSeries;
}
public void setVolumeSeries(TimeSeries volumeSeries) {
this.volumeSeries = volumeSeries;
}
public OHLCSeries getOhlcSeries() {
return ohlcSeries;
}
public TimeSeries getVolumeSeries() {
return volumeSeries;
}
public JFreeChart getCandlestickChart() {
return candlestickChart;
}
}
I haven't looked at jFreeChart in detail, but I think the main difference between it and the in-built JavaFX charting API is that jFreeChart uses a canvas for its implementation, whereas the in-built charts use the scene graph. Roughly, though not exactly, its similar to this definition of a retained mode (scene graph) vs immediate mode (canvas).
It's probably possible to add interactivity to canvas rendered graphics. It is likely technically challenging beyond basic whole-chart zoom and drag ops. Implementing or demonstrating the addition of such interactivity with canvas rendered graphics is beyond what I would be prepared to do within the context of a StackOverflow answer.
JFreeChart includes an interaction package:
https://github.com/jfree/jfreechart-fx/tree/master/src/main/java/org/jfree/chart/fx/interaction
I suggest you investigate the interaction package, try using it and see if it offers the level of interaction you need.
As Roger mentions in the comments, you can get some basic interaction on a JFreeChartFX chart by wrapping the chart in a ChartViewer using ChartViewer(JFreeChart myChart).
Related question:
How do I properly add a MouseHandler to my JFreeChart-FX to drag the chart from left to right
An aside on Canvas vs SceneGraph
This info on how canvas works is included so that you might have a better idea of what is going on here (note everything here may not be 100% correct, but is close enough to help understanding).
Technically, JavaFX only uses a SceneGraph for rendering. How canvas is internally implemented, as far as I understand, is that each canvas is a node in the scenegraph and comes with a command queue of drawing instructions. When you draw to the canvas, it doesn't draw immediately, instead it puts the drawing commands into a queue, then, at some point, before the next 60fps drawing pulse completes, it renders those to an image buffer that it relays into a JavaFX node. Old commands are forgotten by the canvas command queue once executed, so everything just ends up as pixels eventually. You can keep track of drawing commands within your application and re-issue them to repaint the canvas from scratch if you wish, but canvas won't help with that.
What JFreeChartFX is doing is providing an adapter which makes a JavaFX canvas look like a Swing painting surface, so that the heavy lifting and internal engine of JFreeChart can be used to issue all of the drawing commands, and those can be rendered to either a JavaFX canvas or Swing canvas depending upon the desired output UI tech.
If JFreeChart also provided a similar adapter for JavaFX events, rather than Swing events, and if JFreeChart already has a way to do interactivity using Swing events, then it could potentially add interactivity to JFreeChartFX using a similar adapter or replacement for Swing event mechanisms. Perhaps that is what the interaction package linked above is doing.
I'm working on my personal family tree in Java/Eclipse, and happily bumped into prefuse as for graphic representation.
So far the result looks adaquate in regard to my database feed, but I'm still missing key points to make it easier to browse.
Point 1: verteces represent either a person or a union, and my graph is directed from older to younger members. This is reflected by the arrows on the edges. Yet I'd love to group the arrows in 1 direction only (I'm trying to group generations together if you like), but I can't start to find how to do that. For information, I'm using the NodeLinkTreeLayout as of now.
Point 2: aside from the graph itself, my app main window contains a second JPanel where I would like to modify / insert members. So I want to add an action to each node to call the procedures in the second JPanel. My research on how to access a java class from a node are inconclusive so far, it seems that all the examples from the starter prefuse pack are only based on graph interaction.
There it is. You might already have understood that I'm very new to prefuse and not a pro in Java. So any comment / directions / advice would really be appreciated. I will add a screecap and my graph code so you can see what could be done better.
Thank you for your time, and looking forward to reading your insights.
yorran
public class ShowGraph extends Display {
public static final String EDGES = "graph.edges";
public ShowGraph() {
super(new Visualization());
Graph mG = FamGraph.getGraph();
m_vis.addGraph("graph", mG);
m_vis.setInteractive("graphe.edges", null, false);
m_vis.setValue("graph.nodes", null, VisualItem.SHAPE, new Integer(Constants.SHAPE_ELLIPSE));
EdgeRenderer edgeR = new EdgeRenderer(Constants.EDGE_TYPE_CURVE, Constants.EDGE_ARROW_FORWARD);
LabelRenderer nodeR = new LabelRenderer("name");
nodeR.setRoundedCorner(8, 8);
nodeR.setHorizontalAlignment(Constants.LEFT);
DefaultRendererFactory drf = new DefaultRendererFactory();
drf.setDefaultRenderer(nodeR);
drf.setDefaultEdgeRenderer(edgeR);
m_vis.setRendererFactory(drf);
int[] palette = new int[] {
ColorLib.rgb(255, 180, 180), ColorLib.rgb(190, 190, 255)
};
DataColorAction nFill = new DataColorAction("graph.nodes", "label", Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
ColorAction edges = new ColorAction("graph.edges", VisualItem.STROKECOLOR, ColorLib.gray(230));
ColorAction arrow = new ColorAction("graph.edges", VisualItem.FILLCOLOR, ColorLib.gray(230));
ColorAction text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0));
ActionList color = new ActionList();
color.add(nFill);
color.add(edges);
color.add(arrow);
color.add(text);
ActionList layout = new ActionList(Activity.INFINITY);
//layout.add(new ForceDirectedLayout("graph", true));
layout.add(new NodeLinkTreeLayout("graph"));
layout.add(new RepaintAction());
m_vis.putAction("color", color);
m_vis.putAction("layout", layout);
setSize(1200, 900); //size controlled by parent jpanel - Comment out after tests
pan(360, 250);
setHighQuality(true);
addControlListener(new DragControl());
addControlListener(new PanControl());
addControlListener(new ZoomControl());
addControlListener(new ZoomToFitControl());
m_vis.run("color");
m_vis.run("layout");
}
public static void main(String[] args) {
Fulltree.fireUp();
ShowGraph mG = new ShowGraph();
JFrame frame = new JFrame("My family chart");
JPanel thePanel = new JPanel();
frame.getContentPane().add(thePanel);
thePanel.add(mG);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
So after a lot of reseach, I'm answering to my own questions in case someone meets the same issues :
as for point 1 : ForceDirectedGraph is a lot better than NodeLinkTreeLayout, especially when your graph starts counting many members. Family branches make a lot more sense than viewing generations aligned.
as for point 2 : node related actions are the way to go, through a ControlListener:
addControlListener(new ControlAdapter() {
public void itemClicked(VisualItem item, MouseEvent e) {
// anything you need here
// even filter right and left click for a sub menu
}
});
One more thing : if you add actions to your graph (search, predicates...), make sure to stop them if you need to rebuild your graph at some point. If you don't, your actions will generate errors you will spend hours (if not days) to debug.
I want to display a simple Graph with nodes IDs inside Nodes using Prefuse but this seems to be more complicated than it sounds.
Graph g = new Graph();
for (int i = 0; i < 3; ++i) {
Node n1 = g.addNode();
n1.setInt("label", 1); // I am trying to add a field in a node
Node n2 = g.addNode();
Node n3 = g.addNode();
g.addEdge(n1, n2);
g.addEdge(n1, n3);
g.addEdge(n2, n3);
}
g.addEdge(0, 3);
g.addEdge(3, 6);
g.addEdge(6, 0);
// add visual data groups
VisualGraph vg = m_vis.addGraph(GRAPH, g);
m_vis.setInteractive(EDGES, null, false);
m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(Constants.SHAPE_STAR));
However, it seems that this field doesn't exist, it makes sense since I didn't add this field but there isn't an option to add a field neither. I am getting this exception referring to the n1.setInt("DEFAULT_NODE_KEY", 1) line:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(Unknown Source)
at java.util.ArrayList.get(Unknown Source)
at prefuse.data.Table.getColumn(Table.java:457)
at prefuse.data.Table.setInt(Table.java:1032)
at prefuse.data.tuple.TableTuple.setInt(TableTuple.java:215)
at prefuse.demos.AggregateDemo.initDataGroups(AggregateDemo.java:141)
at prefuse.demos.AggregateDemo.<init>(AggregateDemo.java:72)
at prefuse.demos.AggregateDemo.demo(AggregateDemo.java:182)
at prefuse.demos.AggregateDemo.main(AggregateDemo.java:176)
I am not sure how to use fields in Nodes. I tried to read the library's help but I don't manage to figure that out.
You may be looking for a LabelRenderer. In the example below, a LabelRenderer is constructed in such a way as to render nodes having the label GraphLib.LABEL, defined as "label":
LabelRenderer r = new LabelRenderer(GraphLib.LABEL);
The nodes comprising the Graph are given text via setString() using the same key, GraphLib.LABEL. For example, the root Node added to the Graph returned by GraphLib.getDiamondTree() is assigned the key GraphLib.LABEL and the value "0,0".
Node r = t.addRoot();
r.setString(LABEL, "0,0");
Later, when the Visualization runs, the renderer attempts to use text from the GraphLib.LABEL field.
import java.awt.Dimension;
import javax.swing.JFrame;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphLib;
import prefuse.visual.VisualItem;
/** #see https://stackoverflow.com/a/44274886/230513 */
public class Example {
private static final int W = 640;
private static final int H = 480;
public static void main(String[] argv) {
// -- 1. create the data ------------------------------------------------
Graph graph = GraphLib.getDiamondTree(3, 2, 1);
// -- 2. the visualization --------------------------------------------
// add the graph to the visualization as the data group "graph"
// nodes and edges are accessible as "graph.nodes" and "graph.edges"
Visualization vis = new Visualization();
vis.add("graph", graph);
vis.setInteractive("graph.edges", null, false);
// -- 3. the renderers and renderer factory ---------------------------
LabelRenderer r = new LabelRenderer(GraphLib.LABEL);
r.setRoundedCorner(8, 8); // round the corners
// create a new default renderer factory
// return our name label renderer as the default for all non-EdgeItems
// includes straight line edges for EdgeItems by default
vis.setRendererFactory(new DefaultRendererFactory(r));
// -- 4. the processing actions ---------------------------------------
ColorAction fill = new ColorAction("graph.nodes",
VisualItem.FILLCOLOR, ColorLib.rgb(200, 200, 255));
// use black for node text
ColorAction text = new ColorAction("graph.nodes",
VisualItem.TEXTCOLOR, ColorLib.gray(0));
// use light grey for edges
ColorAction edges = new ColorAction("graph.edges",
VisualItem.STROKECOLOR, ColorLib.gray(200));
// create an action list containing all color assignments
ActionList color = new ActionList();
color.add(fill);
color.add(text);
color.add(edges);
// create an action list with an animated layout
ActionList layout = new ActionList(Activity.INFINITY);
layout.add(new ForceDirectedLayout("graph"));
layout.add(new RepaintAction());
// add the actions to the visualization
vis.putAction("color", color);
vis.putAction("layout", layout);
// -- 5. the display and interactive controls -------------------------
Display d = new Display(vis) {
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
};
d.setSize(W, H); // set display size
d.pan(W / 2, H / 2); // pan to center
d.addControlListener(new DragControl());
d.addControlListener(new PanControl());
d.addControlListener(new ZoomControl());
// -- 6. launch the visualization -------------------------------------
JFrame frame = new JFrame("prefuse label example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(d);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true); // show the window
vis.run("color");
vis.run("layout");
}
}
I would like to make a stacked area chart with prefuse similar to the folowing:
http://prefuse.org/gallery/namevoyager/
I'm not quite sure where to start however, and there's no sample code for these charts. I did find prefuse.action.layout.StackedAreaChart, but am not sure what to do with it.
Here's a compilable example for using the StackedAreaChart layout. I'm including it here becase I couldn't find it anywhere else, with the hope that it will be useful as a reference for others. The key here is to understand that the StackedAreaChart assumes that your table follows the following schema:
One column for the id, say "name",
One or more columns for the actual data that corresponds to the id.
Three columns for the calculated polygon named "_polygon", "_polygon:start" and "_polygon:end". That is just the way the StackedAreaChart class has been designed. "_polygon" is actually the constant VisualItem.POLYGON so you can use that instead as shown in the following example.
Here it is:
import javax.swing.JFrame;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.StackedAreaChart;
import prefuse.data.Table;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.PolygonRenderer;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;
class Main {
public static void main(String[] args) {
ActionList color = new ActionList();
int[] palette = new int[] {
ColorLib.rgba(255,200,200,150),
ColorLib.rgba(200,255,200,150)
};
ColorAction fillColor = new DataColorAction("table", "name",
Constants.NOMINAL, VisualItem.FILLCOLOR, palette);
color.add(fillColor);
ActionList layout = new ActionList();
layout.add(new RepaintAction());
String[] fields = { "1980s", "1990s", "2000s" };
layout.add(new StackedAreaChart("table", VisualItem.POLYGON, fields));
Visualization vis = new Visualization();
Table table = new Table();
vis.add("table", table);
table.addColumn("name", String.class);
table.addColumn("1980s", int.class);
table.addColumn("1990s", int.class);
table.addColumn("2000s", int.class);
table.addColumn(VisualItem.POLYGON, float[].class, null);
table.addColumn(VisualItem.POLYGON+":start", float[].class, null);
table.addColumn(VisualItem.POLYGON+":end", float[].class, null);
int rowNumber = table.addRow();
table.setString(rowNumber, "name", "Bob");
table.setInt(rowNumber, "1980s", 1000);
table.setInt(rowNumber, "1990s", 500);
table.setInt(rowNumber, "2000s", 300);
rowNumber = table.addRow();
table.setString(rowNumber, "name", "Mary");
table.setInt(rowNumber, "1980s", 800);
table.setInt(rowNumber, "1990s", 1500);
table.setInt(rowNumber, "2000s", 3200);
vis.putAction("layout", layout);
vis.putAction("color", color);
DefaultRendererFactory drf = new DefaultRendererFactory();
drf.add("ingroup('table')", new PolygonRenderer());
vis.setRendererFactory(drf);
Display display = new Display(vis);
display.setSize(720, 500);
JFrame frame = new JFrame("Prefuse StackedAreaChart Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(display);
frame.pack();
frame.setVisible(true);
vis.run("layout");
vis.run("color");
}
}
To get it to display the axis refer to the Congress.java demo included in the prefuse distribution.
Have you checked the Prefuse manual? (not too complete, but it's something to start with).
In it, you can find an sample application which shows you how to load some data on a Graph element, and how to deploy it to a Visualization item.
For generating a StackedAreaChart you'll need to load your data into a prefuse.data.Table object, which you could load, by the example, from a CSV file:
CSVTableReader reader=new CSVTableReader();
Table myTable=reader.readTable("/myDataFile.csv");
Then, add the table to the visualization as a data group, i.e "table"
Visualization vis = new Visualization();
vis.add("table", myTable);
Then, create the StackedAreaChart, and add it to the visualization actions collection:
//params: name of the data group to layout, name of the data field in which to store computed polygons, and an array containing the names of the various data fields, in sorted order, that should be referenced for each consecutive point of a stack layer
StackedAreaChart chart=new StackedAreaChart ("table", fieldName, csvColumnsToCompute);
//add the layout action with a unique key
vis.putAction("myChartLayout", chart);
Then, you can config various layout actions,or other visual aspects (see the linked example).
At last, for displaying the chart, you'll have to create a Display object, bind the visualization, and run the layout actions on it:
//this Display initialization is extracted from the Example app
Display d = new Display(vis);
d.setSize(720, 500); // set display size
// drag individual items around
d.addControlListener(new DragControl());
// pan with left-click drag on background
d.addControlListener(new PanControl());
// zoom with right-click drag
d.addControlListener(new ZoomControl());
// create a new window to hold the visualization
JFrame frame = new JFrame("prefuse example");
// ensure application exits when window is closed
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(d);
frame.pack(); // layout components in window
frame.setVisible(true); // show the window
//At the end: RUN THE CHART ACTION:
vis.run("myChartLayout");
Hope this helps, at least as a first start (the code snippets are not intended for copy-paste and can contain some compile errors).
Good luck.