Java tutorial
/** * Copyright (C) 2012 * by 52 North Initiative for Geospatial Open Source Software GmbH * * Contact: Andreas Wytzisk * 52 North Initiative for Geospatial Open Source Software GmbH * Martin-Luther-King-Weg 24 * 48155 Muenster, Germany * info@52north.org * * This program is free software; you can redistribute and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * * This program is distributed WITHOUT ANY WARRANTY; even without the implied * WARRANTY OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this program (see gnu-gpl v2.txt). If not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or * visit the Free Software Foundation web page, http://www.fsf.org. */ package org.n52.server.sos.render; import static org.n52.server.mgmt.ConfigurationContext.getServiceMetadata; import static org.n52.server.sos.parser.TimeseriesFactory.compressToTimeSeries; import static org.n52.server.sos.parser.TimeseriesFactory.createTimeSeries; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.geom.Ellipse2D; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.DateAxis; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.ValueMarker; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYAreaRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.chart.title.TextTitle; import org.jfree.chart.urls.XYURLGenerator; import org.jfree.data.general.DatasetGroup; import org.jfree.data.time.Second; import org.jfree.data.time.TimeSeries; import org.jfree.data.time.TimeSeriesCollection; import org.jfree.data.xy.XYDataset; import org.jfree.ui.HorizontalAlignment; import org.jfree.ui.RectangleEdge; import org.jfree.ui.RectangleInsets; import org.jfree.ui.VerticalAlignment; import org.n52.oxf.feature.OXFFeatureCollection; import org.n52.oxf.feature.sos.ObservationSeriesCollection; import org.n52.oxf.util.JavaHelper; import org.n52.server.mgmt.ConfigurationContext; import org.n52.server.sos.generator.MetadataInURLGenerator; import org.n52.server.sos.parser.TimeseriesFactory; import org.n52.shared.serializable.pojos.Axis; import org.n52.shared.serializable.pojos.DesignOptions; import org.n52.shared.serializable.pojos.TimeseriesProperties; import org.n52.shared.serializable.pojos.sos.Feature; import org.n52.shared.serializable.pojos.sos.Phenomenon; import org.n52.shared.serializable.pojos.sos.Procedure; import org.n52.shared.serializable.pojos.sos.SOSMetadata; import org.n52.shared.serializable.pojos.sos.TimeseriesParametersLookup; public class DiagramRenderer { private static final String DOTTED = "3"; private static final String AREA = "2"; private static final String LINE = "1"; private static final int TICK_FONT_SIZE = 10; private static final int LABEL_FONT_SIZE = 12; private static final Color LABEL_COLOR = new Color(0, 0, 0); private static final String LABEL_FONT = "Arial"; private final Font label = new Font(LABEL_FONT, Font.BOLD, LABEL_FONT_SIZE); private final Font tickLabelDomain = new Font(LABEL_FONT, Font.PLAIN, TICK_FONT_SIZE); private HashMap<String, Axis> axisMapping = new HashMap<String, Axis>(); private boolean isOverview = false; public DiagramRenderer(boolean isOverview) { this.isOverview = isOverview; } public HashMap<String, Axis> getAxisMapping() { return this.axisMapping; } /** * Builds up a DesignDescriptionList which stores the information * about the style of each timeseries. * * @param options * the options * @return the design description list */ private DesignDescriptionList buildUpDesignDescriptionList(DesignOptions options) { String domainAxisLabel; if (options.getLanguage() != null && options.getLanguage().equals("de")) { domainAxisLabel = "Zeit"; } else { // default => "en" domainAxisLabel = "Time"; } if (this.isOverview) { domainAxisLabel = null; } DesignDescriptionList designDescriptions = new DesignDescriptionList(domainAxisLabel); String observedPropertyWithGrid = options.getProperties().get(0).getPhenomenon(); for (TimeseriesProperties tsProperties : options.getProperties()) { Color c = JavaHelper.transformToColor(tsProperties.getHexColor()); String phenomenonId = tsProperties.getPhenomenon(); String procedureId = tsProperties.getProcedure(); String featureId = tsProperties.getFeature(); boolean drawGrid = observedPropertyWithGrid.equals(phenomenonId); TimeseriesParametersLookup lookup = getParameterLookup(tsProperties); Feature feature = lookup.getFeature(featureId); Procedure procedure = lookup.getProcedure(procedureId); Phenomenon phenomenon = lookup.getPhenomenon(phenomenonId); designDescriptions.add(phenomenon, procedure, feature, tsProperties, new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) tsProperties.getOpacity() * 255 / 100), tsProperties.getLineStyle(), tsProperties.getLineWidth(), drawGrid); } return designDescriptions; } private TimeseriesParametersLookup getParameterLookup(TimeseriesProperties properties) { String serviceUrl = properties.getServiceUrl(); try { SOSMetadata metadata = getServiceMetadata(serviceUrl); return metadata.getTimeseriesParametersLookup(); } catch (Exception e) { throw new IllegalStateException("No parameter lookup available for service '" + serviceUrl + "'.", e); } } /** * <pre> * dataset := associated to one range-axis; * corresponds to one observedProperty; * may contain multiple series; * series := corresponds to a time series for one foi * </pre> * * . * * @param entireCollMap * the entire coll map * @param options * the options * @param begin * the begin * @param end * the end * @param compress * @return the j free chart */ public JFreeChart renderChart(Map<String, OXFFeatureCollection> entireCollMap, DesignOptions options, Calendar begin, Calendar end, boolean compress) { DesignDescriptionList designDescriptions = buildUpDesignDescriptionList(options); /*** FIRST RUN ***/ JFreeChart chart = initializeTimeSeriesChart(); chart.setBackgroundPaint(Color.white); if (!this.isOverview) { chart.addSubtitle(new TextTitle(ConfigurationContext.COPYRIGHT, new Font(LABEL_FONT, Font.PLAIN, 9), Color.black, RectangleEdge.BOTTOM, HorizontalAlignment.RIGHT, VerticalAlignment.BOTTOM, new RectangleInsets(0, 0, 20, 20))); } XYPlot plot = (XYPlot) chart.getPlot(); plot.setBackgroundPaint(Color.white); plot.setDomainGridlinePaint(Color.lightGray); plot.setRangeGridlinePaint(Color.lightGray); plot.setAxisOffset(new RectangleInsets(2.0, 2.0, 2.0, 2.0)); plot.setDomainCrosshairVisible(true); plot.setRangeCrosshairVisible(true); plot.setDomainGridlinesVisible(options.getGrid()); plot.setRangeGridlinesVisible(options.getGrid()); // add additional datasets: DateAxis dateAxis = (DateAxis) plot.getDomainAxis(); dateAxis.setRange(begin.getTime(), end.getTime()); dateAxis.setDateFormatOverride(new SimpleDateFormat()); // add all axes String[] phenomenaIds = options.getAllPhenomenIds(); // all the axis indices to map them later HashMap<String, Integer> axes = new HashMap<String, Integer>(); for (int i = 0; i < phenomenaIds.length; i++) { axes.put(phenomenaIds[i], i); plot.setRangeAxis(i, new NumberAxis(phenomenaIds[i])); } // list range markers ArrayList<ValueMarker> referenceMarkers = new ArrayList<ValueMarker>(); HashMap<String, double[]> referenceBounds = new HashMap<String, double[]>(); // create all TS collections for (int i = 0; i < options.getProperties().size(); i++) { TimeseriesProperties prop = options.getProperties().get(i); String phenomenonId = prop.getPhenomenon(); TimeSeriesCollection dataset = createDataset(entireCollMap, prop, phenomenonId, compress); dataset.setGroup(new DatasetGroup(prop.getTimeseriesId())); XYDataset additionalDataset = dataset; NumberAxis axe = (NumberAxis) plot.getRangeAxis(axes.get(phenomenonId)); if (this.isOverview) { axe.setAutoRange(true); axe.setAutoRangeIncludesZero(false); } else if (prop.getAxisUpperBound() == prop.getAxisLowerBound() || prop.isAutoScale()) { if (prop.isZeroScaled()) { axe.setAutoRangeIncludesZero(true); } else { axe.setAutoRangeIncludesZero(false); } } else { if (prop.isZeroScaled()) { if (axe.getUpperBound() < prop.getAxisUpperBound()) { axe.setUpperBound(prop.getAxisUpperBound()); } if (axe.getLowerBound() > prop.getAxisLowerBound()) { axe.setLowerBound(prop.getAxisLowerBound()); } } else { axe.setRange(prop.getAxisLowerBound(), prop.getAxisUpperBound()); axe.setAutoRangeIncludesZero(false); } } plot.setDataset(i, additionalDataset); plot.mapDatasetToRangeAxis(i, axes.get(phenomenonId)); // set bounds new for reference values if (!referenceBounds.containsKey(phenomenonId)) { double[] bounds = new double[] { axe.getLowerBound(), axe.getUpperBound() }; referenceBounds.put(phenomenonId, bounds); } else { double[] bounds = referenceBounds.get(phenomenonId); if (bounds[0] >= axe.getLowerBound()) { bounds[0] = axe.getLowerBound(); } if (bounds[1] <= axe.getUpperBound()) { bounds[1] = axe.getUpperBound(); } } double[] bounds = referenceBounds.get(phenomenonId); for (String string : prop.getReferenceValues()) { if (prop.getRefValue(string).show()) { Double value = prop.getRefValue(string).getValue(); if (value <= bounds[0]) { bounds[0] = value; } else if (value >= bounds[1]) { bounds[1] = value; } } } Axis axis = prop.getAxis(); if (axis == null) { axis = new Axis(axe.getUpperBound(), axe.getLowerBound()); } else if (prop.isAutoScale()) { axis.setLowerBound(axe.getLowerBound()); axis.setUpperBound(axe.getUpperBound()); axis.setMaxY(axis.getMaxY()); axis.setMinY(axis.getMinY()); } prop.setAxisData(axis); this.axisMapping.put(prop.getTimeseriesId(), axis); for (String string : prop.getReferenceValues()) { if (prop.getRefValue(string).show()) { referenceMarkers.add(new ValueMarker(prop.getRefValue(string).getValue(), Color.decode(prop.getRefValue(string).getColor()), new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f))); } } plot.mapDatasetToRangeAxis(i, axes.get(phenomenonId)); } for (ValueMarker valueMarker : referenceMarkers) { plot.addRangeMarker(valueMarker); } // show actual time ValueMarker nowMarker = new ValueMarker(System.currentTimeMillis(), Color.orange, new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f)); plot.addDomainMarker(nowMarker); if (!this.isOverview) { Iterator<Entry<String, double[]>> iterator = referenceBounds.entrySet().iterator(); while (iterator.hasNext()) { Entry<String, double[]> boundsEntry = iterator.next(); String phenId = boundsEntry.getKey(); NumberAxis axe = (NumberAxis) plot.getRangeAxis(axes.get(phenId)); axe.setAutoRange(true); // add a margin double marginOffset = (boundsEntry.getValue()[1] - boundsEntry.getValue()[0]) / 25; boundsEntry.getValue()[0] -= marginOffset; boundsEntry.getValue()[1] += marginOffset; axe.setRange(boundsEntry.getValue()[0], boundsEntry.getValue()[1]); } } /**** SECOND RUN ***/ // set domain axis labels: plot.getDomainAxis().setLabelFont(label); plot.getDomainAxis().setLabelPaint(LABEL_COLOR); plot.getDomainAxis().setTickLabelFont(tickLabelDomain); plot.getDomainAxis().setTickLabelPaint(LABEL_COLOR); plot.getDomainAxis().setLabel(designDescriptions.getDomainAxisLabel()); // define the design for each series: for (int datasetIndex = 0; datasetIndex < plot.getDatasetCount(); datasetIndex++) { TimeSeriesCollection dataset = (TimeSeriesCollection) plot.getDataset(datasetIndex); for (int seriesIndex = 0; seriesIndex < dataset.getSeriesCount(); seriesIndex++) { String timeseriesId = (String) dataset.getSeries(seriesIndex).getKey(); RenderingDesign dd = designDescriptions.get(timeseriesId); if (dd != null) { // LINESTYLE: String lineStyle = dd.getLineStyle(); int width = dd.getLineWidth(); if (this.isOverview) { width = width / 2; width = (width == 0) ? 1 : width; } // "1" is lineStyle "line" if (lineStyle.equalsIgnoreCase(LINE)) { XYLineAndShapeRenderer ren = new XYLineAndShapeRenderer(true, false); ren.setStroke(new BasicStroke(width)); plot.setRenderer(datasetIndex, ren); } // "2" is lineStyle "area" else if (lineStyle.equalsIgnoreCase(AREA)) { plot.setRenderer(datasetIndex, new XYAreaRenderer()); } // "3" is lineStyle "dotted" else if (lineStyle.equalsIgnoreCase(DOTTED)) { XYLineAndShapeRenderer ren = new XYLineAndShapeRenderer(false, true); ren.setShape(new Ellipse2D.Double(-width, -width, 2 * width, 2 * width)); plot.setRenderer(datasetIndex, ren); } // "4" is dashed else if (lineStyle.equalsIgnoreCase("4")) { XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(true, false); renderer.setSeriesStroke(0, new BasicStroke(width, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[] { 4.0f * width, 4.0f * width }, 0.0f)); plot.setRenderer(datasetIndex, renderer); } else if (lineStyle.equalsIgnoreCase("5")) { // lines and dots XYLineAndShapeRenderer ren = new XYLineAndShapeRenderer(true, true); int thickness = 2 * width; ren.setShape(new Ellipse2D.Double(-width, -width, thickness, thickness)); ren.setStroke(new BasicStroke(width)); plot.setRenderer(datasetIndex, ren); } else { // default is lineStyle "line" plot.setRenderer(datasetIndex, new XYLineAndShapeRenderer(true, false)); } plot.getRenderer(datasetIndex).setSeriesPaint(seriesIndex, dd.getColor()); // plot.getRenderer(datasetIndex).setShapesVisible(true); XYToolTipGenerator toolTipGenerator = StandardXYToolTipGenerator.getTimeSeriesInstance(); XYURLGenerator urlGenerator = new MetadataInURLGenerator(designDescriptions); plot.getRenderer(datasetIndex).setBaseToolTipGenerator(toolTipGenerator); plot.getRenderer(datasetIndex).setURLGenerator(urlGenerator); // GRID: // PROBLEM: JFreeChart only allows to switch the grid on/off // for the whole XYPlot. And the // grid will always be displayed for the first series in the // plot. I'll always show the // grid. // --> plot.setDomainGridlinesVisible(visible) // RANGE AXIS LABELS: if (isOverview) { plot.getRangeAxisForDataset(datasetIndex).setTickLabelsVisible(false); plot.getRangeAxisForDataset(datasetIndex).setTickMarksVisible(false); plot.getRangeAxisForDataset(datasetIndex).setVisible(false); } else { plot.getRangeAxisForDataset(datasetIndex).setLabelFont(label); plot.getRangeAxisForDataset(datasetIndex).setLabelPaint(LABEL_COLOR); plot.getRangeAxisForDataset(datasetIndex).setTickLabelFont(tickLabelDomain); plot.getRangeAxisForDataset(datasetIndex).setTickLabelPaint(LABEL_COLOR); StringBuilder unitOfMeasure = new StringBuilder(); unitOfMeasure.append(dd.getPhenomenon().getLabel()); String uomLabel = dd.getUomLabel(); if (uomLabel != null && !uomLabel.isEmpty()) { unitOfMeasure.append(" (").append(uomLabel).append(")"); } plot.getRangeAxisForDataset(datasetIndex).setLabel(unitOfMeasure.toString()); } } } } return chart; } private JFreeChart initializeTimeSeriesChart() { String title = null; String xLabel = "Date"; String yLabel = ""; XYDataset data = null; boolean isCreateLegend = false; boolean isCreateTooltips = true; boolean isCreateURLs = true; return ChartFactory.createTimeSeriesChart(title, xLabel, yLabel, data, isCreateLegend, isCreateTooltips, isCreateURLs); } protected JFreeChart renderPreChart(Map<String, OXFFeatureCollection> entireCollMap, String[] observedProperties, ArrayList<TimeSeriesCollection> timeSeries, Calendar begin, Calendar end) { JFreeChart chart = ChartFactory.createTimeSeriesChart(null, // title "Date", // x-axis label observedProperties[0], // y-axis label timeSeries.get(0), // data true, // create legend? true, // generate tooltips? false // generate URLs? ); chart.setBackgroundPaint(Color.white); XYPlot plot = (XYPlot) chart.getPlot(); plot.setBackgroundPaint(Color.white); plot.setDomainGridlinePaint(Color.lightGray); plot.setRangeGridlinePaint(Color.lightGray); plot.setAxisOffset(new RectangleInsets(2.0, 2.0, 2.0, 2.0)); plot.setDomainCrosshairVisible(true); plot.setRangeCrosshairVisible(true); XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) plot.getRenderer(); renderer.setBaseShapesVisible(true); renderer.setBaseShapesFilled(true); // add additional datasets: DateAxis axis = (DateAxis) plot.getDomainAxis(); axis.setRange(begin.getTime(), end.getTime()); axis.setDateFormatOverride(new SimpleDateFormat()); for (int i = 1; i < observedProperties.length; i++) { XYDataset additionalDataset = timeSeries.get(i); plot.setDataset(i, additionalDataset); plot.setRangeAxis(i, new NumberAxis(observedProperties[i])); // plot.getRangeAxis(i).setRange((Double) // overAllSeriesCollection.getMinimum(i), // (Double) overAllSeriesCollection.getMaximum(i)); plot.mapDatasetToRangeAxis(i, i); // plot.getDataset().getXValue(i, i); } return chart; } public TimeSeriesCollection createDataset(Map<String, OXFFeatureCollection> entireCollMap, TimeseriesProperties prop, String observedProperty, boolean compress) { TimeSeriesCollection dataset = new TimeSeriesCollection(); OXFFeatureCollection obsColl = entireCollMap.get(prop.getOffering() + "@" + prop.getServiceUrl()); String foiID = prop.getFeature(); String obsPropID = prop.getPhenomenon(); String procID = prop.getProcedure(); // only if the observation concerns the observedProperty, it // will be added to the dataset if (obsPropID.equals(observedProperty)) { String[] foiIds = new String[] { foiID }; String[] procedureIds = new String[] { procID }; String[] observedPropertyIds = new String[] { obsPropID }; ObservationSeriesCollection seriesCollection = new ObservationSeriesCollection(obsColl, foiIds, observedPropertyIds, procedureIds, true); // // now let's put in the date-value pairs. // ! But put it only in if it differs from the previous // one ! // //TimeSeries timeSeries = new TimeSeries(foiID + "___" + obsPropID + "___" + procID, Second.class); TimeSeries timeSeries = new TimeSeries(prop.getTimeseriesId(), Second.class); if (seriesCollection.getSortedTimeArray().length > 0) { if (compress) { timeSeries = compressToTimeSeries(seriesCollection, prop.getTimeseries(), isOverview, prop.getGraphStyle()); } else { timeSeries = createTimeSeries(seriesCollection, prop.getTimeseries(), prop.getGraphStyle()); } } dataset.addSeries(timeSeries); } dataset.setDomainIsPointsInTime(true); return dataset; } }