Java tutorial
/* * Copyright 2011-2012 Follett Software Company * * This file is part of PerfMon4j(tm). * * Perfmon4j is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License, version 3, * as published by the Free Software Foundation. This program is distributed * WITHOUT ANY WARRANTY OF ANY KIND, WITHOUT AN IMPLIED WARRANTY OF MERCHANTIBILITY, * OR FITNESS FOR A PARTICULAR PURPOSE. You should have received a copy of the GNU Lesser General Public * License, Version 3, along with this program. If not, you can obtain the LGPL v.s at * http://www.gnu.org/licenses/ * * perfmon4j@fsc.follett.com * David Deuchert * Follett Software Company * 1391 Corporate Drive * McHenry, IL 60050 * */ package org.perfmon4j.visualvm.chart; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JPanel; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.DateTickUnit; import org.jfree.chart.axis.DateTickUnitType; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.CrosshairState; import org.jfree.chart.plot.PlotRenderingInfo; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.SamplingXYLineRenderer; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYItemRendererState; import org.jfree.data.Range; import org.jfree.data.time.Second; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.time.TimeSeriesDataItem; import org.jfree.data.xy.XYDataset; import org.perfmon4j.remotemanagement.intf.FieldKey; public class DynamicTimeSeriesChart extends JPanel implements FieldManager.FieldHandler { private static final long serialVersionUID = 1L; private final TimeSeriesCollection dataset; private final XYItemRenderer renderer; private final int maxAgeInSeconds; private final Object elementLock = new Object(); private final List<ElementWrapper> currentFields = new ArrayList<ElementWrapper>(); private static int nextLabel = 0; private final BasicStroke NORMAL_STROKE = new BasicStroke(1.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); private final BasicStroke HIGHLIGHTED_STROKE = new BasicStroke(2.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); public DynamicTimeSeriesChart(int maxAgeInSeconds) { super(new BorderLayout()); this.maxAgeInSeconds = maxAgeInSeconds; dataset = new TimeSeriesCollection(); renderer = new MyXYRenderer(); renderer.setBaseStroke(NORMAL_STROKE); NumberAxis numberAxis = new NumberAxis(); numberAxis.setAutoRange(false); numberAxis.setRange(new Range(0d, 100d)); DateAxis dateAxis = new DateAxis(); dateAxis.setDateFormatOverride(new SimpleDateFormat("HH:mm:ss")); dateAxis.setAutoRange(true); dateAxis.setFixedAutoRange(maxAgeInSeconds * 1000); dateAxis.setTickUnit(new DateTickUnit(DateTickUnitType.SECOND, 30)); XYPlot plot = new XYPlot(dataset, dateAxis, numberAxis, renderer); JFreeChart chart = new JFreeChart(null, null, plot, false); chart.setBackgroundPaint(Color.white); ChartPanel chartPanel = new ChartPanel(chart); chartPanel.setDomainZoomable(false); chartPanel.setRangeZoomable(false); chartPanel.setPopupMenu(null); chartPanel.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1), BorderFactory.createLineBorder(Color.black))); add(chartPanel); } // static class ToolTipGenerator implements XYToolTipGenerator { // // @Override // public String generateToolTip(XYDataset xyd, int series, int item) { // Number number = xyd.getXValue(series, item); // return number.toString(); // } // } static class TimeSeriesWithFactor extends TimeSeries { private float factor; public TimeSeriesWithFactor(Comparable c, Class clazz) { super(c, clazz); factor = 1.0f; } public float getFactor() { return factor; } public void setFactor(float factor) { this.factor = factor; } public Number adjustNumberBasedOnFactor(Number value) { Number result = value; if (value != null) { double dValue = value.doubleValue(); dValue *= factor; dValue = Math.max(0.0d, dValue); result = new Double(Math.min(100.0, dValue)); } return result; } } // public static class MyXYRenderer extends XYLineAndShapeRenderer { public static class MyXYRenderer extends SamplingXYLineRenderer { MyXYRenderer() { super(); } @Override public void drawItem(java.awt.Graphics2D g2, XYItemRendererState state, java.awt.geom.Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item, CrosshairState crosshairState, int pass) { TimeSeriesCollection col = (TimeSeriesCollection) dataset; TimeSeriesWithFactor s = (TimeSeriesWithFactor) col.getSeries(series); TimeSeriesDataItem dataItemCurrent = (TimeSeriesDataItem) s.getItems().get(item); TimeSeriesDataItem dataItemPrevious = null; if (item > 0) { dataItemPrevious = (TimeSeriesDataItem) s.getItems().get(item - 1); } Number current = dataItemCurrent.getValue(); Number previous = dataItemPrevious == null ? null : dataItemPrevious.getValue(); try { dataItemCurrent.setValue(s.adjustNumberBasedOnFactor(current)); if (dataItemPrevious != null) { dataItemPrevious.setValue(s.adjustNumberBasedOnFactor(previous)); } super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis, dataset, series, item, crosshairState, pass); } finally { dataItemCurrent.setValue(current); if (dataItemPrevious != null) { dataItemPrevious.setValue(previous); } } } } private ElementWrapper getWrapperForKey(FieldKey fieldKey) { ElementWrapper result = null; synchronized (elementLock) { Iterator<ElementWrapper> itr = currentFields.iterator(); while (itr.hasNext() && result == null) { ElementWrapper w = itr.next(); if (w.element.getFieldKey().equals(fieldKey)) { result = w; } } } return result; } private ElementWrapper removeWrapperForKey(FieldKey fieldKey) { ElementWrapper result = null; synchronized (elementLock) { result = getWrapperForKey(fieldKey); if (result != null) { currentFields.remove(result); } } return result; } @Override public void addOrUpdateElement(FieldElement element) { if (element.isNumeric()) { synchronized (elementLock) { ElementWrapper wrapper = getWrapperForKey(element.getFieldKey()); if (wrapper == null) { // Adding a new field... TimeSeriesWithFactor timeSeries = new TimeSeriesWithFactor(Integer.toString(nextLabel++), Second.class); timeSeries.setMaximumItemAge(maxAgeInSeconds); dataset.addSeries(timeSeries); int offset = dataset.getSeries().indexOf(timeSeries); renderer.setSeriesPaint(offset, element.getColor()); wrapper = new ElementWrapper(element, timeSeries); currentFields.add(wrapper); } else { // We are updating an existing field... wrapper.element = element; renderer.setSeriesPaint(dataset.getSeries().indexOf(wrapper.timeSeries), element.getColor()); } } } } @Override public void handleData(Map<FieldKey, Object> data) { synchronized (elementLock) { Iterator<FieldKey> itr = data.keySet().iterator(); Second now = new Second(); while (itr.hasNext()) { FieldKey field = itr.next(); ElementWrapper wrapper = getWrapperForKey(field); if (wrapper != null) { wrapper.timeSeries.setFactor(wrapper.element.getFactor()); wrapper.timeSeries.add(now, (Number) data.get(field)); int offset = dataset.getSeries().indexOf(wrapper.timeSeries); renderer.setSeriesVisible(offset, Boolean.valueOf(wrapper.element.isVisibleInChart())); renderer.setSeriesStroke(offset, wrapper.element.isHighlighted() ? HIGHLIGHTED_STROKE : NORMAL_STROKE); } } } } @Override public void removeElement(FieldElement element) { if (element.isNumeric()) { synchronized (elementLock) { ElementWrapper wrapper = removeWrapperForKey(element.getFieldKey()); if (wrapper != null) { // Adding a new field... TimeSeries timeSeries = wrapper.timeSeries; dataset.removeSeries(timeSeries); // Must reset all of the colors Iterator<ElementWrapper> itr = currentFields.iterator(); while (itr.hasNext()) { ElementWrapper w = itr.next(); int offset = dataset.getSeries().indexOf(w.timeSeries); renderer.setSeriesPaint(offset, w.element.getColor()); } } } } } private static class ElementWrapper { private FieldElement element; private TimeSeriesWithFactor timeSeries; ElementWrapper(FieldElement element, TimeSeriesWithFactor timeSeries) { this.element = element; this.timeSeries = timeSeries; } } }