vertex) {
36 | this.vertex = vertex;
37 | }
38 |
39 | /**
40 | * Gets default color.
41 | *
42 | * @return the default color
43 | */
44 | public int getDefaultColor() {
45 | return defaultColor;
46 | }
47 |
48 | /**
49 | * Sets default color.
50 | *
51 | * @param defaultColor the default color
52 | */
53 | public void setDefaultColor(final int defaultColor) {
54 | this.defaultColor = defaultColor;
55 | }
56 |
57 | /**
58 | * Gets edge color.
59 | *
60 | * @return the edge color
61 | */
62 | public int getEdgeColor() {
63 | return edgeColor;
64 | }
65 |
66 | /**
67 | * Sets edge color.
68 | *
69 | * @param edgeColor the edge color
70 | */
71 | public void setEdgeColor(final int edgeColor) {
72 | this.edgeColor = edgeColor;
73 | }
74 |
75 | /**
76 | * Gets node color.
77 | *
78 | * @return the node color
79 | */
80 | public int getNodeColor() {
81 | return nodeColor;
82 | }
83 |
84 | /**
85 | * Sets node color.
86 | *
87 | * @param nodeColor the node color
88 | */
89 | public void setNodeColor(final int nodeColor) {
90 | this.nodeColor = nodeColor;
91 | }
92 |
93 | /**
94 | * Gets node bg color.
95 | *
96 | * @return the node bg color
97 | */
98 | public int getNodeBgColor() {
99 | return nodeBgColor;
100 | }
101 |
102 | /**
103 | * Sets node bg color.
104 | *
105 | * @param nodeBgColor the node bg color
106 | */
107 | public void setNodeBgColor(final int nodeBgColor) {
108 | this.nodeBgColor = nodeBgColor;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # android-network-graph
2 |
3 | ## Description
4 |
5 | Network graph based on :
6 |
7 | - [https://github.com/andreiolaru-ro/AmIciTy-Grph](https://github.com/andreiolaru-ro/AmIciTy-Grph)
8 | - [https://github.com/andreiolaru-ro/net.xqhs.Graphs](https://github.com/andreiolaru-ro/net.xqhs.Graphs)
9 |
10 | [  ](https://bintray.com/giwi/android/android-network-graph/0.0.1/link)
11 |
12 | Here's a screenshot :
13 |
14 | 
15 |
16 | ## Usage
17 |
18 | ````groovy
19 | repositories {
20 | jcenter()
21 | }
22 | dependencies {
23 | compile 'org.giwi:android-network-graph:0.0.1'
24 | }
25 | ````
26 |
27 | ````xml
28 |
39 | ````
40 |
41 | ````java
42 | Node v1 = new SimpleNode("18");
43 | Node v2 = new SimpleNode("24");
44 | graph.getVertex().add(new Vertex(v1, ContextCompat.getDrawable(this, R.drawable.avatar)));
45 | graph.getVertex().add(new Vertex(v2, ContextCompat.getDrawable(this, R.drawable.avatar)));
46 | graph.addEdge(new SimpleEdge(v1, v2, "12"));
47 |
48 | Node v3 = new SimpleNode("7");
49 | graph.getVertex().add(new Vertex(v3, ContextCompat.getDrawable(this, R.drawable.avatar)));
50 | graph.addEdge(new SimpleEdge(v2, v3, "23"));
51 |
52 | v1 = new SimpleNode("14");
53 | graph.getVertex().add(new Vertex(v1, ContextCompat.getDrawable(this, R.drawable.avatar)));
54 | graph.addEdge(new SimpleEdge(v3, v1, "34"));
55 |
56 | v1 = new SimpleNode("10");
57 | graph.getVertex().add(new Vertex(v1, ContextCompat.getDrawable(this, R.drawable.avatar)));
58 | graph.addEdge(new SimpleEdge(v3, v1, "35"));
59 |
60 | v1 = new SimpleNode("11");
61 | graph.getVertex().add(new Vertex(v1, ContextCompat.getDrawable(this, R.drawable.avatar)));
62 | graph.addEdge(new SimpleEdge(v1, v3, "36"));
63 | graph.addEdge(new SimpleEdge(v3, v1, "6"));
64 |
65 | GraphSurfaceView surface = (GraphSurfaceView) findViewById(R.id.mysurface);
66 | surface.init(graph);
67 | ````
68 |
69 | Setting colors programmatically
70 |
71 | ````java
72 | graph.setDefaultColor(ContextCompat.getColor(this, android.R.color.black));
73 | graph.setEdgeColor(ContextCompat.getColor(this, android.R.color.holo_blue_light));
74 | graph.setNodeColor(ContextCompat.getColor(this, android.R.color.holo_blue_light));
75 | graph.setNodeBgColor(ContextCompat.getColor(this, android.R.color.white));
76 | ````
77 |
78 |
--------------------------------------------------------------------------------
/network-graph/src/main/java/giwi/org/networkgraph/beans/ArcUtils.java:
--------------------------------------------------------------------------------
1 | package giwi.org.networkgraph.beans;
2 |
3 | import android.graphics.Canvas;
4 | import android.graphics.Paint;
5 | import android.graphics.Path;
6 | import android.graphics.PointF;
7 | import android.graphics.RectF;
8 |
9 |
10 | /**
11 | * The type Arc utils.
12 | */
13 | public class ArcUtils {
14 |
15 | /**
16 | * Instantiates a new Arc utils.
17 | */
18 | private ArcUtils() {
19 | }
20 |
21 | /**
22 | * https://www.tbray.org/ongoing/When/200x/2009/01/02/Android-Draw-a-Curved-Line
23 | *
24 | * Draw arc.
25 | *
26 | * @param e1 the e 1
27 | * @param e2 the e 2
28 | * @param radius the radius
29 | * @param canvas the canvas
30 | * @param paint the paint
31 | * @param textPaint the text paint
32 | * @param recPaint the rec paint
33 | * @param value the value
34 | */
35 | public static void drawArc(PointF e1, PointF e2, float radius, Canvas canvas, Paint paint, Paint textPaint, Paint recPaint, int value) {
36 | double a1 = Math.toRadians(radius + 5);
37 | // l1 is half the length of the line from e1 to e2
38 | double dx = e2.x - e1.x, dy = e2.y - e1.y;
39 | double l = Math.sqrt((dx * dx) + (dy * dy));
40 | double l1 = l / 2.0;
41 | // h is length of the line from the middle of the connecting line to the center of the circle.
42 | double h = l1 / (Math.tan(a1 / 2.0));
43 | // r is the radius of the circle
44 | double r = l1 / (Math.sin(a1 / 2.0));
45 | // a2 is the angle at which L intersects the x axis
46 | double a2 = Math.atan2(dy, dx);
47 | // a3 is the angle at which H intersects the x axis
48 | double a3 = (Math.PI / 2.0) - a2;
49 | // m is the midpoint of the line from e1 to e2
50 | double mX = (e1.x + e2.x) / 2.0;
51 | double mY = (e1.y + e2.y) / 2.0;
52 |
53 | // c is the the center of the circle
54 | double cY = mY + (h * Math.sin(a3));
55 | double cX = mX - (h * Math.cos(a3));
56 | // rect is the square RectF that bounds the "oval"
57 | RectF oval = new RectF((float) (cX - r), (float) (cY - r), (float) (cX + r), (float) (cY + r));
58 |
59 | // a4 is the starting sweep angle
60 | double rawA4 = Math.atan2(e1.y - cY, e1.x - cX);
61 | float a4 = (float) Math.toDegrees(rawA4);
62 | paint.setStrokeWidth(value + 1);
63 | drawArrow(e2.x, e2.y, a4 + radius + 45f, paint, canvas);
64 | canvas.drawArc(oval, a4, radius, false, paint);
65 | double deltay = -Math.sin(a3) * (r - h);
66 | double deltax = Math.cos(a3) * (r - h);
67 | canvas.drawRect(
68 | (float) (mX + deltax) - 20f,
69 | (float) (mY + deltay) + 20f,
70 | (float) (mX + deltax) + 20f, (float) (mY + deltay) - 20f, recPaint);
71 |
72 | canvas.drawText(String.valueOf(value), (float) (mX + deltax),
73 | (float) (mY + deltay) + 10, textPaint);
74 | }
75 |
76 | /**
77 | * Draw arrow.
78 | *
79 | * @param x the x
80 | * @param y the y
81 | * @param degrees the degrees
82 | * @param paint the paint
83 | * @param canvas the canvas
84 | */
85 | private static void drawArrow(float x, float y, float degrees, Paint paint, Canvas canvas) {
86 | canvas.save();
87 | canvas.rotate(degrees, x, y);
88 | Path path = new Path();
89 | path.setFillType(Path.FillType.EVEN_ODD);
90 | path.moveTo(x - 40f, y - 40f);
91 | path.lineTo(x - 60f, y - 40f);
92 | path.lineTo(x - 40f, y - 60f);
93 | path.lineTo(x - 40f, y - 40f);
94 | path.close();
95 | canvas.drawPath(path, paint);
96 | canvas.restore();
97 | paint.setStyle(Paint.Style.STROKE);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/app/app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | generateDebugSources
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/network-graph/src/main/java/giwi/org/networkgraph/GraphSurfaceView.java:
--------------------------------------------------------------------------------
1 | package giwi.org.networkgraph;
2 |
3 | import net.xqhs.graphs.graph.Edge;
4 |
5 | import android.content.Context;
6 | import android.content.res.TypedArray;
7 | import android.graphics.Bitmap;
8 | import android.graphics.Canvas;
9 | import android.graphics.Color;
10 | import android.graphics.Paint;
11 | import android.graphics.PixelFormat;
12 | import android.graphics.PointF;
13 | import android.graphics.PorterDuff;
14 | import android.graphics.PorterDuffXfermode;
15 | import android.graphics.Rect;
16 | import android.graphics.drawable.BitmapDrawable;
17 | import android.support.annotation.NonNull;
18 | import android.util.AttributeSet;
19 | import android.view.MotionEvent;
20 | import android.view.ScaleGestureDetector;
21 | import android.view.SurfaceHolder;
22 | import android.view.SurfaceView;
23 |
24 | import giwi.org.networkgraph.beans.ArcUtils;
25 | import giwi.org.networkgraph.beans.Dimension;
26 | import giwi.org.networkgraph.beans.NetworkGraph;
27 | import giwi.org.networkgraph.beans.Point2D;
28 | import giwi.org.networkgraph.beans.Vertex;
29 | import giwi.org.networkgraph.layout.FRLayout;
30 |
31 | /**
32 | * The type NetworkGraph surface view.
33 | */
34 | public class GraphSurfaceView extends SurfaceView {
35 |
36 | private ScaleGestureDetector mScaleDetector;
37 |
38 | private TypedArray attributes;
39 |
40 | private float mScaleFactor = 1.f;
41 |
42 | /**
43 | * Instantiates a new NetworkGraph surface view.
44 | *
45 | * @param context the context
46 | */
47 | public GraphSurfaceView(Context context) {
48 | super(context);
49 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
50 | }
51 |
52 | /**
53 | * Instantiates a new Graph surface view.
54 | *
55 | * @param context the context
56 | * @param attrs the attrs
57 | */
58 | public GraphSurfaceView(Context context, AttributeSet attrs) {
59 | super(context, attrs);
60 | attributes = getContext().obtainStyledAttributes(attrs, R.styleable.GraphSurfaceView);
61 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
62 | }
63 |
64 | /**
65 | * Instantiates a new Graph surface view.
66 | *
67 | * @param context the context
68 | * @param attrs the attrs
69 | * @param defStyle the def style
70 | */
71 | public GraphSurfaceView(Context context, AttributeSet attrs, int defStyle) {
72 | super(context, attrs, defStyle);
73 | attributes = getContext().obtainStyledAttributes(attrs, R.styleable.GraphSurfaceView);
74 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
75 | }
76 |
77 |
78 | /**
79 | * Init.
80 | *
81 | * @param graph the graph
82 | */
83 | public void init(final NetworkGraph graph) {
84 | setZOrderOnTop(true);
85 | getHolder().setFormat(PixelFormat.TRANSLUCENT);
86 | getHolder().addCallback(new SurfaceHolder.Callback() {
87 | @Override
88 | public void surfaceCreated(SurfaceHolder holder) {
89 | Canvas canvas = holder.lockCanvas(null);
90 | canvas.drawARGB(0, 225, 225, 255);
91 | drawGraph(canvas, graph);
92 | holder.unlockCanvasAndPost(canvas);
93 | }
94 |
95 | @Override
96 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
97 | // TODO Auto-generated method stub
98 |
99 | }
100 |
101 | @Override
102 | public void surfaceDestroyed(SurfaceHolder holder) {
103 | // TODO Auto-generated method stub
104 |
105 | }
106 | });
107 | }
108 |
109 | private void drawGraph(final Canvas canvas, final NetworkGraph graph) {
110 | Paint paint = new Paint();
111 | Paint whitePaint = new Paint();
112 | paint.setAntiAlias(true);
113 | FRLayout layout = new FRLayout(graph, new Dimension(getWidth(), getHeight()));
114 | whitePaint.setColor(attributes.getColor(R.styleable.GraphSurfaceView_nodeBgColor, graph.getNodeBgColor()));
115 | whitePaint.setStyle(Paint.Style.FILL_AND_STROKE);
116 | whitePaint.setStrokeWidth(2f);
117 | whitePaint.setShadowLayer(5, 0, 0, attributes.getColor(R.styleable.GraphSurfaceView_defaultColor, graph
118 | .getDefaultColor()));
119 | paint.setTextAlign(Paint.Align.CENTER);
120 | paint.setTextSize(20f);
121 | paint.setColor(attributes.getColor(R.styleable.GraphSurfaceView_defaultColor, graph.getDefaultColor()));
122 | for (Edge edge : graph.getEdges()) {
123 | Point2D p1 = layout.transform(edge.getFrom());
124 | Point2D p2 = layout.transform(edge.getTo());
125 | paint.setStrokeWidth(Float.valueOf(edge.getLabel()) + 1f);
126 | paint.setColor(attributes.getColor(R.styleable.GraphSurfaceView_edgeColor, graph.getEdgeColor()));
127 | Paint curve = new Paint();
128 | curve.setAntiAlias(true);
129 | curve.setStyle(Paint.Style.STROKE);
130 | curve.setStrokeWidth(2);
131 | curve.setColor(attributes.getColor(R.styleable.GraphSurfaceView_edgeColor, graph.getEdgeColor()));
132 | PointF e1 = new PointF((float) p1.getX(), (float) p1.getY());
133 | PointF e2 = new PointF((float) p2.getX(), (float) p2.getY());
134 | ArcUtils.drawArc(e1, e2, 36f, canvas, curve, paint, whitePaint, Integer.parseInt(edge.getLabel()));
135 | }
136 | paint.setStyle(Paint.Style.FILL);
137 | paint.setTextAlign(Paint.Align.CENTER);
138 | paint.setTextSize(30f);
139 | paint.setStrokeWidth(0f);
140 | paint.setColor(attributes.getColor(R.styleable.GraphSurfaceView_nodeColor, graph.getNodeColor()));
141 | for (Vertex node : graph.getVertex()) {
142 | Point2D position = layout.transform(node.getNode());
143 | canvas.drawCircle((float) position.getX(), (float) position.getY(), 40, whitePaint);
144 | if (node.getIcon() != null) {
145 | Bitmap b = ((BitmapDrawable) node.getIcon()).getBitmap();
146 | Bitmap bitmap = b.copy(Bitmap.Config.ARGB_8888, true);
147 | Bitmap roundBitmap = getCroppedBitmap(bitmap, 75);
148 | canvas.drawBitmap(roundBitmap,
149 | (float) position.getX() - 38f, (float) position.getY() - 38f, null);
150 | }
151 | canvas.drawRect(
152 | (float) position.getX() - 20,
153 | (float) position.getY() + 50,
154 | (float) position.getX() + 20, (float) position.getY() + 10, whitePaint);
155 | canvas.drawText(node.getNode().getLabel(), (float) position.getX(),
156 | (float) position.getY() + 40, paint);
157 | }
158 | }
159 |
160 | private Bitmap getCroppedBitmap(Bitmap bmp, int radius) {
161 | Bitmap sbmp;
162 | if (bmp.getWidth() != radius || bmp.getHeight() != radius) {
163 | sbmp = Bitmap.createScaledBitmap(bmp, radius, radius, false);
164 | } else {
165 | sbmp = bmp;
166 | }
167 | Bitmap output = Bitmap.createBitmap(sbmp.getWidth(), sbmp.getHeight(), Bitmap.Config.ARGB_8888);
168 | Canvas canvas = new Canvas(output);
169 | final Paint paint = new Paint();
170 | final Rect rect = new Rect(0, 0, sbmp.getWidth(), sbmp.getHeight());
171 | paint.setColor(Color.BLACK);
172 | paint.setAntiAlias(true);
173 | paint.setFilterBitmap(true);
174 | paint.setDither(true);
175 | canvas.drawARGB(0, 0, 0, 0);
176 | canvas.drawCircle(
177 | sbmp.getWidth() / 2 + 0.7f, sbmp.getHeight() / 2 + 0.7f, sbmp.getWidth() / 2 + 0.1f, paint);
178 | paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
179 | canvas.drawBitmap(sbmp, rect, rect, paint);
180 | return output;
181 | }
182 |
183 | /**
184 | * On touch event.
185 | *
186 | * @param ev the ev
187 | * @return the boolean
188 | */
189 | @Override
190 | public boolean onTouchEvent(@NonNull MotionEvent ev) {
191 | mScaleDetector.onTouchEvent(ev);
192 | return true;
193 | }
194 |
195 | /**
196 | * Gets scale factor.
197 | *
198 | * @return the scale factor
199 | */
200 | public float getScaleFactor() {
201 | return mScaleFactor;
202 | }
203 |
204 | /**
205 | * The type Scale listener.
206 | */
207 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
208 |
209 | /**
210 | * On scale.
211 | *
212 | * @param detector the detector
213 | * @return the boolean
214 | */
215 | @Override
216 | public boolean onScale(ScaleGestureDetector detector) {
217 | mScaleFactor *= detector.getScaleFactor();
218 | mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
219 | invalidate();
220 | return true;
221 | }
222 | }
223 |
224 | }
225 |
--------------------------------------------------------------------------------
/network-graph/src/main/java/giwi/org/networkgraph/layout/AbstractLayout.java:
--------------------------------------------------------------------------------
1 | package giwi.org.networkgraph.layout;
2 |
3 | import net.xqhs.graphs.graph.Node;
4 |
5 | import org.apache.commons.collections4.Transformer;
6 | import org.apache.commons.collections4.functors.ChainedTransformer;
7 | import org.apache.commons.collections4.map.LazyMap;
8 |
9 | import android.util.Log;
10 |
11 | import java.util.ConcurrentModificationException;
12 | import java.util.HashMap;
13 | import java.util.HashSet;
14 | import java.util.Map;
15 | import java.util.Set;
16 |
17 | import giwi.org.networkgraph.beans.Dimension;
18 | import giwi.org.networkgraph.beans.NetworkGraph;
19 | import giwi.org.networkgraph.beans.Point2D;
20 |
21 | /**
22 | * The type Abstract layout.
23 | */
24 | abstract class AbstractLayout implements Layout {
25 |
26 | private Set dontmove = new HashSet<>();
27 |
28 | private Dimension size;
29 |
30 | private NetworkGraph graph;
31 |
32 | boolean initialized;
33 |
34 | private Map locations
35 | = LazyMap.lazyMap(new HashMap(), new Transformer() {
36 | public Point2D transform(Node arg0) {
37 | return new Point2D();
38 | }
39 | });
40 |
41 | /**
42 | * Creates an instance which does not initialize the vertex locations.
43 | *
44 | * @param graph the graph for which the layout algorithm is to be created.
45 | */
46 | AbstractLayout(NetworkGraph graph) {
47 | if (graph == null) {
48 | throw new IllegalArgumentException("NetworkGraph must be non-null");
49 | }
50 | this.graph = graph;
51 | }
52 |
53 | /**
54 | * Instantiates a new Abstract layout.
55 | *
56 | * @param graph the graph
57 | * @param initializer the initializer
58 | */
59 | @SuppressWarnings("unchecked")
60 | protected AbstractLayout(NetworkGraph graph, Transformer initializer) {
61 | this.graph = graph;
62 | Transformer chain
63 | = ChainedTransformer.chainedTransformer(new Transformer[]{initializer});
64 | this.locations
65 | = LazyMap.lazyMap(new HashMap(), (Transformer) chain);
66 | initialized = true;
67 | }
68 |
69 | /**
70 | * Instantiates a new Abstract layout.
71 | *
72 | * @param graph the graph
73 | * @param size the size
74 | */
75 | protected AbstractLayout(NetworkGraph graph, Dimension size) {
76 | this.graph = graph;
77 | this.size = size;
78 | }
79 |
80 | /**
81 | * Instantiates a new Abstract layout.
82 | *
83 | * @param graph the graph
84 | * @param initializer the initializer
85 | * @param size the size
86 | */
87 | AbstractLayout(NetworkGraph graph, Transformer initializer, Dimension size) {
88 | this.graph = graph;
89 | this.locations = LazyMap.lazyMap(new HashMap(), initializer);
90 | this.size = size;
91 | }
92 |
93 | /**
94 | * Sets graph.
95 | *
96 | * @param graph the graph
97 | */
98 | public void setGraph(NetworkGraph graph) {
99 | this.graph = graph;
100 | if (size != null && graph != null) {
101 | initialize();
102 | }
103 | }
104 |
105 | /**
106 | * When a visualization is resized, it presumably wants to fix the
107 | * locations of the vertices and possibly to reinitialize its data. The
108 | * current method calls initializeLocations followed by initialize_local.
109 | *
110 | * @param size the size
111 | */
112 | public void setSize(Dimension size) {
113 | if (size != null && graph != null) {
114 | Dimension oldSize = this.size;
115 | this.size = size;
116 | initialize();
117 | if (oldSize != null) {
118 | adjustLocations(oldSize, size);
119 | }
120 | }
121 | }
122 |
123 | /**
124 | * Adjust locations.
125 | *
126 | * @param oldSize the old size
127 | * @param size the size
128 | */
129 | private void adjustLocations(Dimension oldSize, Dimension size) {
130 | int xOffset = (size.getWidth() - oldSize.getWidth()) / 2;
131 | int yOffset = (size.getHeight() - oldSize.getHeight()) / 2;
132 | // now, move each vertex to be at the new screen center
133 | while (true) {
134 | try {
135 | for (Node v : getGraph().getNodes()) {
136 | offsetVertex(v, xOffset, yOffset);
137 | }
138 | break;
139 | } catch (ConcurrentModificationException cme) {
140 | Log.e(AbstractLayout.class.getName(), cme.getMessage());
141 | }
142 | }
143 | }
144 |
145 | /**
146 | * Is locked.
147 | *
148 | * @param v the v
149 | * @return the boolean
150 | */
151 | public boolean isLocked(Node v) {
152 | return dontmove.contains(v);
153 | }
154 |
155 | /**
156 | * Sets initializer.
157 | *
158 | * @param initializer the initializer
159 | */
160 | public void setInitializer(Transformer initializer) {
161 | if (this.equals(initializer)) {
162 | throw new IllegalArgumentException("Layout cannot be initialized with itself");
163 | }
164 | this.locations = LazyMap.lazyMap(new HashMap(), initializer);
165 | initialized = true;
166 | }
167 |
168 | /**
169 | * Returns the current size of the visualization space, according to the
170 | * last call to resize().
171 | *
172 | * @return the current size of the screen
173 | */
174 | public Dimension getSize() {
175 | return size;
176 | }
177 |
178 | /**
179 | * Returns the Coordinates object that stores the vertex' x and y location.
180 | *
181 | * @param v A Vertex that is a part of the NetworkGraph being visualized.
182 | * @return A Coordinates object with x and y locations.
183 | */
184 | private Point2D getCoordinates(Node v) {
185 | return locations.get(v);
186 | }
187 |
188 | /**
189 | * Transform point 2 d.
190 | *
191 | * @param v the v
192 | * @return the point 2 d
193 | */
194 | public Point2D transform(Node v) {
195 | return getCoordinates(v);
196 | }
197 |
198 | /**
199 | * Returns the x coordinate of the vertex from the Coordinates object.
200 | * in most cases you will be better off calling transform(v).
201 | *
202 | * @param v the v
203 | * @return the x
204 | */
205 | public double getX(Node v) {
206 | assert getCoordinates(v) != null : "Cannot getX for an unmapped vertex " + v;
207 | return getCoordinates(v).getX();
208 | }
209 |
210 | /**
211 | * Returns the y coordinate of the vertex from the Coordinates object.
212 | * In most cases you will be better off calling transform(v).
213 | *
214 | * @param v the v
215 | * @return the y
216 | */
217 | public double getY(Node v) {
218 | assert getCoordinates(v) != null : "Cannot getY for an unmapped vertex " + v;
219 | return getCoordinates(v).getY();
220 | }
221 |
222 | /**
223 | * Offset vertex.
224 | *
225 | * @param v the v
226 | * @param xOffset the x offset
227 | * @param yOffset the y offset
228 | */
229 | private void offsetVertex(Node v, double xOffset, double yOffset) {
230 | Point2D c = getCoordinates(v);
231 | c.setLocation(c.getX() + xOffset, c.getY() + yOffset);
232 | setLocation(v, c);
233 | }
234 |
235 | /**
236 | * Accessor for the graph that represets all vertices.
237 | *
238 | * @return the graph that contains all vertices.
239 | */
240 | public NetworkGraph getGraph() {
241 | return graph;
242 | }
243 |
244 | /**
245 | * Forcibly moves a vertex to the (x,y) location by setting its x and y
246 | * locations to the inputted location. Does not add the vertex to the
247 | * "dontmove" list, and (in the default implementation) does not make any
248 | * adjustments to the rest of the graph.
249 | *
250 | * @param picked the picked
251 | * @param x the x
252 | * @param y the y
253 | */
254 | public void setLocation(Node picked, double x, double y) {
255 | Point2D coord = getCoordinates(picked);
256 | coord.setLocation(x, y);
257 | }
258 |
259 | /**
260 | * Sets location.
261 | *
262 | * @param picked the picked
263 | * @param p the p
264 | */
265 | public void setLocation(Node picked, Point2D p) {
266 | Point2D coord = getCoordinates(picked);
267 | coord.setLocation(p);
268 | }
269 |
270 | /**
271 | * Locks {@code v} in place if {@code state} is {@code true}, otherwise unlocks it.
272 | *
273 | * @param v the v
274 | * @param state the state
275 | */
276 | public void lock(Node v, boolean state) {
277 | if (state) {
278 | dontmove.add(v);
279 | } else {
280 | dontmove.remove(v);
281 | }
282 | }
283 |
284 | /**
285 | * Locks all vertices in place if {@code lock} is {@code true}, otherwise unlocks all vertices.
286 | *
287 | * @param lock the lock
288 | */
289 | public void lock(boolean lock) {
290 | for (Node v : graph.getNodes()) {
291 | lock(v, lock);
292 | }
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/network-graph/src/main/java/giwi/org/networkgraph/layout/FRLayout.java:
--------------------------------------------------------------------------------
1 | package giwi.org.networkgraph.layout;
2 |
3 | import net.xqhs.graphs.graph.Edge;
4 | import net.xqhs.graphs.graph.Node;
5 |
6 | import org.apache.commons.collections4.Factory;
7 | import org.apache.commons.collections4.map.LazyMap;
8 |
9 | import android.util.Log;
10 |
11 | import java.util.ConcurrentModificationException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | import giwi.org.networkgraph.beans.Dimension;
16 | import giwi.org.networkgraph.beans.NetworkGraph;
17 | import giwi.org.networkgraph.beans.Point2D;
18 | import giwi.org.networkgraph.beans.RandomLocationTransformer;
19 |
20 | /**
21 | * The type FR layout.
22 | */
23 | public class FRLayout extends AbstractLayout {
24 |
25 | private double temperature;
26 |
27 | private int currentIteration;
28 |
29 | private int mMaxIterations = 700;
30 |
31 | private Map frVertexData = LazyMap.lazyMap(new HashMap(), new Factory() {
32 | public FRVertexData create() {
33 | return new FRVertexData();
34 | }
35 | });
36 |
37 | private double attraction_multiplier = 0.75;
38 |
39 | private double attraction_constant;
40 |
41 | private double repulsion_multiplier = 0.75;
42 |
43 | private double repulsion_constant;
44 |
45 | private double max_dimension;
46 |
47 | /**
48 | * Creates an instance for the specified graph.
49 | *
50 | * @param g the g
51 | */
52 | public FRLayout(NetworkGraph g) {
53 | super(g);
54 | }
55 |
56 | /**
57 | * Creates an instance of size {@code d} for the specified graph.
58 | *
59 | * @param g the g
60 | * @param d the d
61 | */
62 | public FRLayout(NetworkGraph g, Dimension d) {
63 | super(g, new RandomLocationTransformer(d), d);
64 | initialize();
65 | max_dimension = Math.max(d.getHeight(), d.getWidth());
66 | }
67 |
68 | /**
69 | * Sets size.
70 | *
71 | * @param size the size
72 | */
73 | @Override
74 | public void setSize(Dimension size) {
75 | if (!initialized) {
76 | setInitializer(new RandomLocationTransformer(size));
77 | }
78 | super.setSize(size);
79 | max_dimension = Math.max(size.getHeight(), size.getWidth());
80 | }
81 |
82 | /**
83 | * Sets the attraction multiplier.
84 | *
85 | * @param attraction the attraction
86 | */
87 | public void setAttractionMultiplier(double attraction) {
88 | this.attraction_multiplier = attraction;
89 | }
90 |
91 | /**
92 | * Sets the repulsion multiplier.
93 | *
94 | * @param repulsion the repulsion
95 | */
96 | public void setRepulsionMultiplier(double repulsion) {
97 | this.repulsion_multiplier = repulsion;
98 | }
99 |
100 | /**
101 | * Reset void.
102 | */
103 | public void reset() {
104 | doInit();
105 | }
106 |
107 | /**
108 | * Initialize void.
109 | */
110 | public void initialize() {
111 | doInit();
112 | }
113 |
114 | /**
115 | * Sets graph.
116 | *
117 | * @param graph the graph
118 | */
119 | @Override
120 | public void setGraph(final NetworkGraph graph) {
121 | super.setGraph(graph);
122 | }
123 |
124 | /**
125 | * Do init.
126 | */
127 | private void doInit() {
128 | NetworkGraph graph = getGraph();
129 | Dimension d = getSize();
130 | if (graph != null && d != null) {
131 | currentIteration = 0;
132 | temperature = d.getWidth() / 10;
133 | double forceConstant = Math.sqrt(d.getHeight() * d.getWidth() / graph.getVertex().size());
134 | attraction_constant = attraction_multiplier * forceConstant;
135 | repulsion_constant = repulsion_multiplier * forceConstant;
136 | }
137 | }
138 |
139 | /**
140 | * The EPSILON.
141 | */
142 | private double EPSILON = 0.000001D;
143 |
144 | /**
145 | * Moves the iteration forward one notch, calculation attraction and
146 | * repulsion between vertices and edges and cooling the temperature.
147 | */
148 | public synchronized void step() {
149 | currentIteration++;
150 | while (true) {
151 | try {
152 | for (Node v1 : getGraph().getNodes()) {
153 | calcRepulsion(v1);
154 | }
155 | break;
156 | } catch (ConcurrentModificationException cme) {
157 | Log.e(FRLayout.class.getName(), cme.getMessage());
158 | }
159 | }
160 | while (true) {
161 | try {
162 | for (Edge e : getGraph().getEdges()) {
163 |
164 | calcAttraction(e);
165 | }
166 | break;
167 | } catch (ConcurrentModificationException cme) {
168 | Log.e(FRLayout.class.getName(), cme.getMessage());
169 | }
170 | }
171 | while (true) {
172 | try {
173 | for (Node v : getGraph().getNodes()) {
174 | if (isLocked(v)) {
175 | continue;
176 | }
177 | calcPositions(v);
178 | }
179 | break;
180 | } catch (ConcurrentModificationException cme) {
181 | Log.e(FRLayout.class.getName(), cme.getMessage());
182 | }
183 | }
184 | cool();
185 | }
186 |
187 | /**
188 | * Calc positions.
189 | *
190 | * @param v the v
191 | */
192 | private synchronized void calcPositions(Node v) {
193 | FRVertexData fvd = getFRData(v);
194 | if (fvd == null) {
195 | return;
196 | }
197 | Point2D xyd = transform(v);
198 | double deltaLength = Math.max(EPSILON, fvd.norm());
199 | double newXDisp = fvd.getX() / deltaLength * Math.min(deltaLength, temperature);
200 | if (Double.isNaN(newXDisp)) {
201 | throw new IllegalArgumentException("Unexpected mathematical result in FRLayout:calcPositions [xdisp]");
202 | }
203 | double newYDisp = fvd.getY() / deltaLength * Math.min(deltaLength, temperature);
204 | xyd.setLocation(xyd.getX() + newXDisp, xyd.getY() + newYDisp);
205 | double borderWidth = getSize().getWidth() / 50.0;
206 | double newXPos = xyd.getX();
207 | if (newXPos < borderWidth) {
208 | newXPos = borderWidth + Math.random() * borderWidth * 2.0;
209 | } else {
210 | if (newXPos > (getSize().getWidth() - borderWidth)) {
211 | newXPos = getSize().getWidth() - borderWidth - Math.random() * borderWidth * 2.0;
212 | }
213 | }
214 |
215 | double newYPos = xyd.getY();
216 | if (newYPos < borderWidth) {
217 | newYPos = borderWidth + Math.random() * borderWidth * 2.0;
218 | } else {
219 | if (newYPos > (getSize().getHeight() - borderWidth)) {
220 | newYPos = getSize().getHeight() - borderWidth - Math.random() * borderWidth * 2.0;
221 | }
222 | }
223 | xyd.setLocation(newXPos, newYPos);
224 | }
225 |
226 | /**
227 | * Calc attraction.
228 | *
229 | * @param e the e
230 | */
231 | private void calcAttraction(Edge e) {
232 | Node v1 = e.getFrom();
233 | Node v2 = e.getTo();
234 | boolean v1_locked = isLocked(v1);
235 | boolean v2_locked = isLocked(v2);
236 |
237 | if (v1_locked && v2_locked) {
238 | // both locked, do nothing
239 | return;
240 | }
241 | Point2D p1 = transform(v1);
242 | Point2D p2 = transform(v2);
243 | if (p1 == null || p2 == null) {
244 | return;
245 | }
246 | double xDelta = p1.getX() - p2.getX();
247 | double yDelta = p1.getY() - p2.getY();
248 |
249 | double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta) + (yDelta * yDelta)));
250 |
251 | double force = (deltaLength * deltaLength) / attraction_constant;
252 |
253 | if (Double.isNaN(force)) {
254 | throw new IllegalArgumentException("Unexpected mathematical result in FRLayout:calcPositions [force]");
255 | }
256 |
257 | double dx = (xDelta / deltaLength) * force;
258 | double dy = (yDelta / deltaLength) * force;
259 | if (!v1_locked) {
260 | FRVertexData fvd1 = getFRData(v1);
261 | fvd1.offset(-dx, -dy);
262 | }
263 | if (!v2_locked) {
264 | FRVertexData fvd2 = getFRData(v2);
265 | fvd2.offset(dx, dy);
266 | }
267 | }
268 |
269 | /**
270 | * Calc repulsion.
271 | *
272 | * @param v1 the v 1
273 | */
274 | private void calcRepulsion(Node v1) {
275 | FRVertexData fvd1 = getFRData(v1);
276 | if (fvd1 == null) {
277 | return;
278 | }
279 | fvd1.setLocation(0, 0);
280 |
281 | try {
282 | for (Node v2 : getGraph().getNodes()) {
283 | // if (isLocked(v2)) continue;
284 | if (v1 != v2) {
285 | Point2D p1 = transform(v1);
286 | Point2D p2 = transform(v2);
287 | if (p1 == null || p2 == null) {
288 | continue;
289 | }
290 | double xDelta = p1.getX() - p2.getX();
291 | double yDelta = p1.getY() - p2.getY();
292 |
293 | double deltaLength = Math.max(EPSILON, Math.sqrt((xDelta * xDelta) + (yDelta * yDelta)));
294 |
295 | double force = (repulsion_constant * repulsion_constant) / deltaLength;
296 |
297 | if (Double.isNaN(force)) {
298 | throw new RuntimeException("Unexpected mathematical result in FRLayout:calcPositions [repulsion]");
299 | }
300 | fvd1.offset((xDelta / deltaLength) * force, (yDelta / deltaLength) * force);
301 | }
302 | }
303 | } catch (ConcurrentModificationException cme) {
304 | calcRepulsion(v1);
305 | }
306 | }
307 |
308 | /**
309 | * Cool void.
310 | */
311 | private void cool() {
312 | temperature *= (1.0 - currentIteration / (double) mMaxIterations);
313 | }
314 |
315 | /**
316 | * Sets the maximum number of iterations.
317 | *
318 | * @param maxIterations the max iterations
319 | */
320 | public void setMaxIterations(int maxIterations) {
321 | mMaxIterations = maxIterations;
322 | }
323 |
324 | /**
325 | * Gets fR data.
326 | *
327 | * @param v the v
328 | * @return the fR data
329 | */
330 | private FRVertexData getFRData(Node v) {
331 | return frVertexData.get(v);
332 | }
333 |
334 | /**
335 | * This one is an incremental visualization.
336 | *
337 | * @return the boolean
338 | */
339 | public boolean isIncremental() {
340 | return true;
341 | }
342 |
343 | /**
344 | * Returns true once the current iteration has passed the maximum count,
345 | * MAX_ITERATIONS.
346 | *
347 | * @return the boolean
348 | */
349 | public boolean done() {
350 | return currentIteration > mMaxIterations || temperature < 1.0 / max_dimension;
351 | }
352 |
353 | /**
354 | * The type FR vertex data.
355 | */
356 | private static class FRVertexData extends Point2D {
357 |
358 | /**
359 | * Offset void.
360 | *
361 | * @param x the x
362 | * @param y the y
363 | */
364 | void offset(double x, double y) {
365 | this.x += x;
366 | this.y += y;
367 | }
368 |
369 | /**
370 | * Norm double.
371 | *
372 | * @return the double
373 | */
374 | double norm() {
375 | return Math.sqrt(x * x + y * y);
376 | }
377 | }
378 | }
--------------------------------------------------------------------------------