Java Java Swing: gezeichnete Objekte anklickbar

cl0udt

Lt. Junior Grade
Registriert
Sep. 2008
Beiträge
508
Hallo zusammen,

ich versuche bereits gezeichnete Objekte in einem JScrollPane anklickbar zu machen. Bei den gezeichneten Objekten handelt es sich um Knoten und Kanten die einen Graphen darstellen sollen. Die Knoten haben Koordinaten, um die ich einen Kreis zeichne. Eine Kante speichert 2 Knoten und die nächste Kante.
Irgendwie fehlt mir da völlig das Konzept, wie ich die Objekte im Graphen anklicken kann und an die Infos der Objekte komme.
Bis jetzt habe ich bei einem Klick über alle Kanten iteriert und geprüft, ob die Kante bzw. der Knoten den mauspunkt enthält. Ist leider etwas buggy und die Lösung gefällt mir nicht sehr gut.
Habt ihr da vll eine bessere Lösung für mich?

Danke und Gruß
cl0udt
 
Generell ist es nicht verkehrt, mittels dem geklickten Punkt das dazu passende Objekt zu suchen. Du könntest Bounding Boxes um die Kreise/Kanten/Objekte berechnen und diese in einer Relation abspeichern. Bei einem Treffer kannst du dann noch genauer schauen ob tatsächlich ein Treffer stattgefunden hat oder ob knapp vorbei geklickt wurde. Dann geht das mit dem finden etwas schneller. ;)
​Gut, die Bugs musst du schon selbst irgendwie rauskratzen. :D
 
Alternativ kannst du bei Kreisen auch schlicht den Abstand von Klick-Position zum Mittelpunkt betrachten...

Klick sei mx und my, und Punkt px und py

dx = mx - px
dy = my - py
d = sqrt(dx*dx + dy*dy)
(Pythagoras)

und wenn d < r dann hast du einen Treffer für px/py

das Wurzelziehen kann man sich sparen, wenn man auf r² prüft...

=> dx*dx + dy*dy < r*r => Klick
 
Also ich habe beim iterieren über meine Kanten und Knoten immer das Oval bzw. die Line neu gezeichnet und dann mit oval.contains(e.x, e.y) auf einen Treffer gecheckt. Meinst du das auch so oder was hast du im Sinn mit "Relation"?
Ja, hatte gehofft Swing is buggy und es liegt nicht an mir, das dauernd mehrere Knoten gleichzeitig angeklickt sind :-P
Ich habe halt die Befürchung, dass die Methode sehr langsam wird bei 5000+ Punkten. Hatte gehofft es gibt was einfacheres.
 
naja man könnte Punkte "zusammenfassen" zu Gruppen...
also z.B. aufgrund der x-Ordinate...

also Gruppe 1 = Alle Punkte mit 0 <= x < 100
Gruppe 2 = Punkte mit 100 <= x <= 200

dann kuckst du anhand von x um welche Gruppe es geht, dann sind es nur noch 20 Punkte usw...
 
Nein, du speicherst für jedes Oval/Objekt die dazugehörige BoundingBox (BB). Diese besteht aus 4 Werten, x-min, x-max, y-min, y-max. Also eine Box um das Objekt. Du zeichnest nichts neu, sondern iterierst über diese BB-Liste und kannst so schnell einen möglichen Treffer finden. Wenn du den Treffer gefunden hast, schaust du dir das dazugehörige Objekt an und prüfst, ob tatsächlich ein Treffer vorliegt.
 
@1668mib
Stimmt, danke. Damit könnte ich die Suche effektiver gestalten.

@Green Mamba
Aso, jetzt verstehe ich was du meinst. Danke für den Tipp.
Dabei ist halt das Problem, dass eine Bounding Box um eine Kante nicht sehr treffsicher ist oder? Die Kanten sind potentiell schon nah beieinander und überschneiden sich auch.

Naja, alles in allem zeigt es mir aber, dass es keine allzu komfortable Methode gibt, das Ganze zu machen. Ich muss das also schon über die Mausposition machen und dann gucken, ob es in einem meiner gezeichneten Objekte drin liegt.
 
Das BB-Verfahren wird in allen möglichen Bereichen verwendet. Klar kann es mehrere Treffer geben, da muss man dann in jeder getroffenen BB genauer prüfen. Damit geht die Suche aber schonmal sehr viel schneller, da du weder wurzel ziehen, noch multiplizieren musst um gegen die BB zu testen.
Für die Speicherung der BB-Daten gibt es hocheffiziente Datenstrukturen, womit man die Suche noch einmal erheblich beschleunigen kann. Die Frage ist aber eigentlich ob die Laufzeit der Suche überhaupt ein Problem darstellt?
 
Momentan ist es noch kein Problem, aber es funktioniert auch nicht. Hab irgendeinen Schnitzer drin, so dass die Kanten sowieso grade nicht richtig erkannt werden. Hatte halt eher drauf gehofft eine ganz andere Möglichkeit zu finden.

Werde mal die Bounding Boxen probieren, wenn ich den Fehler nicht finde. Ich erstelle die Linien für Kanten und Ovale für Knoten beim Klicken halt momentan wie gesagt immer neu und bei Lines gibts halt diese Methode ptLineDist(), die da schon sehr hilfreich ist...keine Ahnung inwiefern das jetzt krasser ist, als über einmal angelegte Bounding Boxen drüber zu iterieren...

Bei dem Thema würde es mich auch interessieren, ob ihr vll irgendwelche guten Beispiele zur Implementierung von Zoom und Dragging habt. Das draggen bei mir klappt einigermaßen gut, aber das zoomen zur aktuellen Mausposition ist eine Katastrophe.
Habe folgendes fürs zoomen:

Code:
@Override
	public void mouseWheelMoved(MouseWheelEvent e) {



		if(e.getWheelRotation()<0){
			scale=scale>=2.0?2.0:scale+0.05;
		}
		else if(e.getWheelRotation()>0){
			scale=scale<=0.1?0.1:scale-0.05;
		}
		int offX = (int)(e.getX() * scale) - e.getX();
		int offY = (int)(e.getY() * scale) - e.getY();
		setLocation(e.getXOnScreen()-offX, e.getYOnScreen()-offY);

	}

Meine Klasse erbt von JPanel und bevor ich meine Klasse ins JFrame als ContentPane adde, packe ich sie noch in ein JScrollPane...ziemlich murks, bin auch nicht sehr fit in Sachen Swing, aber ohne JScrollPane funktioniert das Zoomen nicht (JFrame frame = new JFrame("..."); JScrollPane jp = new JScrollPane(this); frame.getContentPane().add(jp);)

Hab da schon ewig lange das Internet abgegrast für, aber die ganzen Beispiele wollen nicht funktionieren. Vielleicht habt ihr da ja auch noch eine Idee.

Danke schon mal.
 
Nochmal zwei Fragen:
1. Sind alle Objekte, die man anklicken kann, Kreise?
2. Sind die Abstände in x-Richtung zwischen Kreisen variabel oder konstant?
 
Hi 1668mib,

1. Die Knoten im Graph sind Kreise, die man anklicken kann. Die Kanten im Graph sind Linien. Die soll man ebenfalls anklicken können
2. Die Kanten zwischen den Knoten (Kreisen) sind alle mehr oder weniger unterschiedlich lang, und liegen auch größtenteils kreuz und quer übereinander. (Deswegen ist auch ranzoomen und draggen später wichtig)

Danke und Gruß
cl0udt
 
Wenigstens ja keine aufwändigen geometrischen Objekte.

Bei einem Kreis kannst du wie beschrieben über den Pythagoras einen Klick erkennen.
Bei einer Linie kann man - wenn sie nicht senkrecht ist, das ist ein Spezialfall - über die Steigung (Quotient aus dx und dy, z.B. 1 für 45°) gut feststellen, ob die Linie wirklich getroffen wurde.

Also deine Linie hat x1 und x2 und y1 und y2... Klick ist mx und my.

mx, my in dem Rechteck x1, y1, x2, y2

dx = x2 - x1 // delta x
dy = y2 - y1 // delta y
float m = dx / dy (kein int-division!) //steigung

// mausklick "innerhalb" des rechtecks
dmx = mx - x1
dmy = my - y1

expectedy = dmx * m // multipliziere steigung mit x-position der maus innerhalb des rechtecks

und nun muss expectedy ungefähr gleich dmy sein.. ka ob das klar ist, was ich meine ^^

Senkrechte Linien müssen wie gesagt separat betrachtet werden... aber sind dann ja noch einfacher...

Wenn du dir selbst einen Algorithmus zum Gruppen von Bounding Boxes überlegst, musst du dran denken, dass eine Box zu mehreren "Gruppen" gehören kann... also z.B. wenn du einen Schwellwert für x von 100 hast, und eine Box geht von 90 bis 110...
 
Klingt ein wenig nach meiner Diplomarbeit ;). Der "Graph" ist nicht zufälliger ein endlicher Automat?

Ich habe das damals gelöst, indem meine Kreise Objekte vom Typ Circle2D.Double waren und meine Linien Line2D.Double. Es gibt übrigens auch gebogene Linien QuadCurve2D. Auf jeden Fall hatte das den Vorteil, dass ich nach meinem Mausklick rund um die Mausposition ein kleines Rechteck gemalt habe (Rect2D.Double) und dann konnte ich auf allen meinen Objekten die intersects Methode aufrufen. Jedes Object vom Typ Shape hat diese Methode. Damit kann man halt kontrollieren ob ein Kreis, eine Linie, ein Polygon oder was auch immer ein Rechteck schneidet.

Ist dein Rechteck also deine Mausposition, so kannst Du damit feststellen ob der Klick innerhalb der Grafik war.
Ergänzung ()

ach ja, das Problem, dass zwei Kreise übereinander liegen, kannst Du vermeiden, indem Du bei mouseDragged (oder wie Du auch immer deine Kreise verschiebst) kontrolliert, ob der Kreis einen anderen schneidet (auch hier mit intersects). Wenn ja, dann verschiebe halt nicht. Das ganze klingt jetzt sehr unperformant, das bei jedem mouseDragged aufzurufen, aber ich hatte damals auf 2004ern Rechnern keine Probleme damit. Kommt halt drauf an, wie groß dein Graph ist.
 
Zuletzt bearbeitet:
Naja überschneidende Kreise bekommt man auch raus, wenn der Abstand der Mittelpunkte kleiner als die Summe der Radien ist...
 
Zurück
Oben