/**
* Copyright 2010-2014 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.element.impl;
import java.awt.Graphics2D;
import java.awt.geom.Dimension2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jplot2d.element.Axis;
import org.jplot2d.element.Plot;
import org.jplot2d.element.AxisTransform;
import org.jplot2d.element.AxisOrientation;
import org.jplot2d.element.Element;
import org.jplot2d.element.Layer;
import org.jplot2d.element.Title;
import org.jplot2d.element.TitlePosition;
import org.jplot2d.layout.LayoutDirector;
import org.jplot2d.layout.SimpleLayoutDirector;
import org.jplot2d.notice.RangeAdjustedToValueBoundsNotice;
import org.jplot2d.notice.RangeSelectionNotice;
import org.jplot2d.notice.Notifier;
import org.jplot2d.notice.Notice;
import org.jplot2d.sizing.SizeMode;
import org.jplot2d.transform.PaperTransform;
import org.jplot2d.util.DoubleDimension2D;
import org.jplot2d.util.Range;
/**
* @author Jingjing Li
*
*/
public class PlotImpl extends ContainerImpl implements PlotEx {
/**
* The container size only take effect when this plot is top plot and has a size mode assigned.
*/
private double containerWidth, containerHeight;
/**
* The size mode only take effect when this plot is the root plot.
*/
private SizeMode sizeMode;
private double locX, locY;
private double width = 640, height = 480;
private double scale = 1;
private PaperTransform pxf;
/**
* True when the object is valid. An invalid object needs to be laid out. This flag is set to false when the object
* size is changed. The initial value is true, because the {@link #contentSize} and size are same.
*
* @see #isValid
* @see #validate
* @see #invalidate
*/
private boolean valid = true;
private boolean rerenderNeeded;
private Notifier notifier;
private LayoutDirector layoutDirector = new SimpleLayoutDirector();
/**
* Efficient Immutable
*/
private Dimension2D contentConstraint;
/**
* Must be valid size (positive width and height)
*/
private double preferredContentWidth = 320;
private double preferredContentHeight = 240;
/**
* Efficient Immutable
*/
private Dimension2D contentSize;
private PlotMarginEx margin;
private LegendEx legend;
private final List titles = new ArrayList();
private final List xAxis = new ArrayList();
private final List yAxis = new ArrayList();
private final List layers = new ArrayList();
private final List subplots = new ArrayList();
public PlotImpl() {
margin = new PlotMarginImpl();
margin.setParent(this);
legend = new LegendImpl();
legend.setParent(this);
}
public PlotImpl(LegendEx legend) {
margin = new PlotMarginImpl();
margin.setParent(this);
this.legend = legend;
legend.setParent(this);
}
public String getShortId() {
return null;
}
public String getId() {
if (getParent() != null) {
return "Plot" + getParent().indexOf(this);
} else {
return "Plot@" + Integer.toHexString(System.identityHashCode(this));
}
}
public InvokeStep getInvokeStepFormParent() {
if (parent == null) {
return null;
}
Method method;
try {
method = PlotEx.class.getMethod("getSubplot", Integer.TYPE);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
return new InvokeStep(method, getParent().indexOf(this));
}
@Override
public PlotEx getParent() {
return (PlotEx) super.getParent();
}
@Override
public Map getMooringMap() {
Map result = new HashMap();
for (AxisEx axis : xAxis) {
AxisTransformEx arm = axis.getTickManager().getAxisTransform();
for (LayerEx layer : arm.getLayers()) {
if (layer.getParent() != this) {
result.put(arm, layer);
}
}
}
for (AxisEx axis : yAxis) {
AxisTransformEx arm = axis.getTickManager().getAxisTransform();
for (LayerEx layer : arm.getLayers()) {
if (layer.getParent() != this) {
result.put(arm, layer);
}
}
}
return result;
}
@Override
public Dimension2D getContainerSize() {
return new DoubleDimension2D(containerWidth, containerHeight);
}
@Override
public void setContainerSize(Dimension2D size) {
if (sizeMode == null) {
throw new IllegalStateException("The sizeMode property must be set.");
}
this.containerWidth = size.getWidth();
this.containerHeight = size.getHeight();
}
@Override
public SizeMode getSizeMode() {
return sizeMode;
}
@Override
public void setSizeMode(SizeMode sizeMode) {
this.sizeMode = sizeMode;
if (getParent() == null && sizeMode.isAutoPack()) {
invalidate();
}
}
@Override
public Point2D getLocation() {
return new Point2D.Double(locX, locY);
}
@Override
public final void setLocation(Point2D p) {
setLocation(p.getX(), p.getY());
}
@Override
public void setLocation(double locX, double locY) {
if (getLocation().getX() != locX || getLocation().getY() != locY) {
this.locX = locX;
this.locY = locY;
pxf = null;
// all its sub-components should redraw, even the cacheable sub-component
redrawCascade(this);
}
}
@Override
public Dimension2D getSize() {
return new DoubleDimension2D(width, height);
}
@Override
public final void setSize(Dimension2D size) {
this.setSize(size.getWidth(), size.getHeight());
}
@Override
public void setSize(double width, double height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("paper size must be positive, " + width + "x" + height + " is invalid.");
}
if (this.width != width || this.height != height) {
this.width = width;
this.height = height;
invalidate();
}
}
@Override
public Rectangle2D getBounds() {
return new Rectangle2D.Double(-margin.getLeft() - margin.getExtraLeft(), -margin.getBottom()
- margin.getExtraBottom(), width, height);
}
@Override
public double getScale() {
return this.scale;
}
@Override
public void setScale(double scale) {
if (this.scale != scale) {
this.scale = scale;
pxf = null;
}
}
@Override
public PaperTransform getPaperTransform() {
if (pxf == null) {
if (getParent() != null) {
pxf = getParent().getPaperTransform().translate(locX, locY);
} else if (contentSize == null) {
return null;
} else {
pxf = new PaperTransform(margin.getLeft() + margin.getExtraLeft(), contentSize.getHeight()
+ margin.getTop() + margin.getExtraTop(), scale);
}
}
return pxf;
}
@Override
public void parentPaperTransformChanged() {
pxf = null;
redrawCascade(this);
/* Layer, Axis, Title, Legend do not cache their paper transform */
// notify all subplots to update their paper transform
for (PlotEx sp : subplots) {
sp.parentPaperTransformChanged();
}
}
@Override
public PlotMarginEx getMargin() {
return margin;
}
@Override
public LayoutDirector getLayoutDirector() {
return layoutDirector;
}
@Override
public void setLayoutDirector(LayoutDirector director) {
this.layoutDirector = director;
invalidate();
}
@Override
public Object getConstraint(Plot subplot) {
return layoutDirector.getConstraint((PlotEx) subplot);
}
@Override
public void setConstraint(Plot subplot, Object constraint) {
layoutDirector.setConstraint((PlotEx) subplot, constraint);
invalidate();
}
@Override
public Dimension2D getContentConstrant() {
return contentConstraint;
}
@Override
public void setContentConstrant(Dimension2D constraint) {
if (!constraint.equals(this.contentConstraint)) {
this.contentConstraint = constraint;
invalidate();
}
}
@Override
public boolean isValid() {
return valid;
}
@Override
public void invalidate() {
if (isValid()) {
valid = false;
if (getParent() != null) {
getParent().invalidate();
}
if (layoutDirector != null) {
layoutDirector.invalidateLayout(this);
}
}
}
@Override
public void validate() {
if (isValid()) {
return;
}
if (layoutDirector != null) {
layoutDirector.layout(this);
}
for (PlotEx subplot : subplots) {
subplot.validate();
}
valid = true;
}
@Override
public boolean isRerenderNeeded() {
return rerenderNeeded;
}
@Override
public void setRerenderNeeded(boolean flag) {
rerenderNeeded = flag;
}
@Override
public Notifier getNotifier() {
return notifier;
}
@Override
public void setNotifier(Notifier notifier) {
this.notifier = notifier;
}
@Override
public void notify(Notice msg) {
if ((getParent() != null)) {
getParent().notify(msg);
} else if (notifier != null) {
notifier.notify(msg);
}
}
@Override
public Dimension2D getPreferredContentSize() {
return new DoubleDimension2D(preferredContentWidth, preferredContentHeight);
}
@Override
public void setPreferredContentSize(Dimension2D size) {
if (size == null) {
throw new IllegalArgumentException("Preferred content size cannot be null.");
}
setPreferredContentSize(size.getWidth(), size.getHeight());
}
@Override
public void setPreferredContentSize(double width, double height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException("Size must be positive, " + width + "x" + height + " is invalid.");
}
this.preferredContentWidth = width;
this.preferredContentHeight = height;
// In grid layout or auto pack sizing mode, changing preferred content size will invalidate the layout
invalidate();
}
@Override
public Dimension2D getContentSize() {
return new DoubleDimension2D(contentSize);
}
@Override
public void setContentSize(Dimension2D csize) {
if (csize.getWidth() < 0 || csize.getHeight() < 0) {
throw new IllegalArgumentException("Content size must be positive, " + csize.getWidth() + "x"
+ csize.getHeight() + " is invalid.");
}
this.contentSize = csize;
pxf = null;
}
@Override
public ComponentEx[] getComponents() {
int size = 1 + titles.size() + xAxis.size() + yAxis.size() + layers.size() + subplots.size();
ComponentEx[] comps = new ComponentEx[size];
int n = 0;
comps[n++] = legend;
for (int i = 0; i < titles.size(); i++) {
comps[n++] = titles.get(i);
}
for (int i = 0; i < xAxis.size(); i++) {
comps[n++] = xAxis.get(i);
}
for (int i = 0; i < yAxis.size(); i++) {
comps[n++] = yAxis.get(i);
}
for (int i = 0; i < layers.size(); i++) {
comps[n++] = layers.get(i);
}
for (int i = 0; i < subplots.size(); i++) {
comps[n++] = subplots.get(i);
}
return comps;
}
@Override
public LegendEx getLegend() {
return legend;
}
@Override
public TitleEx getTitle(int index) {
return titles.get(index);
}
@Override
public int indexOf(TitleEx title) {
return titles.indexOf(title);
}
@Override
public TitleEx[] getTitles() {
return titles.toArray(new TitleEx[titles.size()]);
}
@Override
public void addTitle(Title title) {
TitleEx tx = (TitleEx) title;
titles.add(tx);
tx.setParent(this);
redraw(tx);
if (tx.isVisible() && tx.canContribute() && tx.getPosition() != TitlePosition.FREE) {
invalidate();
}
}
@Override
public void removeTitle(Title title) {
TitleEx tx = (TitleEx) title;
redraw(tx);
titles.remove(tx);
tx.setParent(null);
if (tx.isVisible() && tx.canContribute() && tx.getPosition() != TitlePosition.FREE) {
invalidate();
}
}
@Override
public AxisEx getXAxis(int index) {
return xAxis.get(index);
}
@Override
public AxisEx getYAxis(int index) {
return yAxis.get(index);
}
@Override
public int indexOfXAxis(AxisEx axis) {
return xAxis.indexOf(axis);
}
@Override
public int indexOfYAxis(AxisEx axis) {
return yAxis.indexOf(axis);
}
@Override
public AxisEx[] getXAxes() {
return xAxis.toArray(new AxisEx[xAxis.size()]);
}
@Override
public AxisEx[] getYAxes() {
return yAxis.toArray(new AxisEx[yAxis.size()]);
}
@Override
public void addXAxis(Axis axis) {
AxisEx ax = (AxisEx) axis;
if (ax.getTickManager() == null) {
throw new IllegalArgumentException("The axis has no tick manager.");
}
if (ax.getTickManager().getAxisTransform() == null) {
throw new IllegalArgumentException("The axis' tick manager has no range manager.");
}
if (ax.getTickManager().getAxisTransform().getLockGroup() == null) {
throw new IllegalArgumentException("The axis's range manager has no lock group.");
}
xAxis.add(ax);
ax.setParent(this);
ax.setOrientation(AxisOrientation.HORIZONTAL);
redrawCascade(ax);
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
@Override
public void addYAxis(Axis axis) {
AxisEx ax = (AxisEx) axis;
if (ax.getTickManager() == null) {
throw new IllegalArgumentException("The axis has no tick manager.");
}
if (ax.getTickManager().getAxisTransform() == null) {
throw new IllegalArgumentException("The axis' tick manager has no range manager.");
}
if (ax.getTickManager().getAxisTransform().getLockGroup() == null) {
throw new IllegalArgumentException("The axis's range manager has no lock group.");
}
yAxis.add(ax);
ax.setParent(this);
ax.setOrientation(AxisOrientation.VERTICAL);
redrawCascade(ax);
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
@Override
public void addXAxes(Axis[] axes) {
if (axes.length == 0) {
return;
}
AxisTickManagerEx atm = ((AxisEx) axes[0]).getTickManager();
for (int i = 1; i < axes.length; i++) {
if (atm != ((AxisEx) axes[i]).getTickManager()) {
throw new IllegalArgumentException("The axes must have the same tick manager.");
}
}
if (atm == null) {
throw new IllegalArgumentException("The axes have no tick manager.");
}
if (atm.getAxisTransform() == null) {
throw new IllegalArgumentException("The axes' tick manager has no range manager.");
}
if (atm.getAxisTransform().getLockGroup() == null) {
throw new IllegalArgumentException("The axes' range manager has no lock group.");
}
for (int i = 0; i < axes.length; i++) {
AxisEx ax = (AxisEx) axes[i];
xAxis.add(ax);
ax.setParent(this);
ax.setOrientation(AxisOrientation.HORIZONTAL);
redrawCascade(ax);
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
}
@Override
public void addYAxes(Axis[] axes) {
if (axes.length == 0) {
return;
}
AxisTickManagerEx atm = ((AxisEx) axes[0]).getTickManager();
for (int i = 1; i < axes.length; i++) {
if (atm != ((AxisEx) axes[i]).getTickManager()) {
throw new IllegalArgumentException("The axes must have the same tick manager.");
}
}
if (atm == null) {
throw new IllegalArgumentException("The axes have no tick manager.");
}
if (atm.getAxisTransform() == null) {
throw new IllegalArgumentException("The axes' tick manager has no range manager.");
}
if (atm.getAxisTransform().getLockGroup() == null) {
throw new IllegalArgumentException("The axes' range manager has no lock group.");
}
for (int i = 0; i < axes.length; i++) {
AxisEx ax = (AxisEx) axes[i];
yAxis.add(ax);
ax.setParent(this);
ax.setOrientation(AxisOrientation.VERTICAL);
redrawCascade(ax);
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
}
@Override
public void removeXAxis(Axis axis) {
AxisEx ax = (AxisEx) axis;
redrawCascade(ax);
xAxis.remove(ax);
ax.setParent(null);
if (ax.getTickManager().getParent() == null) {
// quit the tick manager if axis is not its only member
ax.setTickManager(null);
} else if (ax.getTickManager().getAxisTransform().getParent() == null) {
// quit the range manager if tick manager is not its only member
ax.getTickManager().setAxisTransform(null);
} else if (ax.getTickManager().getAxisTransform().getLockGroup().getParent() == null) {
// quit the lock group if range manager is not the lock group's only member
ax.getTickManager().getAxisTransform().setLockGroup(null);
}
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
@Override
public void removeYAxis(Axis axis) {
AxisEx ax = (AxisEx) axis;
redrawCascade(ax);
yAxis.remove(ax);
ax.setParent(null);
if (ax.getTickManager().getParent() == null) {
// quit the tick manager if axis is not its only member
ax.setTickManager(null);
} else if (ax.getTickManager().getAxisTransform().getParent() == null) {
// quit the range manager if tick manager is not its only member
ax.getTickManager().setAxisTransform(null);
} else if (ax.getTickManager().getAxisTransform().getLockGroup().getParent() == null) {
// quit the lock group if range manager is not the lock group's only member
ax.getTickManager().getAxisTransform().setLockGroup(null);
}
if (ax.isVisible() && ax.canContribute()) {
invalidate();
}
}
@Override
public Layer getLayer(int index) {
return layers.get(index);
}
@Override
public int indexOf(LayerEx layer) {
return layers.indexOf(layer);
}
@Override
public LayerEx[] getLayers() {
return layers.toArray(new LayerEx[layers.size()]);
}
@Override
public void addLayer(Layer layer) {
LayerEx lx = (LayerEx) layer;
layers.add(lx);
lx.setParent(this);
redrawCascade(lx);
// add legend items
for (GraphEx gx : lx.getGraphs()) {
if (gx instanceof XYGraphEx) {
getLegend().addLegendItem(((XYGraphEx) gx).getLegendItem());
}
}
}
@Override
public void addLayer(Layer layer, AxisTransform xRangeManager, AxisTransform yRangeManager) {
addLayer(layer);
layer.setAxesTransform(xRangeManager, yRangeManager);
}
@Override
public void addLayer(Layer layer, Axis xaxis, Axis yaxis) {
this.addLayer(layer, xaxis.getTickManager().getAxisTransform(), yaxis.getTickManager().getAxisTransform());
}
@Override
public void removeLayer(Layer layer) {
LayerEx lx = (LayerEx) layer;
redrawCascade(lx);
layers.remove(lx);
lx.setParent(null);
lx.setAxesTransform(null, null);
// remove legend items
for (GraphEx gx : lx.getGraphs()) {
if (gx instanceof XYGraphEx) {
getLegend().removeLegendItem(((XYGraphEx) gx).getLegendItem());
}
}
}
@Override
public PlotEx getSubplot(int i) {
return subplots.get(i);
}
@Override
public int indexOf(PlotEx subplot) {
return subplots.indexOf(subplot);
}
@Override
public PlotEx[] getSubplots() {
return subplots.toArray(new PlotEx[subplots.size()]);
}
@Override
public void addSubplot(Plot subplot, Object constraint) {
PlotEx sp = (PlotEx) subplot;
subplots.add(sp);
sp.setParent(this);
redrawCascade(sp);
if (sp.isVisible()) {
invalidate();
}
LayoutDirector ld = getLayoutDirector();
if (ld != null) {
ld.setConstraint(sp, constraint);
}
// push the legend items if the legend is disabled
if (!sp.getLegend().isEnabled()) {
sp.getLegend().putItemsToEnabledLegend();
}
}
@Override
public void setSubplotConstraint(Plot subplot, Object constraint) {
PlotEx sp = (PlotEx) subplot;
LayoutDirector ld = getLayoutDirector();
if (ld != null) {
ld.setConstraint(sp, constraint);
if (sp.isVisible()) {
invalidate();
}
}
}
@Override
public void removeSubplot(Plot subplot) {
PlotEx sp = (PlotEx) subplot;
redrawCascade(sp);
subplots.remove(sp);
sp.setParent(null);
LayoutDirector ld = getLayoutDirector();
if (ld != null) {
ld.remove((PlotEx) subplot);
}
if (subplot.isVisible()) {
invalidate();
}
}
@Override
public boolean canContribute() {
return false;
}
@Override
public PlotImpl copyStructure(Map orig2copyMap) {
PlotImpl result = copyStructureCascade(orig2copyMap);
linkLayerAndAxisTransform(this, orig2copyMap);
return result;
}
@Override
public PlotImpl copyStructureCascade(Map orig2copyMap) {
PlotImpl result = (PlotImpl) super.copyStructure(orig2copyMap);
// copy margin
result.margin = (PlotMarginEx) margin.copyStructure(orig2copyMap);
result.margin.setParent(result);
// copy legend
result.legend = (LegendEx) legend.copyStructure(orig2copyMap);
result.legend.setParent(result);
// copy titles
for (TitleEx title : titles) {
TitleEx titleCopy = (TitleEx) title.copyStructure(orig2copyMap);
titleCopy.setParent(result);
result.titles.add(titleCopy);
}
// copy axes
for (AxisEx va : xAxis) {
AxisEx vaCopy = (AxisEx) va.copyStructure(orig2copyMap);
vaCopy.setParent(result);
result.xAxis.add(vaCopy);
}
for (AxisEx va : yAxis) {
AxisEx vaCopy = (AxisEx) va.copyStructure(orig2copyMap);
vaCopy.setParent(result);
result.yAxis.add(vaCopy);
}
// copy layers
for (LayerEx layer : layers) {
LayerEx layerCopy = (LayerEx) layer.copyStructure(orig2copyMap);
layerCopy.setParent(result);
result.layers.add(layerCopy);
}
// copy subplots
for (PlotEx sp : subplots) {
PlotEx spCopy = (PlotEx) sp.copyStructureCascade(orig2copyMap);
((ComponentEx) spCopy).setParent(result);
result.subplots.add(spCopy);
}
// link legend and legend items
for (LegendItemEx item : getLegend().getItems()) {
LegendItemEx liCopy = (LegendItemEx) orig2copyMap.get(item);
result.legend.addLegendItem(liCopy);
}
return result;
}
@Override
public void copyFrom(ElementEx src) {
super.copyFrom(src);
PlotImpl plot = (PlotImpl) src;
locX = plot.locX;
locY = plot.locY;
width = plot.width;
height = plot.height;
scale = plot.scale;
valid = plot.valid;
layoutDirector = plot.layoutDirector;
pxf = plot.pxf;
preferredContentWidth = plot.preferredContentWidth;
preferredContentHeight = plot.preferredContentHeight;
contentSize = plot.contentSize;
containerWidth = plot.containerWidth;
containerHeight = plot.containerHeight;
sizeMode = plot.sizeMode;
rerenderNeeded = plot.rerenderNeeded;
}
/**
* Deep search layers to find the copy whoes range manager not been linked, and set for them.
*
* @param plot
* the plot to be copied
* @param orig2copyMap
*/
public static void linkLayerAndAxisTransform(PlotEx plot, Map orig2copyMap) {
for (LayerEx layer : plot.getLayers()) {
LayerEx layerCopy = (LayerEx) orig2copyMap.get(layer);
if (layerCopy.getXAxisTransform() == null && layer.getXAxisTransform() != null) {
AxisTransformEx xcopy = (AxisTransformEx) orig2copyMap.get(layer.getXAxisTransform());
layerCopy.linkXAxisTransform(xcopy);
xcopy.linkLayer(layerCopy);
}
if (layerCopy.getYAxisTransform() == null && layer.getYAxisTransform() != null) {
AxisTransformEx ycopy = (AxisTransformEx) orig2copyMap.get(layer.getYAxisTransform());
layerCopy.linkYAxisTransform(ycopy);
ycopy.linkLayer(layerCopy);
}
}
for (PlotEx sp : plot.getSubplots()) {
linkLayerAndAxisTransform(sp, orig2copyMap);
}
}
@Override
public void draw(Graphics2D g) {
// for debugging
// drawBounds(g);
}
@Override
public void commit() {
PaperTransform oldPxf = getPaperTransform();
double scaleResult = 0;
// set the plot size if it is decided by size mode
if (getSizeMode() != null && !getSizeMode().isAutoPack()) {
SizeMode.Result sizeResult = getSizeMode().update(this);
setSize(sizeResult.getSize());
scaleResult = sizeResult.getScale();
}
/*
* Axis is a special component. Its length can be set by layout manager, but its thick depends on its internal
* status, such as tick height, labels. The auto range must be re-calculated after all axes length are set. So
* we cannot use deep-first validate tree. we must layout all subplots, then calculate auto range, then
* calculate thickness of all axes.
*/
/*
* The initial axis has 0 length and no label. The initial legend size as it contains 1 item. In most case, this
* assumption is correct.
*/
/* calculate size may invalidate plot */
calcAxesThickness(this);
calcLegendSize(this);
calcTitleSize(this);
while (true) {
// auto pack the plot size
if (getSizeMode() != null && getSizeMode().isAutoPack()) {
autoPack();
}
/*
* Laying out axes may register some axis that ticks need be re-calculated
*/
this.validate();
/*
* Auto range axes MUST be executed after they are laid out.
Auto range axes may register some axis
* that ticks need be re-calculated
*/
calcPendingLockGroupAutoRange();
/*
* Calculating axes tick may invalidate some axis. Their metrics need be re-calculated
*/
calcAxesTick(this);
/* thickness changes may invalidate the plot */
calcAxesThickness(this);
/* length constraint changes may invalidate the plot */
calcLegendSize(this);
if (this.isValid()) {
break;
}
}
// update the scale if the size is autoPacked
if (getSizeMode() != null && getSizeMode().isAutoPack()) {
scaleResult = getSizeMode().update(this).getScale();
}
// invalidate pxf id scale changed
if (getSizeMode() != null) {
setScale(scaleResult);
}
// invalidate pxf on all children
if (oldPxf == null || !oldPxf.equals(getPaperTransform())) {
this.parentPaperTransformChanged();
}
calcPendingImageMappingLimits();
}
/**
* Sets the plot size according to its contents.
*/
private void autoPack() {
if (getLayoutDirector() != null) {
if (!isValid()) {
Dimension2D prefSize = getLayoutDirector().getPreferredSize(this);
this.setSize(prefSize);
}
}
}
/**
* Re-autorange on all AxisLockGroups whoes autorange are true.
*/
private void calcPendingLockGroupAutoRange() {
Set xalgs = new HashSet();
Set yalgs = new HashSet();
fillLockGroups(this, xalgs, yalgs);
/* calculating auto range may require auto range on y axis, and vice versa */
boolean hasAutoRange = true;
while (hasAutoRange) {
hasAutoRange = false;
for (AxisRangeLockGroupEx alg : xalgs) {
hasAutoRange |= alg.calcAutoRange();
}
for (AxisRangeLockGroupEx alg : yalgs) {
hasAutoRange |= alg.calcAutoRange();
}
}
}
/**
* find all AxisLockGroups in the given plot and fill them into the given set.
*/
private static void fillLockGroups(PlotEx plot, Set xalgs, Set yalgs) {
for (AxisEx axis : plot.getXAxes()) {
AxisRangeLockGroupEx alg = axis.getTickManager().getAxisTransform().getLockGroup();
xalgs.add(alg);
}
for (AxisEx axis : plot.getYAxes()) {
AxisRangeLockGroupEx alg = axis.getTickManager().getAxisTransform().getLockGroup();
yalgs.add(alg);
}
for (PlotEx sp : plot.getSubplots()) {
fillLockGroups(sp, xalgs, yalgs);
}
}
/**
* Calculate axis thickness according to its tick height, label font and label orientation.
*/
private static void calcAxesThickness(PlotEx plot) {
for (AxisEx axis : plot.getXAxes()) {
if (axis.isVisible() && axis.canContribute()) {
calcAxisThickness(axis);
}
}
for (AxisEx axis : plot.getYAxes()) {
if (axis.isVisible() && axis.canContribute()) {
calcAxisThickness(axis);
}
}
for (PlotEx sp : plot.getSubplots()) {
calcAxesThickness(sp);
}
}
private static void calcAxisThickness(AxisEx axis) {
double asc = axis.getAsc();
double desc = axis.getDesc();
axis.calcThickness();
if (Math.abs(asc - axis.getAsc()) > Math.abs(asc) * 1e-12
|| Math.abs(desc - axis.getDesc()) > Math.abs(desc) * 1e-12) {
axis.getParent().invalidate();
}
}
/**
* Calculate axis ticks according to its length, range and tick properties.
*/
private static void calcAxesTick(PlotEx plot) {
Set algs = new HashSet();
fillTickManagers(plot, algs);
for (AxisTickManagerEx alg : algs) {
alg.calcTicks();
}
}
/**
* find all AxisLockGroups in the given plot and fill them into the given set.
*/
private static void fillTickManagers(PlotEx plot, Set algs) {
for (AxisEx axis : plot.getXAxes()) {
AxisTickManagerEx alg = axis.getTickManager();
algs.add(alg);
}
for (AxisEx axis : plot.getYAxes()) {
AxisTickManagerEx alg = axis.getTickManager();
algs.add(alg);
}
for (PlotEx sp : plot.getSubplots()) {
fillTickManagers(sp, algs);
}
}
/**
* Calculate title size according to its contents
*/
private static void calcTitleSize(PlotEx plot) {
for (TitleEx title : plot.getTitles()) {
if (title.isVisible() && title.canContribute()) {
Dimension2D size = title.getSize();
double oldThickness = (size == null) ? 0 : size.getHeight();
title.calcSize();
if (Math.abs(oldThickness - title.getSize().getHeight()) > Math.abs(oldThickness) * 1e-12) {
plot.invalidate();
}
}
}
for (PlotEx sp : plot.getSubplots()) {
calcTitleSize(sp);
}
}
/**
* Calculate legend size according to its length constraint, items and item font.
*/
private static void calcLegendSize(PlotEx plot) {
LegendEx legend = plot.getLegend();
if (legend.isVisible() && legend.canContribute()) {
double oldThickness = legend.getThickness();
plot.getLegend().calcSize();
if (Math.abs(oldThickness - legend.getThickness()) > Math.abs(oldThickness) * 1e-12) {
plot.invalidate();
}
}
for (PlotEx sp : plot.getSubplots()) {
calcLegendSize(sp);
}
}
/**
* Re-autorange on all AxisLockGroups whoes autorange are true.
*/
private void calcPendingImageMappingLimits() {
Set ims = new HashSet();
Set rgbims = new HashSet();
fillImageMappings(this, ims, rgbims);
for (ImageMappingEx im : ims) {
im.calcLimits();
}
for (RGBImageMappingEx im : rgbims) {
im.calcLimits();
}
}
/**
* find all AxisLockGroups in the given plot and fill them into the given set.
*/
private static void fillImageMappings(PlotEx plot, Set ims, Set rgbims) {
for (LayerEx layer : plot.getLayers()) {
for (GraphEx graph : layer.getGraphs()) {
if (graph instanceof ImageGraphEx) {
ims.add(((ImageGraphEx) graph).getMapping());
}
if (graph instanceof RGBImageGraphEx) {
rgbims.add(((RGBImageGraphEx) graph).getMapping());
}
}
}
for (PlotEx sp : plot.getSubplots()) {
fillImageMappings(sp, ims, rgbims);
}
}
@Override
public void zoomXRange(double start, double end) {
Set xarlgs = getXAxisRangeLockGroup();
zoomRange(xarlgs, start, end);
}
@Override
public void zoomYRange(double start, double end) {
Set yarlgs = getYAxisRangeLockGroup();
zoomRange(yarlgs, start, end);
}
/**
* @return all zoomable AxisRangeLockGroup
*/
private Set getXAxisRangeLockGroup() {
Set algs = new HashSet();
for (LayerEx layer : layers) {
if (layer.getXAxisTransform() != null) {
AxisRangeLockGroupEx alg = layer.getXAxisTransform().getLockGroup();
if (alg.isZoomable()) {
algs.add(alg);
}
}
}
return algs;
}
/**
* @return all zoomable AxisRangeLockGroup
*/
private Set getYAxisRangeLockGroup() {
Set algs = new HashSet();
for (LayerEx layer : layers) {
if (layer.getXAxisTransform() != null) {
AxisRangeLockGroupEx alg = layer.getYAxisTransform().getLockGroup();
if (alg.isZoomable()) {
algs.add(alg);
}
}
}
return algs;
}
/**
* Zoom the range on the given Axis Range Lock Groups
*
* @param arlgs
* @param start
* @param end
*/
private void zoomRange(Collection arlgs, double start, double end) {
Collection arms = new ArrayList();
for (AxisRangeLockGroupEx arlg : arlgs) {
arms.addAll(Arrays.asList(arlg.getRangeManagers()));
}
Range range = new Range.Double(start, end);
Range validRange = AxisRangeUtils.validateNormalRange(range, arms, false);
if (!validRange.equals(range)) {
notify(new RangeAdjustedToValueBoundsNotice("Range exceed valid boundary, has been adjusted."));
}
RangeStatus rs = AxisRangeUtils.ensurePrecision(validRange, arms);
if (rs.getStatus() != null) {
notify(new RangeSelectionNotice(rs.getStatus().getMessage()));
}
RangeStatus xrs = AxisRangeUtils.ensureCircleSpan(rs, arms);
if (xrs.getStatus() != null) {
notify(new RangeSelectionNotice(xrs.getStatus().getMessage()));
}
for (AxisRangeLockGroupEx arm : arlgs) {
arm.zoomNormalRange(xrs);
}
}
@Override
public void adaptiveZoomX() {
Set xarlgs = getXAxisRangeLockGroup();
for (AxisRangeLockGroupEx arm : xarlgs) {
arm.reAutoRange();
}
}
@Override
public void adaptiveZoomY() {
Set yarlgs = getYAxisRangeLockGroup();
for (AxisRangeLockGroupEx arm : yarlgs) {
arm.reAutoRange();
}
}
}
Ads help maintain this website.