Einfacher Funktionsplotter

Ein einfacher Funktionsplotter

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;
        }
    }
}