import java.awt.geom.AffineTransform;
import java.awt.image.ImageObserver;
import java.util.BitSet;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
+import java.util.List;
import java.util.Map;
import org.jfree.graphics2d.svg.SVGGraphics2D;
eRes = Math.min(eRes, aa_annotations.length);
- int y1 = y, y2 = y;
+ // (x1,y1), (x2,y2) pixel end points of line to be drawn
+ int y0 = y, y1 = y;
float range = max - min;
// //Draw origin
if (min < 0)
{
- y2 = y - (int) ((0 - min / range) * graphHeight);
+ y1 = y - (int) ((0 - min / range) * graphHeight);
}
g.setColor(Color.gray);
- drawLine(g, squareStroke, x * charWidth, y2, (eRes - sRes) * charWidth,
- y2);
+ drawLine(g, squareStroke, x * charWidth, y1, (eRes - sRes) * charWidth,
+ y1);
if (sRes == 0)
{
continue;
}
- if (aa_annotations[column].colour == null)
- {
- g.setColor(Color.black);
- }
- else
- {
- g.setColor(aa_annotations[column].colour);
- }
+ Color color1 = aa_annotations[column].colour == null ? Color.black
+ : aa_annotations[column].colour;
+ Color color0 = null;
- boolean value1Exists = column > 0
+ /* value0 is the previous value. value2 is the next value. */
+ boolean value0Exists = column > 0
&& aa_annotations[column - 1] != null;
- float value1 = 0f;
- if (value1Exists)
+ float value0 = 0f;
+ if (value0Exists)
{
- value1 = aa_annotations[column - 1].value;
+ Annotation lastAnnotation = aa_annotations[column - 1];
+ value0 = lastAnnotation.value;
+ color0 = lastAnnotation.colour == null ? Color.black
+ : lastAnnotation.colour;
}
- float value2 = aa_annotations[column].value;
- boolean nextValueExists = aa_annotations.length > column + 1
+ float value1 = aa_annotations[column].value;
+ boolean value2Exists = aa_annotations.length > column + 1
&& aa_annotations[column + 1] != null;
// check for standalone value
- if (!value1Exists && !nextValueExists)
+ if (!value0Exists && !value2Exists)
{
- y2 = y - yValueToPixelHeight(value2, min, range, graphHeight);
- drawLine(g, x * charWidth + charWidth / 4, y2,
- x * charWidth + 3 * charWidth / 4, y2);
+ g.setColor(color1);
+ y1 = y - yValueToPixelHeight(value1, min, range, graphHeight);
+ drawLine(g, x * charWidth + charWidth / 4, y1,
+ x * charWidth + 3 * charWidth / 4, y1);
x++;
continue;
}
- if (!value1Exists)
+ if (!value0Exists)
{
x++;
+ // this annotation column value will be drawn with the line segment
+ // attached to the next annotation column value
continue;
}
+ y0 = y - yValueToPixelHeight(value0, min, range, graphHeight);
y1 = y - yValueToPixelHeight(value1, min, range, graphHeight);
- y2 = y - yValueToPixelHeight(value2, min, range, graphHeight);
- int a1 = (x - 1) * charWidth + charWidth / 2;
+ // (a1, b1), (a2, b2) line segment pixel endpoints
+ int a0 = (x - 1) * charWidth + charWidth / 2;
+ int b0 = y0;
+ int a1 = x * charWidth + charWidth / 2;
int b1 = y1;
- int a2 = x * charWidth + charWidth / 2;
- int b2 = y2;
+
+ Color col = null;
if (x == 0)
{
- // only draw an initial half-line
- a1 = x * charWidth;
- b1 = y1 + (y2 - y1) / 2;
+ // only draw an initial half-line (the second half)
+ col = color1;
+ a0 = x * charWidth;
+ b0 = y0 + (y1 - y0) / 2;
}
else if (x == eRes - sRes)
{
- // this is one past the end to draw -- only draw the first half of the
- // line
- a2 = x * charWidth - 1;
- b2 = y1 + (y2 - y1) / 2;
+ // this is one past the end of visible alignment to draw -- only draw
+ // the first half of the line and use last point's colour
+ col = color0;
+ a1 = x * charWidth - 1;
+ b1 = y0 + (y1 - y0) / 2;
+ }
+ if (color1.equals(color0))
+ {
+ // no change in colour, just draw the whole line
+ col = color1;
+ }
+ if (col != null)
+ {
+ g.setColor(col);
+ drawLine(g, a0, b0, a1, b1);
}
else
{
+ drawHalfSegmentedLine(g, color0, color1, a0, b0, a1, b1);
}
- drawLine(g, a1, b1, a2, b2);
+
x++;
}
{
g.setColor(_aa.threshold.colour);
Graphics2D g2 = (Graphics2D) g;
- y2 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
- drawLine(g, dashedLine(charWidth), 0, y2, (eRes - sRes) * charWidth,
- y2);
+ y1 = (int) (y - ((_aa.threshold.value - min) / range) * graphHeight);
+ drawLine(g, dashedLine(charWidth), 0, y1, (eRes - sRes) * charWidth,
+ y1);
}
g2d.setStroke(prevStroke);
}
return (int) (((value - min) / range) * graphHeight);
}
+ private void drawHalfSegmentedLine(Graphics g, Color col0, Color col2,
+ int x0, int y0, int x2, int y2)
+ {
+ // y1 is vertical midpoint between the line ends (x0,y0) and (x2,y2)
+ // restricted to integer (pixel) value for better appearance
+ int y1 = (y0 + y2) / 2;
+ if (y0 == y1 || y1 == y2)
+ {
+ // not enough vertical difference for one of the halves, split
+ // horizontally
+ int x1 = (x0 + x2) / 2;
+ drawHorizontallyClippedLineSegment(g, col0, x0, y0, x1, x2, y2, true);
+ drawHorizontallyClippedLineSegment(g, col2, x0, y0, x1, x2, y2,
+ false);
+ return;
+ }
+
+ drawVerticallyClippedLineSegment(g, col0, x0, y0, y1, x2, y2, true);
+ drawVerticallyClippedLineSegment(g, col2, x0, y0, y1, x2, y2, false);
+ }
+
+ private void drawVerticallyClippedLineSegment(Graphics g, Color col,
+ int x0, int y0, int y1, int x2, int y2, boolean firstPart)
+ {
+ int margin = 1;
+ boolean y0top = y0 <= y2;
+ int clipX = Math.min(x0, x2) - margin;
+ int clipW = Math.abs(x2 - x0) + 2 * margin;
+ int clipY = 0;
+ int clipH = 0;
+ int yA = y0;
+ int yB = y1;
+ boolean marginAtTop = y0top;
+ if (!firstPart)
+ {
+ yA = y1;
+ yB = y2;
+ marginAtTop = !y0top;
+ }
+ clipY = Math.min(yA, yB);
+ clipH = Math.abs(yB - yA) + margin;
+ if (marginAtTop)
+ {
+ clipY -= margin;
+ }
+ // work on a copy (avoid later clipping problems)
+ Graphics gCopy = g.create();
+ gCopy.setClip(clipX, clipY, clipW, clipH);
+ gCopy.setColor(col);
+ drawLine(gCopy, x0, y0, x2, y2);
+ }
+
+ private void drawHorizontallyClippedLineSegment(Graphics g, Color col,
+ int x0, int y0, int x1, int x2, int y2, boolean firstPart)
+ {
+ int margin = 1;
+ boolean x0left = x0 <= x2;
+ int clipX = 0;
+ int clipW = 0;
+ int clipY = Math.min(y0, y2) - margin;
+ int clipH = Math.abs(y2 - y0) + 2 * margin;
+ int xA = x0;
+ int xB = x1;
+ boolean marginAtLeft = x0left;
+ if (!firstPart)
+ {
+ xA = x1;
+ xB = x2;
+ marginAtLeft = !x0left;
+ }
+ clipX = Math.min(xA, xB);
+ clipW = Math.abs(xB - xA) + margin;
+ if (marginAtLeft)
+ {
+ clipX -= margin;
+ }
+ // work on a copy (avoid later clipping problems)
+ Graphics gCopy = g.create();
+ gCopy.setClip(clipX, clipY, clipW, clipH);
+ gCopy.setColor(col);
+ drawLine(gCopy, x0, y0, x2, y2);
+ }
+
+ private void drawSegmentedLine(Graphics g,
+ List<Map.Entry<Float, Color>> valCols, int x1, int y1, int x2,
+ int y2)
+ {
+ if (valCols == null || valCols.size() == 0)
+ {
+ return;
+ }
+ // let's only go forwards+up|down -- try and avoid providing right to left
+ // x values
+ if (x2 < x1)
+ {
+ int tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ Collections.reverse(valCols);
+ }
+ Graphics2D g2d = (Graphics2D) g.create();
+ float yd = y2 - y1;
+ boolean reverse = yd > 0; // reverse => line going DOWN (y increasing)
+ Map.Entry<Float, Color> firstValCol = valCols.remove(0);
+ float firstVal = firstValCol.getKey();
+ Color firstCol = firstValCol.getValue();
+ int yy1 = 0;
+ yy1 = reverse ? (int) Math.ceil(y1 + firstVal * yd)
+ : (int) Math.floor(y1 + firstVal * yd);
+ Color thisCol = firstCol;
+ for (int i = 0; i < valCols.size(); i++)
+ {
+ Map.Entry<Float, Color> valCol = valCols.get(i);
+ float val = valCol.getKey();
+ Color col = valCol.getValue();
+ int clipX = x1 - 1;
+ int clipW = x2 - x1 + 2;
+ int clipY = 0;
+ int clipH = 0;
+ int yy2 = 0;
+ if (reverse) // line going down
+ {
+ yy2 = (int) Math.ceil(y1 + val * yd);
+ g2d.setColor(thisCol);
+ clipY = yy1 - 1;
+ clipH = yy2 - yy1;
+ if (i == 0)
+ {
+ // highest segment, don't clip at the top
+ clipY -= 2;
+ clipH += 2;
+ }
+ if (i == valCols.size() - 1)
+ {
+ // lowest segment, don't clip at the bottom
+ clipH += 2;
+ }
+ }
+ else // line going up (or level)
+ {
+ yy2 = (int) Math.floor(y1 + val * yd);
+ // g2d.setColor(Color.cyan); g2d.drawRect(x1 - 1, yy1, x2 - x1 + 1, yy2
+ // -
+ // yy1 + 1);
+ g2d.setColor(col);
+ clipY = yy2;
+ clipH = yy1 - yy2;
+ if (i == 0)
+ {
+ // lowest segment, don't clip at the bottom
+ clipH += 2;
+ }
+ if (i == valCols.size() - 1)
+ {
+ // highest segment, don't clip at the top
+ clipY -= 2;
+ clipH += 2;
+ }
+ }
+ g2d.setClip(clipX, clipY, clipW, clipH);
+ drawLine(g2d, x1, y1, x2, y2);
+ yy1 = yy2;
+ thisCol = col;
+ }
+ g2d.dispose();
+ }
+
@SuppressWarnings("unused")
void drawBarGraph(Graphics g, AlignmentAnnotation _aa,
Annotation[] aa_annotations, int sRes, int eRes, float min,