/*
Copyright 2006 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.util.media.chart;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.event.*;
import java.util.*;
import sim.util.gui.*;
import sim.util.*;
// From JFreeChart
import org.jfree.data.xy.*;
import org.jfree.chart.*;
import org.jfree.chart.event.*;
import org.jfree.chart.plot.*;
import org.jfree.data.general.*;
import org.jfree.chart.renderer.xy.*;
import org.jfree.data.general.*;
/** A SeriesAttributes used for user control pf time series created with TimeSeriesCharGenerator.
This is done largely through the
manipulation of XYSeries objects and features of the XYPlot class. */
public class TimeSeriesAttributes extends SeriesAttributes
{
/** A dash */
static final float DASH = 6;
/** A dot */
static final float DOT = 1;
/** A short space */
static final float SPACE = 3;
/** A long space */
static final float SKIP = DASH;
public static final int PATTERN_SOLID = 0;
public static final int PATTERN_LONG_DASH = 1;
public static final int PATTERN_STRETCH_DASH = 2;
public static final int PATTERN_DASH = 3;
public static final int PATTERN_DASH_DASH_DOT = 4;
public static final int PATTERN_DASH_DOT = 5;
public static final int PATTERN_DASH_DOT_DOT = 6;
public static final int PATTERN_DOT = 7;
public static final int PATTERN_STRETCH_DOT = 8;
/** Nine dash combinations that the user might find helpful. */
static final float[][] dashPatterns =
{
{ DASH, 0.0f }, // --------
{ DASH * 2, SKIP }, // -- -- --
{ DASH, SKIP } , // - - -
{ DASH, SPACE } , // - - - -
{ DASH, SPACE, DASH, SPACE, DOT, SPACE }, // - - . - - .
{ DASH, SPACE, DOT, SPACE, }, // - . - .
{ DASH, SPACE, DOT, SPACE, DOT, SPACE }, // - . . - . .
{ DOT, SPACE }, // . . . .
{ DOT, SKIP } // . . . .
};
/** How much we should stretch the dashPatterns listed above. 1.0 is normal. */
float stretch;
NumberTextField stretchField;
/** Line thickness. */
float thickness;
NumberTextField thicknessField;
/** Line dash pattern (one of the dashPatterns above). */
int dashPattern;
JComboBox dashPatternList;
/** Line color. */
Color strokeColor;
ColorWell strokeColorWell;
public void setThickness(double value) { thicknessField.setValue(thicknessField.newValue(value)); }
public double getThickness() { return (double)(thicknessField.getValue()); }
public void setStretch(double value) { stretchField.setValue(stretchField.newValue(value)); }
public double getStretch() { return (double)(stretchField.getValue()); }
public void setDashPattern(int value)
{
if (value >= 0 && value < dashPatterns.length)
{
dashPatternList.setSelectedIndex(value);
dashPattern = value;
}
}
public int getDashPattern() { return dashPatternList.getSelectedIndex(); }
public void setStrokeColor(Color value) { strokeColorWell.setColor(strokeColor = value);}
public Color getStrokeColor() { return strokeColor; }
/** The time series in question. */
XYSeries series;
public XYSeries getSeries() { return series; }
/** Clears the existing internal XYSeries, then adds all the series elements in the provided XYSeries to the
internal XYSeries. Does not notify the chart to update.
*/
public void setSeries(XYSeries series)
{
this.series.clear();
int count = series.getItemCount();
for(int i = 0; i < count; i++)
this.series.add(series.getDataItem(i), true);
}
public void setSeriesName(String val) { series.setKey(new ChartGenerator.UniqueString(val)); } // bypasses super.setSeriesName
public String getSeriesName() { return "" + series.getKey(); } // bypasses super.getSeriesName
public void clear() { series.clear(); }
/** Builds a TimeSeriesAttributes with the given generator, series, and index for the series. */
public TimeSeriesAttributes(ChartGenerator generator, XYSeries series, int index, SeriesChangeListener stoppable)
{
super(generator, "" + series.getKey(), index, stoppable);
this.series = series;
}
public void rebuildGraphicsDefinitions()
{
float[] newDashPattern = new float[dashPatterns[dashPattern].length];
for(int x=0;x<newDashPattern.length;x++)
if (stretch*thickness > 0)
newDashPattern[x] = dashPatterns[dashPattern][x] * stretch * thickness; // include thickness so we dont' get overlaps -- will this confuse users?
XYItemRenderer renderer = (XYItemRenderer)(((XYPlot)getPlot()).getRenderer());
// we do two different BasicStroke options here because recent versions of Java (for example, 1.6.0_35_b10-428-11M3811 on Retina Displays)
// break when defining solid strokes as { X, 0.0 } even though that's perfecty cromulent. So instead we hack it so that the "solid" stroke
// is done using a different constructor.
renderer.setSeriesStroke(getSeriesIndex(),
((dashPattern == 0) ? // solid
new BasicStroke(thickness, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 0) :
new BasicStroke(thickness, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND,0,newDashPattern,0)));
renderer.setSeriesPaint(getSeriesIndex(),strokeColor);
repaint();
}
public void buildAttributes()
{
// The following three variables aren't defined until AFTER construction if
// you just define them above. So we define them below here instead.
dashPattern = 0; // dashPatterns[0];
stretch = 1.0f;
thickness = 2.0f;
// strokeColor = Color.black; // rebuildGraphicsDefinitions will get called by our caller afterwards
XYItemRenderer renderer = (((XYPlot)getPlot()).getRenderer());
// NOTE:
// Paint paint = renderer.getSeriesPaint(getSeriesIndex());
// In JFreeChart 1.0.6 getSeriesPaint returns null!!!
// You need lookupSeriesPaint(), but that's not backward compatible.
// The only thing consistent in all versions is getItemPaint
// (which looks like a gross miss-use, but gets the job done)
Paint paint = renderer.getItemPaint(getSeriesIndex(), -1);
strokeColor = (Color)paint;
strokeColorWell = new ColorWell(strokeColor)
{
public Color changeColor(Color c)
{
strokeColor = c;
rebuildGraphicsDefinitions();
return c;
}
};
addLabelled("Color",strokeColorWell);
thicknessField = new NumberTextField(2.0,true)
{
public double newValue(double newValue)
{
if (newValue < 0.0)
newValue = currentValue;
thickness = (float)newValue;
rebuildGraphicsDefinitions();
return newValue;
}
};
addLabelled("Width",thicknessField);
dashPatternList = new JComboBox();
dashPatternList.setEditable(false);
dashPatternList.setModel(new DefaultComboBoxModel(new java.util.Vector(Arrays.asList(
new String[] { "Solid", "__ __ __", "_ _ _ _", "_ _ _ _ _", "_ _ . _ _ .",
"_ . _ . _ .", "_ . . _ . .", ". . . . . . .", ". . . . ." }))));
dashPatternList.setSelectedIndex(0);
dashPatternList.addActionListener(new ActionListener()
{
public void actionPerformed ( ActionEvent e )
{
dashPattern = dashPatternList.getSelectedIndex(); // dashPatterns[dashPatternList.getSelectedIndex()];
rebuildGraphicsDefinitions();
}
});
addLabelled("Dash",dashPatternList);
stretchField = new NumberTextField(1.0,true)
{
public double newValue(double newValue)
{
if (newValue < 0.0)
newValue = currentValue;
stretch = (float)newValue;
rebuildGraphicsDefinitions();
return newValue;
}
};
addLabelled("Stretch",stretchField);
}
public boolean possiblyCull()
{
DataCuller dataCuller = ((TimeSeriesChartGenerator)generator).getDataCuller();
if(dataCuller!=null && dataCuller.tooManyPoints(series.getItemCount()))
{
deleteItems(dataCuller.cull(getXValues(), true));
return true;
}
else
return false;
}
void deleteItems(IntBag items)
{
Bag tmpBag = new Bag();
if(items.numObjs==0)
return;
int currentTabooIndex = 0;
int currentTaboo = items.objs[0];
Iterator iter = series.getItems().iterator();
int index=0;
while(iter.hasNext())
{
Object o = iter.next();
if(index==currentTaboo)
{
//skip the copy, let's move on to next taboo index
if(currentTabooIndex<items.numObjs-1)
{
currentTabooIndex++;
currentTaboo = items.objs[currentTabooIndex];
}
else
currentTaboo=-1;//no more taboos
}
else//save o
tmpBag.add(o);
index++;
}
//now we clear the series and then put back the saved objects only.
series.clear();
//In my test this did not cause the chart to flicker.
//But if it does, one could do an update for the part the will be refill and
//only clear the rest using delete(start, end).
for(int i=0;i<tmpBag.numObjs;i++)
series.add((XYDataItem)(tmpBag.objs[i]), false);//no notifying just yet.
//it doesn't matter that I clear this twice in a row
//(once here, once at next time through this fn), the second time is O(1).
series.fireSeriesChanged();
}
double[] getXValues()
{
double[] xValues = new double[series.getItemCount()];
for(int i=0;i<xValues.length;i++)
xValues[i]=series.getX(i).doubleValue();
return xValues;
}
}