package timeflow.views;
|
|
import timeflow.model.*;
|
import timeflow.views.ListView.LinkIt;
|
import timeflow.data.db.*;
|
import timeflow.data.time.*;
|
|
import javax.swing.*;
|
|
import timeflow.util.*;
|
|
import java.awt.*;
|
import java.awt.event.*;
|
import java.util.*;
|
|
public class BarGraphView extends AbstractView
|
{
|
|
BarGraph graph = new BarGraph();
|
JPanel controls;
|
ArrayList<BarData> bars;
|
|
enum Aggregate
|
{
|
|
TOTAL, AVERAGE, COUNT
|
};
|
Aggregate agg;
|
JComboBox splitFieldChoice, numFieldChoice;
|
|
public BarGraphView(TFModel model)
|
{
|
super(model);
|
|
setLayout(new BorderLayout());
|
controls = new JPanel();
|
add(controls, BorderLayout.NORTH);
|
controls.setLayout(null);
|
controls.setBackground(Color.white);
|
|
JScrollPane scrollPane = new JScrollPane(graph);
|
add(scrollPane, BorderLayout.CENTER);
|
|
makeTop();
|
}
|
|
protected JComponent _getControls()
|
{
|
return controls;
|
}
|
|
void makeTop()
|
{
|
int x = 10, y = 10;
|
int ch = 25, pad = 5, cw = 160;
|
|
controls.removeAll();
|
TFModel model = getModel();
|
if (model.getDB() == null || model.getDB().size() == 0)
|
{
|
JLabel empty = new JLabel("Empty database");
|
controls.add(empty);
|
empty.setBounds(x, y, cw, ch);
|
return;
|
}
|
|
JLabel top = new JLabel("For each value of");
|
controls.add(top);
|
top.setBounds(x, y, cw, ch);
|
y += ch + pad;
|
|
splitFieldChoice = new JComboBox();
|
String splitSelection = null;
|
for (Field f : DBUtils.categoryFields(model.getDB()))
|
{
|
splitFieldChoice.addItem(f.getName());
|
if (f == graph.splitField)
|
{
|
splitSelection = f.getName();
|
}
|
}
|
controls.add(splitFieldChoice);
|
splitFieldChoice.setBounds(x, y, cw, ch);
|
y += ch + 3 * pad;
|
|
if (splitSelection != null)
|
{
|
splitFieldChoice.setSelectedItem(splitSelection);
|
} else if (getModel().getColorField() != null)
|
{
|
splitFieldChoice.setSelectedItem(getModel().getColorField().getName());
|
}
|
splitFieldChoice.addActionListener(new ActionListener()
|
{
|
|
@Override
|
public void actionPerformed(ActionEvent e)
|
{
|
graph.redo();
|
}
|
});
|
|
JLabel showLabel = new JLabel("show");
|
controls.add(showLabel);
|
showLabel.setBounds(x, y, cw, ch);
|
y += ch + pad;
|
|
numFieldChoice = new JComboBox();
|
numFieldChoice.addItem("Number of events");
|
final ArrayList<Field> valueFields = new ArrayList<Field>();
|
for (Field f : model.getDB().getFields(Double.class))
|
{
|
numFieldChoice.addItem("Total: " + f.getName());
|
numFieldChoice.addItem("Average: " + f.getName());
|
valueFields.add(f);
|
}
|
controls.add(numFieldChoice);
|
numFieldChoice.setBounds(x, y, cw, ch);
|
|
boolean chosen = false;
|
for (int i = 0; i < numFieldChoice.getItemCount(); i++)
|
{
|
if (numFieldChoice.getItemAt(i).equals(graph.lastValueMenuChoice))
|
{
|
numFieldChoice.setSelectedIndex(i);
|
chosen = true;
|
}
|
}
|
if (!chosen)
|
{
|
Field size = getModel().getDB().getField(VirtualField.SIZE);
|
if (size != null)
|
{
|
numFieldChoice.setSelectedItem("Total: " + size.getName());
|
}
|
}
|
numFieldChoice.addActionListener(new ActionListener()
|
{
|
|
@Override
|
public void actionPerformed(ActionEvent e)
|
{
|
|
graph.redo();
|
}
|
});
|
revalidate();
|
repaint();
|
}
|
|
void reset()
|
{
|
makeTop();
|
graph.redo();
|
revalidate();
|
repaint();
|
}
|
|
@Override
|
protected void _note(TFEvent e)
|
{
|
if (e.affectsSchema())
|
{
|
reset();
|
} else
|
{
|
graph.redo();
|
}
|
repaint();
|
}
|
|
@Override
|
public String getName()
|
{
|
return "Bar Graph";
|
}
|
|
@Override
|
protected void onscreen(boolean majorChange)
|
{
|
reset();
|
}
|
|
class BarData
|
{
|
|
Object thing;
|
double num;
|
|
BarData(Object thing, double num)
|
{
|
this.thing = thing;
|
this.num = num;
|
}
|
}
|
|
class BarGraph extends JPanel
|
{
|
|
int numVals = 0;
|
int rowHeight = 30;
|
int barHeight = 20;
|
int labelX = 10, barLeft = 300, barRight;
|
int topY = 45;
|
int numX = 210;
|
Field splitField, valueField;
|
String lastValueMenuChoice;
|
double min, max;
|
|
void redo()
|
{
|
bars = new ArrayList<BarData>();
|
splitField = getModel().getDB().getField((String) splitFieldChoice.getSelectedItem());
|
if (splitField != null)
|
{
|
int n = numFieldChoice.getSelectedIndex();
|
|
if (n == 0)
|
{
|
agg = Aggregate.COUNT;
|
} else
|
{
|
agg = n % 2 == 1 ? Aggregate.TOTAL : Aggregate.AVERAGE;
|
}
|
|
if (agg == Aggregate.COUNT)
|
{
|
Bag<String> bag = DBUtils.countValues(getModel().getActs(), splitField);
|
for (String s : bag.list())
|
{
|
bars.add(new BarData(s, bag.num(s)));
|
}
|
} else
|
{
|
lastValueMenuChoice = (String) numFieldChoice.getSelectedItem();
|
int colon = lastValueMenuChoice.indexOf(':');
|
valueField = getModel().getDB().getField(lastValueMenuChoice.substring(colon + 2));
|
DoubleBag<String> bag = new DoubleBag<String>();
|
for (Act a : getModel().getActs())
|
{
|
if (splitField.getType() == String.class)
|
{
|
bag.add(a.getString(splitField), a.getValue(valueField));
|
} else
|
{
|
String[] tags = a.getTextList(splitField);
|
for (String tag : tags)
|
{
|
bag.add(tag, a.getValue(valueField));
|
}
|
}
|
}
|
boolean isSum = agg == Aggregate.TOTAL;
|
for (String s : bag.list(isSum))
|
{
|
bars.add(new BarData(s, isSum ? bag.num(s) : bag.average(s)));
|
}
|
}
|
}
|
revalidate();
|
repaint();
|
}
|
|
public void paintComponent(Graphics g1)
|
{
|
Graphics2D g = (Graphics2D) g1;
|
int w = getSize().width, h = getSize().height;
|
g.setColor(Color.white);
|
g.fillRect(0, 0, w, h);
|
TFModel model = getModel();
|
Display display = model.getDisplay();
|
|
if (display.emptyMessage(g, model))
|
{
|
return;
|
}
|
|
if (bars == null)
|
{
|
return;
|
}
|
|
if (bars.size() == 0)
|
{
|
g.setColor(Color.gray);
|
g.drawString("(No data selected.)", 10, 30);
|
return;
|
}
|
|
int n = bars.size();
|
max = bars.get(0).num;
|
min = Math.min(0, bars.get(n - 1).num);
|
|
|
barRight = w - 30;
|
|
int zero = scaleX(0);
|
boolean isInColor = (splitField != null && getModel().getColorField() == splitField);
|
|
// draw header
|
int titleY = topY - 15;
|
g.setColor(Color.black);
|
g.setFont(display.big());
|
g.drawString(splitField.getName().toUpperCase(), labelX, titleY);
|
String aggLabel = agg.toString();
|
if (agg != Aggregate.COUNT)
|
{
|
aggLabel += " " + valueField.getName().toUpperCase();
|
}
|
g.drawString(aggLabel, barLeft, titleY);
|
g.setFont(display.plain());
|
FontMetrics fm = display.plainFontMetrics();
|
// draw bars
|
|
for (int i = 0; i < n; i++)
|
{
|
int y = topY + i * rowHeight;
|
int ty = y + barHeight;
|
BarData data = bars.get(i);
|
|
Color c = null;
|
|
g.setColor(Color.gray);
|
|
// label value
|
boolean missing = data.thing == null || (data.thing.toString().length() == 0);
|
String label = missing ? "[missing]"
|
: display.format(display.toString(data.thing), 25, false);
|
if (isInColor)
|
{
|
g.setColor(missing ? Color.gray : display.makeColor(data.thing.toString()));
|
display.makeColor(label);
|
}
|
g.drawString(label, labelX, ty);
|
|
// label number
|
String numLabel = display.format(data.num);
|
g.drawString(numLabel, numX + 70 - fm.stringWidth(numLabel), ty);
|
|
// draw bar.
|
g.setColor(missing ? Color.lightGray : (isInColor ? c : Display.barColor));
|
int x = scaleX(data.num);
|
int a = Math.min(x, zero);
|
int b = Math.max(x, zero);
|
g.fillRect(a, y + 5, b - a, barHeight);
|
}
|
}
|
|
int scaleX(double x)
|
{
|
if (max == min)
|
{
|
return barLeft;
|
}
|
return (int) (barLeft + (barRight - barLeft) * (x - min) / (max - min));
|
}
|
|
public Dimension getPreferredSize()
|
{
|
return new Dimension(400, 100 + rowHeight * (bars == null ? 0 : bars.size()));
|
}
|
}
|
}
|