# HG changeset patch # User Sascha L. Teichmann # Date 1261414065 0 # Node ID f426f55d4f7a23eb314ffcaa4afd258164031578 # Parent fd71ee76fa581beca0f369771ce5d3d54fe8badf Added Ingo Weinzierl's special JFreeChart classes. gnv-artifacts/trunk@470 c6561f87-3c4e-4783-a992-168aeb5c3f6f diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/ChangeLog --- a/gnv-artifacts/ChangeLog Mon Dec 21 16:01:35 2009 +0000 +++ b/gnv-artifacts/ChangeLog Mon Dec 21 16:47:45 2009 +0000 @@ -1,3 +1,25 @@ +2009-12-21 Sascha L. Teichmann + + Added Ingo Weinzierl's special JFreeChart classes. + + * src/main/java/de/intevation/gnv/jfreechart: New package. + Should contain general JFreeChart stuff. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java: New. + New type of plot to display multi-polygons with holes. + + * src/main/java/de/intevation/gnv/jfreechart/CompactXYItems.java: New. + Basic vertex data model: a ring of a polygon. ccw = shell, cw = hole. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonSeries.java: New. + Attributes a set of rings with key/value pairs. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonDataset.java: New + List of PolygonSeries which makes it a multi-polygon. + + * src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java: New + A renderer to draw PolygonDatasets into a PolygonPlot. + 2009-12-21 Ingo Weinzierl * doc/conf/products/verticalcrosssection/conf_mesh.xml: Prepared states and diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/CompactXYItems.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/CompactXYItems.java Mon Dec 21 16:47:45 2009 +0000 @@ -0,0 +1,128 @@ +package de.intevation.gnv.jfreechart; + +import java.io.Serializable; + +/** + * @author Sascha Teichmann + * @author Ingo Weinzierl + */ +public class CompactXYItems +implements Serializable +{ + protected double [] data; + + public CompactXYItems(double [] data) { + this.data = data; + } + + public double getX(int index) { + return data[index << 1]; + } + + public double getY(int index) { + return data[(index << 1)+1]; + } + + public void get(int index, double [] xy) { + xy[0] = data[index = (index << 1) + 1]; + xy[1] = data[index + 1]; + } + + public double [] getData() { + return data; + } + + public void setData(double [] data) { + this.data = data; + } + + public int size() { + return data.length >> 1; + } + + public double [] calculateBoundingBox(double [] bbox) { + for (int i = 0; i < data.length;) { + double x = data[i++]; + double y = data[i++]; + if (x < bbox[0]) bbox[0] = x; + if (y < bbox[1]) bbox[1] = y; + if (x > bbox[2]) bbox[2] = x; + if (y > bbox[3]) bbox[3] = y; + } + return bbox; + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.length;) { + if (i > 0) sb.append("; "); + sb.append('('); + sb.append(data[i++]); + sb.append(", "); + sb.append(data[i++]); + sb.append(')'); + } + return sb.toString(); + } + + + public double getMinX() { + double lower = Double.POSITIVE_INFINITY; + + for (int i = 0; i < data.length; i += 2) { + double x = data[i]; + + if (!Double.isNaN(x)) { + lower = Math.min(lower, x); + } + } + + return lower; + } + + + public double getMaxX() { + double upper = Double.NEGATIVE_INFINITY; + + for (int i = 0; i < data.length; i += 2) { + double x = data[i]; + + if (!Double.isNaN(x)) { + upper = Math.max(upper, x); + } + } + + return upper; + } + + + public double getMinY() { + double lower = Double.POSITIVE_INFINITY; + + for (int i = 1; i < data.length; i += 2) { + double y = data[i]; + + if (!Double.isNaN(y)) { + lower = Math.min(lower, y); + } + } + + return lower; + } + + + public double getMaxY() { + double upper = Double.NEGATIVE_INFINITY; + + for (int i = 1; i < data.length; i += 2) { + double y = data[i]; + + if (!Double.isNaN(y)) { + upper = Math.max(upper, y); + } + } + + return upper; + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonDataset.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonDataset.java Mon Dec 21 16:47:45 2009 +0000 @@ -0,0 +1,103 @@ +package de.intevation.gnv.jfreechart; + +import java.util.List; +import java.util.ArrayList; + +import org.jfree.data.Range; +import org.jfree.data.general.AbstractSeriesDataset; + +/** + * @author Ingo Weinzierl + */ +public class PolygonDataset +extends AbstractSeriesDataset +{ + /** PolygonSeries included in this Dataset */ + private List data; + + + public PolygonDataset() { + this(null); + } + + + public PolygonDataset(PolygonSeries series) { + data = new ArrayList(); + + if (series != null) { + data.add(series); + } + } + + + public void addSeries(PolygonSeries series) { + if (series == null) + throw new IllegalArgumentException("Null 'series' argument."); + + data.add(series); + } + + + public Range getDomainBounds() { + double lower = Double.POSITIVE_INFINITY; + double upper = Double.NEGATIVE_INFINITY; + int seriesCount = getSeriesCount(); + + for (int s = 0; s < seriesCount; s++) { + PolygonSeries series = getSeries(s); + + Range domainRange = series.getDomainBounds(); + double minX = domainRange.getLowerBound(); + if (!Double.isNaN(minX)) { + lower = Math.min(lower, minX); + } + + double maxX = domainRange.getUpperBound(); + if (!Double.isNaN(maxX)) { + upper = Math.max(upper, maxX); + } + } + + return new Range(lower, upper); + } + + + public Range getRangeBounds() { + double lower = Double.POSITIVE_INFINITY; + double upper = Double.NEGATIVE_INFINITY; + int seriesCount = getSeriesCount(); + + for (int i = 0; i < seriesCount; i++) { + PolygonSeries series = getSeries(i); + + Range range = series.getRangeBounds(); + double minX = range.getLowerBound(); + if (!Double.isNaN(minX)) { + lower = Math.min(lower, minX); + } + + double maxX = range.getUpperBound(); + if (!Double.isNaN(maxX)) { + upper = Math.max(upper, maxX); + } + } + + return new Range(lower, upper); + } + + + public int getSeriesCount() { + return data.size(); + } + + + public Comparable getSeriesKey(int series) { + return ((PolygonSeries)data.get(series)).getKey(); + } + + + public PolygonSeries getSeries(int idx) { + return (PolygonSeries)data.get(idx); + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonPlot.java Mon Dec 21 16:47:45 2009 +0000 @@ -0,0 +1,416 @@ +package de.intevation.gnv.jfreechart; + +import java.awt.AlphaComposite; +import java.awt.Composite; +import java.awt.Graphics2D; +import java.awt.Shape; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.jfree.chart.axis.Axis; +import org.jfree.chart.axis.AxisCollection; +import org.jfree.chart.axis.AxisLocation; +import org.jfree.chart.axis.AxisSpace; +import org.jfree.chart.axis.AxisState; +import org.jfree.chart.axis.ValueAxis; + +import org.jfree.chart.plot.Plot; +import org.jfree.chart.plot.PlotRenderingInfo; +import org.jfree.chart.plot.PlotState; +import org.jfree.chart.plot.PlotOrientation; + +import org.jfree.data.Range; + +import org.jfree.ui.RectangleEdge; +import org.jfree.ui.RectangleInsets; + +import org.jfree.util.ObjectList; + +/** + * @author Ingo Weinzierl + */ +// TODO implement cloneable +public class PolygonPlot +extends Plot +{ + public static final String PLOT_TYPE = "PolygonPlot"; + + public static final PlotOrientation DEFAULT_PLOT_ORIENTATION = + PlotOrientation.VERTICAL; + + private PolygonDataset dataset; + private transient PolygonRenderer renderer; + + private PlotOrientation orientation; + + private RectangleInsets axisOffset; + + private ObjectList domainAxisLocation; + private ObjectList rangeAxisLocation; + private ObjectList domainAxes; + private ObjectList rangeAxes; + + + public PolygonPlot(PolygonDataset dataset, PolygonRenderer renderer) { + this(dataset, renderer, null, null, PlotOrientation.HORIZONTAL); + } + + + public PolygonPlot( + PolygonDataset dataset, + PolygonRenderer renderer, + PlotOrientation orientation + ) { + this(dataset, renderer, null, null, orientation); + } + + + public PolygonPlot( + PolygonDataset dataset, + PolygonRenderer renderer, + ValueAxis domainAxis, + ValueAxis rangeAxis, + PlotOrientation orientation + ) { + super(); + + this.dataset = dataset; + this.renderer = renderer; + this.domainAxes = new ObjectList(); + this.rangeAxes = new ObjectList(); + this.domainAxisLocation = new ObjectList(); + this.rangeAxisLocation = new ObjectList(); + this.axisOffset = RectangleInsets.ZERO_INSETS; + + if (orientation != null) + this.orientation = orientation; + else + this.orientation = DEFAULT_PLOT_ORIENTATION; + + if (domainAxis != null) { + this.domainAxes.set(0, domainAxis); + domainAxis.setPlot(this); + } + domainAxisLocation.set(0, AxisLocation.BOTTOM_OR_LEFT); + + if (rangeAxis != null) { + this.rangeAxes.set(0, rangeAxis); + rangeAxis.setPlot(this); + } + rangeAxisLocation.set(0, AxisLocation.BOTTOM_OR_LEFT); + + configureDomainAxis(); + configureRangeAxis(); + } + + + public void configureDomainAxis() { + // we just have 1 dataset + Range domainAxisRange = getDataset().getDomainBounds(); + + for (int i = 0; i < domainAxes.size(); i++) { + ValueAxis axis = (ValueAxis) domainAxes.get(i); + + if (axis != null) { + axis.configure(); + axis.setRange(domainAxisRange); + } + } + } + + + public void configureRangeAxis() { + // we just have 1 dataset + Range rangeAxisRange = getDataset().getRangeBounds(); + + for (int i = 0; i < rangeAxes.size(); i++) { + ValueAxis axis = (ValueAxis) rangeAxes.get(i); + + if (axis != null) { + axis.configure(); + axis.setRange(rangeAxisRange); + } + } + } + + + public PolygonDataset getDataset(){ + return this.dataset; + } + + + public String getPlotType() { + return PLOT_TYPE; + } + + + public void setDataset(PolygonDataset dataset) { + this.dataset = dataset; + } + + + public void draw( + Graphics2D g2, + Rectangle2D area, + Point2D anchor, + PlotState parentState, + PlotRenderingInfo info + ) { + System.out.println("Start drawing plot."); + + Graphics2D savedG2 = g2; + Rectangle2D savedDataArea = area; + + if (info != null) { + info.setPlotArea(area); + info.setDataArea(area); + } + + AxisSpace space = calculateAxisSpace(g2, area); + Rectangle2D dataArea = space.shrink(area, null); + + // draw background and outline + drawBackground(g2, area); + drawOutline(g2, area); + + Shape savedClip = g2.getClip(); + g2.clip(area); + + Composite originalComposite = g2.getComposite(); + g2.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, + getForegroundAlpha() + )); + + // draw axis + drawAxes(g2, area, dataArea, info); + + if (!isEmptyOrNull(dataset)) { + // draw data + drawPolygons(savedG2, dataArea, info); + } + + g2.setClip(savedClip); + g2.setComposite(originalComposite); + + System.out.println("Finished drawing plot."); + } + + + private void drawAxes( + Graphics2D g2, + Rectangle2D plotArea, + Rectangle2D dataArea, + PlotRenderingInfo plotState + ) { + AxisCollection axisCollection = new AxisCollection(); + + for (int i = 0; i < domainAxes.size(); i++) { + ValueAxis axis = (ValueAxis) domainAxes.get(i); + if (axis != null) + axisCollection.add(axis, getDomainAxisEdge(i)); + } + + for (int i = 0; i < rangeAxes.size(); i++) { + ValueAxis axis = (ValueAxis) rangeAxes.get(i); + if (axis != null) + axisCollection.add(axis, getRangeAxisEdge(i)); + } + + Map axisStateMap = new HashMap(); + + // draw the top axes + double cursor = dataArea.getMinY() - this.axisOffset.calculateTopOutset( + dataArea.getHeight()); + Iterator iterator = axisCollection.getAxesAtTop().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.TOP, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the bottom axes + cursor = dataArea.getMaxY() + + this.axisOffset.calculateBottomOutset(dataArea.getHeight()); + iterator = axisCollection.getAxesAtBottom().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.BOTTOM, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the left axes + cursor = dataArea.getMinX() + - this.axisOffset.calculateLeftOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtLeft().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.LEFT, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + + // draw the right axes + cursor = dataArea.getMaxX() + + this.axisOffset.calculateRightOutset(dataArea.getWidth()); + iterator = axisCollection.getAxesAtRight().iterator(); + while (iterator.hasNext()) { + ValueAxis axis = (ValueAxis) iterator.next(); + AxisState info = axis.draw(g2, cursor, plotArea, dataArea, + RectangleEdge.RIGHT, plotState); + cursor = info.getCursor(); + axisStateMap.put(axis, info); + } + } + + + private void drawPolygons( + Graphics2D g2, + Rectangle2D area, + PlotRenderingInfo info + ) { + renderer.draw(g2, area, dataset); + } + + + private AxisSpace calculateAxisSpace(Graphics2D g2, Rectangle2D plotArea) { + AxisSpace space = new AxisSpace(); + space = calculateRangeAxisSpace(g2, plotArea, space); + Rectangle2D tmpPlotArea = space.shrink(plotArea, null); + space = calculateDomainAxisSpace(g2, plotArea, space); + + return space; + } + + + private AxisSpace calculateDomainAxisSpace( + Graphics2D g2, + Rectangle2D plotArea, + AxisSpace space + ) { + if (space == null) + space = new AxisSpace(); + + for (int i = 0; i < domainAxes.size(); i++) { + Axis axis = (Axis) domainAxes.get(i); + + if (axis != null) { + RectangleEdge edge = getDomainAxisEdge(i); + space = axis.reserveSpace(g2, this, plotArea, edge, space); + } + } + + return space; + } + + + private AxisSpace calculateRangeAxisSpace( + Graphics2D g2, + Rectangle2D plotArea, + AxisSpace space + ) { + if (space == null) + space = new AxisSpace(); + + for (int i = 0; i < rangeAxes.size(); i++) { + Axis axis = (Axis) rangeAxes.get(i); + + if (axis != null) { + RectangleEdge edge = getRangeAxisEdge(i); + space = axis.reserveSpace(g2, this, plotArea, edge, space); + } + } + + return space; + } + + private RectangleEdge getDomainAxisEdge() { + return Plot.resolveDomainAxisLocation( + getDomainAxisLocation(), orientation + ); + } + + + private RectangleEdge getDomainAxisEdge(int idx) { + AxisLocation location = getDomainAxisLocation(idx); + RectangleEdge result = Plot.resolveDomainAxisLocation( + location, orientation + ); + + if (result == null) + result = RectangleEdge.opposite(getDomainAxisEdge()); + + return result; + } + + + private RectangleEdge getRangeAxisEdge() { + return Plot.resolveRangeAxisLocation( + getRangeAxisLocation(), orientation + ); + } + + + private RectangleEdge getRangeAxisEdge(int idx) { + AxisLocation location = getRangeAxisLocation(idx); + RectangleEdge result = Plot.resolveRangeAxisLocation( + location, + orientation + ); + + if (result == null) + result = RectangleEdge.opposite(getRangeAxisEdge()); + + return result; + } + + + public AxisLocation getDomainAxisLocation() { + return (AxisLocation) domainAxisLocation.get(0); + } + + + public AxisLocation getDomainAxisLocation(int idx) { + if (idx < domainAxisLocation.size()) + return (AxisLocation) domainAxisLocation.get(idx); + + return null; + } + + + public AxisLocation getRangeAxisLocation() { + return (AxisLocation) rangeAxisLocation.get(0); + } + + + public AxisLocation getRangeAxisLocation(int idx) { + if (idx < rangeAxisLocation.size()) + return (AxisLocation) rangeAxisLocation.get(idx); + + return null; + } + + + private boolean isEmptyOrNull(PolygonDataset dataset) { + if (dataset != null) { + int seriesCount = dataset.getSeriesCount(); + for (int s = 0; s < seriesCount; s++) { + PolygonSeries series = dataset.getSeries(s); + if (series.getItemCount() > 0) { + return false; + } + } + } + return true; + } +} diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonRenderer.java Mon Dec 21 16:47:45 2009 +0000 @@ -0,0 +1,129 @@ +package de.intevation.gnv.jfreechart; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.geom.GeneralPath; +import java.awt.geom.Rectangle2D; +import java.awt.geom.Rectangle2D.Double; + +import org.jfree.data.Range; + +/** + * @author Ingo Weinzierl + */ +public class PolygonRenderer +{ + public static final int AREA = 1; + public static final int LINES = 2; + public static final int AREA_AND_LINES = AREA | LINES; + + public interface PaintLookup { + + Paint getPaint(int index); + + } // interface PaintLookup + + protected int type; + + protected PaintLookup lookup; + + protected PolygonPlot plot; + + + public PolygonRenderer(PaintLookup lookup) { + this(lookup, AREA); + } + + public PolygonRenderer(PaintLookup lookup, int type) { + this.lookup = lookup; + this.type = type; + } + + public void draw( + Graphics2D graphics, + Rectangle2D rectangle, + PolygonDataset dataset + ) { + Rectangle2D bbox = getBoundingBox(dataset); + + double sx = (double)rectangle.getWidth()/bbox.getWidth(); + double sy = (double)rectangle.getHeight()/bbox.getHeight(); + double tx = rectangle.getMinX(); + double ty = rectangle.getMinY(); + + graphics.translate(tx, ty); + graphics.scale(sx, sy); + + int seriesCount = dataset.getSeriesCount(); + for (int i = 0; i < seriesCount; i++) { + PolygonSeries series = dataset.getSeries(i); + Integer colorIdx = (Integer)series.getAttribute("fill"); + + if (colorIdx != null) { + Paint paint = lookup.getPaint(colorIdx.intValue()); + graphics.setPaint(paint != null ? paint : Color.black); + graphics.fill(constructShape(series)); + } + else { + graphics.setPaint(Color.black); + graphics.draw(constructShape(series)); + } + } + } + + protected Shape constructShape(PolygonSeries series) { + CompactXYItems [] rings = series.getRings(); + GeneralPath path = new GeneralPath(); + for (int i = 0; i < rings.length; ++i) { + CompactXYItems ring = rings[i]; + double [] data = ring.getData(); + if (data.length >= 2) { + path.moveTo((float)data[0], (float)data[1]); + } + for (int j = 2; j < data.length;) { + float x = (float)data[j++]; + float y = (float)data[j++]; + path.lineTo(x, y); + } + path.closePath(); + } + return path; + } + + public Rectangle2D getBoundingBox(PolygonDataset dataset) { + Rectangle2D bbox = null; + + for (int i = 0, N = dataset.getSeriesCount(); i < N; i++) { + Range domain = dataset.getSeries(i).getDomainBounds(); + Range range = dataset.getSeries(i).getRangeBounds(); + + double x = domain.getLowerBound(); + double y = range.getLowerBound(); + double w = Math.abs(domain.getUpperBound() - x); + double h = Math.abs(range.getUpperBound() - y); + + if (bbox == null) { + bbox = new Rectangle2D.Double(x, y, w, h); + } + else { + bbox.add(new Rectangle2D.Double(x, y, w, h)); + } + } + + return bbox; + } + + public Rectangle2D getBounds(PolygonSeries series) { + + Range domain = series.getDomainBounds(); + Range range = series.getRangeBounds(); + + return new Rectangle2D.Double( + domain.getLowerBound(), range.getLowerBound(), + domain.getUpperBound(), range.getUpperBound() + ); + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 : diff -r fd71ee76fa58 -r f426f55d4f7a gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonSeries.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gnv-artifacts/src/main/java/de/intevation/gnv/jfreechart/PolygonSeries.java Mon Dec 21 16:47:45 2009 +0000 @@ -0,0 +1,151 @@ +package de.intevation.gnv.jfreechart; + +import java.util.Map; +import java.util.HashMap; + +import org.jfree.data.Range; +import org.jfree.data.general.Series; + +/** + * @author Sascha Teichmann + * @author Ingo Weinzierl + */ +public class PolygonSeries +extends Series +{ + protected CompactXYItems [] rings; + protected Map attributes; + + + public PolygonSeries(Comparable key, CompactXYItems [] rings) { + this(key, null, rings, new HashMap()); + } + + public PolygonSeries( + Comparable key, + String description, + CompactXYItems[] rings + ) { + this(key, description, rings, new HashMap()); + } + + public PolygonSeries( + Comparable key, + String description, + CompactXYItems [] rings, + Map attributes + ) { + super(key, description); + this.rings = rings; + this.attributes = attributes; + } + + + public void setRings(CompactXYItems [] rings) { + this.rings = rings; + } + + public CompactXYItems [] getRings() { + return rings; + } + + public void addRings(CompactXYItems [] newRings) { + if (newRings == null || newRings.length == 0) { + return; + } + if (rings == null || rings.length == 0) { + rings = newRings; + } + else { + CompactXYItems [] both = + new CompactXYItems[rings.length + newRings.length]; + System.arraycopy(rings, 0, both, 0, rings.length); + System.arraycopy(newRings, 0, both, rings.length, newRings.length); + rings = both; + } + } + + public Object getAttribute(Object key) { + return attributes.get(key); + } + + + public Object setAttribute(Object key, Object value) { + return attributes.put(key, value); + } + + + public int getItemCount() { + return rings.length; + } + + + public CompactXYItems getItem(int idx) { + return rings[idx]; + } + + + public Object removeAttribute(Object key) { + return attributes.remove(key); + } + + + public boolean hasAttribute(Object key) { + return attributes.containsKey(key); + } + + + public Range getDomainBounds() { + double upper = Double.NEGATIVE_INFINITY; + double lower = Double.POSITIVE_INFINITY; + int count = getItemCount(); + + for (int i = 0; i < count; i++) { + CompactXYItems items = getItem(i); + double minX = items.getMinX(); + double maxX = items.getMaxX(); + + if (!Double.isNaN(minX)) { + lower = Math.min(lower, minX); + } + + if (!Double.isNaN(maxX)) { + upper = Math.max(upper, maxX); + } + } + + if (lower > upper) { + return null; + } + + return new Range(lower, upper); + } + + + public Range getRangeBounds() { + double upper = Double.NEGATIVE_INFINITY; + double lower = Double.POSITIVE_INFINITY; + int count = getItemCount(); + + for (int i = 0; i < count; i++) { + CompactXYItems items = getItem(i); + double minY = items.getMinY(); + double maxY = items.getMaxY(); + + if (!Double.isNaN(minY)) { + lower = Math.min(lower, minY); + } + + if (!Double.isNaN(maxY)) { + upper = Math.max(upper, maxY); + } + } + + if (lower > upper) { + return null; + } + + return new Range(lower, upper); + } +} +// vim:set ts=4 sw=4 si et sta sts=4 fenc=utf-8 :