We’re going to make a simple program that can draw mathematical graphs and in order to do that, we’re going to create a custom JComponent class. Learn how





Preview

Today we’re going to write a program that converts an equation from a string to an operation and that draws its graph.

Does it look difficult? Don’t worry, it’s actually very simple and futhermore I’ll guide you through each step.

The main objective of this project is to learn how to create a custom component.

In fact the Java Swing library doesn’t contain a class for every element you might need (obviously), so you should learn how to make the exact piece of UI you need.

Let’s get started.

Create the UI


In the last two posts you learnt how to create a window, add some elements and some functions for them.

In this first part we’re going to apply that knowledge.

So, what do we need?

  • A window, of course;
  • An input field for typing the function we want to draw;
  • A “Draw” button;
  • A component that displays graphs – we’re going to create it ourselves later.

Window and basic UI

The window

Since we want to create a window and a listener, the best choice is to extend the main class with JFrame and implement the action listener class.

Now our main class is both a window and a listener.


Then we define two global variables:

We’re going to need them in the listener’s function.


Now we create a constructor for our class:

Remeber that our class is a child of the JFrame class, so the super keyword access JFrame’s functions.

Calling one of the JFrame’s constructors you can set the window’s title.

I’ve commented out the two lines that add the graph area, because we haven’t created the GraphComponent class yet.

Merging classes

All this code is pretty much similar to the previous tutorial’s one.

The main differenceand I’d like to make it as clear as possible, because it’s very important – is that this time we’ve included the JFrame class and the ActionListener in our class, we’ve merged them, while last time we created two different objects, one for the window and one for the listener.

The listener

Since the main class implements the ActionListener class, we must override its method to define its behaviour:

Also this time I’ve commented out the lines that use the class that we haven’t created yet.

They’re are the ones marked with dashes.

The main function

Finally, we have to create an object from this class.

The computer doesn’t create automatically an instance of the main class when the program starts, but it will only call the main function.

So, the main function has to create that instance.

Creating a new object from our class will make the program call its constructor, therefore create the window and so on.

The GraphComponent class


This is the part that will draw our graph.

The most important functions in this class are:

  • paintComponent
  • DrawEquation
  • stringToOperation

paintComponent


This is the method that determines the design of an element.

Overriding it means changing the way an instanced element looks and it’s exactly what we’re going to do.

For now we will make it draw a cartesian graph, with two axes, a background grid and labels to mark each unit on the axes.

Part 1: getting the needed information

We’re going to need a Graphics2D object, which allows us to draw geometries and we can get it from the only parameter of the paintComponent function, g.

Then we set the background colour: when an area is cleared, it is filled with this.

Since the clear function requires width and height of the element, we first need those two values.

These are very important, because we’re going to use them a lot to draw not only the cartesian graph, but also the functions’ graphs.

Now we can finally clear the element from the previous design:

The clearRect function requires the coordinates and the width and height of the area we want to clear.

The coordinates are relative to the element as showed here:

Swing's coordinate system

Finally, we have to set the colour and the line type for our geometries:

new BasicStroke(2) represents a two pixels thick straight line. To draw dashed lines you should use new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] {10, 2}, 0), as explained here.

Part 2: drawing the axes

graph's axes

Drawing the axes is simple: we just have to draw a vertical line that goes from width/2, 0 to width/2, height and a horizontal line from 0, height/2 to width, height.

Remember that:

  • 0,0 is the top left corner
  • width/2, height/2 is the center
  • a line that goes from 0 to height goes from the top to the bottom and if its x coordinate remains constant it is perfectly vertical.

Part 3: drawing the grid

Graph's grid

Drawing the grid it’s a bit more difficult.

We have to draw more than one horizontal and vertical line, so it seems using a for loop is the best option, using our unit variable as increment.

However we can’t start from 0,0 because that isn’t the center of our graph. If we did, since the component’s size divided by the unit size isn’t an integer, the grid would fit correctly the Swing’s coordinate system, but not the cartesian one, in other words it would result asymmetrical.

So, we have to make the loop start from the axes, that are two, therefore we need two for loop.

Finally, each axis divides the component’s area in two, left and right or top and bottom, so we’ve got two possibilities:

  • A. For each axis we write one loop that starts from one side (center-(whole number of units along that axis*unit) and the ends to the other (center+(whole number of units along that axis*unit)
  • B. For each axis we write two loops, one from one end to the center and the other from the center to the other end – center excluded.

I’ve chosen the second one, because the first one would paint two lines (one for each for loop) over the axes and that would limit me to using the same colour both for the grid and the axes, otherwise you would clearly see those lines on the axes.

This problem persists in the second option too, but it’s less noticable because the dashes overlap the axes just a bit.

You can choose the first option and solve its problem by inverting the positions of the piece of code which draws the axis with the one which draws the grid, but I wanted to sort the parts of the code by complexity to explain them better in this post.

Here’s the result:

Part 4: labelling the axes

Graph's labels

To be honest, to write the piece of code of this part I copied and pasted the code from the previous one.

Then I made the first loop write also the coordinates for the center – I simply removed +unit from the initilisation.

I also added a variable counter – i2 – which is the number I’ve used to label each unit on the axes.

DrawEquation


This is the heart of our program.

Since it’s highly commented and it would be too long to analyse line by line, I’m going to explain you how it works and the maths behind it.

To make it as simple as possible, this program can draw only two types of equations:

and

Anyway, we these two equation types you can draw any function – so lines, parabolas and so on, but not circles or ellipses.


A short introduction to what we’re going to do

We’re going to:

  1. use -width/2+width/2 as range for the indipendent values;
  2. convert that range from pixels to units because we want to calculate the graph relatively to our system;
  3. increment the independent value by a fraction of unit to get a higher resolution graph;
  4. get the result of a given function for each value of the range;
  5. convert the independent value and the one calculated by the program to the Swing’s coordinate system, with the center at the top left corner and the values in pixels.

The program splits the given equation in its two sides and it checks if the left side is an x or an y.

Each one of the two cases has its own piece of code. They’re pretty much identical, so let’s analyse the second one.

Getting the range of x values

We want to calculate the function graph using the visible range of the x axis.

Therefore we need to calculate the graph’s borders’ coordinates relatively to the center, or, in other words, the distance of the sides from the center, that is half of the component’s width.

Distance from borders

If you don’t know how to change frame of reference, you should read this

Pixels to units

We also want this measure (mw) to be expressed in units and not pixels, so we divide it by the unit variable.

We’re using mw for this process, because the independent value is x. In fact in the else statement I replaced mw with mh.

Now we can write a for loop that goes from the border on the negative side (-mr) to the one on the positive side (+mr).

Furthermore, we don’t want the program to calculate the y value only for whole x coordinates, otherwise the graph’s resolution will be too low.

Therefore we want to increase a (expressed in units, because we initialised it to -mr) by a fraction of unit.

Calculating the y values

In the loop, the stringToOperation function returns the result of each operation and, in order to draw it, we have to convert it from the cartesian coordinate system to the Swing’s one.

Doing that is simple: we multiply it by the unit variable, to convert it from units to pixels, and we add half widthfor a – or half heightfor b – because of the offset between the cartesian center – the component’s center – and the Swing’s center – the top left corner.

The final thing to do is drawing a line from the previous point to the current one – (a2, b2) -.

For the first point the program just skips this part.

stringToOperation


Java can’t automatically convert a string into a mathematical expression.

Since what we get from an input field is a string, we have to find a way to solve this problem.

The solution I found is this.

The scripting engine allows to execute code written in a scripting language such as Javascript.

Basically we can execute a line of code in another language and get the result in our program.

Conclusion

And here you have it, a program that draws the graph of a function.

Just to recap, let’s see the main points of our maths:

  1. We used -width/2+width/2 as range for the indipendent values;
  2. then we converted that range from pixels to units because we wanted to calculate the graph relatively to our system;
  3. we chose to increment the independent value by a fraction of unit to get a higher resolution graph;
  4. for each value in the for loop, the program calculates a result based on a given function;
  5. we had to convert the independent value and the one calculated by the program to the Swing’s coordinate system, with the center at the top left corner and the values in pixels;
  6. Finally we could draw that point on screen.

If you have any questions, feel free to ask them in a comment.

If you liked this tutorial, please share it and subscribe to the site’s newsletter.

Anyway, the post is finished, have a good day and we will see next week!

From Zephyro it’s all, Bye!

Categories: Learn Java

10 Comments

Robert · 23 February 2019 at 5:39 AM

Great! Thank you very much!

    Zephyro · 23 February 2019 at 9:26 AM

    You’re welcome! If you have any questions, about anything, feel free to ask it

Rkey · 27 May 2019 at 3:48 PM

How did you implement the scripting part of it? I am lost at that part only. Thanks

    Zephyro · 27 May 2019 at 8:04 PM

    Do you mean how I implemented the GraphComponent class in the main one?

    If it’s that the case, I just created a GraphComponent object and I added it to the main panel (remember that the class I wrote extends JComponent, so it can be used like every other widget):

    graphArea = new GraphComponent();
    mainPanel.add(graphArea);

    If I misunderstood you, please tell me, so that I can help you better.

Kien · 6 June 2019 at 3:57 PM

GraphComponent.java is incomplete
The for loop “for(double a=-mr; a” is missing
Can you update the rest part?
Thank you

    Zephyro · 6 June 2019 at 6:39 PM

    Thank you so much for pointing that out to me.

    I think an error with the new wordpress update happened, but now it’s all fixed. Actually I think it’s even better than before.

    Do you like the new look?

Leave a Reply

%d bloggers like this: