|
@@ -0,0 +1,370 @@
|
|
|
+package ui.view.holarchy;
|
|
|
+
|
|
|
+import java.awt.Color;
|
|
|
+import java.awt.Graphics;
|
|
|
+import java.awt.Graphics2D;
|
|
|
+import java.awt.event.MouseEvent;
|
|
|
+import java.awt.event.MouseListener;
|
|
|
+import java.awt.geom.Ellipse2D;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import javax.swing.JLabel;
|
|
|
+import javax.swing.JPanel;
|
|
|
+import javax.swing.JSeparator;
|
|
|
+import javax.swing.JSlider;
|
|
|
+import javax.swing.SwingConstants;
|
|
|
+import javax.swing.event.ChangeEvent;
|
|
|
+import javax.swing.event.ChangeListener;
|
|
|
+
|
|
|
+import classes.Holon;
|
|
|
+import ui.controller.Control;
|
|
|
+import util.Random;
|
|
|
+
|
|
|
+public class HolarchyPanel extends JPanel {
|
|
|
+
|
|
|
+ Control control;
|
|
|
+ HolarchyWindow parentFrame;
|
|
|
+ JLabel layerLabelTitle;
|
|
|
+ JSlider slider;
|
|
|
+ JSeparator sep, sep2;
|
|
|
+ int displayedLayer;
|
|
|
+ int minRadius = 24;
|
|
|
+ Holon root;
|
|
|
+ ArrayList<Map<Ellipse2D, Map<Ellipse2D, Holon>>> ellipsesChild;
|
|
|
+ ArrayList<Map<Ellipse2D, Holon>> ellipses;
|
|
|
+ ArrayList<ArrayList<Holon>> layeredHolons;
|
|
|
+ ArrayList<Ellipse2D> surroundings;
|
|
|
+ Map<Holon, Color> coloredHolons;
|
|
|
+
|
|
|
+ public HolarchyPanel(HolarchyWindow parentFrame, Control control) {
|
|
|
+ this.control = control;
|
|
|
+ this.parentFrame = parentFrame;
|
|
|
+ this.root = control.getModel().getStateHolon();
|
|
|
+
|
|
|
+ this.setBounds(0, 0, parentFrame.getWidth()*4/5, parentFrame.getHeight());
|
|
|
+ this.setLayout(null);
|
|
|
+ this.setBackground(Color.white);
|
|
|
+ this.addMouseListener(mouseListener);
|
|
|
+
|
|
|
+ this.layerLabelTitle = new JLabel("Layer");
|
|
|
+ this.layerLabelTitle.setBounds(5, 5, 50, 20);
|
|
|
+ this.layerLabelTitle.setHorizontalAlignment(SwingConstants.CENTER);
|
|
|
+ this.add(this.layerLabelTitle);
|
|
|
+
|
|
|
+ this.slider = new JSlider();
|
|
|
+ this.slider.setOrientation(SwingConstants.VERTICAL);
|
|
|
+ this.slider.setBounds(5, 30, 50, 200);
|
|
|
+ this.slider.setPaintLabels(true);
|
|
|
+ this.slider.setPaintTicks(true);
|
|
|
+ this.slider.setSnapToTicks(true);
|
|
|
+ this.slider.addChangeListener(this.sliderChangeListener);
|
|
|
+ this.slider.setValue(0);
|
|
|
+ this.slider.setInverted(true);
|
|
|
+ this.slider.setFocusable(false);
|
|
|
+ this.slider.setBackground(Color.white);
|
|
|
+ this.add(slider);
|
|
|
+
|
|
|
+ this.sep = new JSeparator(SwingConstants.VERTICAL);
|
|
|
+ this.sep.setBounds(60, 0, 1, 235);
|
|
|
+ this.add(this.sep);
|
|
|
+ this.sep2 = new JSeparator(SwingConstants.HORIZONTAL);
|
|
|
+ this.sep2.setBounds(0, 235, 60, 1);
|
|
|
+ this.add(this.sep2);
|
|
|
+
|
|
|
+ this.ellipses = new ArrayList();
|
|
|
+ this.ellipsesChild = new ArrayList();
|
|
|
+ this.displayedLayer = 0;
|
|
|
+ this.layeredHolons = new ArrayList();
|
|
|
+ this.surroundings = new ArrayList();
|
|
|
+ this.coloredHolons = new HashMap();
|
|
|
+ getAllHolons();
|
|
|
+ setSliderMax(this.layeredHolons.size());
|
|
|
+ createAllLayers();
|
|
|
+ generateColorSet();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void paintComponent(Graphics g) {
|
|
|
+ super.paintComponent(g);
|
|
|
+ paintLayer(g, this.displayedLayer);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void displayLayer(int layer) {
|
|
|
+ this.displayedLayer = layer;
|
|
|
+ this.repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void paintLayer(Graphics g, int layer) {
|
|
|
+ this.displayedLayer = layer;
|
|
|
+ Graphics2D g2 = (Graphics2D) g;
|
|
|
+
|
|
|
+ //resize panel
|
|
|
+ Ellipse2D surrounding = this.surroundings.get(this.displayedLayer);
|
|
|
+ if(surrounding.getWidth() > this.getWidth()) {
|
|
|
+ this.setSize((int) surrounding.getWidth(), this.getHeight());
|
|
|
+// this.parentFrame.resizeScrollPane((int) surrounding.getWidth(), this.getHeight());
|
|
|
+ }
|
|
|
+ if(surrounding.getHeight() > this.getHeight()) {
|
|
|
+ this.setSize(this.getWidth(), (int) surrounding.getHeight());
|
|
|
+// this.parentFrame.resizeScrollPane(this.getWidth(), (int) surrounding.getHeight());
|
|
|
+ }
|
|
|
+
|
|
|
+// System.out.println("painting layer "+layer+"#elements="+this.ellipses.get(layer).size());
|
|
|
+ for(Ellipse2D e : this.ellipses.get(this.displayedLayer).keySet()) {
|
|
|
+// System.out.println("paint ellipse");
|
|
|
+ //draw ellipse
|
|
|
+ g2.setColor( this.coloredHolons.get(this.ellipses.get(this.displayedLayer).get(e)) );
|
|
|
+ g2.draw(e);
|
|
|
+ //draw child ellipses and add them to global list
|
|
|
+ for(Ellipse2D e2 : this.ellipsesChild.get(layer).get(e).keySet()) {
|
|
|
+ g2.setColor( this.coloredHolons.get(this.ellipsesChild.get(layer).get(e).get(e2)) );
|
|
|
+ g2.draw(e2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void createAllLayers() {
|
|
|
+ for(int layer=0; layer<this.layeredHolons.size(); layer++) {
|
|
|
+// System.out.println("creating layer "+layer+" #elements="+this.layeredHolons.get(layer).size());
|
|
|
+ this.ellipsesChild.add(layer, new HashMap());
|
|
|
+// System.out.println("creating ellipse");
|
|
|
+ //estimate the required area, center if it is smaller than the panels size
|
|
|
+ Ellipse2D surrounding = estimateSurrounding(60, 0, layer);
|
|
|
+ double newX = surrounding.getX(), newY = surrounding.getY();
|
|
|
+ if(surrounding.getWidth() <= this.getWidth()) {
|
|
|
+ double diff = this.getWidth() - surrounding.getWidth();
|
|
|
+ newX = diff/2;
|
|
|
+ }
|
|
|
+ if(surrounding.getHeight() <= this.getHeight()) {
|
|
|
+ double diff = this.getHeight() - surrounding.getHeight();
|
|
|
+ newY = diff/2;
|
|
|
+ }
|
|
|
+ surrounding = new Ellipse2D.Double(newX, newY, surrounding.getWidth(), surrounding.getHeight());
|
|
|
+ this.surroundings.add(layer, surrounding);
|
|
|
+
|
|
|
+ //create ellipse for each holon
|
|
|
+ this.ellipses.add(layer, placeHolons(this.layeredHolons.get(layer), surrounding, true));
|
|
|
+// System.out.println("paint surrounding width="+surrounding.getWidth()+" height="+surrounding.getHeight());
|
|
|
+// System.out.println("pending holons "+this.ellipses.get(layer).size());
|
|
|
+ for(Ellipse2D e : this.ellipses.get(layer).keySet()) {
|
|
|
+ //draw child ellipses and add them to global list
|
|
|
+ Map<Ellipse2D, Holon> ellipses2 = placeHolons(ellipses.get(layer).get(e).childHolons, e, false);
|
|
|
+ this.ellipsesChild.get(layer).put(e, ellipses2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * randomly place an ellipse for each holon in holons inside the given ellipse without overlapping
|
|
|
+ * @param holons
|
|
|
+ * @param surrounding
|
|
|
+ * @param drawChildren
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public Map<Ellipse2D, Holon> placeHolons(List<Holon> holons, Ellipse2D surrounding, boolean drawChildren) {
|
|
|
+ double startX = surrounding.getX();
|
|
|
+ double startY = surrounding.getY();
|
|
|
+ double width = surrounding.getWidth();
|
|
|
+ double height = surrounding.getHeight();
|
|
|
+
|
|
|
+ Map<Ellipse2D, Holon> shapes = new HashMap();
|
|
|
+ int abord = 0;
|
|
|
+ int i = 0;
|
|
|
+
|
|
|
+ while(shapes.size() < holons.size() && abord < 1000) {
|
|
|
+ Holon h = holons.get(i);
|
|
|
+ int radius = this.minRadius;
|
|
|
+ if(drawChildren)
|
|
|
+ radius = this.minRadius * (h.getChildCount()+1) + 10;
|
|
|
+
|
|
|
+ //randomly place an ellipse for the holon
|
|
|
+ double x = startX + (width * Math.random());
|
|
|
+ double y = startY + (height * Math.random());
|
|
|
+ //check if ellipse is inside the surrounding
|
|
|
+ boolean overlap = !surrounding.contains(x, y, radius*2, radius*2);
|
|
|
+ if(overlap) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Ellipse2D e = new Ellipse2D.Double(x-5, y-5, radius*2+10, radius*2+10);
|
|
|
+
|
|
|
+ //check whether or not the random ellipse overlaps with an existing one
|
|
|
+ for(Ellipse2D e2 : shapes.keySet()) {
|
|
|
+ if(e.intersects(e2.getX()-5, e2.getY()-5, e2.getWidth()+10, e2.getHeight()+10)) {
|
|
|
+ overlap = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+// System.out.println("try to add ellipse "+i+" x="+e.getX()+" y="+e.getY());
|
|
|
+
|
|
|
+ abord++;
|
|
|
+ //if not add the ellipse to the already existing ones
|
|
|
+ if(!overlap) {
|
|
|
+ shapes.put(e, h);
|
|
|
+// System.out.println("added ellipse "+i+" at ("+e.getX()+", "+e.getY()+")");
|
|
|
+ i++;
|
|
|
+ abord = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return shapes;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Ellipse2D estimateSurrounding(int x, int y, int layer) {
|
|
|
+ if(this.layeredHolons == null || this.layeredHolons.size() < layer+1)
|
|
|
+ return new Ellipse2D.Double(x, y, Integer.MAX_VALUE, Integer.MAX_VALUE);
|
|
|
+
|
|
|
+ int w = 0;
|
|
|
+ for(Holon h : this.layeredHolons.get(layer)) {
|
|
|
+ w += this.minRadius * (h.getChildCount()+1);
|
|
|
+ }
|
|
|
+ w = w * 4 + 10;
|
|
|
+
|
|
|
+ return new Ellipse2D.Double(x, y, w, w);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @deprecated
|
|
|
+ * calculate distance between the two specified ellipses and check if they overlap
|
|
|
+ * @param e1
|
|
|
+ * @param e2
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public boolean overlap(Ellipse2D e1, Ellipse2D e2) {
|
|
|
+ int dist = (int) Math.sqrt( Math.pow((e1.getCenterX()+e2.getCenterX()), 2) + Math.pow((e1.getCenterY()+e2.getCenterY()), 2) );
|
|
|
+
|
|
|
+ if(dist < e1.getWidth()/2 + e2.getWidth()/2 + 10)
|
|
|
+ return true;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void getAllHolons() {
|
|
|
+ this.layeredHolons.add(this.root.childHolons);
|
|
|
+
|
|
|
+ //TODO: potential infinit loop
|
|
|
+ while(true) {
|
|
|
+ //new layer
|
|
|
+ ArrayList<Holon> children = new ArrayList();
|
|
|
+ for(Holon h : this.layeredHolons.get(this.layeredHolons.size()-1)) {
|
|
|
+ children.addAll(h.childHolons);
|
|
|
+ }
|
|
|
+ if(children.size() > 0) {
|
|
|
+ this.layeredHolons.add(children);
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setSliderMax(int n) {
|
|
|
+ if(this.slider == null || n < 0)
|
|
|
+ return;
|
|
|
+ this.slider.setMaximum(n-1);
|
|
|
+ if(n < 11) {
|
|
|
+ this.slider.setMajorTickSpacing(1);
|
|
|
+ } else if (n < 101) {
|
|
|
+ this.slider.setMajorTickSpacing(10);
|
|
|
+ this.slider.setMinorTickSpacing(1);
|
|
|
+ } else {
|
|
|
+ this.slider.setMajorTickSpacing(100);
|
|
|
+ this.slider.setMinorTickSpacing(10);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * generate a distinct color for each Holon in each layer
|
|
|
+ */
|
|
|
+ public void generateColorSet() {
|
|
|
+ for(int layer=0; layer<this.layeredHolons.size(); layer++) {
|
|
|
+ int numColors = 0;
|
|
|
+ numColors = this.layeredHolons.get(layer).size();
|
|
|
+ if(numColors <= 0)
|
|
|
+ continue;
|
|
|
+// System.out.println("generating colors for "+numColors+" holons");
|
|
|
+ ArrayList<Color> colors = new ArrayList();
|
|
|
+ float hue = Random.nextFloatInRange(0, 1);
|
|
|
+ for(float i = 0; i < 360; i += 360 / numColors) {
|
|
|
+ hue += 0.618033988749895f;
|
|
|
+ float saturation = 0.5f; //90 + Random.nextFloatInRange(0, 10);
|
|
|
+ float lightness = 0.95f; //50 + Random.nextFloatInRange(0, 10);
|
|
|
+ Color c = Color.getHSBColor(hue%1, saturation, lightness);
|
|
|
+
|
|
|
+ colors.add(c);
|
|
|
+// System.out.println("generated color "+c.toString()+" by h "+hue+" sat "+saturation+" light "+lightness);
|
|
|
+ }
|
|
|
+
|
|
|
+ for(int j=0;j<this.layeredHolons.get(layer).size(); j++) {
|
|
|
+ this.coloredHolons.put(this.layeredHolons.get(layer).get(j), colors.get(j));
|
|
|
+// System.out.println("generated color "+colors.get(j).toString()+" for holon "+this.layeredHolons.get(layer).get(j));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ MouseListener mouseListener = new MouseListener() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseClicked(MouseEvent arg0) {
|
|
|
+// System.out.println("mouse click at ("+arg0.getX()+","+arg0.getY()+")");
|
|
|
+ //Get clicked Holon
|
|
|
+ for(Ellipse2D e : ellipses.get(displayedLayer).keySet()) {
|
|
|
+ if(e.contains(arg0.getX(), arg0.getY())) {
|
|
|
+ //found ellipse
|
|
|
+ boolean child = false;
|
|
|
+ //check if one its children was clicked
|
|
|
+ for(Ellipse2D e2 : ellipsesChild.get(displayedLayer).get(e).keySet()) {
|
|
|
+ if(e2.contains(arg0.getX(), arg0.getY())) {
|
|
|
+ //child was clicked
|
|
|
+ child = true;
|
|
|
+ parentFrame.displayHolonInfos(ellipsesChild.get(displayedLayer).get(e).get(e2));
|
|
|
+// System.out.println("child holon "+ellipsesChild.get(e).get(e2).name+" was clicked");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(!child) {
|
|
|
+ //no child was clicked
|
|
|
+ parentFrame.displayHolonInfos(ellipses.get(displayedLayer).get(e));
|
|
|
+// System.out.println("holon "+ellipses.get(e).name+" was clicked");
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseEntered(MouseEvent arg0) {
|
|
|
+ //not important
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseExited(MouseEvent arg0) {
|
|
|
+ //not important
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mousePressed(MouseEvent arg0) {
|
|
|
+ //not important
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseReleased(MouseEvent arg0) {
|
|
|
+ //not important
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ ChangeListener sliderChangeListener = new ChangeListener() {
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void stateChanged(ChangeEvent arg0) {
|
|
|
+ JSlider source = (JSlider) arg0.getSource();
|
|
|
+ if(!source.getValueIsAdjusting()) {
|
|
|
+ displayLayer(source.getValue());
|
|
|
+ parentFrame.clearInfos();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+}
|