3

While working on a school project, due this saturday, I encountered the most annoying bug I've ever encountered yet. I can't seem to find a solution anywhere on the World Wide Web. Even my good old friend Google seems to let me down. So, here's the question:

I am working with a JTabbedPane. In this JTabbedPane I'll show different JPanes, each showing their information. In one of the JPanes I'm drawing a Bar graph.

When testing the bar graph seperatly, it all works fine. It also does when I load my information while in the right tab of the JTabbedPane. However, as soon as I switch back to this tab the bar graph shifts up during painting and it looks all distorted and stuff (see pic).

I know for a fact I'm allways drawing in the right coördinates, so it must be my JPanel taking place of the tabs of the JTabbedPane. Have you guys got any idea about what's causing this strange behaviour?

enter image description here Right: How it should be. Left: What it looks like after switching back to this tab.

My BarGraphPanel class is a real chaos, here's the code:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package gui;

import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.text.DecimalFormat;
import java.util.ArrayList;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 *
 * @author Scuba Kay
 */
public class BarGraphPanel extends JPanel implements ManagementPanel {


private ArrayList<String[]> info;
    private String infoName;
    private double maxValue;
    private int size;
    private int barWidth;
    private double left;
    private double right;
    private double width;
    private double height;
    private double bottom;
    private double top;
    private int barSpacing;
    private double barPart;
    private double graphHeight;
    private double graphWidth;
    private int cap;

    public BarGraphPanel(){
        super();

        width = 600;
        height = 480;
        setSize((int) width, (int) height);

        // The maximum number of results to show
        cap = 30;

        this.infoName = "Management information";
        this.add(new JLabel("This query is not suited for Bar Graph view: please select another one."));
    }

    /**
     * Sets both x and y, min and max and the info list
     * @author Kay van Bree
     * @param info 
     */
    @Override
    public void setManagementInfo(ArrayList<String[]> info) {
        reset();
        this.info = info;

        // Set important values
        this.maxValue = getMaxValue(info);
        this.size = info.size();

        setSizes();
        repaint();
    }

    /**
     * Resets the panel, taken from TablePanel
     * @author Joepe Kemperman
     */
    public void reset() {
        removeAll();
        invalidate();
        validate();
    }

    /**
     * Set the sizes needed for calculating bar heights etc.
     * @author Kay van Bree
     */
    protected void setSizes(){      
        left = (width / 100) * 7;
        right = width - left*3;
        top = (height / 100) * 7;
        bottom = height - top*3;

        // The dimensions of the graph
        graphHeight = bottom - top;
        graphWidth = right - left;

        // barWidth is... Just guess what it is.
        barWidth = (int) ((double) (graphWidth / size) * 0.95);

        // barSpacing is the space between the bars
        barSpacing = (int) (graphWidth - (barWidth*size)) / size;
        if(barSpacing == 0){ 
            barSpacing++;
            barWidth--;
        }

        // barPart is the height of a bar of value 1
        barPart = graphHeight / maxValue;
    }

    /**
     * Return the max value of the info arraylist
     * @author Kay van Bree
     * @param info 
     */
    private int getMaxValue(ArrayList<String[]> info){
        int max = 0;
        for(String[] row: info){    
            if(Integer.valueOf(row[1]) > max){
                max = Integer.valueOf(row[1]);
            }
        }
        return max;
    }

    /**
     * Draw the damn thing!
     * @author Kay van Bree
     * @param g 
     */
    @Override
    protected void paintComponent(Graphics g){
        super.paintComponent(g);
        if(info != null){
            g.setColor(Color.BLACK);
            drawTitle(g);
            drawBlocks(g);
            if(size <= cap){
                drawBars(g);
            } else {
                drawError(g);
            }
            drawAxes(g);
        } else {
            this.setInformationNotSuitable();
        }
    }

    /**
     * Set an error displaying to many results
     * @author Kay van Bree
     * @param g 
     */
    private void drawError(Graphics g){
        g.setColor(Color.BLACK);
        g.drawString("Too many results to show!", (int)(right-left*2)/2, (int)(bottom-top*2)/2);
    }

    /**
     * Draw the names under the bars
     * @author Kay van Bree
     * @param g 
     */
    private void drawName(Graphics g, String name, int x){
        Graphics2D g2d = (Graphics2D)g;

        // clockwise 90 degrees
        AffineTransform at = new AffineTransform();
        // thanks to M.C. Henle for the bug fix!
        at.setToRotation(Math.PI/2.0, x + (barWidth/4), bottom + 3);
        g2d.setTransform(at);

        g2d.setColor(Color.BLACK);
        g2d.drawString(name, (int) (x + (barWidth/4)), (int) bottom  + 3);

        g2d.setTransform(new AffineTransform());
    }

    /**
     * Draw the lines behind the bars
     * @author Kay van Bree
     * @param g 
     */
    private void drawBlocks(Graphics g){
        g.setColor(Color.lightGray);
        // Ten parts
        double part = maxValue / 10;
        double total = maxValue;

        // Draw the numbers
        for(int i=0; i<10; i++){
            double y = (((bottom-top)/10)*i)+top;
            g.drawLine(((int) left)-5, (int) y, ((int) right), (int) y);
        }
        g.drawLine((int) right, (int) top, (int) right, (int) bottom);
    }

    /**
     * Draw the title of the Graph
     * @author Kay van Bree
     * @param g 
     */
    private void drawTitle(Graphics g){
        int tLeft = 100;
        g.drawString(infoName, tLeft, (int) top / 2);
    }

    /**
     * Draw the axes of this Bar Graph
     * @author Kay van Bree
     * @param g 
     */
    private void drawAxes(Graphics g){
        g.setColor(Color.BLACK);
        // draw lines from (x, y) to (x, y)
        g.drawLine((int) left, (int) top, (int) left, (int) bottom);
        g.drawLine((int) left, (int) bottom, (int) right, (int) bottom);

        // Ten parts
        double part = maxValue / 10;
        double total = maxValue;

        // Draw the numbers
        for(int i=0; i<10; i++){
            double y = (((bottom-top)/10)*i)+top;
            String number = round(total);
            FontMetrics fontMetrics = g.getFontMetrics();
            g.drawString(number, (int) (left * 0.80) - fontMetrics.stringWidth(number), ((int) y)+5);
            total = (total - part);
            g.drawLine(((int) left)-5 , (int) y, ((int) left), (int) y);
        }
    }

    /**
     * Rounds the number to 1 decimal
     * @author Kay van Bree
     * @param number
     * @return 
     */
    private String round(double number){
        DecimalFormat df = new DecimalFormat("#.#");
        Double dubbel = new Double(number);
        return df.format(dubbel);
    }

    /**
     * Round a number, make it int
     * @author Kay van Bree
     * @param number
     * @return 
     */
    private int roundToInt(double number){
        DecimalFormat df = new DecimalFormat("#");
        Double dubbel = new Double(number);
        return Integer.valueOf(df.format(dubbel));
    }


    /**
     * We need info right?
     * Then draw the damn bars already!
     * @author Kay van Bree
     * @param g 
     */
    private void drawBars(Graphics g){      
        double currentX = left + barSpacing + 1;
        for(String[] row: info){
            double value = Integer.valueOf(row[1]);
            double barHeight = (value * barPart);

            System.out.println("Value: " + value + " height: " + barHeight + " top: " + top);
            double barStart = bottom - barHeight;
            System.out.println("Barstart: " + barStart);
            barHeight = ((int) bottom - (int) barStart);
            g.setColor(Color.BLUE);
            g.fillRect((int) currentX, (int) barStart, (int) barWidth, roundToInt(barHeight));
            drawName(g, row[0], (int) currentX);

            currentX = (currentX + barSpacing + barWidth);
        }
    }

    public void setInformationNotSuitable() {
    //      JOptionPane.showMessageDialog(this, "This query is not suited for Bar Graph view. Please select another", "INSANE WARNING!", JOptionPane.WARNING_MESSAGE);
    }
}

Some helpfull information:

setManagementInformation is called once by the JTabbedPane. Here it loads all content (also the other tabs)

EDIT Oke, so Xeon's answer nearly fixed my problem. The bars of my graph are now perfectly aligning everytime I switch back to this tab. However, the names underneath the bar are still shifting when switching back. Here's my updated code:

/**
 * Draw the names under the bars
 * @author Kay van Bree
 * @param g 
 */
private void drawName(Graphics g, String name, int x){
    Graphics2D g2d = (Graphics2D) g.create();

    // Clockwise 90 degrees
    AffineTransform at = new AffineTransform();
    // Rotate the text
    at.setToRotation(Math.PI/2.0, x + (barWidth/4), bottom + 3);
    AffineTransform prev = g2d.getTransform();
    g2d.setTransform(at);

    g2d.setColor(Color.BLACK);
    g2d.drawString(name, (int) (x + (barWidth/4)), (int) bottom  + 3);

    g2d.dispose();
}

So, does this also have something to do with the AffineTransform, and does anyone have a solution for this problem?

Mike
  • 47,263
  • 29
  • 113
  • 177
Scuba Kay
  • 2,004
  • 3
  • 26
  • 48

3 Answers3

3

Ok. That was tricky:

The problem is the drawName method - you are transforming graphics instance and not restoring it - you just reset the transformation matrix (all values to 0).

You should restore it:

private void drawName(Graphics g, String name, int x) {
    Graphics2D g2d = (Graphics2D) g;

    // clockwise 90 degrees
    AffineTransform at = new AffineTransform();
    // thanks to M.C. Henle for the bug fix!
    at.setToRotation(Math.PI / 2.0, x + (barWidth / 4), bottom + 3);
    AffineTransform prev = g2d.getTransform();
    g2d.setTransform(at);

    g2d.setColor(Color.BLACK);
    g2d.drawString(name, (int) (x + (barWidth / 4)), (int) bottom + 3);

    g2d.setTransform(prev);
}

or you can even use:

    Graphics2D g2d = (Graphics2D) g.create();
    ...
    // free to manipulate Graphics2D
    ,,,
    d2d.dispose();

And I suggest using JFreeChart instead of writing your own Charts library.

Xeon
  • 5,949
  • 5
  • 31
  • 52
  • Problem is, we can't use any 3rd party libraries, 'cause it's a school project. Else I would have used it, probably. – Scuba Kay May 31 '12 at 23:32
  • Thanks, this fixed my bars and axes! However, the names are still distorted, so I edited my question. – Scuba Kay Jun 01 '12 at 08:48
1

After doing some research, and with help of Xeon's answer:

/**
 * Draw the names under the bars
 * @author Kay van Bree
 * @param g 
 */
private void drawName(Graphics g, String name, int x){
    Graphics2D g2d = (Graphics2D) g.create();

    // Rotate
    g2d.rotate(Math.PI/2.0, x + (barWidth/4), bottom + 3);

    // Print
    g2d.setColor(Color.BLACK);
    g2d.drawString(name, (int) (x + (barWidth/4)), (int) bottom  + 3);

    // Reset transformation
    g2d.dispose();
}

Answer found at OTN Discussion Forums:

You are ignoring the current transform on the Graphics, use the following instead of setTransform():

g2D.rotate(-Math.PI / 2.0, 0, 0);

This solved all of my problems.

Scuba Kay
  • 2,004
  • 3
  • 26
  • 48
0

A little note if somebody will use AffineTransform directly:

protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    final Graphics2D g2d = (Graphics2D) g;
    final AffineTransform oldT = g2d.getTransform();
    try {
        final AffineTransform t = g2d.getTransform(); // returns a copy! of current transform
        t.concatenate(viewMatrix); // so we can modify it
        g2d.setTransform(t);
        // draw...
    } finally {
        // restoring transform
        g2d.setTransform(oldT);
    }
}

Disposing Graphics2D isn't neccessary in this case

acc15
  • 1,205
  • 1
  • 11
  • 22