1

For practicing purposes, I challenged myself to write a program that solves the TSP and visualises the results step by step.

As for now, my program uses a simple nearest neighbour algorithm. I want my program to be flexible, so when I add a new algorithm, it will be able to visualise the results, too, without messing with the logic of display.

One of the problems I encountered was- how to display the solution step by step? I solved it by creating multiple partial solutions, storing them, and displaying one after another. I feel like it can be done better, but I am not really good in graphics, I hope to get some clues here.

Here is some code: The Point class- represents a city.

class Point {
    private double x;
    private double y;

    public double getX() {
        return x;
    }
    public double getY() {
        return y;
    }

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public Point(){
        Random r = new Random();
        x=r.nextInt(1000);
        y=r.nextInt(650);
    }

    public double calculateDistanceToPoint(Point p) {
        double dist = Math.sqrt(Math.pow(this.x-p.x, 2) + Math.pow(this.y-p.y, 2));
        return round(dist,2);
    }

    private static double round(double value, int places) {
        if (places < 0) throw new IllegalArgumentException();

        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }
}

Then, the Solver class, which is doing calculations:

class Solver {
    //list of all points to visit
    private static ArrayList<Point> points = new ArrayList<>();

    //adjacency matrix
    private ArrayList<ArrayList<Double>> adjMatrix = new ArrayList<>();

    //found solution
    private static ArrayList<Point> solution = new ArrayList<>();

    //visited points
    private ArrayList<Integer> visitedPoints = new ArrayList<>();

    //used for visualisation
    private static Solution finalSolution = new Solution();

    public void clear() {
        points.clear();
        solution.clear();
        visitedPoints.clear();
        adjMatrix.clear();
        finalSolution.clear();
    }

    public void addPoint(Point p) {
        points.add(p);
    }

    public static ArrayList<Point> getPoints() {
        return Solver.points;
    }

    public void fillAdjacencyMatrix() {
        int iter_x;
        int iter_y;
        for (iter_x = 0; iter_x < points.size(); iter_x++) {
            ArrayList<Double> temp = new ArrayList<>();
            for (iter_y = 0; iter_y < points.size(); iter_y++) {
                if (iter_x == iter_y) {
                    temp.add(-1.0);
                } else {
                    temp.add(points.get(iter_x).calculateDistanceToPoint(points.get(iter_y)));
                }
            }
            adjMatrix.add(temp);
        }
    }

    private int getIndexOfMin(ArrayList<Double> arr) {
        Double min = Double.MAX_VALUE;
        int index = -2;
        for (int i = 0; i < arr.size(); i++) {
            Double val = arr.get(i);
            if (!(val == -1.0) && !visitedPoints.contains(i) && val < min) {
                min = val;
                index = i;
            }
        }
        return index;
    }

    public void solveUsingNN(int startingPoint) {
        int noOfVisited = 0;

        //find nearest point from the starting one
        int nearest = getIndexOfMin(adjMatrix.get(startingPoint));
        Solution sol = new Solution();

        //until we've visited all points
        while (noOfVisited!=points.size()) {
            //get next nearest point and add it to visited
            nearest = getIndexOfMin(adjMatrix.get(nearest));
            visitedPoints.add(nearest);

            //add this point to solution
            Point newPoint = points.get(nearest);
            solution.add(newPoint);

            //create a new frame for animation, containing all previous steps and recently added one
            SolutionStep ss = new SolutionStep();
            Point p;
            for (Point newPoint : solution) {
                p = new Point(newPoint.getX(), newPoint.getY());
                ss.addPoint(p);
            }
            sol.addStep(ss);
            noOfVisited++;
        }
        finalSolution=sol;
    }
}

Then, the SolutionStep class:

class SolutionStep{
    public final ArrayList<Point> step = new ArrayList<>();
    public SolutionStep(){}

    public void addPoint(Point p){
        step.add(p);
    }
    public void draw(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
            for (int i = 0; i < step.size()-1; i++) {
                g2.draw(new Line2D.Double(step.get(i).getX(), step.get(i).getY(), step.get(i + 1).getX(), step.get(i + 1).getY()));
        }
    }
}

And Solution, which contains many steps.

public class Solution {
    private ArrayList<Point> points = new ArrayList<>();
    private static ArrayList<SolutionStep> playbackSolution = new ArrayList<>();
    private int noOfFrames;

    public Solution(ArrayList<SolutionStep> listOfSteps, int noOfFrames){
        this.noOfFrames=noOfFrames;
        playbackSolution=listOfSteps;
    }
    public Solution(){}

    public static ArrayList<SolutionStep> getPlayback(){
        return playbackSolution;
    }
    public void clear(){
        playbackSolution.clear();
    }

    public void addStep(SolutionStep solutionStep){
        playbackSolution.add(solutionStep);
    }

    public void draw(Graphics g) {
        int numberOfPoints;
        points = Solver.getPoints();
        Graphics2D g2 = (Graphics2D) g;
        //draw all points
        for (Point point : points) {
            g2.fill(new Rectangle2D.Double(point.getX(), point.getY(), 6, 6));
        }

        //draw next line
        for(int i = 0;i<noOfFrames;i++) {
            playbackSolution.get(i).draw(g);
        }

        //if we are at the final solution, draw a line from last point to the first
        if (noOfFrames == points.size()){
            numberOfPoints = points.size();
            Point first = playbackSolution.get(0).step.get(0);
            Point last = playbackSolution.get(numberOfPoints-1).step.get(numberOfPoints-1);
            g2.draw(new Line2D.Double(first.getX(), first.getY(), last.getX(), last.getY()));
        }
    }
}

Finally, the Visualisation

class Visualisation extends JFrame {
    private DrawingPanel contentPane;
    private int noOfPoints = 10;
    private int delay_time = 300;

    public Visualisation() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 1100, 700);
        contentPane = new DrawingPanel();
        setContentPane(contentPane);
        JButton start = new JButton("Start");
        start.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                Solver s = new Solver();
                s.clear();
                contentPane.displayNoOfSteps = 0;
                for (int i=0;i<noOfPoints;i++) {
                    s.addPoint(new Point());
                }
                s.fillAdjacencyMatrix();
                s.solveUsingNN(0);
                new javax.swing.Timer(delay_time, new ActionListener(){
                    @Override
                    public void actionPerformed(ActionEvent e){
                            contentPane.repaint();
                    }
                }).start();
                contentPane.repaint();
            }
        });
        contentPane.add(start);
    }
}

And DrawingPanel:

class DrawingPanel extends JPanel{
    public int displayNoOfSteps = 1;

    DrawingPanel(){}
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Solution sol = new Solution(Solution.getPlayback(),displayNoOfSteps);
        sol.draw(g);

        if (displayNoOfSteps< Solution.getPlayback().size())
        displayNoOfSteps++;
        }
    }

Main class:

class Main {
    public static void main(String[] args){
        Visualisation frame = new Visualisation();
        frame.setVisible(true);
    }
} 
  1. Now, in the Visualisation class I have a Timer. This timer calls repaint() on the DrawingPanel every delay_time ms, and in every iteration it increases the number of steps to display. The problem is, if I run one simulation, and then hit Start again, the simulation is running faster, and after a couple of runs, it shows the last step almost immidiately. I don't know what is wrong. How can I deal with this?

  2. I get an error on program start-

    at Solution.draw(Solution.java:57)

    at DrawingPanel.paintComponent(Visualisation.java:53)

which refers to lines:

playbackSolution.get(i).draw(g);

and

sol.draw(g);

but I didn't draw anything yet! The repaint() is in the ActionListener in JButton. Or maybe drawing a JButton calls draw() anyway? How can I get rid of this problem?

  1. Also, I feel like I'm using too much static fields and methods- but on the other hand, would it be better to create instances of, for example, Solver and then have non-static methods to get the solutions? Or maybe make Solver a singleton? There is one instance anyway.

  2. As I mentioned earlier, I want to get some feedback on this code before I write more complex algorithms, such as simulated annealing, so it would be easy to maintain good code quality. What can I change in this code to make it easier to add new features?

RK1
  • 429
  • 2
  • 7
  • 28
  • 1) For better help sooner, post an [MCVE](http://stackoverflow.com/help/mcve) (Minimal Complete Verifiable Example) or [SSCCE](http://www.sscce.org/) (Short, Self Contained, Correct Example). 2) Always copy/paste error and exception output (the *entire* stack trace)! – Andrew Thompson Jun 21 '15 at 13:45
  • BTW - this 'question' which actually contains 4 separate questions, should be split into 4 separate, self contained message threads. SO is a Q&A site, rather than a 'one stop fix-my-code' shop. – Andrew Thompson Jun 21 '15 at 13:47
  • Thank you, I will keep that in mind and post separate follow-up questions. – RK1 Jun 21 '15 at 13:53
  • Well, I've already voted to close this one, and you can [edit it](http://stackoverflow.com/posts/30965065/edit) at any time. – Andrew Thompson Jun 21 '15 at 14:40

1 Answers1

1

Having experience maintaining a rather large Java Swing application, I would never voluntarily commit myself to doing something new in Swing.

I think this particular problem can be solved quite nicely by using a third party tool to visualize graphs. Graphviz is an option, but several other tools exist. Look here for some more examples.

What you do is simply generate the graph in the notation of your visualizing tool. You can use the node names to show the path the Salesman takes: 1, 2, 3, etc.

Community
  • 1
  • 1
wvdz
  • 16,251
  • 4
  • 53
  • 90
  • It was never meant to be a really large application, just something to show nodes and edges. I will take a look at Graphviz, thank you. – RK1 Jun 21 '15 at 13:42