This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
java:prefuse-scatterplot [2009/08/07 16:45] Alexander Rind move attachments to "java" |
java:prefuse-scatterplot [2009/10/05 15:52] (current) Alexander Rind some updates |
||
---|---|---|---|
Line 7: | Line 7: | ||
Further, it discusses an axis-based visualization technique, whereas the documentation of prefuse focuses on graph visualization. | Further, it discusses an axis-based visualization technique, whereas the documentation of prefuse focuses on graph visualization. | ||
The demo application is inspired by Jeffrey Heer's Congress demo, but achieves its functionality in small incremental steps. | The demo application is inspired by Jeffrey Heer's Congress demo, but achieves its functionality in small incremental steps. | ||
+ | ((Please [[http://ike.donau-uni.ac.at/~rind/ | let me know]], if you spot any errors or misleading information.)) | ||
+ | |||
+ | ===== Introduction ===== | ||
+ | |||
+ | Developing with prefuse is for the most part configuration. | ||
+ | You can achieve both basic and advanced visualizations simply by creating objects and setting their properties. | ||
+ | However, this makes it very easy produce source code that is hard to understand. | ||
+ | |||
+ | I recommend to structure your prefuse configuration in four parts: | ||
+ | * data setup | ||
+ | * renderers | ||
+ | * action lists | ||
+ | * displays and controls | ||
===== Phase 0: Infrastructure ===== | ===== Phase 0: Infrastructure ===== | ||
Line 114: | Line 127: | ||
{{:java:scatterplot1.java|Source Code}} | {{:java:scatterplot1.java|Source Code}} | ||
+ | |||
+ | === Further Information === | ||
+ | |||
+ | __Alternative:__ Our visualization class can also be a subclass of either [[http://prefuse.org/doc/api/prefuse/Visualization.html | Visualization]] or [[http://prefuse.org/doc/api/prefuse/Display.html | Display]] instead of referencing them. | ||
+ | |||
+ | __Note:__ A [[http://prefuse.org/doc/api/prefuse/Visualization.html | Visualization]] objects can handle multiple [[http://prefuse.org/doc/api/prefuse/Display.html | Display]]. However, in some situation prefuse only takes the first display in account -- it is still beta. | ||
===== Phase 2: Refinements ===== | ===== Phase 2: Refinements ===== | ||
Line 192: | Line 211: | ||
Second, we create two instances of [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLabelLayout.html | AxisLabelLayout]] and initialize them with the [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLayout.html | AxisLayouts]]. | Second, we create two instances of [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLabelLayout.html | AxisLabelLayout]] and initialize them with the [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLayout.html | AxisLayouts]]. | ||
- | |||
- | When these layout actions are run, they generate new visual items for the axis labels. | ||
<code java> | <code java> | ||
Line 227: | Line 244: | ||
{{:java:scatterplot3.java|Source Code}} | {{:java:scatterplot3.java|Source Code}} | ||
+ | === Further Information === | ||
+ | |||
+ | __Alternative:__ Instead of extending RendererFactory, we can use [[http://prefuse.org/doc/api/prefuse/render/DefaultRendererFactory.html | DefaultRendererFactory]]. | ||
+ | In this case we use predicates to identify the axes. | ||
+ | |||
+ | <code java> | ||
+ | DefaultRendererFactory rf = new DefaultRendererFactory(); | ||
+ | rf.setDefaultRenderer(new ShapeRenderer(7)); | ||
+ | rf.add(new InGroupPredicate("ylab"), | ||
+ | new AxisRenderer(Constants.FAR_LEFT, Constants.CENTER)); | ||
+ | rf.add(new InGroupPredicate("xlab"), | ||
+ | new AxisRenderer(Constants.CENTER, Constants.FAR_BOTTOM)); | ||
+ | vis.setRendererFactory(rf); | ||
+ | </code> | ||
+ | |||
+ | |||
+ | __Warning:__ When the [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLabelLayout.html | AxisLabelLayout]] actions are run, | ||
+ | they generate new visual items for the axis labels. | ||
+ | |||
+ | "xlab" and "xlab" are the group names of these visual items. | ||
+ | Do not put the group name of your data here! | ||
===== Phase 4: Interactivity ===== | ===== Phase 4: Interactivity ===== | ||
Line 272: | Line 310: | ||
{{:java:scatterplot4.java|Source Code}} | {{:java:scatterplot4.java|Source Code}} | ||
+ | |||
+ | === Further Information === | ||
+ | |||
+ | __Warning:__ Panning and Zooming doesn't work as one might expect: it simply performs a geometric transformation of the display. | ||
+ | |||
+ | __Alternative:__ Using range sliders is an possible alternative to zoom and pan. | ||
+ | However the underlying interaction metaphor is different and can be harder to understand. | ||
+ | |||
+ | ===== Phase 5: Refinements on axes ===== | ||
+ | |||
+ | Now, we add a few refinements on the display of axis. | ||
+ | |||
+ | First, we add a new column to the [[http://prefuse.org/doc/api/prefuse/visual/VisualTable.html | VisualTable]], which will contain a derived value from two columns. We also insert captions and format one of the variables. | ||
+ | |||
+ | __Note:__ In order to access the [[http://prefuse.org/doc/api/prefuse/visual/VisualTable.html | VisualTable]] we have to replace ''add'' with ''addTable''. | ||
+ | |||
+ | <code java> | ||
+ | VisualTable vt = vis.addTable("data", data); | ||
+ | |||
+ | vt.addColumn("label", | ||
+ | "CONCAT('NBZ: ', [NBZ], '; BMI: ', FORMAT([BMI],1))"); | ||
+ | </code> | ||
+ | |||
+ | Second, we set the range of the y axis, so that values from 1 to 40 are visible. | ||
+ | <code java> | ||
+ | y_axis.setRangeModel(new NumberRangeModel(1, 40, 1, 40)); | ||
+ | </code> | ||
+ | |||
+ | Third, we use a square root scale for y axis. | ||
+ | <code java> | ||
+ | y_axis.setScale(Constants.SQRT_SCALE); | ||
+ | y_labels.setScale(Constants.SQRT_SCALE); | ||
+ | </code> | ||
+ | |||
+ | Fourth, we define a number format and use it for y axis labels. | ||
+ | <code java> | ||
+ | NumberFormat nf = NumberFormat.getNumberInstance(); | ||
+ | nf.setMaximumFractionDigits(1); | ||
+ | nf.setMinimumFractionDigits(1); | ||
+ | y_labels.setNumberFormat(nf); | ||
+ | </code> | ||
+ | |||
+ | Finally, we use the new derived column for tool tips. | ||
+ | <code java> | ||
+ | ToolTipControl ttc = new ToolTipControl("label"); | ||
+ | </code> | ||
+ | |||
+ | This is the resulting visualization: | ||
+ | |||
+ | {{:java:prefuse-scatterplot5.png?150}} | ||
+ | |||
+ | {{:java:scatterplot5.java|Source Code}} | ||
+ | |||
+ | ===== Phase 6: Bounding boxes for data and axes ===== | ||
+ | |||
+ | Here we manipulate the bounding boxes for the visualization of the data and both axes. This has two effects: | ||
+ | |||
+ | * We have fine-grained access on the location of visual items | ||
+ | * In this example we do not display vertical grid lines behind the data visualizations. | ||
+ | * Further, we draw horizontal grid line to the right edge, but do not place data items there. | ||
+ | * Finally, we added some space between y axis labels and the left-most data items. | ||
+ | * We do not need the empty border defined in phase 2 anymore. | ||
+ | * Now we or someone else using the [[http://prefuse.org/doc/api/prefuse/Display.html | Display]] can add a border, and items at the edge of the visualization (especially axis labels) stay completely visible. | ||
+ | |||
+ | |||
+ | First, we define three rectangles for the visualization of the data and both axes. | ||
+ | |||
+ | <code java> | ||
+ | final Rectangle2D boundsData = new Rectangle2D.Double(); | ||
+ | final Rectangle2D boundsLabelsX = new Rectangle2D.Double(); | ||
+ | final Rectangle2D boundsLabelsY = new Rectangle2D.Double(); | ||
+ | </code> | ||
+ | |||
+ | Second, we set the bounding box for data items on the [[http://prefuse.org/doc/api/prefuse/action/layout/AxisLayout.html | AxisLayouts]]. | ||
+ | |||
+ | <code java> | ||
+ | x_axis.setLayoutBounds(boundsData); | ||
+ | y_axis.setLayoutBounds(boundsData); | ||
+ | </code> | ||
+ | |||
+ | Third, we add the bounding box for axes to their initialization. | ||
+ | |||
+ | <code java> | ||
+ | AxisLabelLayout x_labels = new AxisLabelLayout("xlab", x_axis, | ||
+ | boundsLabelsX); | ||
+ | |||
+ | AxisLabelLayout y_labels = new AxisLabelLayout("ylab", y_axis, | ||
+ | boundsLabelsY); | ||
+ | </code> | ||
+ | |||
+ | Fourth, instead of the empty border, we now use a titled border. | ||
+ | |||
+ | <code java> | ||
+ | display.setBorder(BorderFactory.createTitledBorder("Demo")); | ||
+ | </code> | ||
+ | |||
+ | Fifth, we add the call to a new method ''updateBounds()'', before each call to an action list (two times). | ||
+ | |||
+ | <code java> | ||
+ | updateBounds(display, boundsData, boundsLabelsX, boundsLabelsY); | ||
+ | ... | ||
+ | updateBounds(display, boundsData, boundsLabelsX, boundsLabelsY); | ||
+ | </code> | ||
+ | |||
+ | Finally, this is the method ''updateBounds()'', which sets the bounds as described above. | ||
+ | |||
+ | <code java> | ||
+ | private static void updateBounds(Display display, Rectangle2D boundsData, | ||
+ | Rectangle2D boundsLabelsX, Rectangle2D boundsLabelsY) { | ||
+ | |||
+ | int paddingLeft = 30; | ||
+ | int paddingTop = 15; | ||
+ | int paddingRight = 30; | ||
+ | int paddingBottom = 15; | ||
+ | |||
+ | int axisWidth = 20; | ||
+ | int axisHeight = 10; | ||
+ | |||
+ | Insets i = display.getInsets(); | ||
+ | |||
+ | int left = i.left + paddingLeft; | ||
+ | int top = i.top + paddingTop; | ||
+ | int innerWidth = display.getWidth() - i.left - i.right - paddingLeft | ||
+ | - paddingRight; | ||
+ | int innerHeight = display.getHeight() - i.top - i.bottom - paddingTop | ||
+ | - paddingBottom; | ||
+ | |||
+ | boundsData.setRect(left + axisWidth, top, innerWidth - axisWidth, | ||
+ | innerHeight - axisHeight); | ||
+ | boundsLabelsX.setRect(left + axisWidth, top + innerHeight - axisHeight, | ||
+ | innerWidth - axisWidth, axisHeight); | ||
+ | boundsLabelsY.setRect(left, top, innerWidth + paddingRight, innerHeight | ||
+ | - axisHeight); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | |||
+ | This is the resulting visualization: | ||
+ | |||
+ | {{:java:prefuse-scatterplot6.png?150}} | ||
+ | |||
+ | {{:java:scatterplot6.java|Source Code}} | ||
+ | |||
+ | === Further Information === | ||
+ | |||
+ | __Note:__ If [[http://prefuse.org/doc/api/prefuse/render/AxisRenderer.html | AxisRenderer]] have the ''FAR_LEFT'', ''FAR_RIGHT'', ''FAR_TOP'', or ''FAR_BOTTOM'', the grid lines will fill the bounding box completely and labels will be placed outside :!: the bounding box. | ||
+ | |||
+ | __Warning:__ Be careful, prefuse will not stop you from designing misleading visualizations, if the bounding boxes of data and axes are not correctly aligned :!: | ||
+ | |||
+ | __Note:__ The bounding rectangles are final variables so that they are accessible from inner classes. | ||
+ | Alternatively they could be defined as instance variables. | ||
+ | |||
+ | __Alternative:__ Instead of the method ''updateBounds()'', we can write an action that takes care of updating the bounding rectangles. | ||
+ | Then, this task can be included in the ''update'' action list. | ||
+ | |||
+ |
alex @ ieg: home about me publications research