ingo@647: package de.intevation.flys.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:
ingo@647: import de.intevation.artifacts.common.ArtifactNamespaceContext;
ingo@647: import de.intevation.artifacts.common.utils.XMLUtils;
ingo@647: import de.intevation.artifacts.common.utils.XMLUtils.ElementCreator;
ingo@647:
ingo@2400: import de.intevation.flys.jfree.Bounds;
ingo@2400:
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:
ingo@647: private static final Logger logger =
ingo@647: Logger.getLogger(InfoGeneratorHelper.class);
ingo@647:
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@2400: Bounds bounds = null;
ingo@2400: if (type.equals("range")) {
ingo@2400: bounds = generator.getYBounds(pos);
ingo@2400: }
ingo@2400: else {
ingo@2400: bounds = generator.getXBounds(pos);
ingo@2400: }
ingo@2400:
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@2400: cr.addAttr(e, "min", bounds.getLower().toString(), true);
ingo@2400: 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 :