package org.geogebra.common.kernel.algos; import java.text.DecimalFormat; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GFont; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.font.GTextLayout; import org.geogebra.common.euclidian.EuclidianViewInterfaceCommon; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.kernel.Construction; import org.geogebra.common.kernel.arithmetic.FunctionNVar; import org.geogebra.common.kernel.arithmetic.MyDouble; import org.geogebra.common.kernel.commands.Commands; import org.geogebra.common.kernel.geos.GeoBoolean; import org.geogebra.common.kernel.geos.GeoCanvasImage; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoFunctionNVar; import org.geogebra.common.kernel.geos.GeoNumeric; /** * draw density for 2-variables function * * @author Giuliano Bellucci 05/04/2013 * */ public class AlgoDensityPlot extends AlgoElement { private GeoCanvasImage outputImage; private GeoFunctionNVar function; private double minX; private double maxX; private double minY; private double maxY; private int offset = 30; private int imageSize = 280; private int gridPixel = 14; private int[] colors; private int i; private int j; private GColor color; private double incX; private double incY; private double[] vals = new double[2]; private GGraphics2D g; private FunctionNVar f; private DecimalFormat df; private GTextLayout t; private GFont font = kernel.getApplication().getFontCanDisplay("-999") .deriveFont(GFont.PLAIN, 8); private double scaleX; private double scaleY; private int grade; private EuclidianViewInterfaceCommon view; private boolean fixed; private int imagePlusOffset; private double value; private boolean prevGrid; /** * @param c * Construction * @param function * 2-variables function * */ public AlgoDensityPlot(final Construction c, final GeoFunctionNVar function) { this(c, function, -2, 2, -2, 2, false); } /** * @param cons * Construction * @param geoFunctionNVar * 2-variables function * @param lowX * min x * @param highX * max x * @param lowY * min y * @param highY * max y * @param fixed * true for fixed scale, false for scale handled by euclidean * view */ public AlgoDensityPlot(Construction cons, GeoFunctionNVar geoFunctionNVar, double lowX, double highX, double lowY, double highY, boolean fixed) { super(cons); grade = 1; // for web image, area and resolution are a quarter of desktop if (kernel.getApplication().isHTML5Applet()) { grade = 2; offset = 25; } function = geoFunctionNVar; f = function.getFunction(); view = kernel.getApplication().getActiveEuclidianView(); this.fixed = fixed; minX = -2; minY = -2; maxX = 2; maxY = 2; if (fixed) { minX = lowX; minY = lowY; maxX = highX; maxY = highY; } scaleX = maxX - minX; scaleY = maxY - minY; imageSize /= grade; gridPixel /= grade; outputImage = new GeoCanvasImage(cons, imageSize + 2 * offset, imageSize + 2 * offset); g = outputImage.getGraphics(); g.setFont(font); g.setColor(GColor.WHITE); g.fillRect(0, 0, imageSize + 2 * offset, offset); df = new DecimalFormat("0.##"); imagePlusOffset = imageSize + offset; outputImage.setAbsoluteScreenLocActive(true); outputImage.setAbsoluteScreenLoc( view.getViewWidth() / 2 - (imageSize + 2 * offset) / 2, view.getViewHeight() / 2 + (imageSize + 2 * offset) / 2); setInputOutput(); deleteAxes(); if (fixed) { compute(); } update(); } @Override protected void setInputOutput() { input = new GeoElement[6]; input[0] = function; input[1] = new GeoNumeric(cons, minX); input[2] = new GeoNumeric(cons, maxX); input[3] = new GeoNumeric(cons, minY); input[4] = new GeoNumeric(cons, maxY); input[5] = new GeoBoolean(cons, fixed); super.setOutputLength(1); super.setOutput(0, outputImage); setDependencies(); // done by AlgoElement } @Override public void compute() { incX = scaleX / imageSize * grade; incY = scaleY / imageSize * grade; for (j = offset, vals[1] = maxY; j < imagePlusOffset; vals[1] -= incY, j += grade) { for (i = offset, vals[0] = minX; i < imagePlusOffset; vals[0] += incX, i += grade) { value = f.evaluate(vals); colors = rgbColor(value); color = GColor.newColor(colors[0], colors[1], colors[1]); g.setColor(color); g.fillRect(i, j, grade, grade); } } } private void drawGrid() { g.setColor(GColor.LIGHT_GRAY); for (i = offset; i <= imagePlusOffset; i += gridPixel * 5) { g.drawLine(i, offset, i, imagePlusOffset); } for (i = offset; i <= imagePlusOffset; i += gridPixel * 5) { g.drawLine(offset, i, imagePlusOffset, i); } } private void drawAxes() { double xx = minX; double yy = maxY; g.setColor(GColor.GRAY); for (i = offset; i <= imagePlusOffset; i += gridPixel * 5) { g.drawLine(i, imagePlusOffset, i, imagePlusOffset + 2); g.drawLine(offset - 2, i, offset, i); } g.setColor(GColor.BLACK); for (i = offset; i <= imagePlusOffset; i += gridPixel * 5) { t = AwtFactory.getPrototype().newTextLayout(df.format(xx), font, g.getFontRenderContext()); g.drawString(df.format(xx), i - t.getAdvance() / 2, imageSize + 2 * offset - offset / 3); g.drawString(df.format(yy), 1, i + 4); yy -= incY * gridPixel * 5 / grade; xx += incX * gridPixel * 5 / grade; } } /** * @return GeoCanvasImage of function */ public GeoCanvasImage getResult() { return outputImage; } @Override public GetCommand getClassName() { return Commands.DensityPlot; } @Override public void update() { if (!fixed) { if (minX != view.getXmin() || minY != view.getYmin() || maxX != view.getXmax() || maxY != view.getYmax()) { minX = view.getXmin(); minY = view.getYmin(); maxX = view.getXmax(); maxY = view.getYmax(); scaleX = maxX - minX; scaleY = maxY - minY; compute(); setInputOutput(); } } deleteAxes(); if (view.getShowAxis(EuclidianViewInterfaceCommon.AXIS_X) || view.getShowAxis(EuclidianViewInterfaceCommon.AXIS_Y)) { drawAxes(); } showGrid(); } private void deleteAxes() { g.setColor(GColor.WHITE); g.fillRect(0, imagePlusOffset, imagePlusOffset + 2 * offset, offset); g.fillRect(0, offset, offset, imagePlusOffset); g.fillRect(0, 0, imagePlusOffset + 2 * offset, offset); g.fillRect(imagePlusOffset, offset, offset, imageSize); } private void showGrid() { if (view.getShowGrid()) { drawGrid(); prevGrid = true; } else { if (view.getShowGrid() != prevGrid && prevGrid) { prevGrid = false; compute(); } } } /* * This code is based on Zoltan Kovacs's <kovacsz@nyf.hu> idea. For details * see Teaching Math. and Comp. Sci. *2*, pp. 321-331. */ private static int[] hlsToRgb(double h, double l, double s) { int[] rgb = new int[2]; double m2; if (l < 0.5) { m2 = l * (1 + s); } else { m2 = l + s - l * s; } if (h == 180) { rgb[1] = MyDouble.normalize0to255(m2); rgb[0] = MyDouble.normalize0to255(2.0 * l - m2); } else { rgb[1] = MyDouble.normalize0to255(2.0 * l - m2); rgb[0] = MyDouble.normalize0to255(m2); } return rgb; } private static int[] rgbColor(double zre) { double xx; xx = 1.0 - 2.0 * Math.atan(Math.sqrt(zre * zre)) / Math.PI; double x1 = xx <= 0.5 ? 2 * xx : 2 - 2 * xx; double arg = zre < 0 ? 180 : 0; return hlsToRgb(arg, xx, x1); } }