package org.geogebra.common.euclidian.draw;
import java.util.ArrayList;
import org.geogebra.common.awt.GBufferedImage;
import org.geogebra.common.awt.GColor;
import org.geogebra.common.awt.GGraphics2D;
import org.geogebra.common.awt.GPaint;
import org.geogebra.common.awt.GRectangle;
import org.geogebra.common.euclidian.BoundingBox;
import org.geogebra.common.euclidian.Drawable;
import org.geogebra.common.euclidian.EuclidianView;
import org.geogebra.common.euclidian.GeneralPathClipped;
import org.geogebra.common.euclidian.HatchingHandler;
import org.geogebra.common.kernel.algos.AlgoBarChart;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoElement.FillType;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.kernel.geos.GeoPoint;
import org.geogebra.common.plugin.EuclidianStyleConstants;
import org.geogebra.common.util.debug.Log;
/**
* Drawable representation of a bar graph
*
*/
public class DrawBarGraph extends Drawable {
/** graph types */
public enum DrawType {
/** vertical bars */
VERTICAL_BAR,
/** horizontal bars */
HORIZONTAL_BAR,
/** step graph */
STEP_GRAPH_CONTINUOUS,
/** stick graph */
STEP_GRAPH_JUMP
}
private DrawType drawType = DrawType.VERTICAL_BAR;
// point types -- given by user, hence not enum
/** closed point on the right */
public static final int POINT_RIGHT = 1;
/** closed point on the right, open on the left */
public static final int POINT_RIGHT_OPEN_LEFT = 2;
/** no points */
public static final int POINT_NONE = 0;
/** closed point on the left */
public static final int POINT_LEFT = -1;
/** closed point on the left, open on the right */
public static final int POINT_LEFT_OPEN_RIGHT = -2;
private int pointType = POINT_NONE;
private boolean isVisible, labelVisible;
private double[] coords = new double[2];
/*
* Use an array to customize bars
*/
private GeneralPathClipped[] gp;
private GeoNumeric sum;
private AlgoBarChart algo;
private ArrayList<GeoPoint> pts;
private ArrayList<DrawPoint> drawPoints;
/*************************************************
* @param view
* view
* @param n
* number (bar chart)
*/
public DrawBarGraph(EuclidianView view, GeoNumeric n) {
this.view = view;
sum = n;
geo = n;
n.setDrawable(true);
init();
update();
}
private void init() {
algo = (AlgoBarChart) geo.getDrawAlgorithm();
drawType = algo.getDrawType();
if (algo.hasPoints()) {
pts = new ArrayList<GeoPoint>();
drawPoints = new ArrayList<DrawPoint>();
updatePointLists();
}
}
/**
* Returns the bounding box of this Drawable in screen coordinates.
*/
@Override
final public GRectangle getBounds() {
if (!geo.isDefined() || !geo.isEuclidianVisible()) {
return null;
}
// GRectangle
// rect=geogebra.common.factories.AwtFactory.getPrototype().newRectangle(
// (int)(algo.getLeftBorder()[0]),(int)algo.getFreqMax(),
// (int)(algo.getLeftBorder().length*algo.getWidth()),(int)algo.getFreqMax());
// return rect;
GRectangle rect = gp[0].getBounds();
for (int i = 1; i < gp.length; i++) {
rect.add(gp[i].getBounds());
}
return rect;
}
private ArrayList<HatchingHandler> hatchingHandlers = null;
@Override
public void draw(GGraphics2D g2) {
// Save fill, color and alfa of object
GColor color = geo.getObjectColor();
GColor selColor = geo.getSelColor();
FillType fillType = geo.getFillType();
String fileName = geo.getImageFileName();
double alpha = geo.getAlphaValue();
AlgoBarChart algop = (AlgoBarChart) geo.getParentAlgorithm();
if (isVisible) {
try {
if (geo.doHighlighting()) {
g2.setPaint(sum.getSelColor());
g2.setStroke(selStroke);
for (int i = 0; i < gp.length; i++) {
int k = i + 1;
if (algop.getBarColor(k) != null) {
GColor col = algop.getBarColor(k);
g2.setPaint(GColor.newColor(col.getRed(),
col.getGreen(), col.getBlue(),
col.getAlpha()));
} else {
g2.setPaint(selColor);
}
g2.draw(gp[i]);
}
g2.setPaint(selColor);
}
} catch (Exception e) {
Log.debug(e.getMessage());
}
try {
if (algo.getDrawType() != DrawType.STEP_GRAPH_CONTINUOUS) {
/*
* Use tags for draw if there are
*/
for (int i = 0; i < gp.length; i++) {
int k = i + 1;
if (algop.getBarColor(k) != null) {
GColor col = algop.getBarColor(k);
geo.setObjColor(col);
geo.setAlphaValue(col.getAlpha());
}
double barAlpha = algop.getBarAlpha(k);
if (barAlpha != -1.0) {
geo.setAlphaValue(barAlpha);
}
geo.setFillType(
algop.getBarFillType(k, geo.getFillType()));
// if (algop.getBarSymbol(k) != null) {
// geo.setFillSymbol(algop.getBarSymbol(k));
// }
// if (algop.getBarImage(k) != null) {
// geo.setImageFileName(algop.getBarImage(k));
// }
// if (algop.getBarHatchDistance(k) != -1) {
// geo.setHatchingDistance(algop
// .getBarHatchDistance(k));
// }
// if (algop.getBarHatchAngle(k) != -1) {
// geo.setHatchingAngle(algop.getBarHatchAngle(k));
// }
GPaint gpaint = null;
GBufferedImage subImage = null;
if (algop.getBarFillType(k).isHatch()) {
initHatchingHandlerArray();
HatchingHandler handler = hatchingHandlers.get(i);
if (handler == null) {
handler = new HatchingHandler();
hatchingHandlers.set(i, handler);
}
GColor barColor = algop.getBarColor(k);
if (barColor == null) {
barColor = geo.getObjectColor();
}
gpaint = handler.setHatching(g2, decoStroke,
barColor, geo.getBackgroundColor(),
algop.getBarAlpha(k),
algop.getBarHatchDistance(k),
algop.getBarHatchAngle(k),
algop.getBarFillType(k),
algop.getBarSymbol(k),
geo.getKernel().getApplication());
if (geo.getKernel().getApplication()
.isHTML5Applet()) {
// not needed in desktop
subImage = handler.getSubImage();
}
}
fill(g2, gp[i], gpaint, subImage);
// appropriate
// Restore values
geo.setObjColor(color);
geo.setFillType(fillType);
// geo.setHatchingAngle((int) hatchingAngle);
// geo.setHatchingDistance(hatchingDistance);
// geo.setFillSymbol(symbol);
geo.setImageFileName(fileName);
geo.setAlphaValue(alpha);
}
}
} catch (Exception e) {
Log.debug(e);
}
try {
if (geo.getLineThickness() > 0) {
g2.setPaint(getObjectColor());
g2.setStroke(objStroke);
for (int i = 0; i < gp.length; i++) {
int k = i + 1;
if (algop.getBarColor(k) != null) {
GColor col = algop.getBarColor(k);
g2.setPaint(GColor.newColor(col.getRed(),
col.getGreen(), col.getBlue(),
geo.getLineOpacity()));
} else {
g2.setPaint(color);
}
g2.draw(gp[i]);
}
g2.setPaint(color);
}
} catch (Exception e) {
Log.debug(e.getMessage());
}
if (labelVisible) {
g2.setFont(view.getFontConic());
g2.setPaint(geo.getLabelColor());
drawLabel(g2);
}
// point
if (algo.hasPoints()) {
for (int i = 0; i < drawPoints.size(); i++) {
drawPoints.get(i).draw(g2);
}
}
}
}
private void initHatchingHandlerArray() {
if (hatchingHandlers == null) {
hatchingHandlers = new ArrayList<HatchingHandler>();
}
// fill array, we might not need hatching for all bars
// but probably will
if (hatchingHandlers.size() < gp.length) {
for (int i = hatchingHandlers.size() - 1; i < gp.length; i++) {
hatchingHandlers.add(null);
}
}
}
@Override
public GeoElement getGeoElement() {
return geo;
}
@Override
public boolean hit(int x, int y, int hitThreshold) {
if (gp != null) {
for (int i = 0; i < gp.length; i++) {
if ((gp[i].contains(x, y)
|| gp[i].intersects(x, y, hitThreshold))) {
setToolTip(i);
return true;
}
}
}
return false;
}
@Override
public boolean intersectsRectangle(GRectangle rect) {
if (gp != null) {
for (int i = 0; i < gp.length; i++) {
if (gp[i].intersects(rect)) {
return true;
}
}
}
return false;
}
@Override
public boolean isInside(GRectangle rect) {
// TODO Auto-generated method stub
return false;
}
@Override
public void setGeoElement(GeoElement geo) {
this.geo = geo;
}
@Override
public void update() {
isVisible = geo.isEuclidianVisible();
if (!isVisible) {
return;
}
if (!geo.getDrawAlgorithm().equals(geo.getParentAlgorithm())) {
init();
}
labelVisible = geo.isLabelVisible();
updateStrokes(sum);
// init gp
gp = new GeneralPathClipped[algo.getIntervals()];
for (int i = 0; i < gp.length; i++) {
gp[i] = new GeneralPathClipped(view);
gp[i].reset();
}
double[] xVal = algo.getLeftBorder();
double[] yVal = algo.getValues();
double width = algo.getWidth();
int N = algo.getIntervals();
if (algo.hasPoints()) {
updatePointLists();
}
drawType = algo.getDrawType();
pointType = algo.getPointType();
int pointStyle;
if (algo.hasPoints() && pointType != POINT_NONE) {
if (pointType == POINT_LEFT || pointType == POINT_LEFT_OPEN_RIGHT) {
pointStyle = EuclidianStyleConstants.POINT_STYLE_DOT;
} else {
pointStyle = EuclidianStyleConstants.POINT_STYLE_CIRCLE;
}
for (int i = 0; i < N; i++) {
coords[0] = xVal[i];
coords[1] = yVal[i];
pts.get(i).setCoords(coords[0], coords[1], 1.0);
pts.get(i).setObjColor(geo.getObjectColor());
pts.get(i).setPointSize(2 + (geo.getLineThickness() + 1) / 3);
pts.get(i).setPointStyle(pointStyle);
if (pointType == POINT_RIGHT) {
pts.get(i).setEuclidianVisible(false);
}
drawPoints.get(i).update();
}
if (drawType == DrawType.STEP_GRAPH_CONTINUOUS
|| drawType == DrawType.STEP_GRAPH_JUMP) {
if (pointType == POINT_LEFT
|| pointType == POINT_LEFT_OPEN_RIGHT) {
pointStyle = EuclidianStyleConstants.POINT_STYLE_CIRCLE;
} else {
pointStyle = EuclidianStyleConstants.POINT_STYLE_DOT;
}
// step graph right points
for (int i = 0; i < N - 1; i++) {
coords[0] = xVal[i + 1];
coords[1] = yVal[i];
pts.get(N + i).setCoords(coords[0], coords[1], 1.0);
pts.get(N + i).setObjColor(geo.getObjectColor());
pts.get(N + i)
.setPointSize(2 + (geo.getLineThickness() + 1) / 3);
pts.get(N + i).setPointStyle(pointStyle);
if (pointType == POINT_LEFT) {
pts.get(N + i).setEuclidianVisible(false);
}
drawPoints.get(N + i).update();
}
}
}
double halfWidth = width / 2;
switch (drawType) {
case VERTICAL_BAR:
if (width <= 0) {
for (int i = 0; i < N; i++) {
coords[0] = xVal[i];
coords[1] = 0;
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
} else {
for (int i = 0; i < N; i++) {
coords[0] = xVal[i];
coords[1] = 0;
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = xVal[i] + width;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = xVal[i] + width;
coords[1] = 0;
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = 0;
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
}
break;
case HORIZONTAL_BAR:
if (width <= 0) {
for (int i = 0; i < N; i++) {
coords[0] = 0;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
} else {
for (int i = 0; i < N; i++) {
coords[0] = 0;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = xVal[i];
coords[1] = yVal[i] + width;
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = 0;
coords[1] = yVal[i] + width;
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
coords[0] = 0;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
}
break;
case STEP_GRAPH_CONTINUOUS:
for (int i = 0; i < N - 1; i++) {
// move to start point
coords[0] = xVal[i] + halfWidth;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
// across
coords[0] = xVal[i + 1] + halfWidth;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
// up
coords[0] = xVal[i + 1] + halfWidth;
coords[1] = yVal[i + 1];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
// up to last point
coords[0] = xVal[N - 1] + halfWidth;
coords[1] = yVal[N - 1];
view.toScreenCoords(coords);
gp[gp.length - 1].lineTo(coords[0], coords[1]);
break;
case STEP_GRAPH_JUMP:
for (int i = 0; i < N - 1; i++) {
// move to start point
coords[0] = xVal[i] + halfWidth;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].moveTo(coords[0], coords[1]);
// across
coords[0] = xVal[i + 1] + halfWidth;
coords[1] = yVal[i];
view.toScreenCoords(coords);
gp[i].lineTo(coords[0], coords[1]);
}
break;
}
// gp on screen?
isVisible = false;
// don't return here to make sure that getBounds() works for
// off screen points too
for (int i = 0; i < gp.length; i++) {
if (gp[i].intersects(0, 0, view.getWidth(), view.getHeight())) {
isVisible = true;
break;
}
}
// TODO: improve label position
if (labelVisible) {
xLabel = (int) coords[0];
yLabel = (int) coords[1] - view.getFontSize();
labelDesc = geo.getLabelDescription();
addLabelOffset();
}
}
private void updatePointLists() {
// find the number of points to draw
int n;
if (drawType == DrawType.STEP_GRAPH_CONTINUOUS
|| drawType == DrawType.STEP_GRAPH_JUMP) {
n = 2 * algo.getIntervals() - 1;
} else {
n = algo.getIntervals();
}
// adjust the lists
if (n > pts.size()) {
// add
for (int i = pts.size(); i < n; i++) {
addPt();
}
} else if (n < pts.size()) {
// remove
// for (int i = n; n < pts.size(); i++) {
// remove in reverse order!
for (int i = pts.size() - 1; i >= n; i--) {
pts.remove(i);
drawPoints.remove(i);
}
}
}
private void addPt() {
GeoPoint p = new GeoPoint(view.getKernel().getConstruction());
p.setLabelVisible(false);
DrawPoint d = new DrawPoint(view, p);
d.setGeoElement(p);
pts.add(p);
drawPoints.add(d);
}
private void setToolTip(int barIndex) {
algo.setToolTipText(barIndex);
view.setToolTipText(null);
}
@Override
public BoundingBox getBoundingBox() {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateBoundingBox() {
// TODO Auto-generated method stub
}
}