package com.webfoot.prefuse; import java.awt.Insets; import java.awt.geom.Rectangle2D; import javax.swing.BorderFactory; import javax.swing.JComboBox; 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.layout.AxisLabelLayout; import prefuse.action.layout.AxisLayout; import prefuse.data.Table; import prefuse.data.query.NumberRangeModel; import prefuse.render.AxisRenderer; import prefuse.render.PolygonRenderer; import prefuse.render.Renderer; import prefuse.render.RendererFactory; import prefuse.util.ColorLib; import prefuse.visual.VisualItem; import prefuse.visual.expression.VisiblePredicate; import com.webfoot.prefuse.BarRenderer; import com.webfoot.prefuse.HistogramTable; /** * A simple histogram visualization that allows different columns * in a data table to be histogramized and displayed. * The starting point was ScatterPlot.java by Jeffrey Heer, but * Kaitlin Duck Sherwood has modified it quite extensively. * * Kaitlin Duck Sherwood's modifications are granted as is for any * commercial or non-commercial use, with or without attribution. * The only conditions are that you can't pretend that you wrote them, * and that you leave these notices about her authorship in the source. * * Known bug: See the comments for HistogramFrame; there might * be a bug in HistogramGraph::updateAxes(), but I sure can't figure * out what it is. I suspect that it is a prefuse bug. * * Note: I wanted to use Prefuse's StackedAreaChart, but couldn't * get it to work. If you figure out how to make it work, please * email me. -- KDS * * @author jeffrey heer * @author Kaitlin Duck Sherwood */ public class HistogramGraph extends Display { protected static final String group = "data"; public static final int DEFAULT_BIN_COUNT = 15; // KDS -- I tend to make things protected instead of private so // that people can subclass them. I'm not sure that's the right // thing to do. protected Rectangle2D m_dataB = new Rectangle2D.Double(); protected Rectangle2D m_xlabB = new Rectangle2D.Double(); protected Rectangle2D m_ylabB = new Rectangle2D.Double(); protected BarRenderer m_shapeR = new BarRenderer(10); protected HistogramTable m_histoTable; private AxisLayout m_xAxis, m_yAxis; private AxisLabelLayout m_xLabels, m_yLabels; /** * @param histoTable a histogrammized version of dataTable * @param dataTable a prefuse Table which holds the raw (unhistogrammized) data. * @param startingField the name of the field (column) of the data table * whose histogram is to be shown in the histogram graph. * * Note that the data table isn't used much here, and could maybe be moved into * Histo */ public HistogramGraph(HistogramTable histoTable, String startingField) { super(new Visualization()); m_histoTable = histoTable; startingField = getStartingField(startingField); // -------------------------------------------------------------------- // STEP 1: setup the visualized data m_vis.addTable(group, m_histoTable); initializeRenderer(); // -------------------------------------------------------------------- // STEP 2: create actions to process the visual data initializeAxes(startingField); ColorAction color = new ColorAction(group, VisualItem.STROKECOLOR, ColorLib.rgb(100,100,255)); m_vis.putAction("color", color); ActionList draw = new ActionList(); draw.add(m_xAxis); draw.add(m_yAxis); draw.add(m_xLabels); draw.add(m_yLabels); draw.add(color); draw.add(new RepaintAction()); m_vis.putAction("draw", draw); // -------------------------------------------------------------------- // STEP 3: set up a display and ui components to show the visualization initializeWindowCharacteristics(); // -------------------------------------------------------------------- // STEP 4: launching the visualization m_vis.run("draw"); } /** * This sets up various things about the window, including the size. * TODO include the size in the constructor */ private void initializeWindowCharacteristics() { setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); setSize(1200,800); setHighQuality(true); initializeLayoutBoundsForDisplay(); m_shapeR.setBounds(m_dataB); } /** * @param fieldName the name of the field (column) to display */ private void initializeAxes(String fieldName) { m_xAxis = new AxisLayout(group, fieldName, Constants.X_AXIS, VisiblePredicate.TRUE); m_xAxis.setLayoutBounds(m_dataB); m_vis.putAction("x", m_xAxis); String countField = HistogramTable.getCountField(fieldName); m_yAxis = new AxisLayout(group, countField, Constants.Y_AXIS, VisiblePredicate.TRUE); m_yAxis.setLayoutBounds(m_dataB); m_vis.putAction("y", m_yAxis); m_xLabels = new AxisLabelLayout("xlabels", m_xAxis, m_xlabB); m_vis.putAction("xlabels", m_xLabels); m_yLabels = new AxisLabelLayout("ylabels", m_yAxis, m_ylabB); m_vis.putAction("ylabels", m_yLabels); updateAxes(fieldName); } private void initializeRenderer() { m_vis.setRendererFactory(new RendererFactory() { Renderer yAxisRenderer = new AxisRenderer(Constants.RIGHT, Constants.TOP); Renderer xAxisRenderer = new AxisRenderer(Constants.CENTER, Constants.FAR_BOTTOM); Renderer barRenderer = new PolygonRenderer(Constants.POLY_TYPE_LINE); public Renderer getRenderer(VisualItem item) { if(item.isInGroup("ylabels")) return yAxisRenderer; if(item.isInGroup("xlabels")) return xAxisRenderer; if(item.isInGroup("barchart")) return barRenderer; return m_shapeR; } }); } // This is taken from CongressDemo.displayLayout. // This puts the axes on the right public void initializeLayoutBoundsForDisplay() { Insets i = getInsets(); int w = getWidth(); int h = getHeight(); int insetWidth = i.left+i.right; int insetHeight = i.top+i.bottom; int yAxisWidth = 85; int xAxisHeight = 10; m_dataB.setRect(i.left, i.top, w-insetWidth-yAxisWidth, h-insetHeight-xAxisHeight); m_xlabB.setRect(i.left, h-xAxisHeight-i.bottom, w-insetWidth-yAxisWidth, xAxisHeight); m_ylabB.setRect(i.left, i.top, w-insetWidth, h-insetHeight-xAxisHeight); m_vis.run("update"); // m_vis.run("xlabels"); // This didn't seem to do anything, but was in the Congress demo } /** * @param dataField the name of the column in histoTable to display */ public void updateAxes(String dataField) { // The extra variable defs are probably unneeded, but // date from the time I was trying to debug the // xaxis labelling bug. Left in to make future // debugging easier. AxisLayout xaxis = getXAxis(); AxisLayout yaxis = getYAxis(); AxisLabelLayout xlabels = getXLabels(); AxisLabelLayout ylabels = getYLabels(); xaxis.setScale(Constants.LINEAR_SCALE); xaxis.setDataField(dataField); xaxis.setDataType(getAxisType(dataField)); xlabels.setRangeModel(null); // setting to null seems to force a recalc -> redraw /* also used trying to debug the axis bug -- KDS if(isNumeric(dataField)) { double min, max; min = m_histoTable.getBinMin(dataField); max =m_histoTable.getBinMax(dataField); NumberRangeModel xrangeModel = new NumberRangeModel(min, max, min, max); xlabels.setRangeModel(xrangeModel); } */ // yaxis is the bin counts, which are always numeric String countField = HistogramTable.getCountField(dataField); yaxis.setDataField(countField); // I could set the range model to null as above, but with histograms, // you really want the bars to go from 0 to max, not from min to max. -- KDS NumberRangeModel rangeModel = new NumberRangeModel(0, m_histoTable.getCountMax(dataField), 0, m_histoTable.getCountMax(dataField)); yaxis.setRangeModel(rangeModel); ylabels.setRangeModel(rangeModel); m_vis.run("draw"); } /** * @param dataField the name of a column in histoTable to display * @return isNumeric boolean which says if the column named by dataField * is int, float, or double or if it is not. Note that booleans are * treated as non-numerics under this logic. */ private boolean isNumeric(String dataField) { return m_histoTable.getColumn(dataField).canGetDouble(); } /** * @param dataField the name of a column to display * @return the type of the axis (NUMERICAL for numbers and ORDINAL for strings) * Note that HistogramGraph hasn't been tested with boolean or derived fields. * I believe that HistogramTable treats booleans as strings. -- KDS */ protected int getAxisType(String dataField) { if( isNumeric(dataField) ) { return Constants.NUMERICAL; } else { // completely untested with derived columns or strings return Constants.ORDINAL; } } /** * @param startingField * @return either the input or the first field in the data table */ protected String getStartingField(String startingField) { if(null == startingField) { startingField = m_histoTable.getColumnName(0); } return startingField; } // These getters were purely for help debugging the axis problem. // As I haven't chased that down completely, I've left them in. -- KDS protected AxisLayout getXAxis() { return m_xAxis; } protected AxisLayout getYAxis() { return m_yAxis; } protected AxisLabelLayout getXLabels() { return m_xLabels; } protected AxisLabelLayout getYLabels() { return m_yLabels; } protected HistogramTable getHistoTable() { return m_histoTable; } public static int getDefaultBinCount() { return DEFAULT_BIN_COUNT; } } // end of class HistogramGraph