yacy_search_server/source/net/yacy/visualization/GraphPlotter.java
Michael Peter Christen 39317a6c66 enhanced webstructure image: introduced
- multiple hosts can be listed (comma-separated) as host argument
- new 'bf'-attribut (branch factor): the maximum number of edges per
node
- the bf-value is computed automatically
- ordering of nodes when the graphic is drawed: mostly the drawing ends
with an limitation eg. number of nodes. When this happens, it should be
ensured that more 'interesting' nodes are painted in advance. This is
now done by sorting all nodes by the number of links they have in de
distant sub-graph.
2012-10-22 16:23:39 +02:00

296 lines
12 KiB
Java

// GraphPlotter.java
// (C) 2007 by Michael Peter Christen; mc@yacy.net, Frankfurt a. M., Germany
// first published 22.05.2007 on http://yacy.net
//
// This is a part of YaCy, a peer-to-peer based web search engine
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// LICENSE
//
// This program 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.
//
// This program is distributed 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package net.yacy.visualization;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/* this class is a container for graph coordinates and it can draw such coordinates into a graph
* all coordinates are given in a artificial coordinate system, in the range from
* -1 to +1. The lower left point of the graph has the coordinate -1, -1 and the upper
* right is 1,1
* 0,0 is the center of the graph
*/
public class GraphPlotter implements Cloneable {
// a ymageGraph is a set of points and borders between the points
// to reference the points, they must all have a nickname
private final Map<String, Point> nodes; // the interconnected objects
private final Set<String> edges; // the links that connect pairs of vertices
private double leftmost, rightmost, topmost, bottommost;
public GraphPlotter() {
this.nodes = new HashMap<String, Point>();
this.edges = new HashSet<String>();
this.leftmost = 1.0;
this.rightmost = -1.0;
this.topmost = -1.0;
this.bottommost = 1.0;
}
@Override
public Object clone() {
GraphPlotter g = new GraphPlotter();
g.nodes.putAll(this.nodes);
g.edges.addAll(this.edges);
g.leftmost = this.leftmost;
g.rightmost = this.rightmost;
g.topmost = this.topmost;
g.bottommost = this.bottommost;
return g;
}
public static class Ribbon {
double length, attraction, repulsion;
public Ribbon(double length, double attraction, double repulsion) {
this.length = length;
this.attraction = attraction;
this.repulsion = repulsion;
}
}
public static class Point implements Cloneable {
public double x, y;
public int layer;
public Point(final double x, final double y, final int layer) {
/*
assert x >= -1;
assert x <= 1;
assert y >= -1;
assert y <= 1;
*/
this.x = x;
this.y = y;
this.layer = layer;
}
@Override
public Object clone() {
return new Point(this.x, this.y, this.layer);
}
}
private final static double p2 = Math.PI / 2.0;
private final static double p23 = p2 * 3.0;
public static void force(Point calcPoint, Point currentPoint, Point otherPoint, Ribbon r) {
double dx = otherPoint.x - currentPoint.x;
double dy = otherPoint.y - currentPoint.y;
double a = Math.atan(dy / dx); // the angle from this point to the other point
if (a < 0) a += Math.PI * 2.0; // this makes it easier for the asserts
double d = Math.sqrt(dx * dx + dy * dy); // the distance of the points
boolean attraction = d > r.length; // if the distance is greater than the ribbon length, then they attract, otherwise they repulse
double f = attraction ? r.attraction * (d - r.length) * (d - r.length) : - r.repulsion * (r.length - d) * (r.length - d); // the force
double x1 = Math.cos(a) * f;
double y1 = Math.sin(a) * f;
// verify calculation
assert !(attraction && a < Math.PI) || y1 >= 0 : "attraction = " + attraction + ", a = " + a + ", y1 = " + y1;
assert !(!attraction && a < Math.PI) || y1 <= 0 : "attraction = " + attraction + ", a = " + a + ", y1 = " + y1;
assert !(attraction && a > Math.PI) || y1 <= 0 : "attraction = " + attraction + ", a = " + a + ", y1 = " + y1;
assert !(!attraction && a > Math.PI) || y1 >= 0 : "attraction = " + attraction + ", a = " + a + ", y1 = " + y1;
assert !(attraction && (a < p2 || a > p23)) || x1 >= 0 : "attraction = " + attraction + ", a = " + a + ", x1 = " + x1;
assert !(!attraction && (a < p2 || a > p23)) || x1 <= 0 : "attraction = " + attraction + ", a = " + a + ", x1 = " + x1;
assert !(attraction && !(a < p2 || a > p23)) || x1 <= 0 : "attraction = " + attraction + ", a = " + a + ", x1 = " + x1;
assert !(!attraction && !(a < p2 || a > p23)) || x1 >= 0 : "attraction = " + attraction + ", a = " + a + ", x1 = " + x1;
calcPoint.x += x1;
calcPoint.y += y1;
}
public GraphPlotter physics(Ribbon all, Ribbon edges) {
GraphPlotter g = new GraphPlotter();
// compute force for every node
Point calc, current;
for (Map.Entry<String, Point> node: this.nodes.entrySet()) {
calc = (Point) node.getValue().clone();
current = (Point) node.getValue().clone();
for (Map.Entry<String, Point> p: this.nodes.entrySet()) {
if (!node.getKey().equals(p.getKey())) {
//System.out.println("force all: " + node.getKey() + " - " + p.getKey());
force(calc, current, p.getValue(), all);
}
}
for (String e: this.getEdges(node.getKey(), true)) {
//System.out.println("force edge start: " + node.getKey() + " - " + e);
force(calc, current, this.getNode(e), edges);
}
for (String e: this.getEdges(node.getKey(), false)) {
//System.out.println("force edge stop: " + node.getKey() + " - " + e);
force(calc, current, this.getNode(e), edges);
}
g.addNode(node.getKey(), calc);
}
g.edges.addAll(this.edges);
return g;
}
public Point getNode(final String node) {
return this.nodes.get(node);
}
private Point[] getEdge(final String edge) {
final int p = edge.indexOf('$',0);
if (p < 0) return null;
final Point from = getNode(edge.substring(0, p));
final Point to = getNode(edge.substring(p + 1));
if ((from == null) || (to == null)) return null;
return new Point[] {from, to};
}
public Point addNode(final String node, Point p) {
this.nodes.put(node, p);
if (p.x > this.rightmost) this.rightmost = p.x;
if (p.x < this.leftmost) this.leftmost = p.x;
if (p.y > this.topmost) this.topmost = p.y;
if (p.y < this.bottommost) this.bottommost = p.y;
return p;
}
public Point addNode(final String node, final double x, final double y, final int layer) {
return addNode(node, new Point(x, y, layer));
}
public boolean hasEdge(final String fromNode, final String toNode) {
return this.edges.contains(fromNode + "-" + toNode);
}
public void setEdge(final String fromNode, final String toNode) {
final Point from = this.nodes.get(fromNode);
final Point to = this.nodes.get(toNode);
assert from != null;
assert to != null;
this.edges.add(fromNode + "$" + toNode);
}
public Collection<String> getEdges(final String node, boolean start) {
Collection<String> c = new ArrayList<String>();
if (start) {
String s = node + "$";
for (String e: this.edges) {
if (e.startsWith(s)) c.add(e.substring(s.length()));
}
} else {
String s = "$" + node;
for (String e: this.edges) {
if (e.endsWith(s)) c.add(e.substring(0, e.length() - s.length()));
}
}
return c;
}
public void print() {
// for debug purpose: print out all coordinates
final Iterator<Map.Entry<String, Point>> i = this.nodes.entrySet().iterator();
Map.Entry<String, Point> entry;
String name;
Point c;
while (i.hasNext()) {
entry = i.next();
name = entry.getKey();
c = entry.getValue();
System.out.println("point(" + c.x + ", " + c.y + ", " + c.layer + ") [" + name + "]");
}
final Iterator<String> j = this.edges.iterator();
while (j.hasNext()) {
System.out.println("border(" + j.next() + ")");
}
}
public RasterPlotter draw(
final int width,
final int height,
final int leftborder,
final int rightborder,
final int topborder,
final int bottomborder,
final String color_back,
final String color_dot,
final String color_line,
final String color_lineend,
final String color_text
) {
final RasterPlotter.DrawMode drawMode = (RasterPlotter.darkColor(color_back)) ? RasterPlotter.DrawMode.MODE_ADD : RasterPlotter.DrawMode.MODE_SUB;
final RasterPlotter image = new RasterPlotter(width, height, drawMode, color_back);
final double xfactor = ((this.rightmost - this.leftmost) == 0.0) ? 0.0 : (width - leftborder - rightborder) / (this.rightmost - this.leftmost);
final double yfactor = ((this.topmost - this.bottommost) == 0.0) ? 0.0 : (height - topborder - bottomborder) / (this.topmost - this.bottommost);
// draw dots and names
final Iterator<Map.Entry<String, Point>> i = this.nodes.entrySet().iterator();
Map.Entry<String, Point> entry;
String name;
Point c;
int x, y;
while (i.hasNext()) {
entry = i.next();
name = entry.getKey();
c = entry.getValue();
x = (xfactor == 0.0) ? width / 2 : (int) (leftborder + (c.x - this.leftmost) * xfactor);
y = (yfactor == 0.0) ? height / 2 : (int) (height - bottomborder - (c.y - this.bottommost) * yfactor);
image.setColor(color_dot);
image.dot(x, y, 6, true, 100);
image.setColor(color_text);
PrintTool.print(image, x, y + 10, 0, name.toUpperCase(), 0);
}
// draw lines
final Iterator<String> j = this.edges.iterator();
Point[] border;
image.setColor(color_line);
int x0, x1, y0, y1;
while (j.hasNext()) {
border = getEdge(j.next());
if (border == null) continue;
if (xfactor == 0.0) {
x0 = width / 2;
x1 = width / 2;
} else {
x0 = (int) (leftborder + (border[0].x - this.leftmost) * xfactor);
x1 = (int) (leftborder + (border[1].x - this.leftmost) * xfactor);
}
if (yfactor == 0.0) {
y0 = height / 2;
y1 = height / 2;
} else {
y0 = (int) (height - bottomborder - (border[0].y - this.bottommost) * yfactor);
y1 = (int) (height - bottomborder - (border[1].y - this.bottommost) * yfactor);
}
// draw the line, with the dot at the beginning of the line
image.lineDot(x1, y1, x0, y0, 3, 4, color_line, color_lineend);
}
return image;
}
}