package org.geogebra.common.euclidian.draw; import java.util.ArrayList; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GGraphics2D; 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.kernel.StringTemplate; import org.geogebra.common.kernel.algos.AlgoElement; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.GeoNumeric; import org.geogebra.common.kernel.geos.GeoPoint; import org.geogebra.common.kernel.statistics.AlgoDotPlot; /** * Drawable representation of a point plot * */ public class DrawPointPlot extends Drawable { /** graph types */ public enum DrawType { /** vertical bars */ DOT_PLOT, /** horizontal bars */ POINT_PLOT } private DrawType drawType = DrawType.DOT_PLOT; private boolean isVisible, labelVisible; private double[] coords = new double[2]; private AlgoElement algo; private ArrayList<DrawPoint> drawPoints; private int pointStyle; private int pointSize; private int oldPointSize = -1; private int oldPointStyle = -1; private GColor oldColor = null; private GColor pointColor; private double scaleFactor = 1; private GeoList pointList; /************************************************* * @param view * view * @param pointList * list of GeoPoints to plot * @param drawType * type */ public DrawPointPlot(EuclidianView view, GeoList pointList, DrawType drawType) { this.view = view; this.drawType = drawType; geo = pointList; this.pointList = pointList; init(); update(); } private void init() { algo = geo.getParentAlgorithm(); 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 = drawPoints.get(0).getBounds(); for (int i = 1; i < drawPoints.size(); i++) { rect.add(drawPoints.get(i).getBounds()); } return rect; } @Override public void draw(GGraphics2D g2) { if (isVisible) { for (int i = 0; i < drawPoints.size() && i < pointList.size(); i++) { pointList.get(i).setHighlighted(geo.doHighlighting()); drawPoints.get(i).draw(g2); } if (labelVisible) { g2.setFont(view.getFontConic()); g2.setPaint(geo.getLabelColor()); drawLabel(g2); } } } @Override public GeoElement getGeoElement() { return geo; } @Override public boolean hit(int x, int y, int hitThreshold) { for (int i = 0; i < drawPoints.size(); i++) { if (drawPoints.get(i).hit(x, y, hitThreshold)) { setToolTipForPoint(i); return true; } } return false; } @Override public boolean intersectsRectangle(GRectangle rect) { for (int i = 0; i < drawPoints.size(); i++) { Drawable d = drawPoints.get(i); if (d.intersectsRectangle(rect)) { return true; } } return false; } @Override public boolean isInside(GRectangle rect) { int size = drawPoints.size(); for (int i = 0; i < size; i++) { Drawable d = drawPoints.get(i); if (!d.isInside(rect)) { return false; } } return size > 0; } @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(); // add or remove point drawables updatePointLists(); // adjust point coordinates for a density plot if (drawType == DrawType.DOT_PLOT && ((AlgoDotPlot) algo).stackAdjacentDots()) { doDotDensity(); } pointStyle = pointList.getPointStyle(); pointSize = pointList.getPointSize(); pointColor = geo.getObjectColor(); boolean doVisualStyleUpdate = (oldPointSize != pointSize) || (oldPointStyle != pointStyle) || !(oldColor.equals(pointColor)); oldPointSize = pointSize; oldPointStyle = pointStyle; oldColor = geo.getObjectColor(); for (int i = 0; i < pointList.size(); i++) { GeoPoint pt = (GeoPoint) pointList.get(i); if (doVisualStyleUpdate) { pt.setObjColor(pointColor); pt.setPointSize(pointSize); pt.setPointStyle(pointStyle); } drawPoints.get(i).update(); } GeoPoint pt = (GeoPoint) pointList.get(0); coords[0] = pt.getX(); coords[1] = pt.getY(); view.toScreenCoords(coords); if (labelVisible) { xLabel = (int) coords[0]; yLabel = (int) coords[1] + 2 * view.getFontSize(); labelDesc = geo.getLabelDescription(); addLabelOffset(); } } private void updatePointLists() { // find the number of points to draw int n = pointList.size(); drawPoints.ensureCapacity(n); // adjust the lists if (n > drawPoints.size()) { // add for (int i = drawPoints.size(); i < n; i++) { GeoPoint pt = (GeoPoint) pointList.get(i); DrawPoint d = new DrawPoint(view, pt); d.setGeoElement(pt); drawPoints.add(d); } } else if (n < drawPoints.size()) { // remove for (int i = n; n < drawPoints.size();) { drawPoints.remove(i); } } } /** * Sets the real world height of a point so that it fits in a stack of dots * * @param pt * @param dotCount * number of dots the point is stacked above the x-axis */ private void setDotHeight(GeoPoint pt, int dotCount) { double y; pointSize = pointList.getPointSize(); // get y coord for the stacked dot y = (view.getYZero() - pointSize); // first dot on axis y = y - 2 * (dotCount - 1) * pointSize * scaleFactor; // higher dot y = view.toRealWorldCoordY(y); // set the y coord of the GeoPoint pt.setY(y); pt.updateCoords(); } /** * Stacks dots when x values are less than a dot diameter away. Follows * Wilkinson's algorithm. */ private void doDotDensity() { pointSize = pointList.getPointSize(); double h = 2 * pointSize * view.getInvXscale(); scaleFactor = ((AlgoDotPlot) algo).getScaleFactor(); GeoPoint pt = null; GeoList xList = ((AlgoDotPlot) algo).getUniqueXList(); GeoList freqList = ((AlgoDotPlot) algo).getFrequencyList(); int xIndex = 0; int dotCount = 1; double stackX = ((GeoNumeric) xList.get(xIndex)).getDouble(); for (int i = 0; i < xList.size(); i++) { double x = ((GeoNumeric) xList.get(i)).getDouble(); int freq = (int) ((GeoNumeric) freqList.get(i)).getDouble(); if (x > stackX + h) { stackX = x; dotCount = 1; } for (int k = 0; k < freq; k++) { pt = (GeoPoint) pointList.get(xIndex); pt.setX(stackX); pt.updateCoords(); setDotHeight(pt, dotCount); dotCount++; xIndex++; } } } /** * @param index * index of point in the algorithm output list */ public void setToolTipForPoint(int index) { double x = getDotPlotX(index); String text = view.getKernel().format(x, StringTemplate.defaultTemplate); ((AlgoDotPlot) geo.getParentAlgorithm()).setToolTipPointText(text); // force automatic tool tip update view.setToolTipText(" "); } private double getDotPlotX(int index) { double x = 0; int xIndex = 0; GeoList list1 = ((AlgoDotPlot) algo).getUniqueXList(); GeoList list2 = ((AlgoDotPlot) algo).getFrequencyList(); for (int i = 0; i < list1.size(); i++) { x = ((GeoNumeric) list1.get(i)).getDouble(); int freq = (int) ((GeoNumeric) list2.get(i)).getDouble(); for (int k = 0; k < freq; k++) { if (index == xIndex) { return x; } xIndex++; } } return x; } @Override public BoundingBox getBoundingBox() { // TODO Auto-generated method stub return null; } @Override public void updateBoundingBox() { // TODO Auto-generated method stub } }