# Funktionsplotter



## sk72 (28. Mrz 2012)

Hallo, 

Ich versuche einen Funktionsplotter zu schreiben und hänge gerade bei folgendem Problem:

Als Test lasse ich mir die Funktion x² zeichnen:


```
for (double i=0; i<=10; i++)
	{
		g.setColor(Color.RED);
		g.drawRect((int) i*scala, (int) (-Math.pow(i, 2)*scala), 1, 1);
	}
```

Dementsprechend zeichnet meine Funktion nur die Funktionswerte für i=1,2,3,...,10. Wie kann ich die "Zwischenräume" füllen, dass meine Funktion durchgehend gezeichnet wird, also auch für 1,01, 1,02, ...9,89, 9,99, 10,0?

Das Inkrement in der for-Schleife zu verringern (um die Abstände zu verringern) macht keinen Sinn, da drawRect() als Eingabeparameter int Werte erwartet und dementsprechend die x bzw. y-Werte nach int gecastet werden müssen.


Danke
sk72


Grafik, siehe Anhang.


----------



## Final_Striker (28. Mrz 2012)

Meinst du nicht, dass es sinnvoller wäre, Linien anstatt Rechtecke zu zeichnen?


----------



## sk72 (28. Mrz 2012)

Final_Striker hat gesagt.:


> Meinst du nicht, dass es sinnvoller wäre, Linien anstatt Rechtecke zu zeichnen?



Wieso?
Das macht ja keinen Sinn, bei beispielsweise quadratischen Funktionen beispielsweise x². 
Prinzipiell kann man doch für jeden y-Wert einen Pixel (Rechteck) zeichnen, oder nicht?


----------



## SlaterB (28. Mrz 2012)

du kannst schon mehr Zwischenwerte nehmen, wenn scala bisher anscheinend 10 oder mehr ist,

für x=1 wird 1,1 auf 10,10 abgebildet
x=2 -> 2,4 = 20,40
x=1.5 = 1.5, 2.25 = 15,23 oder ähnliches

schreibe dir zur besseren Übersicht unbedingt eine Methode drawPixel(double x, double y),
in der Methode dann mit scala multiplizieren, auf int runden und drawRect aufrufen

das kann man dann auch zu drawLine(x1,y1,x2,y2) und anderen Methoden ausbauen,
in erster Ebene arbeitest du mit Realwelt-double-Koordinaten, 
die versteckten Methoden rechnen es dann in skalierte int-Zeichenkorridinaten um

----

wieviele Zwischenschritte aus x-Sicht nötig sind hängt von scala und der Funktion ab,
das ist nicht ganz trivial zu bestimmen,
denkbar ist Halbierung: doch die letztendlichen Pixel anschauen, 
wenn der Folgewert zum vorherigen in x- oder y-Richtung um > 1 Pixel abweicht,
dann noch mit halben x dazwischen ausführen,
in Rekursion/ weiteren Halbierungen kann das hässlich werden wenn man es nicht gut sortiert,
stelle ich mir jedenfalls gerade vor, dann doch lieber 1000 x durchlaufen


----------



## Marco13 (28. Mrz 2012)

Wenn die Punkte verbunden werden sollen, verwendet man ohnehin nicht mehr drawRect sondern drawLine. 

Die Frage, wie viele Punkte man abhängig von der Skala braucht ist vermutlich nicht relevant: Man braucht im Zweifelsfall genau so viele Punkte, wie es Pixel gibt. Und zu versuchen, das auszurechnen ist ein Krampf :autsch:

In http://www.java-forum.org/codeschnipsel-u-projekte/122994-einfacher-funktionsplotter.html habe ich einen Weg gezeigt, wie man das lösen kann: Man geht einfach in _Pixel_-Schritten durch's Panel. Den Pixel-X-Wert rechnet man um in den "Welt"-X-Wert (abhängig von der Skala - auch wenn das in dem Beispiel durch die Definition eines zu zeichnenden Bereiches gelöst ist). Für diesen Welt-X-Wert berechnet man den Welt-Y-Wert - also genau den Funktionswert an dieser Position. Und diesen Welt-Y-Wert rechnet man dann in einen Pixel-Y-Wert um (wieder abhängig von der Skala bzw. dem gewählten Bereich).


----------



## Andi_CH (29. Mrz 2012)

sk72 hat gesagt.:


> Das Inkrement in der for-Schleife zu verringern (um die Abstände zu verringern) macht keinen Sinn, da drawRect() als Eingabeparameter int Werte erwartet und dementsprechend die x bzw. y-Werte nach int gecastet werden müssen.



Das würde, falls du wirklich Rechtecke zeichnen möchtest, durchaus Sinn machen. Es hat ja niemand gesagt, dass die Skala auf der Grafik auch der Skala deiner Funktion entsprechen muss - umskalieren heisst das Zauberwort, aber dass Rechtecke in diesem Zusammenhang nicht sinnvoll sind wurde ja schon gesagt.

Z.B. hochzählen in 0.1 - Schritten, * 10 und dann zu int casten. So einfach kann die Welt sein ;-)

EDIT - endlich gefunden - ich weiss nicht mehr in welchem Thread das entsanden ist, aber der Plot sieht doch gar nicht so schlecht aus 


```
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Hauptklasse für SimplePlot. Enthält eine main-Methode
 * und erstellt das GUI. 
 */
public class SimplePlotMain {
	/**
	 * Main-Methode
	 * 
	 * @param args Nicht benutzt
	 */
	public static void main (String args[]) {
		// Erzeuge das GUI auf dem Event-Dispatch-Thread
		SwingUtilities.invokeLater(new Runnable() {
			@Override
			public void run() {
				createAndShowGUI();
			}
		});
	}

	/**
	 * Erzeuge das GUI und zeige es an
	 */
	private static void createAndShowGUI() {
		// Erzeuge einen JFrame
		JFrame frame = new JFrame("SimplePlot");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(new BorderLayout());
		frame.setSize(800,600);

		// Erstelle das SimplePlotPanel und füge es zum Frame hinzu
		SimplePlotPanel plotPanel = new SimplePlotPanel();
		frame.getContentPane().add(plotPanel, BorderLayout.CENTER);

		// Erstelle eine Funktion, die geplottet werden soll 
		// und weise sie dem SimplePlotPanel zum plotten zu
		Function function = new Function() {
			@Override
			public float compute(float argument) {
				return (float)Math.sin(argument)*argument;
			}
		};
		plotPanel.setFunction(function);

		// Füge ein control-panel für die Einstellungen zum Frame hinzu
		JComponent controlPanel = createControlPanel(plotPanel);
		frame.getContentPane().add(controlPanel, BorderLayout.EAST);

		// Ganz am Ende den Frame anzeigen
		frame.setVisible(true);
	}

	/**
	 * Erzeugt ein control-panel mit Spinnern, mit denen man den Bereich
	 * einstellen kann, der von der Funktion angezeigt werden soll
	 * 
	 * @param plotPanel Das SimplePlotPanel, auf das die Einstellungen
	 * der Spinner übertragen werden 
	 * @return Das control-panel
	 */
	private static JComponent createControlPanel(final SimplePlotPanel plotPanel) {
		JPanel controlPanel = new JPanel(new BorderLayout());
		JPanel panel = new JPanel(new GridLayout(0,2));
		controlPanel.add(panel, BorderLayout.NORTH);

		// Erstelle Spinner für die minimalen und maximalen X- und Y-Werte
		final JSpinner minXSpinner = new JSpinner(
				new SpinnerNumberModel(-1.0, -1000.0, 1000.0, 0.1));
		final JSpinner maxXSpinner = new JSpinner(
				new SpinnerNumberModel( 1.0, -1000.0, 1000.0, 0.1));
		final JSpinner minYSpinner = new JSpinner(
				new SpinnerNumberModel(-1.0, -1000.0, 1000.0, 0.1));
		final JSpinner maxYSpinner = new JSpinner(
				new SpinnerNumberModel( 1.0, -1000.0, 1000.0, 0.1));

		// Füge die Spinner und entsprechende Labels dem Panel hinzu
		panel.add(new JLabel("minX"));
		panel.add(minXSpinner);
		panel.add(new JLabel("maxX"));
		panel.add(maxXSpinner);
		panel.add(new JLabel("minY"));
		panel.add(minYSpinner);
		panel.add(new JLabel("maxY"));
		panel.add(maxYSpinner);

		// Erstelle einen ChangeListener, der bei einer Änderung an
		// den Spinnern die Werte ans SimplePlotPanel überträgt
		ChangeListener changeListener = new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent event) {
				float minX = ((Double)minXSpinner.getValue()).floatValue();
				float maxX = ((Double)maxXSpinner.getValue()).floatValue();
				float minY = ((Double)minYSpinner.getValue()).floatValue();
				float maxY = ((Double)maxYSpinner.getValue()).floatValue();
				plotPanel.setRangeX(minX, maxX);
				plotPanel.setRangeY(minY, maxY);
			}
		};
		minXSpinner.addChangeListener(changeListener);
		maxXSpinner.addChangeListener(changeListener);
		minYSpinner.addChangeListener(changeListener);
		maxYSpinner.addChangeListener(changeListener);

		// Setze einige Default-Werte, die für die Beispielfunktion 
		// hübsch aussehen...
		minXSpinner.setValue(-10.0);
		maxXSpinner.setValue( 10.0);
		minYSpinner.setValue(-10.0);
		maxYSpinner.setValue( 10.0);

		return controlPanel;
	}
}

/**
 * Interface für eine allgemeine Funktion, die einen float-Wert
 * in einen anderen float-Wert umrechnet. Solche Funktionen können
 * mit dem SimplePlotPanel geplottet werden.
 */
interface Function {
	/**
	 * Berechne den Funktionswert an der angegebenen Stelle
	 * 
	 * @param argument Die Stelle, an der die Funktion berechnet wird
	 * @return Den Funktionswert an der angegebenen Stelle
	 */
	float compute(float argument);
}



/**
 * Das JPanel in dem eine Funktion geplottet wird
 */
class SimplePlotPanel extends JPanel {
	private static final long serialVersionUID = -6588061082489436970L;

	/**
	 * Die Funktion, die geplottet wird
	 */
	private Function function;

	/**
	 * Der minimale x-Wert für die Funktion
	 */
	private float minX = -1.0f;

	/**
	 * Der maximale x-Wert für die Funktion
	 */
	private float maxX = 1.0f;

	/**
	 * Der minimale Y-Wert, der angezeigt werden soll
	 */
	private float minY = -1.0f;

	/**
	 * Der maximale Y-Wert, der angezeigt werden soll
	 */
	private float maxY = 1.0f;

	/**
	 * Erstellt das SimplePlotPanel
	 */
	public SimplePlotPanel() {
	}

	/**
	 * Setze die Fuktion, die geplottet werden soll
	 * 
	 * @param function Die Funktion, die geplottet werden soll
	 */
	public void setFunction(Function function) {
		this.function = function;
		repaint();
	}

	/**
	 * Setzt den Bereich der Funktion, der geplottet werden soll
	 * 
	 * @param minX Der minimale X-Wert
	 * @param maxX Der maximale X-Wert
	 */
	public void setRangeX(float minX, float maxX) {
		this.minX = minX;
		this.maxX = maxX;
		repaint();
	}

	/**
	 * Setzt den Bereich der Funktion, der geplottet werden soll
	 * 
	 * @param minY Der minimale Y-Wert
	 * @param maxY Der maximale Y-Wert
	 */
	public void setRangeY(float minY, float maxY) {
		this.minY = minY;
		this.maxY = maxY;
		repaint();
	}

	/**
	 * Überschriebene Methode aus JComponent: Zeichnet dieses Panel (also
	 * die Funktion) in das übergebene Graphics-Objekt.  
	 */
	@Override
	protected void paintComponent(Graphics gr) {
		super.paintComponent(gr);
		Graphics2D g = (Graphics2D)gr;
		g.setColor(Color.WHITE);
		g.fillRect(0,0,getWidth(),getHeight());
		g.setRenderingHint(
				RenderingHints.KEY_ANTIALIASING, 
				RenderingHints.VALUE_ANTIALIAS_ON);

		paintAxes(g);
		paintFunction(g);
	}

	/**
	 * Rechnet einen x-Wert für die Funktion um in eine x-Position
	 * auf diesem JPanel
	 * 
	 * @param x Der x-Wert für die Funktion
	 * @return Der x-Wert auf diesem JPanel
	 */
	private int toScreenX(float x) {
		float relativeX = (x-minX)/(maxX-minX);
		int screenX = (int)(getWidth() * relativeX);
		return screenX;
	}

	/**
	 * Rechnet einen y-Wert der Funktion um in eine y-Position
	 * auf diesem JPanel
	 * 
	 * @param y Der y-Wert der Funktion
	 * @return Der y-Wert auf diesem JPanel
	 */
	private int toScreenY(float y) {
		float relativeY = (y-minY)/(maxY-minY);
		int screenY = getHeight() - 1 - (int)(getHeight() * relativeY);
		return screenY;
	}

	/**
	 * Rechnet eine x-Position auf diesem Panel um in den X-Wert für
	 * die Funktion, der dort liegt
	 * 
	 * @param x Der x-Wert auf diesem JPanel
	 * @return Der x-Wert für die Funktion
	 */
	private float toFunctionX(int x) {
		float relativeX = (float)x/getWidth();
		float functionX = minX + relativeX * (maxX - minX);
		return functionX;
	}


	/**
	 * Malt einfache Koordinatenachsen in das übergebene Graphics-Objekt
	 * 
	 * @param g Das Graphics-Objekt zum Zeichnen
	 */
	private void paintAxes(Graphics2D g) {
		int x0 = toScreenX(0);
		int y0 = toScreenY(0);
		g.setColor(Color.BLACK);
		g.drawLine(0,y0,getWidth(),y0);
		g.drawLine(x0,0,x0,getHeight());
	}

	/**
	 * Zeichnet die Funktion in das übergebene Graphics-Objekt
	 * 
	 * @param g Das Graphics-Objekt zum Zeichnen
	 */
	private void paintFunction(Graphics2D g) {
		g.setColor(Color.BLUE);

		int previousScreenX = 0;
		float previouseFunctionX = toFunctionX(previousScreenX);
		float previousFunctionY = function.compute(previouseFunctionX);
		int previousScreenY = toScreenY(previousFunctionY);

		for (int screenX=1; screenX<getWidth(); screenX++) {
			float functionX = toFunctionX(screenX);
			float functionY = function.compute(functionX);
			int screenY = toScreenY(functionY);

			g.drawLine(previousScreenX, previousScreenY, screenX, screenY);
			previousScreenX = screenX;
			previousScreenY = screenY;
		}
	}
}
```


----------



## Marco13 (29. Mrz 2012)

Andi_CH hat gesagt.:


> EDIT - endlich gefunden - ich weiss nicht mehr in welchem Thread das entsanden ist, aber der Plot sieht doch gar nicht so schlecht aus



Schon verlinkt: http://www.java-forum.org/codeschnipsel-u-projekte/122994-einfacher-funktionsplotter.html !? :bahnhof:


----------



## sk72 (31. Mrz 2012)

> Z.B. hochzählen in 0.1 - Schritten, * 10 und dann zu int casten. So einfach kann die Welt sein




Sorry, aber ich verstehe nicht ganz wie du das meinst?


----------



## Tobse (31. Mrz 2012)

Schau mal hier: Diagramm UI.
Dort werden auch Funktionen gezeichnet, dazu benutze ich so ein Interface:

```
public interface Function {
        public double calc(double x);
    }
```
Die Methoden in Diagramm.java, die dich intereresieren sollten sind:
[c](Zeile 374) private void drawFunction(double startX, double endX, Function f, Graphics g);[/c]
Zeichnet die Funktion f im Definitionsbereich [startX, endX] auf g.
und, da diese Methode viel verwendet wird:
[c](Zeile 387) private int coordToPxOnX(double);[/c]
Rechnet die X-Koordinate in Pixel um (siehe werte von [c]preCalcVars()[/c])
[c](Zeile 390) private int coordToPxOnY(double);[/c]
Rechnet die Y-Koordinate in Pixel um (siehe werte von [c]preCalcVars()[/c])


----------

