/**
* NetworkPanel.java
* @author Fabio G. Cozman
* Original version by Sreekanth Nagarajan, totally rewritten by Fabio Cozman.
* The panel code was originally based on an applet written by Carla Laffra
* (see http://www.cs.pace.edu/~carla/dijkstra.html for the applet);
* adapted with permission from the author.
* Copyright 1996 - 1999, Fabio G. Cozman,
* Carnergie Mellon University, Universidade de Sao Paulo
* fgcozman@usp.br, http://www.cs.cmu.edu/~fgcozman/home.html
*
* The JavaBayes distribution is free software; you can
* redistribute it and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation (either
* version 2 of the License or, at your option, any later version),
* provided that this notice and the name of the author appear in all
* copies. Upon request to the author, some of the packages in the
* JavaBayes distribution can be licensed under the GNU Lesser General
* Public License as published by the Free Software Foundation (either
* version 2 of the License, or (at your option) any later version).
* If you're using the software, please notify fgcozman@usp.br so
* that you can receive updates and patches. JavaBayes is distributed
* "as is", in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with the JavaBayes distribution. If not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package JavaBayesInterface;
import InferenceGraphs.*;
import java.io.*;
import java.awt.*;
import java.util.*;
public class NetworkPanel extends Canvas {
private EditorFrame frame; // Used for changing mouse cursor
private ScrollingPanel scrollPanel; // Used to control scrolling
private int mode; // Store the mode for events in the panel
private InferenceGraph ig; // The object with the Bayes net
private Point group_start, group_end; // The region that is considered the group
// Variables that store quantities shared among event handling functions
boolean new_arc = false;
Point new_arc_head = null;
boolean modify_group = false;
InferenceGraphNode movenode = null;
Vector moving_nodes = null;
InferenceGraphNode arcbottomnode = null;
InferenceGraphNode archeadnode = null;
// for scrolling
int x_scroll, y_scroll;
// Constants for drawing entities.
private static final int NODE_SIZE = 26;
private static final int NODE_RADIUS = 13;
private static final int SPACE_DRAW_NODE_NAME = 24;
private static final double ARROW_SIZE = 6.0;
private static final double ARROW_HALF_SIZE = 3.0;
private static final double DISTANCE_HIT_ARC = 200.0;
// Color constants for various graphical elements.
private static final Color nodeColor = Color.green;
private static final Color observedNodeColor = Color.blue;
private static final Color explanationNodeColor = Color.orange;
private static final Color nodeBorderColor = Color.black;
private static final Color nodenameColor = Color.black;
private static final Color arcColor = Color.gray;
private static final Color backgroundColor = Color.white;
// Network editing modes.
private static final int CREATE_MODE = 1;
private static final int MOVE_MODE = 2;
private static final int DELETE_MODE = 3;
private static final int OBSERVE_MODE = 4;
private static final int QUERY_MODE = 5;
private static final int EDIT_VARIABLE_MODE = 6;
private static final int EDIT_FUNCTION_MODE = 7;
private static final int EDIT_NETWORK_MODE = 8;
// Fonts.
private Font roman = new Font("TimesRoman", Font.BOLD, 12);
private Font helvetica = new Font("Helvetica", Font.BOLD, 15);
private FontMetrics fmetrics = getFontMetrics(roman);
private int h = (int)fmetrics.getHeight()/3;
// For double buffering.
private Image offScreenImage;
private Graphics offScreenGraphics;
private Dimension offScreenSize;
/**
* Default constructor for NetworkPanel.
*/
NetworkPanel(EditorFrame frame, ScrollingPanel scroll) {
this.frame = frame;
this.scrollPanel = scroll;
// Create default InferenceGraph
ig = new InferenceGraph();
// Create the group object.
group_start = new Point(0, 0);
group_end = new Point(0, 0);
// set initial mode to be MOVE.
mode = MOVE_MODE;
frame.setCursor(Frame.MOVE_CURSOR);
// set color for background
setBackground(backgroundColor);
}
/**
* Process mouse down events.
*/
public boolean mouseDown(Event evt, int x, int y) {
x += x_scroll;
y += y_scroll;
InferenceGraphNode node = nodehit(x, y);
if (node == null) { // If no node was clicked on.
if ( (mode == DELETE_MODE) && ( archit(x, y) ) ) { // Delete arc
delete_arc();
archeadnode = null;
arcbottomnode = null;
} else if (mode == CREATE_MODE) { // Create a node
create_node(x, y);
} else {
// Start the creation of a group.
group_start.move(x, y);
group_end.move(x, y);
modify_group = true;
}
} else { // If a node was clicked on.
if (mode == OBSERVE_MODE) { // Observe node
observe(node);
} else if (mode == QUERY_MODE) { // Query node
frame.process_query(ig, node.get_name());
} else if (mode == MOVE_MODE) { // Move node
movenode = node;
generate_moving_nodes();
} else if (mode == DELETE_MODE) { // Delete node
delete_node(node);
} else if (mode == CREATE_MODE) { // Create arc
new_arc = true;
arcbottomnode = node;
new_arc_head = new Point(x, y);
} else if (mode == EDIT_VARIABLE_MODE) { // Edit variable node
edit_variable(node);
} else if (mode == EDIT_FUNCTION_MODE) { // Edit function node
edit_function(node);
}
}
repaint();
return true;
}
/**
* Process mouse drag events.
*/
public boolean mouseDrag(Event evt, int x, int y) {
x += x_scroll;
y += y_scroll;
if (movenode != null) {
move_node(x, y);
} else if (new_arc == true) {
new_arc_head = new Point(x, y);
} else if (modify_group == true) {
group_end.move(x, y);
}
repaint();
return true;
}
/**
* Process mouse up events.
*/
public boolean mouseUp(Event evt, int x, int y) {
x += x_scroll;
y += y_scroll;
if (movenode != null) {
ig.set_pos(movenode, new Point(x, y));
movenode = null;
} else if (new_arc == true) {
archeadnode = nodehit(x, y);
if ((archeadnode != null) && (arcbottomnode != null)) {
if (archeadnode == arcbottomnode) {
JavaBayesHelpMessages.show(JavaBayesHelpMessages.selfarc);
}
else if (ig.hasCycle(arcbottomnode, archeadnode)) {
JavaBayesHelpMessages.show(JavaBayesHelpMessages.circular);
}
else {
create_arc();
}
}
archeadnode = null;
arcbottomnode = null;
new_arc_head = null;
new_arc = false;
} else if (modify_group == true) {
modify_group = false;
}
repaint();
return true;
}
/*
* Determine whether a node was hit by a mouse click.
*/
private InferenceGraphNode nodehit(int x, int y) {
InferenceGraphNode node;
for (Enumeration e = ig.elements(); e.hasMoreElements(); ) {
node = (InferenceGraphNode)(e.nextElement());
if ( (x-node.get_pos_x()) * (x-node.get_pos_x()) +
(y-node.get_pos_y()) * (y-node.get_pos_y()) <
NODE_RADIUS * NODE_RADIUS ) {
return(node);
}
}
return(null);
}
/*
* Determine whether an arc was hit by a mouse click.
*/
boolean archit(int x, int y) {
InferenceGraphNode hnode, pnode;
double sdpa;
for (Enumeration e = ig.elements(); e.hasMoreElements(); ) {
hnode = (InferenceGraphNode)(e.nextElement());
for (Enumeration ee = (hnode.get_parents()).elements(); ee.hasMoreElements(); ) {
pnode = (InferenceGraphNode)(ee.nextElement());
sdpa = square_distance_point_arc(hnode, pnode, x, y);
if ((sdpa >= 0.0) && (sdpa <= DISTANCE_HIT_ARC)) {
archeadnode = hnode;
arcbottomnode = pnode;
}
}
}
if ( (archeadnode != null) && (arcbottomnode != null) ) return true;
else return(false);
}
/*
* Determine whether a point is close to the segment
* between two nodes (hnode and pnode); if the point
* does not lie over or above the segment, return -1.0
*/
double square_distance_point_arc(InferenceGraphNode hnode,
InferenceGraphNode pnode, int x3, int y3) {
int x1, y1, x2, y2;
double area, square_base, square_height, square_hyp;
x1 = hnode.get_pos_x();
y1 = hnode.get_pos_y();
x2 = pnode.get_pos_x();
y2 = pnode.get_pos_y();
// Area of the triangle defined by the three points
area = 0.5 * (double)(x1 * y2 + y1 * x3 + x2 * y3 -
x3 * y2 - y3 * x1 - x2 * y1);
// Base of the triangle
square_base = (double)( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) );
// Height of the triangle
square_height = 4.0 * (area*area)/square_base;
// Maximum possible distance from point to extreme points
square_hyp = square_base + square_height;
// Check first extreme point
if (square_hyp < ((double)( (x3-x1)*(x3-x1) + (y3-y1)*(y3-y1)) ))
return(-1.0);
// Check second extreme point
if (square_hyp < ((double)( (x3-x2)*(x3-x2) + (y3-y2)*(y3-y2)) ))
return(-1.0);
// Requested distance is the height of the triangle
return( square_height );
}
/**
* Update the screen with the network.
*/
public void update(Graphics g) {
// Prepare new offscreen image, for double buffering.
Dimension d=size();
MediaTracker tracker;
if ( (offScreenImage == null) ||
(d.width != offScreenSize.width) ||
(d.height != offScreenSize.height) ) {
offScreenImage = createImage(d.width, d.height);
tracker = new MediaTracker(this);
try { // Wait to image to be constructed.
tracker.addImage(offScreenImage, 0);
tracker.waitForID(0,0);
} catch (InterruptedException e) { }
offScreenSize = d;
offScreenGraphics = offScreenImage.getGraphics();
}
// Generate the contents of the image.
offScreenGraphics.setColor(backgroundColor);
offScreenGraphics.fillRect(0, 0, d.width, d.height);
paint(offScreenGraphics);
g.drawImage(offScreenImage, 0, 0, null);
}
/**
* Paint the network.
* This is not nearly as efficient as it should be, because the
* whole graph is redrawn every time there is a call to paint(). A much
* more efficient approach would be to only add/move/delete nodes and arcs
* as needed, in response to user commands.
*
* The rendering engine changed in Java2, and the circles
* are not drawn correctly. The following workaround for the change
* in the rendering engine in Java2 was proposed
* by Michael Becke, Nov 21 2000.
try {
((Graphics2D) g).setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
} catch (java.lang.NoClassDefFoundError e) { // Does nothing if new engine fails.
}
* Instead of using this Java2-specific code, the solution used
* here and also proposed by Michael Becke is to fill first a
* whole oval with the border color and then fill the inside
* of the circles.
*/
public void paint(Graphics g) {
InferenceGraphNode node, parent;
Enumeration e, ee;
int explanation_status = frame.get_mode();
if (ig == null) return;
// Draw a new arc upto current mouse position.
g.setColor(arcColor);
if (new_arc)
g.drawLine(arcbottomnode.get_pos_x() - x_scroll,
arcbottomnode.get_pos_y() - y_scroll,
new_arc_head.x - x_scroll, new_arc_head.y - y_scroll);
// Draw all arcs.
for (e = ig.elements(); e.hasMoreElements(); ) {
node = (InferenceGraphNode)(e.nextElement());
for (ee = (node.get_parents()).elements(); ee.hasMoreElements(); ) {
parent = (InferenceGraphNode)(ee.nextElement());
draw_arc(g, node, parent);
}
}
// Draw the nodes.
g.setFont(helvetica);
for (e = ig.elements(); e.hasMoreElements(); ) {
node = (InferenceGraphNode)e.nextElement();
g.setColor(nodeBorderColor);
if ((node.get_pos_x() - x_scroll) >= 0)
g.fillOval( (node.get_pos_x() - x_scroll) - NODE_RADIUS - 1,
(node.get_pos_y() - y_scroll) - NODE_RADIUS - 1,
NODE_SIZE + 2, NODE_SIZE + 2);
switch (explanation_status) {
case InferenceGraph.FULL_EXPLANATION:
g.setColor(explanationNodeColor);
break;
case InferenceGraph.EXPLANATION:
if (node.is_explanation())
g.setColor(explanationNodeColor);
else
g.setColor(nodeColor);
break;
case InferenceGraph.SENSITIVITY_ANALYSIS:
case InferenceGraph.MARGINAL_POSTERIOR:
case InferenceGraph.EXPECTATION:
g.setColor(nodeColor);
}
if (node.is_observed())
g.setColor(observedNodeColor);
if ( (node.get_pos_x() - x_scroll) >= 0)
g.fillOval( (node.get_pos_x() - x_scroll) - NODE_RADIUS,
(node.get_pos_y() - y_scroll) - NODE_RADIUS,
NODE_SIZE, NODE_SIZE);
g.setColor(nodenameColor);
g.drawString(node.get_name(),
(node.get_pos_x() - x_scroll) - SPACE_DRAW_NODE_NAME,
(node.get_pos_y() - y_scroll) + SPACE_DRAW_NODE_NAME);
}
// Draw the group.
g.setXORMode(backgroundColor);
int group_x, group_y, group_width, group_height;
if (group_start.x < group_end.x) {
group_x = group_start.x;
group_width = group_end.x - group_start.x;
}
else {
group_x = group_end.x;
group_width = group_start.x - group_end.x;
}
if (group_start.y < group_end.y) {
group_y = group_start.y;
group_height = group_end.y - group_start.y;
}
else {
group_y = group_end.y;
group_height = group_start.y - group_end.y;
}
g.drawRect(group_x - x_scroll, group_y - y_scroll,
group_width, group_height);
g.setPaintMode();
// Resize the scrollbars.
scrollPanel.setScrollbars(size());
}
/*
* Auxiliary function that draws an arc.
*/
private void draw_arc(Graphics g, InferenceGraphNode node,
InferenceGraphNode parent) {
int node_x, node_y, parent_x, parent_y;
int x1, x2, x3, y1, y2, y3;
double dir_x, dir_y, distance;
double head_x, head_y, bottom_x, bottom_y;
// calculate archead
node_x = node.get_pos_x() - x_scroll;
node_y = node.get_pos_y() - y_scroll;
parent_x = parent.get_pos_x() - x_scroll;
parent_y = parent.get_pos_y() - y_scroll;
dir_x = (double)(node_x - parent_x);
dir_y = (double)(node_y - parent_y);
distance = Math.sqrt(dir_x * dir_x + dir_y * dir_y);
dir_x /= distance;
dir_y /= distance;
head_x = node_x - (NODE_RADIUS + ARROW_SIZE) * dir_x;
head_y = node_y - (NODE_RADIUS + ARROW_SIZE) * dir_y;
bottom_x = parent_x + NODE_RADIUS * dir_x;
bottom_y = parent_y + NODE_RADIUS * dir_y;
x1= (int)(head_x - ARROW_HALF_SIZE*dir_x + ARROW_SIZE*dir_y);
x2= (int)(head_x - ARROW_HALF_SIZE*dir_x - ARROW_SIZE*dir_y);
x3= (int)(head_x + ARROW_SIZE*dir_x);
y1= (int)(head_y - ARROW_HALF_SIZE*dir_y - ARROW_SIZE*dir_x);
y2= (int)(head_y - ARROW_HALF_SIZE*dir_y + ARROW_SIZE*dir_x);
y3= (int)(head_y + ARROW_SIZE*dir_y);
int archead_x[] = { x1, x2, x3, x1 };
int archead_y[] = { y1, y2, y3, y1 };
// draw archead
g.drawLine((int)bottom_x, (int)bottom_y,
(int)head_x, (int)head_y);
g.fillPolygon(archead_x, archead_y, 4);
}
/*
* Set the mode for the NetworkPanel.
*/
void set_mode(String label) {
if (label.equals(EditorFrame.createLabel))
mode = CREATE_MODE;
else if (label.equals(EditorFrame.moveLabel))
mode = MOVE_MODE;
else if (label.equals(EditorFrame.deleteLabel))
mode = DELETE_MODE;
else if (label.equals(EditorFrame.queryLabel))
mode = QUERY_MODE;
else if (label.equals(EditorFrame.observeLabel))
mode = OBSERVE_MODE;
else if (label.equals(EditorFrame.editVariableLabel))
mode = EDIT_VARIABLE_MODE;
else if (label.equals(EditorFrame.editFunctionLabel))
mode = EDIT_FUNCTION_MODE;
else // default mode;
mode = CREATE_MODE;
}
/*
* Return the QuasiBayesNet object displayed int the NetworkPanel.
*/
InferenceGraph get_inference_graph() {
return(ig);
}
/*
* Store the QuasiBayesNet object to be displayed in
* the NetworkPanel.
*/
void load(InferenceGraph inference_graph) {
ig = inference_graph;
repaint();
}
/*
* Clear the NetworkPanel.
*/
void clear() {
ig = new InferenceGraph();
repaint();
}
/*
* Observe a node.
*/
void observe(InferenceGraphNode node) {
ig.reset_marginal();
Dialog d = new ObserveDialog(this, frame, ig, node);
d.show();
}
/*
* Create a node.
*/
void create_node(int x, int y) {
ig.create_node(x, y);
ig.reset_marginal();
}
/*
* Create an arc. The bottom and head nodes of the arc
* are stored in the variables arcbottomnode and
* archeadnode.
*/
void create_arc() {
boolean flag_created = ig.create_arc(arcbottomnode, archeadnode);
if (flag_created == true)
ig.reset_marginal();
}
/*
* Make a list of all moving nodes.
*/
void generate_moving_nodes() {
InferenceGraphNode node;
if (!inside_group(movenode)) {
moving_nodes = null;
return;
}
else {
moving_nodes = new Vector();
for (Enumeration e = ig.elements(); e.hasMoreElements(); ) {
node = (InferenceGraphNode)e.nextElement();
if (inside_group(node))
moving_nodes.addElement(node);
}
}
}
/*
* Move a node.
*/
void move_node(int x, int y) {
InferenceGraphNode node;
int delta_x = movenode.get_pos_x() - x;
int delta_y = movenode.get_pos_y() - y;
// Check whether the movenode is in the group.
if ( moving_nodes == null )
ig.set_pos(movenode, new Point(x, y)); // Move only the movenode.
else {
group_start.x -= delta_x; group_end.x -= delta_x;
group_start.y -= delta_y; group_end.y -= delta_y;
for (Enumeration e = moving_nodes.elements(); e.hasMoreElements(); ) {
node = (InferenceGraphNode)e.nextElement();
ig.set_pos(node, // Move all nodes in the group.
new Point(node.get_pos_x() - delta_x, node.get_pos_y() - delta_y));
}
}
}
/*
* Delete a node.
*/
void delete_node(InferenceGraphNode node) {
InferenceGraphNode dnode;
Enumeration e;
Vector nodes_to_delete;
// Check whether the node is in the group.
if ( !inside_group(node) )
ig.delete_node(node); // Delete only the movenode.
else {
nodes_to_delete = new Vector();
for (e = ig.elements(); e.hasMoreElements(); ) {
dnode = (InferenceGraphNode)e.nextElement();
if (inside_group(dnode))
nodes_to_delete.addElement(dnode);
}
for (e = nodes_to_delete.elements(); e.hasMoreElements(); ) {
dnode = (InferenceGraphNode)e.nextElement();
ig.delete_node(dnode); // Delete node.
}
}
ig.reset_marginal();
}
/*
* Determine whether a given InferenceGraphNode is inside the group.
*/
boolean inside_group(InferenceGraphNode node) {
return( ( node.get_pos_x() > Math.min( group_start.x, group_end.x ) ) &&
( node.get_pos_x() < Math.max( group_start.x, group_end.x ) ) &&
( node.get_pos_y() > Math.min( group_start.y, group_end.y ) ) &&
( node.get_pos_y() < Math.max( group_start.y, group_end.y ) ) );
}
/*
* Delete an arc. The bottom and head nodes of the arc
* are stored in the variables arcbottomnode and
* archeadnode.
*/
void delete_arc() {
ig.delete_arc(arcbottomnode, archeadnode);
ig.reset_marginal();
}
/*
* Edit the components of a node.
*/
void edit_variable(InferenceGraphNode node) {
ig.reset_marginal();
Dialog d = new EditVariableDialog(this, frame, ig, node);
d.show();
}
/*
* Edit the function in a node.
*/
void edit_function(InferenceGraphNode node) {
ig.reset_marginal();
Dialog d = new EditFunctionDialog(frame, ig, node);
d.show();
}
/*
* Edit the network.
*/
void edit_network() {
ig.reset_marginal();
Dialog d = new EditNetworkDialog(frame, ig);
d.show();
}
}