/**
* OrbisGIS is a java GIS application dedicated to research in GIScience.
* OrbisGIS is developed by the GIS group of the DECIDE team of the
* Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
*
* The GIS group of the DECIDE team is located at :
*
* Laboratoire Lab-STICC – CNRS UMR 6285
* Equipe DECIDE
* UNIVERSITÉ DE BRETAGNE-SUD
* Institut Universitaire de Technologie de Vannes
* 8, Rue Montaigne - BP 561 56017 Vannes Cedex
*
* OrbisGIS is distributed under GPL 3 license.
*
* Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
* Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285)
*
* This file is part of OrbisGIS.
*
* OrbisGIS is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later
* version.
*
* OrbisGIS is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even 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
* OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
*
* For more information, please consult: <http://www.orbisgis.org/>
* or contact directly:
* info_at_ orbisgis.org
*/
package org.orbisgis.coremap.renderer.se.graphic;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBElement;
import net.opengis.se._2_0.thematic.AxisChartSubtypeType;
import net.opengis.se._2_0.thematic.AxisChartType;
import net.opengis.se._2_0.thematic.CategoryType;
import net.opengis.se._2_0.thematic.ObjectFactory;
import org.orbisgis.coremap.map.MapTransform;
import org.orbisgis.coremap.renderer.se.FillNode;
import org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle;
import org.orbisgis.coremap.renderer.se.StrokeNode;
import org.orbisgis.coremap.renderer.se.SymbolizerNode;
import org.orbisgis.coremap.renderer.se.UomNode;
import org.orbisgis.coremap.renderer.se.common.ShapeHelper;
import org.orbisgis.coremap.renderer.se.common.Uom;
import org.orbisgis.coremap.renderer.se.fill.Fill;
import org.orbisgis.coremap.renderer.se.parameter.ParameterException;
import org.orbisgis.coremap.renderer.se.parameter.SeParameterFactory;
import org.orbisgis.coremap.renderer.se.parameter.real.RealParameter;
import org.orbisgis.coremap.renderer.se.parameter.real.RealParameterContext;
import org.orbisgis.coremap.renderer.se.stroke.Stroke;
import org.orbisgis.coremap.renderer.se.transform.Transform;
/**
* {@code AxisChart} references all the supported types of chart that uses axis
* in their representation (by opposition to {@link PieChart}, for instance,
* that don't need any). Three types of axis charts are supported, as defined in
* the {@code AxisChartSubType} enumeration : ortho, polar and stacked.
* It's defined using :
* <ul>
* <li>A unit of measure, as every {@link UomNode}.</li>
* <li>A (compulsory) {@link AxisScale}.</li>
* <li>A width, used to render the categories</li>
* <li>An optional value to specify the gap between two {@link Category}s.</li>
* <li>A {@link Fill} used to render the background.</li>
* <li>A {@code Transform}</li>
* <li>A set of {@link Category} instances.</li>
* <li>And as said upper, a particular type. It is optional, and defaulted to
* ortho if not set.</li>
* </ul>
* @author Maxence Laurent
* @todo Implements drawGraphic
*/
public final class AxisChart extends Graphic implements UomNode, FillNode,
StrokeNode, TransformNode {
private List<CategoryListener> listeners;
private Uom uom;
private RealParameter normalizeTo;
//private boolean isPolarChart;
private AxisScale axisScale;
private RealParameter categoryWidth;
private RealParameter categoryGap;
private Fill areaFill;
private Transform transform;
private Stroke lineStroke;
private List<Category> categories;
private AxisChartSubType subtype;
/**
* The default gap between two bars.
*/
public static final double DEFAULT_GAP_PX = 5; //px
/**
* The default gap before the first bar.
*/
public static final double INITIAL_GAP_PX = 5; //px
/**
* The default width of each bar.
*/
public static final double DEFAULT_WIDTH_PX = 15; //px
//private Categories stakc;
/**
* The three supported types of {@code AxisChart}.
*/
public static enum AxisChartSubType {
ORTHO, POLAR, STACKED;
};
/**
* Build a new; default, {@code AxisChart}. It's an ortho one, built with
* an empty list of categories and with a default {@link
* AxisScale#AxisScale() AxisScale}.
*/
public AxisChart() {
subtype = AxisChartSubType.ORTHO;
categories = new ArrayList<Category>();
listeners = new ArrayList<CategoryListener>();
this.setAxisScale(new AxisScale());
}
/**
* Build a new {@code AxisChart} from a {@code JAXBElement} instance
* that embeds an {@link AxisChartType}.
* @param chartE
* @throws org.orbisgis.coremap.renderer.se.SeExceptions.InvalidStyle
*/
AxisChart(JAXBElement<AxisChartType> chartE) throws InvalidStyle {
this();
AxisChartType t = chartE.getValue();
if (t.getUom() != null) {
this.setUom(Uom.fromOgcURN(t.getUom()));
}
if (t.getTransform() != null) {
this.setTransform(new Transform(t.getTransform()));
}
if (t.getNormalization() != null) {
this.setNormalizeTo(SeParameterFactory.createRealParameter(t.getNormalization()));
}
if (t.getCategoryWidth() != null) {
this.setCategoryWidth(SeParameterFactory.createRealParameter(t.getCategoryWidth()));
}
if (t.getCategoryGap() != null) {
this.setCategoryGap(SeParameterFactory.createRealParameter(t.getCategoryGap()));
}
if (t.getAxisChartSubtype() != null) {
String type = t.getAxisChartSubtype().value();
if (type.equalsIgnoreCase("polar")) {
subtype = AxisChartSubType.POLAR;
} else if (type.equalsIgnoreCase("stacked")) {
subtype = AxisChartSubType.STACKED;
} else {
subtype = AxisChartSubType.ORTHO;
}
}
if (t.getFill() != null) {
this.setFill(Fill.createFromJAXBElement(t.getFill()));
}
if (t.getStroke() != null) {
this.setStroke(Stroke.createFromJAXBElement(t.getStroke()));
}
if (t.getAxisScale() != null) {
this.setAxisScale(new AxisScale(t.getAxisScale()));
}
for (CategoryType ct : t.getCategory()) {
addCategory(new Category(ct));
}
}
@Override
public Uom getUom() {
if (uom != null) {
return uom;
} else if(getParent() instanceof UomNode){
return ((UomNode)getParent()).getUom();
} else {
return Uom.PX;
}
}
@Override
public Uom getOwnUom() {
return uom;
}
@Override
public void setUom(Uom uom) {
this.uom = uom;
}
@Override
public Transform getTransform() {
return transform;
}
@Override
public void setTransform(Transform transform) {
this.transform = transform;
if (transform != null) {
transform.setParent(this);
}
}
public void registerListerner(CategoryListener lsner) {
if (lsner != null) {
listeners.add(lsner);
}
}
/**
* Get the number of {@link Category} registered in this {@code
* AxisChart}.
* @return
* The number of registered {@link Category} instances.
*/
public int getNumCategories() {
return categories.size();
}
/**
* Add a {@link Category} to this {@code AxisChart}.
* @param c
* The {@link Category} that must be added.
*/
public void addCategory(Category c) {
categories.add(c);
c.setParent(this);
}
@Override
public Fill getFill() {
return areaFill;
}
@Override
public void setFill(Fill areaFill) {
this.areaFill = areaFill;
areaFill.setParent(this);
}
/**
* Get the {@link AxisScale} used in this {@code AxisChart}.
* @return
* An {@link AxisScale} instance.
*/
public AxisScale getAxisScale() {
return axisScale;
}
/**
* Set the {@link AxisScale} used in this {@code AxisChart}.
* @param axisScale
*/
public void setAxisScale(AxisScale axisScale) {
this.axisScale = axisScale;
axisScale.setParent(this);
}
/**
* Get the gap that must be set between two categories.
* @return
* The gap as a {@link RealParameter}. This object is placed in a
* {@link RealParameterContext#NON_NEGATIVE_CONTEXT non-negative context}.
*/
public RealParameter getCategoryGap() {
return categoryGap;
}
/**
* Set the gap that must be set between two categories.
* @param categoryGap
* A {@link RealParameter}, that will be placed in a
* {@link RealParameterContext#NON_NEGATIVE_CONTEXT non-negative context}.
*/
public void setCategoryGap(RealParameter categoryGap) {
this.categoryGap = categoryGap;
if (this.categoryGap != null) {
this.categoryGap.setContext(RealParameterContext.NON_NEGATIVE_CONTEXT);
this.categoryGap.setParent(this);
}
}
/**
* Get the width of each category.
* @return
* The wifth as a {@link RealParameter}. This object is placed in a
* {@link RealParameterContext#NON_NEGATIVE_CONTEXT non-negative context}.
*/
public RealParameter getCategoryWidth() {
return categoryWidth;
}
/**
* Set the width of each category.
* @param categoryWidth
* A {@link RealParameter}, that will be placed in a
* {@link RealParameterContext#NON_NEGATIVE_CONTEXT non-negative context}.
* Consequently, if given a negative number here, the categories won't
* be displayed, as their width will be set to 0...
*/
public void setCategoryWidth(RealParameter categoryWidth) {
this.categoryWidth = categoryWidth;
if (categoryWidth != null) {
categoryWidth.setContext(RealParameterContext.NON_NEGATIVE_CONTEXT);
this.categoryWidth.setParent(this);
}
}
@Override
public Stroke getStroke() {
return lineStroke;
}
@Override
public void setStroke(Stroke lineStroke) {
this.lineStroke = lineStroke;
lineStroke.setParent(this);
}
public RealParameter getNormalizeTo() {
return normalizeTo;
}
public void setNormalizeTo(RealParameter normalizeTo) {
this.normalizeTo = normalizeTo;
if (normalizeTo != null) {
normalizeTo.setContext(RealParameterContext.REAL_CONTEXT);
this.normalizeTo.setParent(this);
}
}
/**
* Get the subtype that accurately define this {@code AxisChart} type.
* @return
*/
public AxisChartSubType getSubtype() {
return subtype;
}
/**
* Set the subtype that accurately define this {@code AxisChart} type.
* @param subtype
*/
public void setSubtype(AxisChartSubType subtype) {
this.subtype = subtype;
}
/**
* Get the categories that must be represented in this {@code AxisChart}.
* @return
* A list of {@link Category} instances. Their oreder is important, as
* it determines their rendering order.
*/
public List<Category> getCategories() {
return categories;
}
/**
* Get the ith category registered in this {@code AxisChart}.
* @param i
* @return
* The ith {@code Category}, if i is a valid index.
* @throws ParameterException
*/
public Category getCategory(int i) throws ParameterException {
if (i >= 0 && i < categories.size()) {
return categories.get(i);
}
throw new ParameterException("Category index out of bounds!");
}
@Override
public void updateGraphic() {
}
private double[] getMeasuresInPixel(Map<String,Object> map, MapTransform mt) throws ParameterException {
double rLength = Uom.toPixel(axisScale.getAxisLength().getValue(map),
getUom(), mt.getDpi(), mt.getScaleDenominator(), null);
double rMesure = axisScale.getMeasureValue().getValue(map);
double[] heights = new double[categories.size()];
int i = 0;
for (Category c : categories) {
heights[i] = c.getMeasure().getValue(map) * rLength / rMesure;
i++;
}
return heights;
}
/**
* Move the ith {@code Category} down, ie swap the ith and (i-1)th elements.
* @param i
*/
public void moveCategoryDown(int i) {
if (i >= 0 && i < categories.size() - 1) {
Category tmp = categories.get(i);
categories.set(i, categories.get(i + 1));
categories.set(i + 1, tmp);
fireCatDown(i);
}
}
/**
* Move the ith {@code Category} up, ie swap the ith and (i+1)th elements.
* @param i
*/
public void moveCategoryUp(int i) {
if (i > 0 && i < categories.size()) {
Category tmp = categories.get(i);
categories.set(i, categories.get(i - 1));
categories.set(i - 1, tmp);
fireCatUp(i);
}
}
/**
* Remove the ith {@code Category}.
* @param i
*/
public void removeCategory(int i) {
if (i >= 0 && i < categories.size()) {
categories.remove(i);
fireCatRm(i);
}
}
private void fireCatDown(int i) {
for (CategoryListener l : listeners) {
l.categoryMoveDown(i);
}
}
private void fireCatUp(int i) {
for (CategoryListener l : listeners) {
l.categoryMoveUp(i);
}
}
private void fireCatRm(int i) {
for (CategoryListener l : listeners) {
l.categoryRemoved(i);
}
}
/**
* Draw method for polar chart
* @param g2
* @param map
* @param selected
* @param mt
* @param at
* @throws ParameterException
* @throws IOException
*/
private void drawOrthoChart(Graphics2D g2, Map<String,Object> map,
boolean selected, MapTransform mt, AffineTransform at)
throws ParameterException, IOException {
int nCat = categories.size();
double heights[] = getMeasuresInPixel(map, mt);
double maxHeight = 0;
double minHeight = 0;
// Determine min and max heights
for (double h : heights) {
if (h > maxHeight) {
maxHeight = h;
}
if (h < minHeight) {
minHeight = h;
}
}
double cGap = DEFAULT_GAP_PX;
if (categoryGap != null) {
cGap = Uom.toPixel(categoryGap.getValue(map), getUom(), mt.getDpi(),
mt.getScaleDenominator(), null);
}
double cWidth = DEFAULT_WIDTH_PX;
if (categoryWidth != null) {
cWidth = Uom.toPixel(categoryWidth.getValue(map), getUom(), mt.getDpi(),
mt.getScaleDenominator(), null);
}
// compute chart width, according to number of categories
double width = (nCat - 1) * cGap + nCat * cWidth + INITIAL_GAP_PX;
// chart bounds
Rectangle2D bounds = new Rectangle2D.Double(-width / 2, -maxHeight, width, maxHeight + -1 * minHeight);
//AffineTransform at = null;
if (transform != null) {
at.concatenate(transform.getGraphicalAffineTransform(false, map, mt, minHeight, minHeight));
Shape shp = at.createTransformedShape(bounds);
bounds.setRect(shp.getBounds2D());
}
double currentX = -width / 2 + INITIAL_GAP_PX;
double xOffset[] = new double[nCat];
int i;
for (i = 0; i < nCat; i++) {
//Category c = categories.get(i);
xOffset[i] = currentX;
currentX += cGap + cWidth;
}
// First, draw bar chart
for (i = 0; i < nCat; i++) {
Category c = categories.get(i);
if (c.getFill() != null || c.getStroke() != null) {
Path2D.Double bar = new Path2D.Double();
bar.moveTo(xOffset[i], 0);
bar.lineTo(xOffset[i], -heights[i]);
bar.lineTo(xOffset[i] + cWidth, -heights[i]);
bar.lineTo(xOffset[i] + cWidth, 0);
bar.closePath();
Shape shp = bar;
if (at != null) {
shp = at.createTransformedShape(bar);
}
if (c.getFill() != null) {
c.getFill().draw(g2, map, shp, selected, mt);
}
if (c.getStroke() != null) {
c.getStroke().draw(g2, map, shp, selected, mt, 0.0);
}
}
}
// then draw main area (if required)
if (areaFill != null) {
Path2D area = new Path2D.Double();
area.moveTo(xOffset[0] + cWidth / 2, 0);
for (i = 0; i < nCat; i++) {
area.lineTo(xOffset[i] + cWidth / 2, -heights[i]);
}
area.lineTo(xOffset[nCat - 1] + cWidth / 2, 0);
area.closePath();
Shape shp = area;
if (at != null) {
shp = at.createTransformedShape(area);
}
areaFill.draw(g2, map, shp, selected, mt);
}
// then the line chart
if (lineStroke != null) {
Path2D line = new Path2D.Double();
line.moveTo(xOffset[0] + cWidth / 2, -heights[0]);
for (i = 0; i < nCat; i++) {
line.lineTo(xOffset[i] + cWidth / 2, -heights[i]);
}
//area.lineTo(xOffset[nCat-1]+cWidth/2, 0);
//area.closePath();
Shape shp = line;
if (at != null) {
shp = at.createTransformedShape(line);
}
lineStroke.draw(g2, map, shp, selected, mt, 0.0);
}
// and finally, points
for (i = 0; i < nCat; i++) {
Category c = categories.get(i);
if (c.getGraphicCollection() != null) {
AffineTransform at2 = AffineTransform.getTranslateInstance(xOffset[i] + cWidth / 2, -heights[i]);
if (at != null) {
at2.concatenate(at);
}
c.getGraphicCollection().draw(g2, map, selected, mt, at2);
}
}
/* Following code try to draw x&y axis, TODO tyke into account AT
g2.setPaint(Color.black);
Point2D origin = at.transform(new Point2D.Double(0, 0), null);
Point2D maxX_y0 = at.transform(new Point2D.Double(0, 0), null);
g2.drawLine((int) bounds.getMinX(), (int) bounds.getMinY(), (int) bounds.getMinX(), (int) bounds.getMaxY());
g2.drawLine((int) bounds.getMinX(), (int) origin.getY(), (int) bounds.getMaxX(), (int) maxX_y0.getY());
*
*/
/*
MarkGraphic arrow = new MarkGraphic();
arrow.setSource(WellKnownName.TRIANGLE);
arrow.setUom(Uom.MM);
arrow.setFill(new SolidFill(Color.black, 100.0));
arrow.setViewBox(new ViewBox(new RealLiteral(20)));
RenderableGraphics rArrow = arrow.getRenderableGraphics(map, selected, mt);
g2.drawRenderableImage(rArrow, AffineTransform.getTranslateInstance(0, bounds.getMinY()));
*/
}
private void drawStackedChart(Graphics2D g2, Map<String,Object> map,
boolean selected, MapTransform mt, AffineTransform at) throws ParameterException, IOException {
}
/**
*
* Create polar chart
*
* @param map
* @param selected
* @param mt
* @return
* @throws ParameterException
* @throws IOException
*/
private void drawPolarChart(Graphics2D g2, Map<String,Object> map,
boolean selected, MapTransform mt, AffineTransform at) throws ParameterException, IOException {
int nCat = categories.size();
double heights[] = getMeasuresInPixel(map, mt);
double maxHeight = 0;
double minHeight = 0;
/* compute min & max height */
for (double h : heights) {
if (h > maxHeight) {
maxHeight = h;
}
if (h < minHeight) {
minHeight = h;
}
}
// make sure min value > 0
if (minHeight < 0.0) {
throw new ParameterException("Negative measures are not allowed for polar charts!");
}
//double width = (nCat - 1) * cGap + nCat * cWidth + INITIAL_GAP_PX;
double radius = maxHeight;
if (transform != null) {
at.concatenate(transform.getGraphicalAffineTransform(false, map, mt, 2 * radius, 2 * radius));
}
double alphas[] = new double[nCat];
double beta = 2 * Math.PI / nCat;
double alpha = Math.PI / 2.0; // The first is vertical !
double xpos[] = new double[nCat];
double ypos[] = new double[nCat];
int i;
// Compute effective position for each category
for (i = 0; i < nCat; i++) {
ypos[i] = Math.sin(alpha) * heights[i];
xpos[i] = Math.cos(alpha) * heights[i];
alphas[i] = alpha;
alpha += beta;
}
// First fill the area, with general fill & stroke
// This is a net-chart
if (this.areaFill != null || this.lineStroke != null) {
Path2D.Double area = new Path2D.Double();
area.moveTo(xpos[0], ypos[0]);
for (i = 1; i < nCat; i++) {
area.lineTo(xpos[i], ypos[i]);
}
area.closePath();
Shape shp = area;
if (at != null) {
shp = at.createTransformedShape(area);
}
if (this.areaFill != null) {
areaFill.draw(g2, map, shp, selected, mt);
}
if (this.lineStroke != null) {
lineStroke.draw(g2, map, shp, selected, mt, 0.0);
}
}
for (i = 0; i < nCat; i++) {
Category cat = this.categories.get(i);
if (cat.getGraphicCollection() != null) {
AffineTransform at2 = AffineTransform.getTranslateInstance(xpos[i], ypos[i]);
if (at != null) {
at2.concatenate(at);
}
cat.getGraphicCollection().draw(g2, map, selected, mt, at2);
}
Shape shp;
// Draw specific categorie fill & stroke
// this is a polar bar chart
if (cat.getFill() != null || cat.getStroke() != null) {
Arc2D.Double slice = new Arc2D.Double(-heights[i], -heights[i],
2 * heights[i], 2 * heights[i],
(-alphas[i] - beta / 2) / ShapeHelper.ONE_DEG_IN_RAD, beta / ShapeHelper.ONE_DEG_IN_RAD, Arc2D.PIE);
shp = slice;
if (at != null) {
shp = at.createTransformedShape(slice);
}
if (cat.getFill() != null) {
cat.getFill().draw(g2, map, shp, selected, mt);
}
if (cat.getStroke() != null) {
cat.getStroke().draw(g2, map, shp, selected, mt, 0.0);
}
}
// DRAW measure:
Path2D.Double stick = new Path2D.Double();
stick.moveTo(0, 0);
stick.lineTo(xpos[i], ypos[i]);
shp = stick;
if (at != null) {
shp = at.createTransformedShape(stick);
}
g2.setStroke(new BasicStroke(1));
//g2.setColor(Color.black);
g2.setPaint(Color.GRAY);
g2.draw(shp);
}
}
@Override
public void draw(Graphics2D g2, Map<String,Object> map,
boolean selected, MapTransform mt, AffineTransform fat) throws ParameterException, IOException {
AffineTransform at = new AffineTransform(fat);
switch (subtype) {
case POLAR:
drawPolarChart(g2, map, selected, mt, at);
break;
case STACKED:
drawStackedChart(g2, map, selected, mt, at);
break;
case ORTHO:
default:
drawOrthoChart(g2, map, selected, mt, at);
break;
}
}
private Rectangle2D getPolarBounds(Map<String,Object> map, MapTransform mt) throws ParameterException, IOException {
double[] measuresInPixel = getMeasuresInPixel(map, mt);
double max = 0.0;
for (double m : measuresInPixel) {
max = Math.max(max, m);
}
Rectangle2D bounds = new Rectangle2D.Double(-max, -max, 2 * max, 2 * max);
if (transform != null) {
AffineTransform at = transform.getGraphicalAffineTransform(false, map, mt, 2 * max, 2 * max);
return at.createTransformedShape(bounds).getBounds2D();
} else {
return bounds;
}
}
private Rectangle2D getStackedBounds(Map<String,Object> map, MapTransform mt) throws ParameterException, IOException {
double[] measuresInPixel = getMeasuresInPixel(map, mt);
double sum = 0.0;
for (double m : measuresInPixel) {
if (m < 0) {
throw new ParameterException("Negative values not allowed with Stacked charts");
}
sum += m;
}
double width = AxisChart.DEFAULT_WIDTH_PX;
if (categoryWidth != null) {
width = Uom.toPixel(categoryWidth.getValue(map), getUom(), mt.getDpi(), mt.getScaleDenominator(), null);
}
width += AxisChart.INITIAL_GAP_PX;
Rectangle2D bounds = new Rectangle2D.Double(0, -sum, width, sum);
if (transform != null) {
AffineTransform at = transform.getGraphicalAffineTransform(false, map, mt, width, sum);
return at.createTransformedShape(bounds).getBounds2D();
} else {
return bounds;
}
}
private Rectangle2D getOrthoBounds(Map<String,Object> map, MapTransform mt) throws ParameterException, IOException {
double[] measuresInPixel = getMeasuresInPixel(map, mt);
double max = 0.0;
for (double m : measuresInPixel) {
max += Math.abs(m);
}
double width = AxisChart.DEFAULT_WIDTH_PX;
if (categoryWidth != null) {
width = Uom.toPixel(categoryWidth.getValue(map), getUom(), mt.getDpi(), mt.getScaleDenominator(), null);
}
width *= categories.size();
width += AxisChart.INITIAL_GAP_PX;
Rectangle2D bounds = new Rectangle2D.Double(0, -max, width, 2 * max);
if (transform != null) {
AffineTransform at = transform.getGraphicalAffineTransform(false, map, mt, width, 2 * max);
return at.createTransformedShape(bounds).getBounds2D();
} else {
return bounds;
}
}
@Override
public Rectangle2D getBounds(Map<String,Object> map, MapTransform mt) throws ParameterException, IOException {
switch (subtype) {
case POLAR:
return getPolarBounds(map, mt);
case STACKED:
return getStackedBounds(map, mt);
case ORTHO:
default:
return getOrthoBounds(map, mt);
}
}
@Override
public JAXBElement<AxisChartType> getJAXBElement() {
AxisChartType a = new AxisChartType();
if (axisScale != null) {
a.setAxisScale(axisScale.getJAXBType());
}
if (categoryGap != null) {
a.setCategoryGap(categoryGap.getJAXBParameterValueType());
}
if (categoryWidth != null) {
a.setCategoryWidth(categoryWidth.getJAXBParameterValueType());
}
if (areaFill != null) {
a.setFill(areaFill.getJAXBElement());
}
if (normalizeTo != null) {
a.setNormalization(normalizeTo.getJAXBParameterValueType());
}
if (lineStroke != null) {
a.setStroke(lineStroke.getJAXBElement());
}
if (transform != null) {
a.setTransform(transform.getJAXBType());
}
if (uom != null) {
a.setUom(uom.toString());
}
switch (subtype) {
case ORTHO:
a.setAxisChartSubtype(AxisChartSubtypeType.ORTHO);
break;
case POLAR:
a.setAxisChartSubtype(AxisChartSubtypeType.POLAR);
break;
case STACKED:
a.setAxisChartSubtype(AxisChartSubtypeType.STACKED);
break;
}
List<CategoryType> category = a.getCategory();
for (Category c : categories) {
category.add(c.getJAXBType());
}
ObjectFactory of = new ObjectFactory();
return of.createAxisChart(a);
}
@Override
public List<SymbolizerNode> getChildren() {
List<SymbolizerNode> ls = new ArrayList<SymbolizerNode>();
if (areaFill != null) {
ls.add(areaFill);
}
if (lineStroke != null) {
ls.add(lineStroke);
}
if (this.categoryGap != null) {
ls.add(categoryGap);
}
if (categoryWidth != null) {
ls.add(categoryWidth);
}
if (axisScale != null) {
ls.add(axisScale);
}
ls.addAll(categories);
return ls;
}
}