2
Copyright 2008-2011 Gephi
3
Authors : Yudi Xue <yudi.xue@usask.ca>, Mathieu Bastian
4
Website : http://www.gephi.org
6
This file is part of Gephi.
8
Gephi is free software: you can redistribute it and/or modify
9
it under the terms of the GNU Affero General Public License as
10
published by the Free Software Foundation, either version 3 of the
11
License, or (at your option) any later version.
13
Gephi is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU Affero General Public License for more details.
18
You should have received a copy of the GNU Affero General Public License
19
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
21
package org.gephi.preview.plugin.renderers;
23
import java.awt.Color;
24
import java.util.Locale;
25
import org.gephi.graph.api.Edge;
26
import org.gephi.graph.api.Node;
27
import org.gephi.preview.api.Item;
28
import org.gephi.preview.api.PreviewModel;
29
import org.gephi.preview.api.PreviewProperties;
30
import org.gephi.preview.api.PreviewProperty;
31
import org.gephi.preview.api.ProcessingTarget;
32
import org.gephi.preview.api.RenderTarget;
33
import org.gephi.preview.api.SVGTarget;
34
import org.gephi.preview.plugin.items.EdgeItem;
35
import org.gephi.preview.plugin.items.NodeItem;
36
import org.gephi.preview.spi.Renderer;
37
import org.gephi.preview.types.EdgeColor;
38
import org.openide.util.NbBundle;
39
import org.openide.util.lookup.ServiceProvider;
40
import org.w3c.dom.Element;
41
import processing.core.PGraphics;
42
import processing.core.PVector;
46
* @author Yudi Xue, Mathieu Bastian
48
@ServiceProvider(service = Renderer.class, position = 100)
49
public class EdgeRenderer implements Renderer {
52
public static final String EDGE_MIN_WEIGHT = "edge.min-weight";
53
public static final String EDGE_MAX_WEIGHT = "edge.max-weight";
54
public static final String BEZIER_CURVENESS = "edge.bezier-curveness";
55
public static final String SOURCE = "edge.source";
56
public static final String TARGET = "edge.target";
57
public static final String TARGET_RADIUS = "edge.target.radius";
59
private boolean defaultShowEdges = true;
60
private float defaultThickness = 1;
61
private boolean defaultRescaleWeight = true;
62
private EdgeColor defaultColor = new EdgeColor(EdgeColor.Mode.MIXED);
63
private boolean defaultEdgeCurved = true;
64
private float defaultBezierCurviness = 0.2f;
65
private int defaultOpacity = 100;
67
public void preProcess(PreviewModel previewModel) {
68
PreviewProperties properties = previewModel.getProperties();
69
Item[] edgeItems = previewModel.getItems(Item.EDGE);
71
//Put nodes in edge item
72
for (Item item : edgeItems) {
73
Edge edge = (Edge) item.getSource();
74
Node source = edge.getSource().getNodeData().getRootNode();
75
Node target = edge.getTarget().getNodeData().getRootNode();
76
Item nodeSource = previewModel.getItem(Item.NODE, source);
77
Item nodeTarget = previewModel.getItem(Item.NODE, target);
78
item.setData(SOURCE, nodeSource);
79
item.setData(TARGET, nodeTarget);
82
//Calculate max and min weight
83
float minWeight = Float.POSITIVE_INFINITY;
84
float maxWeight = Float.NEGATIVE_INFINITY;
86
for (Item edge : edgeItems) {
87
minWeight = Math.min(minWeight, (Float) edge.getData(EdgeItem.WEIGHT));
88
maxWeight = Math.max(maxWeight, (Float) edge.getData(EdgeItem.WEIGHT));
90
properties.putValue(EDGE_MIN_WEIGHT, minWeight);
91
properties.putValue(EDGE_MAX_WEIGHT, maxWeight);
93
//Put bezier curveness in properties
94
if (!properties.hasProperty(BEZIER_CURVENESS)) {
95
properties.putValue(BEZIER_CURVENESS, defaultBezierCurviness);
98
//Rescale weight if necessary - and avoid negative weights
99
boolean rescaleWeight = properties.getBooleanValue(PreviewProperty.EDGE_RESCALE_WEIGHT);
100
for (Item item : edgeItems) {
101
float weight = (Float) item.getData(EdgeItem.WEIGHT);
105
if (!Double.isInfinite(minWeight) && !Double.isInfinite(maxWeight) && maxWeight != minWeight) {
106
float ratio = 1f / (maxWeight - minWeight);
107
weight = (weight - minWeight) * ratio;
109
} else if (minWeight <= 0) {
110
//Avoid negative weight
111
weight += Math.abs(minWeight) + 1;
113
//Multiply by thickness
114
weight *= properties.getFloatValue(PreviewProperty.EDGE_THICKNESS);
115
item.setData(EdgeItem.WEIGHT, weight);
119
for (Item item : edgeItems) {
120
if ((Boolean) item.getData(EdgeItem.DIRECTED) && !(Boolean) item.getData(EdgeItem.SELF_LOOP)) {
121
Item targetItem = (Item) item.getData(TARGET);
122
Float weight = item.getData(EdgeItem.WEIGHT);
123
float radius = properties.getFloatValue(PreviewProperty.ARROW_RADIUS);
124
float size = properties.getFloatValue(PreviewProperty.ARROW_SIZE) * weight;
125
radius = -(radius + (Float) targetItem.getData(NodeItem.SIZE) / 2f + properties.getFloatValue(PreviewProperty.NODE_BORDER_WIDTH));
126
item.setData(TARGET_RADIUS, radius - size);
131
public void render(Item item, RenderTarget target, PreviewProperties properties) {
133
Item sourceItem = item.getData(SOURCE);
134
Item targetItem = item.getData(TARGET);
137
Float weight = item.getData(EdgeItem.WEIGHT);
138
EdgeColor edgeColor = (EdgeColor) properties.getValue(PreviewProperty.EDGE_COLOR);
139
Color color = edgeColor.getColor((Color) item.getData(EdgeItem.COLOR),
140
(Color) sourceItem.getData(NodeItem.COLOR),
141
(Color) targetItem.getData(NodeItem.COLOR));
142
int alpha = (int) ((properties.getIntValue(PreviewProperty.EDGE_OPACITY) / 100f) * 255f);
143
color = new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha);
145
if (sourceItem == targetItem) {
146
renderSelfLoop(sourceItem, weight, color, properties, target);
147
} else if (properties.getBooleanValue(PreviewProperty.EDGE_CURVED)) {
148
renderCurvedEdge(item, sourceItem, targetItem, weight, color, properties, target);
150
renderStraightEdge(item, sourceItem, targetItem, weight, color, properties, target);
154
public void renderSelfLoop(Item nodeItem, float thickness, Color color, PreviewProperties properties, RenderTarget renderTarget) {
155
Float x = nodeItem.getData(NodeItem.X);
156
Float y = nodeItem.getData(NodeItem.Y);
157
Float size = nodeItem.getData(NodeItem.SIZE);
158
Node node = (Node) nodeItem.getSource();
160
PVector v1 = new PVector(x, y);
161
v1.add(size, -size, 0);
163
PVector v2 = new PVector(x, y);
164
v2.add(size, size, 0);
166
if (renderTarget instanceof ProcessingTarget) {
167
PGraphics graphics = ((ProcessingTarget) renderTarget).getGraphics();
168
graphics.strokeWeight(thickness);
169
graphics.stroke(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
171
graphics.bezier(x, y, v1.x, v1.y, v1.x, v2.y, x, y);
172
} else if (renderTarget instanceof SVGTarget) {
173
SVGTarget svgTarget = (SVGTarget) renderTarget;
175
Element selfLoopElem = svgTarget.createElement("path");
176
selfLoopElem.setAttribute("d", String.format(Locale.ENGLISH, "M %f,%f C %f,%f %f,%f %f,%f",
177
x, y, v1.x, v1.y, v2.x, v2.y, x, y));
178
selfLoopElem.setAttribute("class", node.getNodeData().getId());
179
selfLoopElem.setAttribute("stroke", svgTarget.toHexString(color));
180
selfLoopElem.setAttribute("stroke-opacity", (color.getAlpha() / 255f) + "");
181
selfLoopElem.setAttribute("stroke-width", Float.toString(thickness * svgTarget.getScaleRatio()));
182
selfLoopElem.setAttribute("fill", "none");
183
svgTarget.getTopElement(SVGTarget.TOP_EDGES).appendChild(selfLoopElem);
187
public void renderCurvedEdge(Item edgeItem, Item sourceItem, Item targetItem, float thickness, Color color, PreviewProperties properties, RenderTarget renderTarget) {
188
Edge edge = (Edge) edgeItem.getSource();
189
Float x1 = sourceItem.getData(NodeItem.X);
190
Float x2 = targetItem.getData(NodeItem.X);
191
Float y1 = sourceItem.getData(NodeItem.Y);
192
Float y2 = targetItem.getData(NodeItem.Y);
195
PVector direction = new PVector(x2, y2);
196
direction.sub(new PVector(x1, y1));
197
float length = direction.mag();
198
direction.normalize();
200
float factor = properties.getFloatValue(BEZIER_CURVENESS) * length;
202
// normal vector to the edge
203
PVector n = new PVector(direction.y, -direction.x);
206
// first control point
207
PVector v1 = new PVector(direction.x, direction.y);
209
v1.add(new PVector(x1, y1));
212
// second control point
213
PVector v2 = new PVector(direction.x, direction.y);
215
v2.add(new PVector(x2, y2));
218
if (renderTarget instanceof ProcessingTarget) {
219
PGraphics graphics = ((ProcessingTarget) renderTarget).getGraphics();
220
graphics.strokeWeight(thickness);
221
graphics.stroke(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
223
graphics.bezier(x1, y1, v1.x, v1.y, v2.x, v2.y, x2, y2);
224
} else if (renderTarget instanceof SVGTarget) {
225
SVGTarget svgTarget = (SVGTarget) renderTarget;
226
Element edgeElem = svgTarget.createElement("path");
227
edgeElem.setAttribute("class", edge.getSource().getNodeData().getId() + " " + edge.getTarget().getNodeData().getId());
228
edgeElem.setAttribute("d", String.format(Locale.ENGLISH, "M %f,%f C %f,%f %f,%f %f,%f",
229
x1, y1, v1.x, v1.y, v2.x, v2.y, x2, y2));
230
edgeElem.setAttribute("stroke", svgTarget.toHexString(color));
231
edgeElem.setAttribute("stroke-width", Float.toString(thickness * svgTarget.getScaleRatio()));
232
edgeElem.setAttribute("stroke-opacity", (color.getAlpha() / 255f) + "");
233
edgeElem.setAttribute("fill", "none");
234
svgTarget.getTopElement(SVGTarget.TOP_EDGES).appendChild(edgeElem);
238
public void renderStraightEdge(Item edgeItem, Item sourceItem, Item targetItem, float thickness, Color color, PreviewProperties properties, RenderTarget renderTarget) {
239
Edge edge = (Edge) edgeItem.getSource();
240
Float x1 = sourceItem.getData(NodeItem.X);
241
Float x2 = targetItem.getData(NodeItem.X);
242
Float y1 = sourceItem.getData(NodeItem.Y);
243
Float y2 = targetItem.getData(NodeItem.Y);
245
//Target radius - to start at the base of the arrow
246
Float targetRadius = edgeItem.getData(TARGET_RADIUS);
247
if (targetRadius != 0) {
248
PVector direction = new PVector(x2, y2);
249
direction.sub(new PVector(x1, y1));
250
direction.normalize();
251
direction = new PVector(direction.x, direction.y);
252
direction.mult(targetRadius);
253
direction.add(new PVector(x2, y2));
258
if (renderTarget instanceof ProcessingTarget) {
259
PGraphics graphics = ((ProcessingTarget) renderTarget).getGraphics();
260
graphics.strokeWeight(thickness);
261
graphics.strokeCap(PGraphics.SQUARE);
262
graphics.stroke(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
264
graphics.line(x1, y1, x2, y2);
265
} else if (renderTarget instanceof SVGTarget) {
266
SVGTarget svgTarget = (SVGTarget) renderTarget;
267
Element edgeElem = svgTarget.createElement("path");
268
edgeElem.setAttribute("class", edge.getSource().getNodeData().getId() + " " + edge.getTarget().getNodeData().getId());
269
edgeElem.setAttribute("d", String.format(Locale.ENGLISH, "M %f,%f L %f,%f",
271
edgeElem.setAttribute("stroke", svgTarget.toHexString(color));
272
edgeElem.setAttribute("stroke-width", Float.toString(thickness * svgTarget.getScaleRatio()));
273
edgeElem.setAttribute("stroke-opacity", (color.getAlpha() / 255f) + "");
274
edgeElem.setAttribute("fill", "none");
275
svgTarget.getTopElement(SVGTarget.TOP_EDGES).appendChild(edgeElem);
279
public PreviewProperty[] getProperties() {
280
return new PreviewProperty[]{
281
PreviewProperty.createProperty(this, PreviewProperty.SHOW_EDGES, Boolean.class,
282
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.display.displayName"),
283
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.display.description"),
284
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category")).setValue(defaultShowEdges),
285
PreviewProperty.createProperty(this, PreviewProperty.EDGE_THICKNESS, Float.class,
286
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.thickness.displayName"),
287
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.thickness.description"),
288
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category"), PreviewProperty.SHOW_EDGES).setValue(defaultThickness),
289
PreviewProperty.createProperty(this, PreviewProperty.EDGE_RESCALE_WEIGHT, Boolean.class,
290
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.rescaleWeight.displayName"),
291
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.rescaleWeight.description"),
292
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category"), PreviewProperty.SHOW_EDGES).setValue(defaultRescaleWeight),
293
PreviewProperty.createProperty(this, PreviewProperty.EDGE_COLOR, EdgeColor.class,
294
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.color.displayName"),
295
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.color.description"),
296
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category"), PreviewProperty.SHOW_EDGES).setValue(defaultColor),
297
PreviewProperty.createProperty(this, PreviewProperty.EDGE_OPACITY, Float.class,
298
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.opacity.displayName"),
299
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.opacity.description"),
300
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category"), PreviewProperty.SHOW_EDGES).setValue(defaultOpacity),
301
PreviewProperty.createProperty(this, PreviewProperty.EDGE_CURVED, Boolean.class,
302
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.curvedEdges.displayName"),
303
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.property.curvedEdges.description"),
304
NbBundle.getMessage(EdgeRenderer.class, "EdgeRenderer.category"), PreviewProperty.SHOW_EDGES).setValue(defaultEdgeCurved)};
307
public boolean isRendererForitem(Item item, PreviewProperties properties) {
308
if (item instanceof EdgeItem && properties.getBooleanValue(PreviewProperty.SHOW_EDGES)
309
&& !properties.getBooleanValue(PreviewProperty.MOVING)) {