package automenta.vivisect.timeline;
import automenta.vivisect.TreeMLData;
import automenta.vivisect.timeline.Chart.MultiChart;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
public class LineChart extends Chart implements MultiChart {
protected final List<TreeMLData> data;
float min;
float max;
boolean showVerticalLines = false;
boolean showPoints = true;
float lineThickness = 1f;
float borderThickness = 0.5f;
private int end;
private int start;
private boolean specifiedRange;
boolean xorOverlay = false;
public LineChart(TreeMLData... series) {
super();
this.data = new ArrayList(series.length);
for (TreeMLData s : series)
data.add(s);
}
public LineChart(float min, float max, TreeMLData... series) {
this(series);
range(min, max);
}
public LineChart range(float min, float max) {
this.specifiedRange = true;
this.min = min;
this.max = max;
return this;
}
public LineChart thickness(float thick) {
this.lineThickness = thick;
return this;
}
@Override
public void draw(TimelineVis l, float y, float timeScale, float yScale) {
yScale *= height;
float screenyHi = l.g.screenY(l.cycleStart * timeScale, y);
float screenyLo = l.g.screenY(l.cycleStart * timeScale, y + yScale);
int displayHeight = l.g.height;
//TODO Horizontal clipping
//Vertical Clipping:
if ( ((screenyHi < 0) && (screenyLo < 0)) || ((screenyHi >= displayHeight) && (screenyLo >= displayHeight)) ) {
return;
}
if (!specifiedRange)
updateRange(l);
l.g.stroke(127);
l.g.strokeWeight(borderThickness);
//bottom line
l.g.line(0, y + yScale, width * (l.cycleEnd-l.cycleStart) * timeScale, y + yScale);
//top line
l.g.line(0, y, width * (l.cycleEnd-l.cycleStart) * timeScale, y);
drawData(l, timeScale, yScale, y);
if (overlayEnable) {
drawOverlay(l, screenyLo, screenyHi);
}
}
protected void updateRange(TimelineVis l) {
min = Float.POSITIVE_INFINITY;
max = Float.NEGATIVE_INFINITY;
for (TreeMLData chart : data) {
double[] mm = chart.getMinMax(l.cycleStart, l.cycleEnd);
min = (float)Math.min(min,mm[0]);
max = (float)Math.max(max,mm[1]);
}
}
protected void drawOverlay(TimelineVis l, float screenyLo, float screenyHi) {
//draw overlay
l.g.pushMatrix();
l.g.resetMatrix();
if (xorOverlay) {
Graphics2D g2 = l.g2;
g2.setXORMode(Color.white);
}
int dsy = (int) Math.abs(screenyLo - screenyHi);
float ytspace = dsy * 0.75f / data.size() / 2;
l.g.textSize(11f);
l.g.fill(210);
//TODO number precision formatting
l.g.text("" + ((double) min), 0, screenyLo - dsy / 10f);
l.g.text("" + ((double) max), 0, screenyHi + dsy / 10f);
l.g.textSize(15f);
float dsyt = screenyHi + 0.15f * dsy;
for (TreeMLData chart : data) {
l.g.fill(chart.getColor() | 0x77777777);
dsyt += ytspace;
l.g.text(chart.label, 0, dsyt);
dsyt += ytspace;
}
if (xorOverlay) {
Graphics2D g2 = l.g2;
g2.setPaintMode();
}
l.g.popMatrix();
}
protected void drawData(TimelineVis l, float timeScale1, float yScale1, float y) {
int ccolor = 0;
float w = lineThickness * 2.75f;
for (TreeMLData chart : data) {
ccolor = chart.getColor();
float lx = 0;
float ly = 0;
l.g.fill(255f);
boolean firstPoint = false;
l.g.stroke(ccolor);
l.g.strokeWeight(lineThickness);
int cs = l.cycleStart;
for (int t = cs; t < l.cycleEnd; t++) {
l.g.stroke = true;
float x = (t-cs) * timeScale1;
float v = (float)chart.getData(t);
if (Float.isNaN(v)) {
continue;
}
float p = (float)((max == min) ? 0 : (double) ((v - min) / (max - min)));
float px = width * x;
float h = p * yScale1;
float py = y + yScale1 - h;
if (firstPoint) {
if (showVerticalLines) {
l.g.line(px, py, px, py + h);
}
if (t != l.cycleStart) {
l.g.line(lx, ly, px, py);
}
}
lx = px;
ly = py;
firstPoint = true;
if (showPoints) {
l.g.stroke = false;
//TODO create separate size and opacity get/set parameter for the points
l.g.fill(ccolor, 128f * (p * 0.5f + 0.5f));
l.g.rect(px - w / 2f, py - w / 2f, w, w);
}
}
}
}
@Override
public int getStart() {
start = Integer.MAX_VALUE;
end = 0;
for (TreeMLData s : data) {
int ss = s.getStart();
int se = s.getEnd();
if (start > ss) start = ss;
if (end < se) end = se;
}
return start;
}
@Override
public int getEnd() {
//call getStart() prior to this
return end;
}
@Override
public List<TreeMLData> getData() {
return data;
}
public void setLineThickness(float lineThickness) {
this.lineThickness = lineThickness;
}
}