package org.autoplot.dom; import java.awt.Rectangle; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import org.das2.graph.DasRow; import org.das2.util.LoggerManager; import org.autoplot.datasource.DataSourceUtil; import org.das2.graph.DasAxis; import org.das2.graph.DasColumn; import org.das2.graph.DasDevicePosition; import org.das2.graph.LegendPosition; /** * Many operations are defined within the DOM object controllers that needn't * be. This class is a place for operations that are performed on the DOM * independent of the controllers. For example, the operation to swap the * position of two plots is easily implemented by changing the rowid and columnid * properties of the two plots. * * @author jbf */ public class DomOps { private static final Logger logger = LoggerManager.getLogger("autoplot.dom"); /** * swap the position of the two plots. If one plot has its tick labels hidden, * then this is swapped as well. * @param a * @param b */ public static void swapPosition( Plot a, Plot b ) { if ( a==b ) return; if ( a.controller!=null ) { a.controller.dom.options.setAutolayout( false ); } String trowid= a.getRowId(); String tcolumnid= a.getColumnId(); boolean txtv= a.getXaxis().isDrawTickLabels(); boolean tytv= a.getYaxis().isDrawTickLabels(); String ticksUriA= a.getTicksURI(); String ticksUriB= b.getTicksURI(); a.setTicksURI( ticksUriB ); b.setTicksURI( ticksUriA ); String ticksUriALabels= a.getEphemerisLabels(); String ticksUriBLabels= b.getEphemerisLabels(); a.setTicksURI( ticksUriBLabels ); b.setTicksURI( ticksUriALabels ); a.setRowId(b.getRowId()); a.setColumnId(b.getColumnId()); a.getXaxis().setDrawTickLabels(b.getXaxis().isDrawTickLabels()); a.getYaxis().setDrawTickLabels(b.getYaxis().isDrawTickLabels()); b.setRowId(trowid); b.setColumnId(tcolumnid); b.getXaxis().setDrawTickLabels(txtv); b.getYaxis().setDrawTickLabels(tytv); if ( a.controller!=null ) { a.controller.dom.controller.waitUntilIdle(); a.controller.dom.options.setAutolayout( true ); } } /** * Copy the plot and its axis settings, optionally binding the axes. Whether * the axes are bound or not, the duplicate plot is initially synchronized to * the source plot. * See {@link org.autoplot.dom.ApplicationController#copyPlot(org.autoplot.dom.Plot, boolean, boolean, boolean) copyPlot} * @param srcPlot * @param bindx * @param bindy * @param direction * @return the new plot * */ public static Plot copyPlot(Plot srcPlot, boolean bindx, boolean bindy, Object direction ) { Application application= srcPlot.getController().getApplication(); ApplicationController ac= application.getController(); Plot that = ac.addPlot( direction ); that.getController().setAutoBinding(false); that.syncTo( srcPlot, Arrays.asList( DomNode.PROP_ID, Plot.PROP_ROWID, Plot.PROP_COLUMNID ) ); if (bindx) { BindingModel bb = ac.findBinding(application, Application.PROP_TIMERANGE, srcPlot.getXaxis(), Axis.PROP_RANGE); if (bb == null) { ac.bind(srcPlot.getXaxis(), Axis.PROP_RANGE, that.getXaxis(), Axis.PROP_RANGE); } else { ac.bind(application, Application.PROP_TIMERANGE, that.getXaxis(), Axis.PROP_RANGE); } } if (bindy) { ac.bind(srcPlot.getYaxis(), Axis.PROP_RANGE, that.getYaxis(), Axis.PROP_RANGE); } return that; } /** * copy the plot elements from srcPlot to dstPlot. This does not appear * to be used. * See {@link org.autoplot.dom.ApplicationController#copyPlotElement(org.autoplot.dom.PlotElement, org.autoplot.dom.Plot, org.autoplot.dom.DataSourceFilter) copyPlotElement} * @param srcPlot plot containing zero or more plotElements. * @param dstPlot destination for the plotElements. * @return */ public static List copyPlotElements( Plot srcPlot, Plot dstPlot ) { ApplicationController ac= srcPlot.getController().getApplication().getController(); List srcElements = ac.getPlotElementsFor(srcPlot); List newElements = new ArrayList<>(); for (PlotElement srcElement : srcElements) { if (!srcElement.getComponent().equals("")) { if ( srcElement.getController().getParentPlotElement()==null ) { PlotElement newp = ac.copyPlotElement(srcElement, dstPlot, null); newElements.add(newp); } } else { PlotElement newp = ac.copyPlotElement(srcElement, dstPlot, null); newElements.add(newp); List srcKids = srcElement.controller.getChildPlotElements(); DataSourceFilter dsf1 = ac.getDataSourceFilterFor(newp); for (PlotElement k : srcKids) { if (srcElements.contains(k)) { PlotElement kidp = ac.copyPlotElement(k, dstPlot, dsf1); kidp.getController().setParentPlotElement(newp); newElements.add(kidp); } } } } return newElements; } /** * copyPlotAndPlotElements. This does not appear to be used. * See {@link org.autoplot.dom.ApplicationController#copyPlotAndPlotElements(org.autoplot.dom.Plot, org.autoplot.dom.DataSourceFilter, boolean, boolean) copyPlotAndPlotElements} * @param srcPlot * @param copyPlotElements * @param bindx * @param bindy * @param direction * @return */ public static Plot copyPlotAndPlotElements( Plot srcPlot, boolean copyPlotElements, boolean bindx, boolean bindy, Object direction ) { Plot dstPlot= copyPlot( srcPlot, bindx, bindy, direction ); if ( copyPlotElements ) copyPlotElements( srcPlot, dstPlot ); return dstPlot; } /** * Used in the LayoutPanel's add hidden plot, get the column of * the selected plot or create a new column if several plots are * selected. * @param dom the application. * @param selectedPlots the selected plots. * @param create allow a new column to be created. * @return */ public static Column getOrCreateSelectedColumn( Application dom, List selectedPlots, boolean create ) { Set n= new HashSet<>(); for ( Plot p: selectedPlots ) { n.add( p.getColumnId() ); } if ( n.size()==1 ) { return (Column) DomUtil.getElementById(dom,n.iterator().next()); } else { if ( create ) { Canvas c= dom.getCanvases(0); //TODO: do this Column col= c.getController().addColumn(); col.setLeft("0%"); col.setRight("100%"); return col; } else { return null; } } } /** * Used in the LayoutPanel's add hidden plot, get the row of * the selected plot or create a new row if several plots are * selected. * @param dom the application. * @param selectedPlots the selected plots. * @param create allow a new column to be created. * @return */ public static Row getOrCreateSelectedRow( Application dom, List selectedPlots, boolean create ) { Set n= new HashSet<>(); for ( Plot p: selectedPlots ) { if ( !n.contains(p.getRowId()) ) n.add( p.getRowId() ); } if ( n.size()==1 ) { return (Row) DomUtil.getElementById(dom,n.iterator().next()); } else { if ( create ) { Iterator iter= n.iterator(); Row r= (Row) DomUtil.getElementById( dom.getCanvases(0), iter.next() ); Row rmax= r; Row rmin= r; for ( int i=1; iter.hasNext(); i++ ) { r= (Row) DomUtil.getElementById( dom.getCanvases(0), iter.next() ); if ( r.getController().getDasRow().getDMaximum()>rmax.getController().getDasRow().getDMaximum() ) { rmax= r; } if ( r.getController().getDasRow().getDMinimum() plots ) { Plot pmax=plots.get(0); Plot pmin=plots.get(0); Row r= (Row) DomUtil.getElementById( dom.getCanvases(0), pmax.getRowId() ); Row rmax= r; Row rmin= r; for ( Plot p: plots ) { r= (Row) DomUtil.getElementById( dom.getCanvases(0), p.getRowId() ); if ( r.getController().getDasRow().getDMaximum()>rmax.getController().getDasRow().getDMaximum() ) { rmax= r; pmax= p; } if ( r.getController().getDasRow().getDMinimum() plots ) { Plot pmax=plots.get(0); Plot pmin=plots.get(0); Column r= (Column) DomUtil.getElementById( dom.getCanvases(0), pmax.getColumnId() ); Column rmax= r; Column rmin= r; for ( Plot p: plots ) { r= (Column) DomUtil.getElementById( dom.getCanvases(0), p.getColumnId() ); if ( r.getController().getDasColumn().getDMaximum()>rmax.getController().getDasColumn().getDMaximum() ) { rmax= r; pmax= p; } if ( r.getController().getDasColumn().getDMinimum() getPlotsFor( Application dom, Row row, boolean visible ) { ArrayList result= new ArrayList(); for ( Plot p: dom.getPlots() ) { if ( p.getRowId().equals(row.getId()) ) { if ( visible ) { if ( p.isVisible() ) result.add(p); } else { result.add(p); } } } return result; } /** * return a list of the plots using the given row. * This does not use controllers. * @param dom a dom * @param column the column to search for. * @param visible if true, then the plot must also be visible. (Note its colorbar visible is ignored.) * @return a list of plots. */ public static List getPlotsFor( Application dom, Column column, boolean visible ) { ArrayList result= new ArrayList(); for ( Plot p: dom.getPlots() ) { if ( p.getColumnId().equals(column.getId()) ) { if ( visible ) { if ( p.isVisible() ) result.add(p); } else { result.add(p); } } } return result; } /** * count the number of lines in the string, breaking on "!c" * @param s * @return */ private static int lineCount( String s ) { String[] ss= s.split("(\\!c|\\!C|\\)"); int emptyLines=0; while ( emptyLines *
  • Removes extra whitespace *
  • Preserves relative size weights. *
  • Preserves em heights, to support components which should not be rescaled. (Not yet supported.) *
  • Preserves space taken by strange objects, to support future canvas components. *
  • Renormalizes the margin row, so it is nice. (Not yet supported. This should consider font size, where large fonts don't need so much space.) * * This should also be idempotent, where calling this a second time should have no effect. * @param dom an application state. */ public static void fixLayout( Application dom) { fixLayout( dom, Collections.emptyMap() ); } /** * See https://sourceforge.net/p/autoplot/feature-requests/811/ */ public static final String OPTION_FIX_LAYOUT_HIDE_TITLES = "hideTitles"; public static final String OPTION_FIX_LAYOUT_HIDE_TIME_AXES = "hideTimeAxes"; public static final String OPTION_FIX_LAYOUT_HIDE_Y_AXES = "hideYAxes"; public static final String OPTION_FIX_LAYOUT_MOVE_LEGENDS_TO_OUTSIDE_NE = "moveLegendsToOutsideNE"; public static final String OPTION_FIX_LAYOUT_VERTICAL_SPACING = "verticalSpacing"; public static final String OPTION_FIX_LAYOUT_HORIZONTAL_SPACING = "horizontalSpacing"; private static double[] parseLayoutStr( String s, double[] deflt ) { try { return DasDevicePosition.parseLayoutStr(s); } catch ( ParseException ex ) { return deflt; } } /** * New layout mechanism which fixes a number of shortcomings of the old layout mechanism, * newCanvasLayout. This one:
      *
    • Removes extra whitespace *
    • Preserves relative size weights. *
    • Preserves em heights, to support components which should not be rescaled. (Not yet supported.) *
    • Preserves space taken by strange objects, to support future canvas components. *
    • Renormalizes the margin row, so it is nice. (Not yet supported. This should consider font size, where large fonts don't need so much space.) *
    * This should also be idempotent, where calling this a second time should have no effect. * Additional options include:
      *
    • interPlotVerticalSpacing - 1em *
    * @param dom an application state. * @param options additional options, including interPlotVerticalSpacing. */ public static void fixLayout( Application dom, Map options ) { Logger logger= LoggerManager.getLogger("autoplot.dom.layout.fixlayout"); logger.fine( "enter fixLayout" ); dom.getController().waitUntilIdle(); boolean autoLayout= dom.options.isAutolayout(); dom.options.setAutolayout(false); try { Canvas canvas= dom.getCanvases(0); Column marginColumn= canvas.getMarginColumn(); Row[] rows= canvas.getRows(); int nrow= rows.length; Column[] columns= canvas.getColumns(); //kludge: check for duplicate names of rows. Use the first one found. Map rowsCheck= new HashMap(); List rm= new ArrayList<>(); for ( int i=0; i plots= DomOps.getPlotsFor( dom, rows[i], true ); if ( plots.size()>0 ) { if ( rowsCheck.containsKey(rows[i].getId()) ) { logger.log(Level.FINE, "duplicate row id: {0}", rows[i].getId()); rm.add( rows[i] ); } else { rowsCheck.put( rows[i].getId(), rows[i] ); } } else { logger.log(Level.FINE, "unused row: {0}", rows[i]); rm.add( rows[i] ); } } List rowsList= new ArrayList<>(Arrays.asList(rows)); rm.forEach((r) -> { rowsList.remove(r); }); canvas.setRows( rowsList.toArray(new Row[rowsList.size()])); rows= new Row[ rowsList.size() ]; nrow= rows.length; for ( int i=0; i { int d1= DomUtil.getRowPositionPixels( dom, r1, r1.getTop() ); int d2= DomUtil.getRowPositionPixels( dom, r2, r2.getTop() ); return d1-d2; }); String topRowId= rows[0].getId(); String bottomRowId= rows[rows.length-1].getId(); String leftColumnId= columns.length>0 ? columns[0].getId() : ""; if ( options.getOrDefault( OPTION_FIX_LAYOUT_HIDE_TITLES, "false" ).equals("true") ) { for ( Plot p: dom.plots ) { if ( p.getRowId().equals(topRowId) ) { logger.fine("not hiding top plot's title"); } else { p.setDisplayTitle(false); } } } if ( options.getOrDefault( OPTION_FIX_LAYOUT_HIDE_TIME_AXES, "false" ).equals("true") ) { for ( Plot p: dom.plots ) { if ( p.getRowId().equals(bottomRowId) ) { logger.fine("not hiding bottom plot's time axis"); } else { // TODO: check bindings to see that this axis is bound to the timerange p.xaxis.setDrawTickLabels(false); } } } if ( options.getOrDefault( OPTION_FIX_LAYOUT_HIDE_Y_AXES, "false" ).equals("true") ) { if ( columns.length!=0 ) { for ( Plot p: dom.plots ) { if ( p.getColumnId().equals(leftColumnId) || p.getColumnId().equals(marginColumn.getId()) ) { logger.fine("not hiding leftmost plot's Y axis"); } else { // TODO: check bindings to see that this axis is bound to the timerange p.yaxis.setDrawTickLabels(false); } } } } if ( options.getOrDefault( OPTION_FIX_LAYOUT_MOVE_LEGENDS_TO_OUTSIDE_NE, "false" ).equals("true") ) { for ( Plot p: dom.plots ) { if ( p.isDisplayLegend() ) { if ( !p.getZaxis().isVisible() ) { p.setLegendPosition(LegendPosition.OutsideNE); } } } } fixVerticalLayout( dom, options ); fixHorizontalLayout( dom, options ); } finally { dom.options.setAutolayout(autoLayout); } dom.getController().waitUntilIdle(); } /** * return the number of lines taken by the x-axis, including the ticks. * @param plotj * @return */ private static int getXAxisLines( Plot plotj ) { if ( !plotj.getXaxis().isDrawTickLabels() ) { return 1; } int lc= lineCount(plotj.getXaxis().getLabel()); int ephemerisLineCount; if ( plotj.getEphemerisLineCount()>-1 ) { ephemerisLineCount= plotj.getEphemerisLineCount(); } else { if ( plotj.getTicksURI().trim().length()>0 ) { if ( plotj.getXaxis().getController()!=null ) { DasAxis a= plotj.getXaxis().getController().getDasAxis(); ephemerisLineCount= a.getTickLines(); } else { ephemerisLineCount= 5; // complete guess } } else { if ( lc==0 ) { // without the label used to label the range, midnights will be indicated with the date, so add one em. lc=1; } ephemerisLineCount= 0; } } ephemerisLineCount+= Math.ceil(ephemerisLineCount/4.); // there's an extra 25% added, see DasAxis.getLineSpacing! return ephemerisLineCount+2+1+lc; // +1 is for ticks } /** * This is the new layout mechanism (fixLayout), correcting the layout in the vertical direction. This one:
      *
    • Removes extra whitespace *
    • Preserves relative size weights. *
    • Preserves em heights, to support components which should not be rescaled. (Not yet supported.) *
    • Preserves space taken by strange objects, to support future canvas components. *
    • Renormalizes the margin row, so it is nice. (Not yet supported. This should consider font size, where large fonts don't need so much space.) *
    * This should also be idempotent, where calling this a second time should have no effect. * @param dom an application state, with controller nodes. * @see #fixLayout(org.autoplot.dom.Application) */ public static void fixVerticalLayout( Application dom ) { fixVerticalLayout( dom, Collections.emptyMap() ); } /** * This is the new layout mechanism (fixLayout), correcting the layout in the vertical direction. This one:
      *
    • Renormalizes the margin row, so it is nice. *
    • Removes extra whitespace *
    • Preserves relative size heights. *
    • Preserves em heights, to support components which should not be rescaled. *
    • Try to make each row's em offsets similar, using the marginRow, so that fonts can be scaled. *
    * This should also be idempotent, where calling this a second time should have no effect. * @param dom an application state, with controller nodes. * @param options * @see #fixLayout(org.autoplot.dom.Application, java.util.Map) */ public static void fixVerticalLayout( Application dom, Map options ) { Canvas canvas= dom.getCanvases(0); Row marginRow= (Row)canvas.getMarginRow().copy(); double emToPixels= java.awt.Font.decode(dom.getCanvases(0).font).getSize(); Row[] rows= canvas.getRows(); // note this is a shallow copy int nrow= rows.length; for ( int i=0; i0 ) { Pattern p= Pattern.compile("([0-9\\.]*)em"); if ( p.matcher(verticalSpacing).matches() ) { Double d= Double.parseDouble(verticalSpacing.substring(0,verticalSpacing.length()-2)); double extraEms=0; for ( int i=0; i plots= DomOps.getPlotsFor( dom, rows[i], true ); double MaxUpJEm; double MaxDownPx; for ( Plot plotj : plots ) { if ( rows[i].parent.equals(marginRow.id) ) { String title= plotj.getTitle(); String content= title; // title.replaceAll("(\\!c|\\!C|\\)", " "); boolean addLines= plotj.isDisplayTitle() && content.trim().length()>0; int lc= lineCount(title); if ( rows[i].id.equals(topRowId) ) { MaxUpJEm= ( addLines ? lc : 0. ) - ntopEm; } else { MaxUpJEm= addLines ? lc : 0.; } MaxUp[i]= Math.max( MaxUp[i], MaxUpJEm*emToPixels ); MaxUpEm[i]= Math.max( MaxUpEm[i], MaxUpJEm ); if ( rows[i].id.equals(bottomRowId) ) { MaxDownEm[i]= Math.min( MaxDownEm[i], 0 ); } else { MaxDownEm[i]= Math.min( MaxDownEm[i], -getXAxisLines(plotj) ); } MaxDown[i]= MaxDownEm[i]*emToPixels; doAdjust[i]= true; } else { doAdjust[i]= false; } } if ( verticalSpacing.trim().length()>0 ) { MaxDownEm[i]= emsDownSize[i]-emsUpSize[i]; MaxUpEm[i]= 0.; } } } if ( logger.isLoggable(Level.FINER) ) { logger.log(Level.FINER, "2. space needed to the top and bottom of each plot:" ); for ( int i=0; i1 ) { // when all but the top have the equal ems, moving ems to the marginRow if will make things equal boolean adjust=true; double em= MaxUpEm[1]; for ( int i=2; i plots= DomOps.getPlotsFor( dom, rows[i], true ); if ( plots.size()>0 ) { int d1 = DomUtil.getRowPositionPixels( dom, rows[i], rows[i].getTop() ); int d2 = DomUtil.getRowPositionPixels( dom, rows[i], rows[i].getBottom() ); resizablePixels[i]= ( d2-d1 ); if ( isEmRow[i] ) { logger.fine("here's an events bar row!"); } else { totalPlotHeightPixels= totalPlotHeightPixels + resizablePixels[i]; } } } logger.log(Level.FINER, "3. number of pixels used by plots which are resizable: {0}", totalPlotHeightPixels); // 4. express this as a fraction of all the pixels which could be resized. double [] relativePlotHeight= new double[ nrow ]; for ( int i=0; i0 ) { logger.finer("we already accounted for this."); } else { extraEms+= nominalSpacingEms + ( MaxDownEm[i] + MaxUpEm[i] ); } } rows[i].setTop( newTop ); rows[i].setBottom( newBottom ); if ( logger.isLoggable( Level.FINE ) ) { int r0= (int)DomUtil.getRowPositionPixels( dom, rows[i], rows[i].getTop() ); int r1= (int)DomUtil.getRowPositionPixels( dom, rows[i], rows[i].getBottom() ); logger.log(Level.FINE, "row {0}: {1},{2} ({3} pixels)", new Object[]{i, newTop, newBottom, r1-r0 }); } } else { logger.log(Level.FINE, "row {0} is not adjusted", i ); } } if ( logger.isLoggable(Level.FINER) ) { logger.finer("8. new layout strings: "); for ( int i=0; i *
  • Removes extra whitespace *
  • Preserves relative size weights. *
  • Preserves em heights, to support components which should not be rescaled. (Not yet supported.) *
  • Preserves space taken by strange objects, to support future canvas components. *
  • Renormalizes the margin row, so it is nice. (Not yet supported. This should consider font size, where large fonts don't need so much space.) * * This should also be idempotent, where calling this a second time should have no effect. * @param dom an application state, with controller nodes. * @see #fixLayout(org.autoplot.dom.Application) */ public static void fixHorizontalLayout( Application dom ) { fixHorizontalLayout( dom, Collections.emptyMap() ); } /** * This is the new layout mechanism (fixLayout), but changed from vertical layout to horizontal. This one:
      *
    • Renormalizes the margin column, so it is nice. *
    • Removes extra whitespace *
    • Preserves relative size weights. *
    • Preserves em widths, to support components which should not be rescaled. *
    • Try to make each column's em offsets similar, using the marginColumn, so that fonts can be scaled. *
    * This should also be idempotent, where calling this a second time should have no effect. * @param dom an application state, with controller nodes. * @param options * @see #fixLayout(org.autoplot.dom.Application) */ public static void fixHorizontalLayout( Application dom, Map options ) { Logger logger= LoggerManager.getLogger("autoplot.dom.layout.fixlayout"); logger.fine( "enter fixHorizontalLayout" ); Canvas canvas= dom.getCanvases(0); Column marginColumn= (Column)canvas.getMarginColumn().copy(); double emToPixels= java.awt.Font.decode(dom.getCanvases(0).font).getSize(); Column[] columns= canvas.getColumns(); int ncolumn= columns.length; boolean[] doAdjust= new boolean[ncolumn]; //kludge: check for duplicate names of columns. Use the first one found. Map columnCheck= new HashMap(); List rm= new ArrayList<>(); for ( int i=0; i plots= DomOps.getPlotsFor( dom, columns[i], true ); if ( plots.size()>0 ) { if ( columnCheck.containsKey(columns[i].getId()) ) { logger.log(Level.FINE, "duplicate row id: {0}", columns[i].getId()); rm.add( columns[i] ); } else { columnCheck.put( columns[i].getId(), columns[i] ); } } else { logger.log(Level.FINE, "unused row: {0}", columns[i]); rm.add( columns[i] ); } } ArrayList columnsList= new ArrayList(Arrays.asList(columns)); rm.forEach((r) -> { columnsList.remove(r); }); canvas.setColumns((Column[]) columnsList.toArray( new Column[columnsList.size()])); columns= new Column[ columnsList.size() ]; ncolumn= columns.length; for ( int i=0; i { int d1= DomUtil.getColumnPositionPixels( dom, c1, c1.getLeft() ); int d2= DomUtil.getColumnPositionPixels( dom, c2, c2.getLeft() ); return d1-d2; }); String leftColumnId= ncolumn>0 ? columns[0].id : ""; String rightColumnId= ncolumn>0 ? columns[columns.length-1].id : ""; String horizontalSpacing= options.getOrDefault( OPTION_FIX_LAYOUT_HORIZONTAL_SPACING, "" ); double [] MaxLeft= new double[ ncolumn ]; double [] MaxRight= new double[ ncolumn ]; double [] MaxLeftEm= new double[ ncolumn ]; double [] MaxRightEm= new double[ ncolumn ]; if ( horizontalSpacing.trim().length()>0 ) { Pattern p= Pattern.compile("([0-9\\.]*)em"); if ( p.matcher(horizontalSpacing).matches() ) { Double d= Double.parseDouble(horizontalSpacing.substring(0,horizontalSpacing.length()-2)); double extraEms=0; for ( int i=0; i0 ) nrightEm= 0; marginColumn.setLeft( DasDevicePosition.formatLayoutStr( new double[] { 0, nleftEm+2, 0 } ) ); marginColumn.setRight( DasDevicePosition.formatLayoutStr( new double[] { 1, -nrightEm, 0 } ) ); if ( ncolumn==0 ) { logger.finer("0. No adjustable columns, returning!"); return; } double[] resizablePixels= new double[ncolumn]; boolean[] isEmColumn= new boolean[ncolumn]; double[] emsLeftSize= new double[ncolumn]; double[] emsRightSize= new double[ncolumn]; logger.log(Level.FINER, "1. new settings for the margin column:{0} {1}", new Object[]{marginColumn.getLeft(), marginColumn.getRight()}); // 2. For each column, identify the space to the left and right of each plot. for ( int i=0; i plots= DomOps.getPlotsFor( dom, columns[i], true ); double MaxLeftJEm; double MaxRightPx; for ( Plot plotj : plots ) { if ( columns[i].parent.equals(marginColumn.id) ) { String label= plotj.getYaxis().getLabel(); boolean addLines= plotj.getYaxis().isDrawTickLabels(); int lc= lineCount(label); int lcPlusTicks= lc + 4; MaxLeftJEm= addLines ? lcPlusTicks : 0.; MaxLeft[i]= Math.max( MaxLeft[i], MaxLeftJEm*emToPixels ); MaxLeftEm[i]= Math.max( MaxLeftEm[i], MaxLeftJEm ); double nnrightEm; if ( plotj.getZaxis().isVisible() ) { nnrightEm= -4; } else { nnrightEm= -1; } if ( plotj.getLegendPosition()==LegendPosition.OutsideNE || plotj.getLegendPosition()==LegendPosition.OutsideSE ) { if ( plotj.isDisplayLegend() ) { double legendWidthEms= -8; nnrightEm= Math.min( nnrightEm, legendWidthEms ); } } MaxRightEm[i]= Math.min( MaxRightEm[i], nnrightEm ); MaxRight[i]= MaxRightEm[i]*emToPixels; doAdjust[i]= true; } else { doAdjust[i]= false; } } if ( horizontalSpacing.trim().length()>0 ) { MaxRightEm[i]= emsRightSize[i]-emsLeftSize[i]; MaxLeftEm[i]= 0.; } } } if ( logger.isLoggable(Level.FINER) ) { logger.log(Level.FINER, "2. space needed to the right and left of each plot:" ); for ( int i=0; i1 ) { // when all but the top have the equal ems, moving ems to the marginColumn if will make things equal boolean adjust=true; double em= MaxLeftEm[1]; for ( int i=2; i plots= DomOps.getPlotsFor( dom, columns[i], true ); if ( plots.size()>0 ) { int d1 = DomUtil.getColumnPositionPixels( dom, columns[i], columns[i].getLeft() ); int d2 = DomUtil.getColumnPositionPixels( dom, columns[i], columns[i].getRight() ); resizablePixels[i]= ( d2-d1 ); if ( isEmColumn[i] ) { logger.fine("here's a fixed-width column!"); } else { totalPlotWidthPixels= totalPlotWidthPixels + resizablePixels[i]; } } } logger.log(Level.FINER, "3. number of pixels used by plots which are resizable: {0}", totalPlotWidthPixels); // 4. express this as a fraction of all the pixels which could be resized. double [] relativePlotWidth= new double[ ncolumn ]; for ( int i=0; i0 ) { logger.finest("we already accounted for this."); } else { extraEms+= nominalSpacingEms + ( MaxRightEm[i] + MaxLeftEm[i] ); } } columns[i].setLeft( newLeft ); columns[i].setRight( newRight ); if ( logger.isLoggable( Level.FINE ) ) { int r0= (int)DomUtil.getColumnPositionPixels( dom, columns[i], columns[i].getLeft() ); int r1= (int)DomUtil.getColumnPositionPixels( dom, columns[i], columns[i].getRight() ); } } else { logger.log(Level.FINEST, "row {0} is not adjusted", i ); } } if ( logger.isLoggable(Level.FINER) ) { logger.finer("8. new layout strings: "); for ( int i=0; i