1

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.

Chopin
  • 113
  • 1
  • 10
  • 1
    Don't scale the graphics context, but scale the virtual coordinates themselves – MadProgrammer Nov 30 '21 at 19:37
  • @MadProgrammer can you please elaborate on that more? – Chopin Nov 30 '21 at 19:39
  • Don't use `Graphics#scale`, instead, multiple the position of the points by the scale instead (virtual coordinates ;)) – MadProgrammer Nov 30 '21 at 19:46
  • 1
    I'd go with [tag:jfreechart] `ChartFactory.createScatterPlot` and invoke `setRange()` as desired, for [example](https://stackoverflow.com/search?tab=votes&q=user%3a230513%20%5bjfreechart%5d%20ChartFactory.createScatterPlot). – trashgod Nov 30 '21 at 19:48
  • Having played around with it for a but, I suspect you'd be better off coming up with a "virtual" coordinate system, which would allow you to take the physical point and translate it a "virtual" point which can be more easily rendered with appropriate separation. Exactly how you'd do that I can't say right now, need more tea – MadProgrammer Nov 30 '21 at 19:55
  • @MadProgrammer can you give an example of it? because I don't really know what exactly you say. Perhaps it is due to the fact that I am new to Swing. – Chopin Nov 30 '21 at 20:02
  • @Chopin It's got nothing to do with swing. You basically need a translator which can take a value like `35.19589389346247` and translate it to a more meaningful value, like `34` (for example) and something like `35.21310882485876` might become `35` or `36` based on how you need to translate it – MadProgrammer Nov 30 '21 at 20:15
  • Check out: [Drawing an image using sub-pixel level accuracy using Graphics2D](https://stackoverflow.com/questions/8676909/drawing-an-image-using-sub-pixel-level-accuracy-using-graphics2d). I notice a difference in the ball movement using the example code from the selected answer. Not sure if it will help with your issue. – camickr Nov 30 '21 at 20:33
  • Using a smaller radius will help – tgdavies Nov 30 '21 at 20:45
  • Is it always the case where all x coordinates have the same integer value (35 in this example) and y coordinates have the same integer value (32 in this example) ? – c0der Dec 01 '21 at 06:36

1 Answers1

4

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

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you so much! – Chopin Nov 30 '21 at 22:00
  • @MadProgrammer how I can contact you privately? like to chat with you or something? – ATB Nov 30 '21 at 22:02
  • 2
    @ATB Generally, I don't (personal thing) – MadProgrammer Nov 30 '21 at 22:10
  • @MadProgrammer okay, anyway if I want to add edges ("lines") between each point, and the string ID above each point? the edges got from point(a) to point (b) and have a weight string I need to print above the edges – ATB Nov 30 '21 at 23:18
  • @ATB Assuming you know the "from" and "to" points you want to join you can use the same calculations to determine the x/y position of the start and end points of the line, drawing a line is simple. Drawing the text isn't "hard". For example, the two points used to create a line form a rectangle, from that you can determine the centre x/y position of the line and draw your text, if that's how you wan it displayed – MadProgrammer Nov 30 '21 at 23:26
  • @MadProgrammer okay the line I working on it now. 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! , another question can make a line with an arrow in the tip? – ATB Nov 30 '21 at 23:30
  • @MadProgrammer Thanks for everything, I have a little question about how I can make the line at the tip of the point and not in the center of the point I mean I want to delete the radius to make the line just "touch" the point and not get inside if you can show me or give me an idea to do that I really appreciate that and god bless you. – ATB Dec 02 '21 at 01:20
  • @ATB This is what [Connect two circles with a line](https://stackoverflow.com/questions/47369565/connect-two-circles-with-a-line/47371140#47371140) is doing. I projects a line from the centre of two circles, but only draws from/to their edges – MadProgrammer Dec 02 '21 at 02:04