/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package gov.nasa.arc.mct.fastplot.bridge;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.AxisOrientationSetting;
import gov.nasa.arc.mct.fastplot.utils.ComponentTraverser;
import gov.nasa.arc.mct.fastplot.utils.ComponentTraverser.ComponentProcedure;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JLabel;
import plotter.xy.SlopeLineDisplay;
import plotter.xy.XYAxis;
import plotter.xy.XYLocationDisplay;
import plotter.xy.XYPlot;
/**
* Manages layout for stack plots. Primarily functions as a grid bag layout, but
* also forces alignment on y axes by monitoring / adjusting y axis width and
* legend manager width.
*
* @author vwoeltje
*
*/
public class StackPlotLayout extends GridBagLayout {
private static final long serialVersionUID = 1L;
private PlotView plotView;
public StackPlotLayout (PlotView plotView) {
this.plotView = plotView;
}
@Override
public void layoutContainer(Container parent) {
final List<XYPlotComponents> componentList = new ArrayList<XYPlotComponents>();
/* Find all XYPlots and create mappings to their internal elements */
ComponentTraverser.traverse(parent, new ComponentProcedure() {
@Override
public void run(Component c) {
if (c instanceof XYPlot) componentList.add(new XYPlotComponents((XYPlot) c));
}
});
/* Desired widths */
int yAxisWidth = 1;
int legendWidth = 1;
/* Desired height (in case legend gets tall) */
int legendHeight = 1;
/* Swap minimum width and height if we are a sideways stackplot */
int minWidth, minHeight;
if (plotView.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
minWidth = PlotConstants.MINIMUM_PLOT_WIDTH;
minHeight = PlotConstants.MINIMUM_PLOT_HEIGHT;
} else if (plotView.getAxisOrientationSetting() == AxisOrientationSetting.Y_AXIS_AS_TIME){
minWidth = PlotConstants.MINIMUM_PLOT_HEIGHT;
minHeight = PlotConstants.MINIMUM_PLOT_WIDTH;
} else {
minWidth = PlotConstants.MINIMUM_PLOT_WIDTH;
minHeight = PlotConstants.MINIMUM_PLOT_HEIGHT;
}
/* Find the largest "desired" width/height for the components */
for (XYPlotComponents xyPlotComps : componentList) {
int textMargin = xyPlotComps.getYAxis().getTextMargin();
yAxisWidth = Math.max(xyPlotComps.getYAxis().getWidth(), yAxisWidth);
for (Component c : xyPlotComps.getYAxis().getComponents()) {
yAxisWidth = Math.max(c.getWidth() + textMargin, yAxisWidth);
}
for (LegendEntry c : xyPlotComps.getLegend().getLegendEntryList()) {
legendWidth = Math.max(c.getLabelWidth(), legendWidth);
}
legendHeight = Math.max(xyPlotComps.getLegend().getHeight(), legendHeight);
if (plotView.getAxisOrientationSetting() == AxisOrientationSetting.Y_AXIS_AS_TIME) {
legendWidth = constrainLegendWidth(legendWidth, yAxisWidth, minWidth, parent.getWidth());
realign(xyPlotComps, yAxisWidth, legendWidth,
yAxisWidth + minWidth + (componentList.size() > 1 ? legendWidth : 0),
Math.max(legendHeight, minHeight + xyPlotComps.getSlopeLineDisplay().getHeight()) +
xyPlotComps.getXAxis().getHeight());
yAxisWidth = legendWidth = 1; // reset widths
}
}
/* Set all PreferredSizes to the desired widths we've discovered */
if (plotView.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
legendWidth = constrainLegendWidth(legendWidth, yAxisWidth, minWidth, parent.getWidth());
for (XYPlotComponents xyPlotComps : componentList) {
realign(xyPlotComps, yAxisWidth, legendWidth + PlotLineGlobalConfiguration.getLegendPadding(),
yAxisWidth + minWidth,
Math.max(legendHeight, minHeight + xyPlotComps.getSlopeLineDisplay().getHeight()) +
xyPlotComps.getXAxis().getHeight());
}
}
super.layoutContainer(parent);
}
private int constrainLegendWidth(int legendWidth, int yAxisWidth, int minWidth, int parentWidth) {
int pad = PlotLineGlobalConfiguration.getLegendPadding();
/* If there's not enough room for minimum-sized plot, shrink legends */
if (yAxisWidth + legendWidth + pad + minWidth > parentWidth) {
int reducedWidth = parentWidth - minWidth - yAxisWidth - pad;
return (reducedWidth < 0) ? 0 : reducedWidth;
} else {
return legendWidth;
}
}
private void realign (XYPlotComponents xyPlotComps, int yAxisWidth, int legendWidth, int fullWidth, int fullHeight) {
XYPlot plot = xyPlotComps.getPlot();
XYAxis yAxis = xyPlotComps.getYAxis();
LegendManager legend = xyPlotComps.getLegend();
yAxis.setPreferredSize(new Dimension ( yAxisWidth,
yAxis.getPreferredSize().height));
/* X Axis starts at left edge of Y Axis, so margin must equal Y Axis width */
xyPlotComps.getXAxis().setStartMargin(yAxisWidth);
legend.setPreferredSize(new Dimension(legendWidth,
legend.getPreferredSize().height));
plot.setPreferredSize(new Dimension(fullWidth, fullHeight));
/* Initially, let the slope & location displays be as big as they want */
JLabel slopeDisplay = xyPlotComps.getSlopeLineDisplay();
JLabel locationDisplay = xyPlotComps.getLocationDisplay();
letSizeFreely(slopeDisplay);
letSizeFreely(locationDisplay);
/* Then clamp them to content width */
int contentWidth = plot.getContents().getWidth();
if (slopeDisplay.getPreferredSize().width > contentWidth) {
slopeDisplay.setPreferredSize(new Dimension (contentWidth, slopeDisplay.getPreferredSize().height));
}
if (locationDisplay.getPreferredSize().width > contentWidth) {
locationDisplay.setPreferredSize(new Dimension (contentWidth, locationDisplay.getPreferredSize().height));
}
/* Hide the location display if there is insufficient room */
if (!slopeDisplay.getText().isEmpty() && locationDisplay.getPreferredSize().width + slopeDisplay.getPreferredSize().width > contentWidth) {
locationDisplay.setPreferredSize(new Dimension (1,locationDisplay.getPreferredSize().height));
}
/* Finally, request that these new preferred sizes get enforced */
plot.revalidate();
}
/**
* Prevent JLabels from shrinking when empty, but size freely when filled
* (originally from PlotDataCursor)
*/
private void letSizeFreely(JLabel label) {
if (label.getText().isEmpty() || !label.isVisible()) {
label.setText("Ag");
Dimension size = label.getPreferredSize();
size.width = 1;
label.setText("");
label.setPreferredSize(size);
} else {
label.setPreferredSize(null);
}
}
/*
* Provide access to components in an XYPlot. Helpful insofar as getLegend() is
* not available in XYPlot itself (legends are tacked on external to Plotter package)
*/
private static class XYPlotComponents {
private XYPlot plot;
private LegendManager legend = null;
private SlopeLineDisplay slope = null;
private XYLocationDisplay location = null;
public XYPlotComponents(XYPlot xyPlot) {
plot = xyPlot;
findLegend();
}
public XYPlot getPlot() {
return plot;
}
public XYAxis getYAxis() {
return plot.getYAxis();
}
public XYAxis getXAxis() {
return plot.getXAxis();
}
public JLabel getSlopeLineDisplay() {
return slope;
}
public JLabel getLocationDisplay() {
return location;
}
public LegendManager getLegend() {
/* It may be the case that a legend has been added since
* this object was created, so look for it */
if (legend == null) findLegend();
return legend;
}
private void findLegend() {
for (Component c : plot.getComponents()) {
if (c instanceof LegendManager) {
legend = (LegendManager) c;
}
if (c instanceof SlopeLineDisplay) {
slope = (SlopeLineDisplay) c;
}
if (c instanceof XYLocationDisplay) {
location = (XYLocationDisplay) c;
}
}
}
}
}