/* * EventsRenderer.java * * Created on December 13, 2005, 3:37 PM * * */ package org.das2.graph; import java.awt.Shape; import javax.swing.Icon; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumUtil; import org.das2.event.LabelDragRenderer; import org.das2.event.MouseModule; import org.das2.system.DasLogger; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ImageIcon; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import static org.das2.graph.Renderer.encodeBooleanControl; import org.das2.util.GrannyTextRenderer; import org.das2.qds.DDataSet; import org.das2.qds.DataSetOps; import org.das2.qds.DataSetUtil; import org.das2.qds.IDataSet; import org.das2.qds.JoinDataSet; import org.das2.qds.QDataSet; import org.das2.qds.RankZeroDataSet; import org.das2.qds.SemanticOps; import org.das2.qds.TagGenDataSet; import org.das2.qds.WritableDataSet; import org.das2.qds.ops.Ops; import org.das2.qds.util.DataSetBuilder; import org.das2.util.LoggerManager; /** * Draw colored horizontal bars for the dataset, marking events datasets or modes of the data. This expects * a QDataSet with the canonical scheme: *
{@code
 *    Events[:,BUNDLE_1=4] where the columns are:
 *      BUNDLE_1=startTime,stopTime,Color,Message
 *    startTime,stopTime are in some time location unit.  stopTime may also be an offset from startTime (e.g. seconds)
 *    Color is an int, that is either 0xRRGGBB or 0xAARRGGBB.
 *    Message is any datum, so typically an enumeration unit is used.
 *}
* Note this also contains systems for coloring data in old schemes, such as the colorSpecifier interface and textSpecifier. * These should not be used when a dataset will be sufficient. * * @see org.das2.qds.examples.Schemes#eventsList() * @author Jeremy */ public class EventsRenderer extends Renderer { public static final String PROP_COLOR = "color"; /** * if true, then only then eventsMap is used, otherwise we look though all the events for hits. */ private boolean useOnlyEventsMap= false; private static Logger logger= LoggerManager.getLogger("das2.graphics.renderer.events"); /** * return bounding cube * @param ds * @return */ public static QDataSet doAutorange(QDataSet ds) { QDataSet xrange; DDataSet yrange; yrange= DDataSet.createRank1(2); yrange.putValue(0,0); yrange.putValue(1,10); QDataSet xmins; QDataSet xmaxs; if ( ds.rank()==1 && ds.property(QDataSet.DEPEND_0)==null ) { xmins= ds; //vap+inline:2010-002T03:50,2010-002T03:54,2010-002T03:56&RENDER_TYPE=eventsBar xmaxs= ds; } else if ( ds.rank()==1 && ds.property(QDataSet.DEPEND_0)!=null ) { xmins= (QDataSet)ds.property(QDataSet.DEPEND_0); //vap+inline:2010-002T03:50,2010-002T03:54,2010-002T03:56&RENDER_TYPE=eventsBar xmaxs= xmins; } else if ( ds.rank()==0 ) { xmins= Ops.join( null, ds ); xmaxs= xmins; } else { xmins= SemanticOps.xtagsDataSet(ds); if ( ds.length(0)>1 ) { xmaxs= DataSetOps.unbundle( ds,1 ); } else { xmaxs= xmins; } } Units u0= SemanticOps.getUnits(xmins); Units u1= SemanticOps.getUnits(xmaxs); if ( xmins.length()==0 ) { xrange= DDataSet.wrap( new double[] {0,1}, u0 ); } else { if ( UnitsUtil.isIntervalOrRatioMeasurement(u1) ) { //TODO: probably the day/days containing would be better xrange= Ops.extent(xmins); if ( !u1.isConvertibleTo(u0) && u1.isConvertibleTo(u0.getOffsetUnits()) ) { xmaxs= Ops.add( xmins, xmaxs ); xrange= Ops.extent(xmaxs,xrange); } else { xrange= Ops.extent(xmaxs,xrange); } } else { xrange= DDataSet.createRank1(2); ((DDataSet)xrange).putValue(0,0); ((DDataSet)xrange).putValue(1,10); } if ( xrange.value(0)= eventMap.length ) { setLabel(null); } else { Units sxunits= SemanticOps.getUnits(xmins); Units zunits= SemanticOps.getUnits(msgs); Units sxmaxunits= SemanticOps.getUnits( xmaxs ); List ii= new ArrayList(); if ( useOnlyEventsMap==true && eventMap[ix]>-1 ) { ii.add( eventMap[ix] ); } else { if ( eventMap[ix]>-1 ) ii.add( eventMap[ix] ); for ( int i=0; i0 ) { StringBuilder sb= new StringBuilder(); int count= 0; for ( Integer ii1 : ii ) { int i = ii1; double sxmin= xmins.value(i); double sxmax= xmaxs.value(i); if ( !sxmaxunits.isConvertibleTo(sxunits) ) { if ( sxmaxunits.isConvertibleTo(sxunits.getOffsetUnits() ) ) { sxmax= sxmin + sxmaxunits.convertDoubleTo( sxunits.getOffsetUnits(), sxmax ); } else { sxmax= sxmin; } } else { sxmax= sxmaxunits.convertDoubleTo( sxunits, sxmax ); } if ( sxmax10 ) { break; } } if ( ii.size()>count ) { sb.append("(").append(ii.size()-count).append( " more items not shown)"); } setLabel( sb.toString() ); } else { setLabel(null); } } return super.renderDrag( g, p1, p2 ); } } private MouseModule mouseModule=null; private MouseModule getMouseModule() { if ( mouseModule==null ) { DasPlot parent= getParent(); mouseModule= new MouseModule( parent, new EventLabelDragRenderer(parent), "Event Lookup" ); } return mouseModule; } /** * canonical dataset containing rank2 bundle of start,stop,color,text. */ private QDataSet cds=null; /** * make the canonical dataset smaller by combining adjacent records. This is introduced * because we now support event datasets with a regular cadence. * @param vds * @return */ private QDataSet coalesce( QDataSet vds ) { QDataSet bds= (QDataSet) vds.property(QDataSet.BUNDLE_1); DataSetBuilder build= new DataSetBuilder(2,100,4); DDataSet v= DDataSet.createRank1(4); QDataSet dep0= DataSetOps.unbundle( vds,0 ); double tlim= 1e-31; RankZeroDataSet cad= org.das2.qds.DataSetUtil.guessCadenceNew( dep0, null ); if ( cad!=null ) { tlim= cad.value()/100; } int count=0; if ( vds.length()==0 ) { return vds; } v.putValue( 0,vds.value(0,0) ); v.putValue( 1,vds.value(0,1) ); v.putValue( 2,vds.value(0,2) ); v.putValue( 3,vds.value(0,3) ); for ( int i=1; i tlim // they don't connect || vds.value(i,3)!=vds.value(i-1,3) // the message changed || Math.abs( vds.value(i,2)-vds.value(i-1,2) ) > 1e-31 // the color changed ) { build.putValues( -1, v, 4 ); build.nextRecord(); v.putValue( 0,vds.value(i,0) ); v.putValue( 1,vds.value(i,1) ); v.putValue( 2,vds.value(i,2) ); v.putValue( 3,vds.value(i,3) ); count=1; } else { v.putValue( 1,vds.value(i,1) ); count++; } } build.putValues( -1, v, 4 ); build.putProperty( QDataSet.BUNDLE_1, bds ); return build.getDataSet(); } /** * make canonical rank 2 bundle dataset of min,max,color,text * @param vds events list in one of several supported forms * @return rank 2 N by 4 dataset. */ private QDataSet makeCanonical( QDataSet vds ) { logger.entering( "EventsRenderer", "makeCanonical" ); QDataSet xmins; QDataSet xmaxs; QDataSet colors; QDataSet msgs; if ( vds==null ) { return null; } if ( vds.rank()==2 ) { QDataSet dep0= (QDataSet) vds.property(QDataSet.DEPEND_0); if ( dep0==null ) { try { xmins= DataSetOps.unbundle( vds,0 ); xmaxs= DataSetOps.unbundle( vds,1 ); } catch ( IndexOutOfBoundsException ex ) { if ( vds.length()==0 ) { //TODO: unbundle should be able to handle this. logger.exiting( "EventsRenderer", "makeCanonical", "null"); return null; } else { throw ex; } } if ( useColor ) { colors= Ops.replicate( getColor().getRGB(), xmins.length() ); } else { if ( vds.length(0)>3 ) { colors= DataSetOps.unbundle( vds,2 ); } else { colors= Ops.replicate( getColor().getRGB(), xmins.length() ); } } } else if ( dep0.rank()==2 ) { if ( SemanticOps.isBins(dep0) ) { xmins= DataSetOps.slice1( dep0, 0 ); xmaxs= DataSetOps.slice1( dep0, 1 ); colors= Ops.replicate( getColor().getRGB(), xmins.length() ); Units u0= SemanticOps.getUnits(xmins ); Units u1= SemanticOps.getUnits(xmaxs ); if ( !u1.isConvertibleTo(u0) && u1.isConvertibleTo(u0.getOffsetUnits()) ) { xmaxs= Ops.add( xmins, xmaxs ); } } else { postMessage( "DEPEND_0 is rank 2 but not bins", DasPlot.WARNING, null, null ); logger.exiting( "EventsRenderer", "makeCanonical", "null" ); return null; } } else if ( dep0.rank() == 1 ) { Datum width= SemanticOps.guessXTagWidth( dep0, null ); if ( width!=null ) { width= width.divide(2); } else { QDataSet sort= Ops.sort(dep0); QDataSet diffs= Ops.diff( DataSetOps.applyIndex(dep0,0,sort,false) ); QDataSet w= Ops.reduceMin( diffs,0 ); width= DataSetUtil.asDatum(w); } xmins= Ops.subtract(dep0, org.das2.qds.DataSetUtil.asDataSet(width) ); xmaxs= Ops.add(dep0, org.das2.qds.DataSetUtil.asDataSet(width) ); colors= Ops.replicate( getColor().getRGB(), xmins.length() ); } else { postMessage( "rank 2 dataset must have dep0 of rank 1 or rank 2 bins", DasPlot.WARNING, null, null ); logger.exiting( "EventsRenderer", "makeCanonical", "null"); return null; } msgs= DataSetOps.unbundle( vds, vds.length(0)-1 ); } else if ( vds.rank()==1 ) { QDataSet dep0= (QDataSet) vds.property(QDataSet.DEPEND_0); if ( dep0==null ) { if ( UnitsUtil.isNominalMeasurement(SemanticOps.getUnits(vds)) ) { xmins= new TagGenDataSet(vds.length(),1.0,0.0); xmaxs= new TagGenDataSet(vds.length(),1.0,1.0); msgs= vds; } else { xmins= vds; xmaxs= vds; msgs= vds; } } else if ( dep0.rank() == 2 ) { if ( SemanticOps.isBins(dep0) ) { xmins= DataSetOps.slice1( dep0, 0 ); xmaxs= DataSetOps.slice1( dep0, 1 ); Units u0= SemanticOps.getUnits(xmins ); Units u1= SemanticOps.getUnits(xmaxs ); if ( !u1.isConvertibleTo(u0) && u1.isConvertibleTo(u0.getOffsetUnits()) ) { xmaxs= Ops.add( xmins, xmaxs ); } msgs= vds; } else { postMessage( "DEPEND_0 is rank 2 but not bins", DasPlot.WARNING, null, null ); logger.exiting( "EventsRenderer", "makeCanonical", "null"); return null; } } else if ( dep0.rank() == 1 ) { Datum width= SemanticOps.guessXTagWidth( dep0, null ); if ( width!=null ) { width= width.divide(2); } else { Units dep0units= SemanticOps.getUnits(dep0); if ( UnitsUtil.isNominalMeasurement(dep0units) ) { throw new IllegalArgumentException("dep0units are norminal units"); } else { QDataSet sort= Ops.sort(dep0); QDataSet diffs= Ops.diff( DataSetOps.applyIndex(dep0,0,sort,false) ); QDataSet w= Ops.reduceMin( diffs,0 ); width= DataSetUtil.asDatum(w); } } xmins= Ops.subtract(dep0, org.das2.qds.DataSetUtil.asDataSet(width) ); xmaxs= Ops.add(dep0, org.das2.qds.DataSetUtil.asDataSet(width) ); msgs= vds; } else { postMessage( "dataset is not correct form", DasPlot.WARNING, null, null ); logger.exiting( "EventsRenderer", "makeCanonical", "null"); return null; } Color c0= getColor(); int alpha= c0.getAlpha()==255 ? ( opaque ? 255 : 128 ) : c0.getAlpha(); Color c1= new Color( c0.getRed(), c0.getGreen(), c0.getBlue(), alpha ); int irgb= c1.getRGB(); colors= Ops.replicate( irgb, xmins.length() ); } else if ( vds.rank()==0 ) { xmins= Ops.replicate(vds,1); // increase rank from 0 to 1. xmaxs= xmins; Color c0= getColor(); int alpha= c0.getAlpha()==255 ? ( opaque ? 255 : 128 ) : c0.getAlpha(); Color c1= new Color( c0.getRed(), c0.getGreen(), c0.getBlue(), alpha ); int irgb= c1.getRGB(); colors= Ops.replicate( irgb, xmins.length() ); msgs= Ops.replicate(vds,1); } else { postMessage( "dataset must be rank 0, 1 or 2", DasPlot.WARNING, null, null ); logger.exiting( "EventsRenderer", "makeCanonical", "null"); return null; } if ( this.colorSpecifier!=null ) { Units u= SemanticOps.getUnits(msgs); WritableDataSet wds= IDataSet.copy(colors); for ( int i=0; i0 && vds.length() == 0) ) { DasLogger.getLogger(DasLogger.GRAPHICS_LOG).fine("empty data set"); return; } Graphics2D g= ( Graphics2D ) g1; g.setColor(color); if ( cds==null ) { // a message should be posted by makeCanonical return; } String mode= this.mode.intern(); QDataSet cds1= cds; if ( mode.equals("gantt2") ) { QDataSet xmins= DataSetOps.unbundle( cds,0 ); QDataSet xmaxs= DataSetOps.unbundle( cds,1 ); QDataSet allBefore= Ops.lt( xmaxs, xAxis.getDatumRange().min() ); QDataSet allAfter= Ops.gt( xmins, xAxis.getDatumRange().max() ); QDataSet r= Ops.where( Ops.not( Ops.or( allBefore, allAfter ) ) ); cds1= Ops.applyIndex( cds, r ); ganttMode= true; } QDataSet xmins= DataSetOps.unbundle( cds1,0 ); QDataSet xmaxs= DataSetOps.unbundle( cds1,1 ); QDataSet msgs= DataSetOps.unbundle( cds1,3 ); Units eu= SemanticOps.getUnits( msgs ); QDataSet lcolor= DataSetOps.unbundle( cds1,2 ); long t0= System.currentTimeMillis(); DasPlot parent= getParent(); Rectangle current= null; if ( lastException!=null ) { renderException( g, xAxis, yAxis, lastException ); } else { DasColumn column= xAxis.getColumn(); DasRow row= parent.getRow(); eventMap= new int[column.getWidth()]; for ( int k=0; k0 ) { int ivds0= 0; int ivds1= xmins.length(); Font f= getParent().getFont(); if ( getFontSize()!=null && getFontSize().length()>0 && !getFontSize().equals("1em") ) { try { double[] size= DasDevicePosition.parseLayoutStr(getFontSize()); double s= f.getSize2D() * size[0]/100 + f.getSize2D() * size[1] + size[2]; f= f.deriveFont((float)s); } catch ( ParseException ex ) { logger.log( Level.WARNING, ex.getMessage(), ex ); } } g1.setFont(f); GrannyTextRenderer gtr= new GrannyTextRenderer(); int gymax,gymin; QDataSet u; Map map= new HashMap<>(); Map pam= new HashMap<>(); try { QDataSet s= Ops.sort(msgs); u= Ops.uniq(msgs,s); gymax= u.length(); gymin= 0; for ( int i=0; i0; if ( drawLineThick ) { double t= DasDevicePosition.parseLayoutStr( this.lineThick, f.getSize2D(), getParent().getWidth(), 1.0 ); g.setStroke( lineStyle.getStroke( (float)t ) ); } for ( int i=ivds0; i renderTimeLimitMs ) { parent.postMessage( this, "renderer ran out of time, dataset truncated", DasPlot.WARNING, null, null); break; } if ( wxmins.value(i)==0.0 ) { // allow for fill in events dataset. continue; } int ixmin= (int)xAxis.transform( xmins.value(i),xunits); int ixmax= (int)xAxis.transform( xmaxs.value(i),xunits); if ( ixmax<=-10000 ) { continue; } if ( ixmin>=10000 ) { continue; } int ixmin0= ixmin; ixmin= Math.max( ixmin, imin ); ixmax= Math.min( ixmax, imax ); int iwidth= Math.max( ixmax- ixmin, 1 ); if ( lcolor!=null ) { int irgb= (int)lcolor.value(i); int rr= ( irgb & 0xFF0000 ) >> 16; int gg= ( irgb & 0x00FF00 ) >> 8; int bb= ( irgb & 0x0000FF ); int aa= ( irgb >> 24 & 0xFF ); if ( aa>0 ) { g.setColor( new Color( rr, gg, bb, aa ) ); } else { g.setColor( new Color( rr, gg, bb, 128 ) ); } } if ( column.getDMinimum() < ixmax && ixmin0 < column.getDMaximum() ) { // if any part is visible if ( iwidth==0 ) iwidth=1; Rectangle r1; if ( this.orbitMode ) { r1= new Rectangle( ixmin, row.getDMaximum()-textHeight, iwidth-1, textHeight ); g.fill( r1 ); } else if ( this.ganttMode ) { int ord=(int)msgs.value(i); int iy= map.get(ord); int iymin= row.getDMinimum() + row.getHeight() * (iy) / ( gymax - gymin ) + 1; int iymax= row.getDMinimum() + row.getHeight() * (1+iy) / ( gymax - gymin ) - 1; //int iymin= row.getDMinimum() + row.getHeight() * ((int)msgs.value(i)-gymin) / ( gymax - gymin + 1 ) + 1; //int iymax= row.getDMinimum() + row.getHeight() * (1+(int)msgs.value(i)-gymin) / ( gymax - gymin + 1 ) - 1; r1= new Rectangle( ixmin, iymin, iwidth, Math.max( iymax-iymin, 2 ) ); g.fill( r1 ); } else { if ( iwidth<=1 && drawLineThick ) { r1= new Rectangle( ixmin, row.getDMinimum(), iwidth, row.getHeight() ); Line2D.Double l1= new Line2D.Double( ixmin, row.getDMinimum(), ixmin, row.getDMaximum() ); g.draw( l1 ); } else { r1= new Rectangle( ixmin, row.getDMinimum(), iwidth, row.getHeight() ); g.fill( r1 ); if ( drawLineThick ) { g.draw( r1 ); } } } r1.x= r1.x-2; r1.y= r1.y-2; r1.width= r1.width+4; r1.height= r1.height+4; if ( current==null ) { current= r1; } else if ( current.intersects(r1) ) { current= current.union(r1); } else { sa.append( current, false ); current= r1; } int im= ixmin-column.getDMinimum(); int em0= im-1; int em1= im+iwidth+1; for ( int k=em0; k=0 && k controls= new LinkedHashMap(); controls.put( "showLabels", encodeBooleanControl( isShowLabels() ) ); controls.put( "orbitMode", encodeBooleanControl( isOrbitMode() ) ); controls.put( Renderer.CONTROL_KEY_FONT_SIZE, getFontSize() ); controls.put( "ganttMode", encodeBooleanControl( isGanttMode() ) ); controls.put( Renderer.CONTROL_KEY_LINE_STYLE, getLineStyle().toString() ); controls.put("lineThick", getLineThick() ); controls.put("opaque", encodeBooleanControl( isOpaque() ) ); if ( this.useColor ) { controls.put( Renderer.CONTROL_KEY_COLOR, encodeColorControl(color) ); } return Renderer.formatControl(controls); } private boolean useColor= false; private Color color= new Color(100,100,100); public Color getColor() { return color; } private PsymConnector lineStyle = PsymConnector.SOLID; public static final String PROP_LINESTYLE = "lineStyle"; public PsymConnector getLineStyle() { return lineStyle; } public void setLineStyle(PsymConnector lineStyle) { PsymConnector oldLineStyle = this.lineStyle; this.lineStyle = lineStyle; if ( !oldLineStyle.equals(lineStyle) ) { super.invalidateParentCacheImage(); } propertyChangeSupport.firePropertyChange(PROP_LINESTYLE, oldLineStyle, lineStyle); } private String lineThick = ""; public static final String PROP_LINETHICK = "lineThick"; /** * the line thickness, examples include "5pt" and "0.1em" * @return */ public String getLineThick() { return lineThick; } public void setLineThick(String lineThick) { String oldLineThick = this.lineThick; this.lineThick = lineThick; if ( !oldLineThick.equals(lineThick) ) { super.invalidateParentCacheImage(); } propertyChangeSupport.firePropertyChange(PROP_LINETHICK, oldLineThick, lineThick); } private boolean opaque = false; public static final String PROP_OPAQUE = "opaque"; public boolean isOpaque() { return opaque; } public void setOpaque(boolean opaque) { boolean oldOpaque = this.opaque; this.opaque = opaque; if ( oldOpaque!=opaque ) { cds= makeCanonical(ds); super.invalidateParentCacheImage(); } propertyChangeSupport.firePropertyChange( PROP_OPAQUE, oldOpaque, opaque ); } /** * set the color to use when the data doesn't specify a color. If an alpha channel is specified, then * this alpha value is used, otherwise 80% is used. * @param color */ public void setColor( Color color ) { Color old= this.color; this.color= color; if ( !old.equals(color) ) { cds= makeCanonical(ds); super.invalidateParentCacheImage(); } propertyChangeSupport.firePropertyChange( PROP_COLOR, old, color); } public int getRenderTimeLimitMs() { return renderTimeLimitMs; } public void setRenderTimeLimitMs(int renderTimeLimitMs) { this.renderTimeLimitMs = renderTimeLimitMs; } /** * true means draw the event label next to the bar. */ protected boolean showLabels = false; public static final String PROP_SHOWLABELS = "showLabels"; public boolean isShowLabels() { return showLabels; } public void setShowLabels(boolean showLabels) { boolean oldShowLabels = this.showLabels; this.showLabels = showLabels; DasPlot parent= getParent(); if ( parent!=null ) { parent.invalidateCacheImage(); parent.repaint(); } propertyChangeSupport.firePropertyChange(PROP_SHOWLABELS, oldShowLabels, showLabels); } private String mode = ""; public static final String PROP_MODE = "mode"; public String getMode() { return mode; } /** * if non-empty, then use this named mode * @param mode */ public void setMode(String mode) { String oldMode = this.mode; this.mode = mode; propertyChangeSupport.firePropertyChange(PROP_MODE, oldMode, mode); } /** * orbitMode true means don't show times, draw bars with 1-pixel breaks */ protected boolean orbitMode = false; public static final String PROP_ORBITMODE = "orbitMode"; public boolean isOrbitMode() { return orbitMode; } public void setOrbitMode(boolean orbitMode) { boolean oldOrbitMode = this.orbitMode; this.orbitMode = orbitMode; propertyChangeSupport.firePropertyChange(PROP_ORBITMODE, oldOrbitMode, orbitMode); } /** * gantt mode true means the event types are laid out vertically. The user * has very little control over the position, but at least you can see * common messages. */ private boolean ganttMode = false; public static final String PROP_GANTTMODE = "ganttMode"; public boolean isGanttMode() { return ganttMode; } public void setGanttMode(boolean ganttMode) { boolean oldGanttMode = this.ganttMode; this.ganttMode = ganttMode; propertyChangeSupport.firePropertyChange(PROP_GANTTMODE, oldGanttMode, ganttMode); } private int rotateLabel = 0; /** * rotate the label counter clockwise to make more room in orbitMode. */ public static final String PROP_ROTATELABEL = "rotateLabel"; public int getRotateLabel() { return rotateLabel; } public void setRotateLabel(int rotateLabel) { int oldRotateLabel = this.rotateLabel; this.rotateLabel = rotateLabel; propertyChangeSupport.firePropertyChange(PROP_ROTATELABEL, oldRotateLabel, rotateLabel); } /** * fontSize allows the font to be rescaled. 1em is the default size. 2em is twice the size. 12pt is 12 pixels. */ protected String fontSize = "1em"; public static final String PROP_FONTSIZE = "fontSize"; public String getFontSize() { return fontSize; } public void setFontSize(String fontSize) { String oldFontSize = this.fontSize; this.fontSize = fontSize; propertyChangeSupport.firePropertyChange(PROP_FONTSIZE, oldFontSize, fontSize); } public static final String PROP_COLOR_SPECIFIER= "colorSpecifier"; private ColorSpecifier colorSpecifier=null; /** * set this to be an object implementing ColorSpecifier interface, if more than * one color is to be used when drawing the bars. Setting this to null will * restore the initial behavior of drawing all bars in one color (or with rank 2 bundle containing color). * @param spec the color specifier. */ public void setColorSpecifier( ColorSpecifier spec ) { Object old= this.colorSpecifier; this.colorSpecifier= spec; cds= makeCanonical(ds); propertyChangeSupport.firePropertyChange( PROP_COLOR_SPECIFIER, old , spec ); super.invalidateParentCacheImage(); } public ColorSpecifier getColorSpecifier( ) { return this.colorSpecifier; } /** * Old TextSpecifier provided an alternate means to get text for any datum, allowing the user to avoid use of EnumerationUnits. * This is currently not used in this version of the library. */ private TextSpecifier textSpecifier= DEFAULT_TEXT_SPECIFIER; /** * Getter for property textSpecifier. * @return Value of property textSpecifier. */ public TextSpecifier getTextSpecifier() { return this.textSpecifier; } /** * Setter for property textSpecifier. * @param textSpecifier New value of property textSpecifier. */ public void setTextSpecifier(TextSpecifier textSpecifier) { TextSpecifier oldTextSpecifier = this.textSpecifier; this.textSpecifier = textSpecifier; propertyChangeSupport.firePropertyChange("textSpecifier", oldTextSpecifier, textSpecifier); } }