Home > front end >  Drawing super crowded points in Swing
Drawing super crowded points in Swing

Time:12-01

I need to be able to draw very crowded points for a graph, via Swing. E.g: let's look at the following points:

p1=(35.19589389346247,32.10152879327731),

p2 = (35.20319591121872,32.10318254621849),

p3 = (35.20752617756255,32.1025646605042),

p4 = (35.21007339305892,32.10107446554622),

p5 = (35.21310882485876,32.104636394957986),

etc...

I want to draw them, however, as can be seen, their coordinates are very dense to each other. In addition, expanding the scale also didn't work, but just moved the points inside the frame.

Here is a glance at my attempt:

private void Oval(Graphics2D g2, String id, int locationX, int locationY) {
        AffineTransform old = g2.getTransform();
        g2.scale(4,4);
        g2.setPaint(Color.blue);
        g2.fillOval(locationX - Radius, locationY - Radius, Radius * 2, Radius * 2);
        g2.setPaint(Color.black);
        g2.drawString(id, locationX   Radius, locationY - Radius);
        g2.setTransform(old);
    }

This is the code for the point drawing, considering the panel dimension is (1000,1000).

Here is the output of the points' drawing:

enter image description here

As you can see, they override each other, and it's clearly nothing like I was intended to do. My goal is to separate them so we can see the points clearly.

CodePudding user response:

So, the basic concept is to have some kind of "translation" for the "virtual" coordinates (your points) to the "physical" coordinates (of the screen)

You did try and do this by scaling the graphics context, but the problem with this is it will also scale the size of the balls as well, which isn't really what you want to do.

The following is a really basic example of one possible solution.

It calculates the min/max range of the area represented by the points and then uses the component size to translate the points so that they will fit within the visible space

Spaced Out

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;

class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class GraphPoint {
        private String id;
        private Point2D point;

        public GraphPoint(String id, Point2D point) {
            this.id = id;
            this.point = point;
        }

        public String getId() {
            return id;
        }

        public Point2D getPoint() {
            return point;
        }

    }

    public class TestPane extends JPanel {

        private List<GraphPoint> points;
        private int radius = 10;

        private double virtualScale = 1.0;

        private Point2D minRange;
        private Point2D maxRange;

        public TestPane() {
            points = new ArrayList<>(16);
            points.add(new GraphPoint("1", new Point2D.Double(35.19589389346247, 32.10152879327731)));
            points.add(new GraphPoint("2", new Point2D.Double(35.20319591121872, 32.10318254621849)));
            points.add(new GraphPoint("3", new Point2D.Double(35.20752617756255, 32.1025646605042)));
            points.add(new GraphPoint("4", new Point2D.Double(35.21007339305892, 32.10107446554622)));
            points.add(new GraphPoint("5", new Point2D.Double(35.21310882485876, 32.104636394957986)));

            double minX = Double.MAX_VALUE;
            double maxX = Double.MIN_VALUE;
            double minY = Double.MAX_VALUE;
            double maxY = Double.MIN_VALUE;

            for (GraphPoint gp : points) {
                minX = Math.min(minX, gp.getPoint().getX());
                maxX = Math.max(maxX, gp.getPoint().getX());
                minY = Math.min(minY, gp.getPoint().getY());
                maxY = Math.max(maxY, gp.getPoint().getY());
            }

            minRange = new Point2D.Double(minX, minY);
            maxRange = new Point2D.Double(maxX, maxY);

            double xRange = maxRange.getX() - minRange.getX();
            double yRange = maxRange.getY() - minRange.getY();

            System.out.println(minRange.getX()   " - "   minRange.getY());
            System.out.println(maxRange.getX()   " - "   maxRange.getY());
            System.out.println(xRange   " - "   yRange);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            for (GraphPoint gp : points) {
                paintPoint(g2d, gp);
            }
            g2d.dispose();
        }

        private void paintPoint(Graphics2D g2d, GraphPoint gp) {
            Graphics2D g2 = (Graphics2D) g2d.create();

            Point2D translated = translate(gp);

            double xPos = translated.getX();
            double yPos = translated.getY();

            double offset = radius;

            g2.translate(xPos - offset, yPos - offset);
            g2.setPaint(Color.blue);
            g2.fill(new Ellipse2D.Double(0, 0, offset * 2, offset * 2));
            g2.dispose();
        }

        protected Point2D translate(GraphPoint gp) {

            double xRange = maxRange.getX() - minRange.getX();
            double yRange = maxRange.getY() - minRange.getY();

            double offset = radius;
            double width = getWidth() - (offset * 2);
            double height = getHeight() - (offset * 2);

            double xScale = width / xRange;
            double yScale = height / yRange;

            Point2D original = gp.getPoint();

            double x = offset   ((original.getX() - minRange.getX()) * xScale);
            double y = offset   ((original.getY() - minRange.getY()) * yScale);

            System.out.println(gp.getId()   " "   x   " x "   y);

            return new Point2D.Double(x, y);
        }
    }
}

I have to stress that this is one possible solution and that your actual requirements might differ, but this should give you starting point from which you can define your own algorithm, for example, you could define your own min/max range (ie 34x30 to 36x33)

the String I have tried now for 1 hour and I didn't get it I did edit your code already you help us a lot. the string above the points the id I mean point "0" this string above the point if u can help us or show us it will be appreciated a lot!

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;

class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class GraphPoint {

        private String id;
        private Point2D point;

        public GraphPoint(String id, Point2D point) {
            this.id = id;
            this.point = point;
        }

        public String getId() {
            return id;
        }

        public Point2D getPoint() {
            return point;
        }

    }

    public class TestPane extends JPanel {

        private List<GraphPoint> points;
        private int radius = 10;

        private double virtualScale = 1.0;

        private Point2D minRange;
        private Point2D maxRange;

        public TestPane() {
            points = new ArrayList<>(16);
            points.add(new GraphPoint("1", new Point2D.Double(35.19589389346247, 32.10152879327731)));
            points.add(new GraphPoint("2", new Point2D.Double(35.20319591121872, 32.10318254621849)));
            points.add(new GraphPoint("3", new Point2D.Double(35.20752617756255, 32.1025646605042)));
            points.add(new GraphPoint("4", new Point2D.Double(35.21007339305892, 32.10107446554622)));
            points.add(new GraphPoint("5", new Point2D.Double(35.21310882485876, 32.104636394957986)));

            double minX = Double.MAX_VALUE;
            double maxX = Double.MIN_VALUE;
            double minY = Double.MAX_VALUE;
            double maxY = Double.MIN_VALUE;

            for (GraphPoint gp : points) {
                minX = Math.min(minX, gp.getPoint().getX());
                maxX = Math.max(maxX, gp.getPoint().getX());
                minY = Math.min(minY, gp.getPoint().getY());
                maxY = Math.max(maxY, gp.getPoint().getY());
            }

            minRange = new Point2D.Double(minX, minY);
            maxRange = new Point2D.Double(maxX, maxY);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(400, 400);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            Graphics2D g2d = (Graphics2D) g.create();

            FontMetrics fm = g2d.getFontMetrics();
            double insets = fm.getHeight()   radius;

            // I'm lazy, so I'm drawing the lines first, then painting
            // the points over the top as I can't be bothered to workout
            // a clever way to ensure the lines are always painted under
            // the dots
            GraphPoint lastPoint = null;
            for (GraphPoint gp : points) {
                if (lastPoint != null) {
                    paintLine(g2d, lastPoint, gp, insets);
                }
                lastPoint = gp;
            }
            for (GraphPoint gp : points) {
                paintPoint(g2d, gp, insets);
            }
            g2d.dispose();
        }

        private void paintLine(Graphics2D g2d, GraphPoint from, GraphPoint to, double insets) {
            Point2D fromPoint = translate(from, insets);
            Point2D toPoint = translate(to, insets);
            g2d.setColor(Color.RED);
            g2d.draw(new Line2D.Double(fromPoint, toPoint));
        }

        private void paintPoint(Graphics2D g2d, GraphPoint gp, double insets) {
            Graphics2D g2 = (Graphics2D) g2d.create();

            Point2D translated = translate(gp, insets);

            double xPos = translated.getX();
            double yPos = translated.getY();

            double offset = radius;

            g2.translate(xPos - offset, yPos - offset);
            g2.setPaint(Color.blue);
            g2.fill(new Ellipse2D.Double(0, 0, offset * 2, offset * 2));

            FontMetrics fm = g2d.getFontMetrics();
            String text = gp.getId();
            double x = xPos - (fm.stringWidth(text) / 2);
            double y = (yPos - radius - fm.getHeight())   fm.getAscent();
            g2d.drawString(text, (float)x, (float)y);

            g2.dispose();
        }

        protected Point2D translate(GraphPoint gp, double insets) {
            double xRange = maxRange.getX() - minRange.getX();
            double yRange = maxRange.getY() - minRange.getY();

            double offset = insets;
            double width = getWidth() - (offset * 2);
            double height = getHeight() - (offset * 2);

            double xScale = width / xRange;
            double yScale = height / yRange;

            Point2D original = gp.getPoint();

            double x = offset   ((original.getX() - minRange.getX()) * xScale);
            double y = offset   ((original.getY() - minRange.getY()) * yScale);

            System.out.println(gp.getId()   " "   x   " x "   y);

            return new Point2D.Double(x, y);
        }
    }
}

The trick with the text is in understanding that I've already translated the origin point of the context, so the 0x0 position is actually the middle of the circle, so you need to subtract the text offset from it.

I've also made a slight update to allow the workflow to pass in a insets value which will decrease the available visible area to "help" compensate for the text

another question can make a line with an arrow in the tip

See...

for some ideas. Just beware, this becomes increasingly more complex, as the arrows need to be rotated towards the point they are looking, something like Connect two circles with a line might help

  • Related