0

I'm trying to make a simple function drawer in Java. I'm using the ScriptEngine API to parse the equation from a string, but it gets very slow while drawing. Is there another way to do the same thing? Here is the code:

private String v;
@Override
public void init(){
    setSize(600,600);
    v = JOptionPane.showInputDialog("Input function:");
}
@Override
public void paint(Graphics g){
    drawQuadrants(g);
    drawEquation(g);
}
private void drawEquation(Graphics g) {
    g.setColor(Color.BLUE);
    ScriptEngineManager mgr = new ScriptEngineManager();
    ScriptEngine engine = mgr.getEngineByName("JavaScript");
    v = v.replace("sin", "Math.sin")
    .replace("cos", "Math.cos")
    .replace("sen", "Math.sin")
    .replace("tan", "Math.tan")
    .replace("tg", "Math.tan")
    .replace("log", "Math.log")
    .replace("Log(x)","(Math.log(x)/Math.LN10)");
    for(double x0 = -10;x0<=10;x0+=0.001){
        engine.put("x", x0);
        try {
            double y0 = (Double)engine.eval(v);
            drawPoint(g,x0,-y0);
        } catch (HeadlessException | ScriptException e) {
            e.printStackTrace();
        }
    }
}
private void drawQuadrants(Graphics g) {
    g.setColor(Color.BLACK);
    g.drawLine(0, 300, 600, 300);
    g.drawLine(300, 0, 300, 600);
    g.setFont(new Font("Arial",Font.BOLD,15));
    g.drawString("x", 580, 320);
    g.drawString("y", 280, 20);
    for(int l = 0;l<=600;l+=30){
        g.drawLine(l, 297, l, 303);
    }
    for(int l = 0;l<=600;l+=30){
        g.drawLine(297, l, 303, l);
    }
}
private void drawPoint(Graphics g, double x0, double y0) {
    int newx0 = (int)map((float)x0, (float)-10, (float)10, (float)0.0, (float)600.0);
    int newy0 = (int)map((float)y0, (float)-10, (float)10, (float)0.0, (float)600.0);
    g.drawOval(newx0, newy0, 1, 1);
}
public static final float map(float value, float start1, float stop1, float start2, float stop2)
{
    return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1));
}
1
  • g.setFont(new Font("Arial",Font.BOLD,15)); would better be g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,15)); since a) It provides compile time checking and b) It will select an undecorated font on whatever system it is running, as opposed to those systems that have Arial font installed. Commented Jan 6, 2015 at 1:09

3 Answers 3

1

Well you could always try my code,it is simple fast and elegant.It can also plot any graph by using an external parser.

    import javax.swing.*;
    import java.awt.*;
     import java.util.Scanner;
       import net.objecthunter.exp4j.*;

     class math extends JFrame
     {
       public static void main(String args[])
      {
    math m=new math();
    m.setVisible(true);
    m.setLocationRelativeTo(null);


}

public void paintallies(Graphics G1,double sf)
{int i;
 Graphics2D g21=(Graphics2D) G1;
 g21.setColor(Color.GREEN);
    for(i=0;i<=600;i=(int) (i+sf))
    {
        g21.drawLine(i,0,i,600);
        g21.drawLine(0,i,600,i);

    }

}
public void paintaxes(Graphics G1)
{
    Graphics2D g21=(Graphics2D) G1;
    g21.setColor(Color.BLACK);
    g21.drawLine(300,0,300,600);//y axis

    g21.drawLine(0,300,600,300); //x axis

}


public void paint(Graphics G)
{
    int i;
    double j,k;

    Scanner s=new Scanner(System.in);
    System.out.println("Enter input");
    String input=s.nextLine();
    System.out.println("Enter scale factor");
    double sf=s.nextDouble();
    double sff=300/sf;
    double kf=sff;
    double count=0;






    Graphics g2=(Graphics) G;
    paintallies(G,sf);
    paintaxes(G);

    g2.translate(300,300);
    do
    {
        kf=kf-(1/sf);
        count++;

    }while(kf>=0);
    double counts=2*count;


    Color c=Color.RED;
    g2.setColor(c.darker());

    double yarr[]=new double[(int)counts];
    double xarr[]=new double[(int)counts];



    Expression E=new ExpressionBuilder(input).variables("x").build();


     j=-sff; k=-sff;
    for(i=0;i<counts;i++)
    {

        xarr[i]=j;
        j=j+(1/sf);


        E.setVariable("x",k);
        yarr[i]=E.evaluate();
        k=k+(1/sf);

        xarr[i]=sf*xarr[i];
        yarr[i]=-sf*yarr[i];

    }

    for(i=0;i<counts;i++)
    {
        if(i==counts-1)
        {
            break;
        }
        else
        {
        g2.drawLine((int)xarr[i],(int)yarr[i],(int)xarr[i+1],(int)yarr[i+1]);

        }
    }


}






math()
{
    super("Grapher");
    setSize(600,600);
    setResizable(true);



}

}

Sign up to request clarification or add additional context in comments.

Comments

0

One low-hanging fruit may be to increase the value in the for-loop step in the drawEquation() method. When choosing this value, take into consideration that you have ~ 2K to 3K pixels max horizontally. Yet, you're iterating over 20K points on the X axis. Try x0+=0.01 first, then adjust as needed. This may cause your program to run in 1/10th the time.

5 Comments

I set the for-loop step value to 0.03, and it made the program to run faster, but it also made the drawn points less dense.
Ideally, the step value should be adaptive depending on the slope of the function you are graphing - the sharper the slope, the smaller the step value.
And how could I do this?
If you have the first derivative of the function you are graphing, great. :) Otherwise, take a sample of the slope by calculating y1 and y2 for x1=x and x2=x+0.001. If abs(y1-y2) is "small", make the step # small. Otherwise, make it large. You will have to determine "small" and "large" for your app.
I imagine that crossing the scriptengine / app boundary to calculate y for each point is a major drag on execution efficiency. You may want to pass in an array of X values to the script engine and iterate over the array in Javascript to come up with an array of Y values. It complicates the code, but will probably make it must faster.
0

It would be much faster if you - make ScriptEngineManager mgr object as member variable and create it once (in constructor of your class)

  • invoke parsing and calcuations from drawEquation only when text in input dialog changes, not in every draw. You could save computed values in two arrays (one array for x, another one for y values).

As general rule (for every graphic engime I do know), onDraw method should be small and fast.

  • do not perform object cration and initalization in onDraw. That crates A LOT of work for garbage collector.
  • do not perform extensive computation in onDraw, if those computation may be performed before.

EDIT: Calculate and draw only points you need to draw. You need to know width of your Graphic objects, compute increment value of the for loop and have only g.width() number of iterations. More iterations is just a waste- you are drawing many points in the same screen location.

1 Comment

I put the ScriptEngine initalization in the class constructor, but it doesn't make my applet faster.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.