teichmann@5831: package org.dive4elements.river.exports; ingo@647: ingo@647: import java.awt.geom.AffineTransform; ingo@647: import java.awt.geom.NoninvertibleTransformException; ingo@647: import java.awt.geom.Rectangle2D; ingo@647: ingo@2273: import java.util.Date; ingo@2273: ingo@647: import org.w3c.dom.Document; ingo@647: import org.w3c.dom.Element; ingo@647: ingo@647: import org.apache.log4j.Logger; ingo@647: ingo@647: import org.jfree.chart.ChartRenderingInfo; ingo@647: import org.jfree.chart.JFreeChart; ingo@2261: import org.jfree.chart.axis.DateAxis; ingo@2273: import org.jfree.chart.axis.NumberAxis; ingo@648: import org.jfree.chart.axis.ValueAxis; ingo@647: import org.jfree.chart.plot.XYPlot; ingo@647: import org.jfree.data.Range; ingo@673: import org.jfree.data.xy.XYDataset; ingo@647: teichmann@5831: import org.dive4elements.artifacts.common.ArtifactNamespaceContext; teichmann@5831: import org.dive4elements.artifacts.common.utils.XMLUtils; teichmann@5831: import org.dive4elements.artifacts.common.utils.XMLUtils.ElementCreator; ingo@647: teichmann@5831: import org.dive4elements.river.jfree.Bounds; ingo@2330: ingo@647: ingo@647: /** ingo@647: * This class helps generating chart info documents. ingo@647: * ingo@647: * @author Ingo Weinzierl ingo@647: */ ingo@647: public class InfoGeneratorHelper { ingo@647: felix@5335: /** Private logging instance. */ ingo@647: private static final Logger logger = ingo@647: Logger.getLogger(InfoGeneratorHelper.class); ingo@647: ingo@2261: protected ChartGenerator generator; ingo@673: ingo@673: ingo@2261: public InfoGeneratorHelper(ChartGenerator generator) { ingo@673: this.generator = generator; ingo@647: } ingo@647: ingo@647: ingo@647: /** ingo@647: * Triggers the creation of the chart info document. ingo@647: * ingo@647: * @param chart The JFreeChart chart. ingo@647: * @param info An info object that has been created while chart creation. ingo@647: * ingo@647: * @return the info document. ingo@647: */ ingo@673: public Document createInfoDocument( ingo@647: JFreeChart chart, ingo@647: ChartRenderingInfo info) ingo@647: { ingo@647: logger.debug("InfoGeneratorHelper.createInfoDocument"); ingo@647: ingo@647: Document doc = XMLUtils.newDocument(); ingo@647: ingo@647: ElementCreator cr = new ElementCreator( ingo@647: doc, ingo@647: ArtifactNamespaceContext.NAMESPACE_URI, ingo@647: ArtifactNamespaceContext.NAMESPACE_PREFIX); ingo@647: ingo@647: Element chartinfo = cr.create("chartinfo"); ingo@647: ingo@659: chartinfo.appendChild(createAxesElements(cr, chart)); ingo@673: chartinfo.appendChild(createTransformationElements(cr, chart, info)); ingo@647: ingo@647: doc.appendChild(chartinfo); ingo@647: ingo@647: return doc; ingo@647: } ingo@647: ingo@647: ingo@647: /** ingo@659: * This method create a axes element that contains all domain and range ingo@659: * axes of the given chart. ingo@659: * ingo@659: * @param cr The ElementCreator. ingo@659: * @param chart The chart that provides range information of its axes. ingo@659: * ingo@659: * @return an element with axes information. ingo@659: */ ingo@673: protected Element createAxesElements( ingo@659: ElementCreator cr, ingo@659: JFreeChart chart) ingo@659: { ingo@659: logger.debug("InfoGeneratorHelper.createRangeElements"); ingo@659: ingo@659: Element axes = cr.create("axes"); ingo@659: ingo@659: XYPlot plot = (XYPlot) chart.getPlot(); ingo@659: ingo@659: int dAxisCount = plot.getDomainAxisCount(); ingo@659: for (int i = 0; i < dAxisCount; i++) { sascha@663: ValueAxis axis = plot.getDomainAxis(i); ingo@673: XYDataset data = plot.getDataset(i); ingo@673: sascha@663: if (axis != null) { ingo@673: Element e = createAxisElement(cr, axis, data, "domain", i); ingo@659: axes.appendChild(e); ingo@659: } ingo@659: } ingo@659: ingo@659: int rAxisCount = plot.getRangeAxisCount(); ingo@659: for (int i = 0; i < rAxisCount; i++) { sascha@663: ValueAxis axis = plot.getRangeAxis(i); ingo@673: XYDataset data = plot.getDataset(i); ingo@673: ingo@673: if (axis == null || data == null) { ingo@673: logger.warn("Axis or dataset is empty at pos: " + i); ingo@673: continue; ingo@659: } ingo@673: ingo@673: Element e = createAxisElement(cr, axis, data, "range", i); ingo@673: axes.appendChild(e); ingo@659: } ingo@659: ingo@659: return axes; ingo@659: } ingo@659: ingo@659: ingo@659: /** ingo@659: * This method create a axis element for a given axis and ingo@659: * type. Type can be one of 'domain' or 'range'. ingo@659: * ingo@659: * @param cr The ElementCreator ingo@659: * @param axis The axis that provides range information. ingo@673: * @param dataset The dataset for min/max determination. ingo@659: * @param type The axis type ('domain' or 'range'). ingo@659: * @param pos The position in the chart. ingo@659: * ingo@659: * @return An element that contains range information of a given axis. ingo@659: */ ingo@673: protected Element createAxisElement( ingo@659: ElementCreator cr, ingo@659: ValueAxis axis, ingo@673: XYDataset dataset, ingo@659: String type, ingo@659: int pos) ingo@659: { felix@1944: logger.debug("createAxisElement " + pos); ingo@2273: logger.debug("Axis is from type: " + axis.getClass()); ingo@659: ingo@659: Element e = cr.create(type); felix@1944: cr.addAttr(e, "pos", String.valueOf(pos), true); ingo@2261: ingo@2261: if (axis instanceof DateAxis) { ingo@2273: prepareDateAxisElement( ingo@2273: e, cr, (DateAxis) axis, dataset, type, pos); ingo@2261: } ingo@2261: else { ingo@2273: prepareNumberAxisElement( ingo@2273: e, cr, (NumberAxis) axis, dataset, type, pos); ingo@2261: } ingo@2261: ingo@2273: return e; ingo@2273: } ingo@2273: ingo@2273: ingo@2273: protected Element prepareNumberAxisElement( ingo@2273: Element e, ingo@2273: ElementCreator cr, ingo@2273: NumberAxis axis, ingo@2273: XYDataset dataset, ingo@2273: String type, ingo@2273: int pos ingo@2273: ) { ingo@2273: Range range = axis.getRange(); ingo@2273: ingo@2273: cr.addAttr(e, "from", String.valueOf(range.getLowerBound()), true); ingo@2273: cr.addAttr(e, "to", String.valueOf(range.getUpperBound()), true); ingo@2273: cr.addAttr(e, "axistype", "number", true); ingo@2273: felix@1944: Range[] rs = generator.getRangesForAxis(pos); ingo@673: Range r = null; ingo@673: ingo@673: if (type.equals("range")) { ingo@673: r = rs[1]; ingo@673: } ingo@673: else { ingo@673: r = rs[0]; ingo@673: } ingo@673: ingo@673: cr.addAttr(e, "min", String.valueOf(r.getLowerBound()), true); ingo@673: cr.addAttr(e, "max", String.valueOf(r.getUpperBound()), true); ingo@673: ingo@659: return e; ingo@659: } ingo@659: ingo@659: ingo@2273: protected Element prepareDateAxisElement( ingo@2273: Element e, ingo@2273: ElementCreator cr, ingo@2273: DateAxis axis, ingo@2273: XYDataset dataset, ingo@2273: String type, ingo@2273: int pos ingo@2273: ) { ingo@2273: Date from = axis.getMinimumDate(); ingo@2273: Date to = axis.getMaximumDate(); ingo@2273: ingo@2330: Bounds bounds = null; ingo@2330: if (type.equals("range")) { ingo@2330: bounds = generator.getYBounds(pos); ingo@2330: } ingo@2330: else { ingo@2330: bounds = generator.getXBounds(pos); ingo@2330: } ingo@2330: ingo@2273: cr.addAttr(e, "axistype", "date", true); ingo@2273: cr.addAttr(e, "from", String.valueOf(from.getTime()), true); ingo@2273: cr.addAttr(e, "to", String.valueOf(to.getTime()), true); ingo@2273: ingo@2330: cr.addAttr(e, "min", bounds.getLower().toString(), true); ingo@2330: cr.addAttr(e, "max", bounds.getUpper().toString(), true); ingo@2273: ingo@2273: return e; ingo@2273: } ingo@2273: ingo@2273: ingo@659: /** ingo@647: * This method appends the values of a transformation matrix to transform ingo@647: * image pixel coordinates into chart coordinates. ingo@647: * ingo@647: * @param cr The ElementCreator. ingo@647: * @param chart The chart object. ingo@647: * @param info The ChartRenderingInfo that is filled while chart creation. ingo@647: * ingo@647: * @return an element that contains one or more transformation matrix. ingo@647: */ ingo@673: protected Element createTransformationElements( ingo@647: ElementCreator cr, ingo@647: JFreeChart chart, ingo@647: ChartRenderingInfo info) ingo@647: { ingo@673: logger.debug("InfoGeneratorHelper.createTransformationElements"); ingo@647: ingo@647: Element tf = cr.create("transformation-matrix"); ingo@647: ingo@647: Rectangle2D dataArea = info.getPlotInfo().getDataArea(); ingo@647: ingo@648: XYPlot plot = (XYPlot) chart.getPlot(); ingo@648: ValueAxis xAxis = plot.getDomainAxis(); ingo@647: ingo@673: if (xAxis == null) { ingo@673: logger.error("There is no x axis in the chart!"); ingo@673: return null; ingo@673: } ingo@673: ingo@673: for (int i = 0, num = plot.getRangeAxisCount(); i < num; i++) { ingo@673: ValueAxis yAxis = plot.getRangeAxis(i); ingo@673: ingo@673: if (yAxis == null) { ingo@673: logger.warn("No y axis at pos " + i + " existing."); ingo@673: continue; ingo@673: } ingo@673: ingo@673: Element matrix = createTransformationElement( ingo@673: cr, xAxis, yAxis, dataArea, i); ingo@673: ingo@673: tf.appendChild(matrix); ingo@673: } ingo@673: ingo@673: return tf; ingo@673: } ingo@673: ingo@673: ingo@673: /** ingo@673: * Creates an element that contains values used to transform coordinates ingo@673: * of a coordinate system A into a coordinate system B. ingo@673: * ingo@673: * @param cr The ElementCreator. ingo@673: * @param xAxis The x axis of the target coordinate system. ingo@673: * @param yAxis The y axis of the target coordinate system. ingo@673: * @param dataArea The pixel coordinates of the chart image. ingo@673: * @param pos The dataset position. ingo@673: * ingo@673: * @return an element that contains transformation matrix values. ingo@673: */ ingo@673: protected Element createTransformationElement( ingo@673: ElementCreator cr, ingo@673: ValueAxis xAxis, ingo@673: ValueAxis yAxis, ingo@673: Rectangle2D dataArea, ingo@673: int pos) ingo@673: { ingo@648: double[] tm = createTransformationMatrix(dataArea, xAxis, yAxis); ingo@647: ingo@673: Element matrix = cr.create("matrix"); ingo@647: ingo@673: cr.addAttr(matrix, "pos", String.valueOf(pos), true); ingo@673: cr.addAttr(matrix, "sx", String.valueOf(tm[0]), true); ingo@673: cr.addAttr(matrix, "sy", String.valueOf(tm[1]), true); ingo@673: cr.addAttr(matrix, "tx", String.valueOf(tm[2]), true); ingo@673: cr.addAttr(matrix, "ty", String.valueOf(tm[3]), true); ingo@673: ingo@2273: if (xAxis instanceof DateAxis) { ingo@2273: cr.addAttr(matrix, "xtype", "date", true); ingo@2273: } ingo@2273: else { ingo@2273: cr.addAttr(matrix, "xtype", "number", true); ingo@2273: } ingo@2273: ingo@2273: if (yAxis instanceof DateAxis) { ingo@2273: cr.addAttr(matrix, "ytype", "date", true); ingo@2273: } ingo@2273: else { ingo@2273: cr.addAttr(matrix, "ytype", "number", true); ingo@2273: } ingo@2273: ingo@673: return matrix; ingo@647: } ingo@647: ingo@647: ingo@647: /** ingo@647: * This method determines a transformation matrix to transform pixel ingo@647: * coordinates of the chart image into chart coordinates. ingo@647: * ingo@647: * @param dataArea The rectangle that contains the data points of the chart. felix@3270: * @param xAxis The x axis. felix@3270: * @param yAxis The y axis. ingo@647: * ingo@647: * @return a double array as follows: [sx, sy, tx, ty]. ingo@647: */ ingo@647: protected static double[] createTransformationMatrix( ingo@647: Rectangle2D dataArea, ingo@648: ValueAxis xAxis, ingo@648: ValueAxis yAxis) ingo@647: { ingo@648: logger.debug("InfoGeneratorHelper.createTransformationMatrix"); ingo@648: ingo@647: double offsetX = dataArea.getX(); sascha@657: double width = dataArea.getWidth(); ingo@647: double offsetY = dataArea.getY(); ingo@647: double height = dataArea.getHeight(); ingo@647: ingo@2273: Range xRange = getRangeFromAxis(xAxis); ingo@2273: Range yRange = getRangeFromAxis(yAxis); ingo@648: ingo@647: double lowerX = xRange.getLowerBound(); ingo@647: double upperX = xRange.getUpperBound(); ingo@647: double lowerY = yRange.getLowerBound(); ingo@647: double upperY = yRange.getUpperBound(); ingo@647: ingo@648: if (xAxis.isInverted()) { ingo@648: logger.info("X-Axis is inverted!"); ingo@648: ingo@648: double tmp = upperX; ingo@648: upperX = lowerX; ingo@648: lowerX = tmp; ingo@648: } ingo@648: ingo@647: double dMoveX = upperX - lowerX; ingo@647: double fMoveX = width * lowerX; ingo@647: double dMoveY = lowerY - upperY; ingo@647: double fMoveY = height * upperY; ingo@647: ingo@647: AffineTransform t1 = AffineTransform.getTranslateInstance( ingo@647: offsetX - ( fMoveX / dMoveX ), ingo@647: offsetY - ( fMoveY / dMoveY ) ); ingo@647: ingo@647: AffineTransform t2 = AffineTransform.getScaleInstance( ingo@647: width / (upperX - lowerX), ingo@647: height / (lowerY - upperY)); ingo@647: ingo@647: t1.concatenate(t2); ingo@647: ingo@647: try { ingo@647: t1.invert(); ingo@647: ingo@647: double[] c = new double[6]; ingo@647: t1.getMatrix(c); ingo@647: ingo@647: return new double[] { c[0], c[3], c[4], c[5] }; ingo@647: } ingo@647: catch (NoninvertibleTransformException e) { ingo@647: // do nothing ingo@647: logger.warn("Matrix is not invertible."); ingo@647: } ingo@647: ingo@647: return new double[] { 1d, 1d, 0d, 0d }; ingo@647: } ingo@2273: ingo@2273: ingo@2273: protected static Range getRangeFromAxis(ValueAxis axis) { ingo@2273: if (axis instanceof DateAxis) { ingo@2273: DateAxis dAxis = (DateAxis) axis; ingo@2273: Date min = dAxis.getMinimumDate(); ingo@2273: Date max = dAxis.getMaximumDate(); ingo@2273: ingo@2273: return new Range(min.getTime(), max.getTime()); ingo@2273: } ingo@2273: else { ingo@2273: return axis.getRange(); ingo@2273: } ingo@2273: } ingo@647: } ingo@647: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :