package com.ibm.nmon.gui.chart.builder; import org.slf4j.Logger; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.util.List; import java.util.Set; import org.jfree.chart.JFreeChart; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.Plot; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.title.LegendTitle; import org.jfree.chart.title.TextTitle; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import com.ibm.nmon.data.DataSet; import com.ibm.nmon.data.DataTuple; import com.ibm.nmon.data.DataType; import com.ibm.nmon.analysis.Statistic; import com.ibm.nmon.chart.definition.BaseChartDefinition; import com.ibm.nmon.chart.definition.YAxisChartDefinition; import com.ibm.nmon.data.definition.DataDefinition; import com.ibm.nmon.data.definition.NamingMode; import static com.ibm.nmon.data.definition.NamingMode.*; import com.ibm.nmon.gui.chart.data.DataTupleDataset; import com.ibm.nmon.interval.Interval; import com.ibm.nmon.util.GranularityHelper; /** * <p> * Base class that defines a framework for creating {@link JFreeChart JFreeChart charts}. * </p> * * <p> * Users of this class should first call {@link #initChart()} to initialize and set up the chart. * Any subclass methods should then be called to perform other setup or add data to the chart. * Finally, {@link #getChart()} should be called to retrieve the chart from the builder. Once * <code>getChart()</code> is called, it <em>cannot</em> be called again until * <code>initChart()</code> is called. * </p> */ abstract class BaseChartBuilder<C extends BaseChartDefinition> { protected static final Font TITLE_FONT = new Font("null", Font.BOLD, 18); protected static final Font SUBTITLE_FONT = new Font("null", Font.PLAIN, 16); protected static final Font LABEL_FONT = new Font("null", Font.BOLD, 16); protected static final Font AXIS_FONT = new Font("null", Font.PLAIN, 14); protected static final Font LEGEND_FONT = new Font("null", Font.PLAIN, 14); protected static final Color GRID_COLOR = Color.LIGHT_GRAY; protected static final BasicStroke GRID_LINES = new BasicStroke(0.5f, 0, 0, 1.0f, new float[] { 5.0f, 2.0f }, 0); protected static final java.awt.Color OUTLINE_COLOR = new java.awt.Color(0xCCCCCC); protected static final BasicStroke OUTLINE_STROKE = new BasicStroke(3); protected final Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); private Interval interval; private int granularity = GranularityHelper.DEFAULT_GRANULARITY; private List<ChartBuilderPlugin> plugins; protected JFreeChart chart; protected C definition; protected BaseChartBuilder() { interval = Interval.DEFAULT; } public Interval getInterval() { return interval; } public final void setInterval(Interval interval) { this.interval = interval; } public final int getGranularity() { return granularity; } public final void setGranularity(int granularity) { if (granularity < 1) { throw new IllegalArgumentException("granularity must be greater than 1"); } this.granularity = granularity; } /** * Creates the chart, formats it and calls any {@link ChartBuilderPlugin plugins}. */ public final void initChart(C definition) { if (definition == null) { throw new IllegalArgumentException("chart definition cannot be null"); } this.definition = definition; chart = createChart(); chart.setSubtitles(java.util.Collections.singletonList(new TextTitle())); formatChart(); if (plugins != null) { for (ChartBuilderPlugin plugin : plugins) { plugin.configureChart(chart); } } } /** * Retrieve the chart from the builder. * * @throws IllegalStateException if {@link #initChart()} has not been called} */ public final JFreeChart getChart() { if (chart == null) { throw new IllegalStateException("initChart() must be called first"); } updateSubtitle(); try { return chart; } finally { chart = null; definition = null; } } public final void addPlugin(ChartBuilderPlugin plugin) { if (chart != null) { throw new IllegalStateException("plugins must be added before initChart() is called"); } if (plugins == null) { plugins = new java.util.ArrayList<ChartBuilderPlugin>(2); } plugins.add(plugin); } /** * Create a usable chart object. This method should not format the chart, it should only * instantiate it. * * @see #formatChart() */ protected abstract JFreeChart createChart(); /** * Apply formatting to the chart created by {@link #createChart()}. */ protected void formatChart() { if (chart == null) { throw new IllegalStateException("initChart() must be called first"); } chart.getTitle().setFont(TITLE_FONT); ((TextTitle) chart.getSubtitle(0)).setFont(SUBTITLE_FONT); ((TextTitle) chart.getSubtitle(0)).setPadding(new RectangleInsets(0, 0, 0, 0)); chart.setBackgroundPaint(Color.WHITE); chart.setPadding(new RectangleInsets(5, 5, 5, 5)); Plot plot = chart.getPlot(); // chart has no outline on a white background plot.setOutlineStroke(null); plot.setBackgroundPaint(Color.WHITE); } protected final void addLegend() { if (chart == null) { throw new IllegalStateException("initChart() must be called first"); } LegendTitle legend = new LegendTitle(chart.getPlot()); legend.setItemFont(LEGEND_FONT); legend.setBorder(0, 0, 0, 0); legend.setBackgroundPaint(Color.WHITE); legend.setPosition(RectangleEdge.BOTTOM); RectangleInsets padding = new RectangleInsets(5, 5, 5, 5); legend.setItemLabelPadding(padding); chart.addLegend(legend); } private final void updateSubtitle() { TextTitle subtitle = (TextTitle) chart.getSubtitle(0); if (definition.getSubtitleNamingMode() == NONE) { subtitle.setText(""); return; } Set<Statistic> stats = new java.util.HashSet<Statistic>(); for (DataDefinition dataDefinition : definition.getData()) { stats.add(dataDefinition.getStatistic()); } if (definition.getSubtitleNamingMode() == STAT) { if (stats.size() == 1) { subtitle.setText(stats.iterator().next().toString()); } else { subtitle.setText(""); } return; } // get the chart Datasets Plot plot = chart.getPlot(); Iterable<DataTuple> allTuples = null; DataTupleDataset data1 = null; DataTupleDataset data2 = null; boolean combineDataSets = false; if (definition instanceof YAxisChartDefinition) { combineDataSets = ((YAxisChartDefinition) definition).hasSecondaryYAxis(); } if (plot instanceof XYPlot) { data1 = ((DataTupleDataset) ((XYPlot) plot).getDataset(0)); if (combineDataSets) { data2 = ((DataTupleDataset) ((XYPlot) plot).getDataset(1)); } } else { // currently only XYPlot and CategoryPlot are used data1 = ((DataTupleDataset) ((CategoryPlot) plot).getDataset(0)); if (combineDataSets) { data2 = ((DataTupleDataset) ((CategoryPlot) plot).getDataset(1)); } } allTuples = data1.getAllTuples(); // combine the tuples if needed if (combineDataSets) { Set<DataTuple> combined = new java.util.HashSet<DataTuple>(); for (DataTuple tuple : allTuples) { combined.add(tuple); } allTuples = data2.getAllTuples(); for (DataTuple tuple : allTuples) { combined.add(tuple); } allTuples = combined; } // get the unique set of each DataSet, DataType and field Set<DataSet> dataSets = new java.util.HashSet<DataSet>(); Set<DataType> dataTypes = new java.util.HashSet<DataType>(); Set<String> fields = new java.util.HashSet<String>(); for (DataTuple t : allTuples) { dataSets.add(t.getDataSet()); dataTypes.add(t.getDataType()); fields.add(t.getField()); } // now set the subtitle based on the NamingMode NamingMode mode = definition.getSubtitleNamingMode(); // only set if there is a single data set, type of field if (mode == HOST) { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname()); } else { subtitle.setText(""); } } else if (mode == TYPE) { if (dataTypes.size() == 1) { subtitle.setText(dataTypes.iterator().next().toString()); } else { subtitle.setText(""); } } else if (mode == FIELD) { if (fields.size() == 1) { subtitle.setText(fields.iterator().next()); } else { subtitle.setText(""); } } // for compound modes, both must be unique; if one part is unique, use that instead else if (mode == HOST_TYPE) { if (dataTypes.size() == 1) { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname() + SEPARATOR + dataTypes.iterator().next().toString()); } else { subtitle.setText(dataTypes.iterator().next().toString()); } } else { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname()); } else { subtitle.setText(""); } } } else if (mode == HOST_FIELD) { if (fields.size() == 1) { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname() + SEPARATOR + fields.iterator().next()); } else { subtitle.setText(fields.iterator().next()); } } else { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname()); } else { subtitle.setText(""); } } } else if (mode == HOST_STAT) { if (stats.size() == 1) { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname() + SEPARATOR + stats.iterator().next().toString()); } else { subtitle.setText(stats.iterator().next().toString()); } } else { if (dataSets.size() == 1) { subtitle.setText(dataSets.iterator().next().getHostname()); } else { subtitle.setText(""); } } } else if (mode == TYPE_FIELD) { if (fields.size() == 1) { if (dataTypes.size() == 1) { subtitle.setText(dataTypes.iterator().next().toString() + SEPARATOR + fields.iterator().next()); } else { subtitle.setText(fields.iterator().next()); } } else { if (dataTypes.size() == 1) { subtitle.setText(dataTypes.iterator().next().toString()); } else { subtitle.setText(""); } } } else if (mode == TYPE_STAT) { if (stats.size() == 1) { if (dataTypes.size() == 1) { subtitle.setText(dataTypes.iterator().next().toString() + SEPARATOR + stats.iterator().next().toString()); } else { subtitle.setText(stats.iterator().next().toString()); } } else { if (dataTypes.size() == 1) { subtitle.setText(stats.iterator().next().toString()); } else { subtitle.setText(""); } } } else if (mode == FIELD_STAT) { if (stats.size() == 1) { if (fields.size() == 1) { subtitle.setText(fields.iterator().next() + SEPARATOR + stats.iterator().next().toString()); } else { subtitle.setText(stats.iterator().next().toString()); } } else { if (fields.size() == 1) { subtitle.setText(fields.iterator().next()); } else { subtitle.setText(""); } } } } }