Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions gephi-desktop/docs/Plugins/Appearance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
id: Appearance
title: Appearance
sidebar_position: 12
---

Appearance transformers add ways to map graph values to visual properties such as node size, element color, label size, or label color. They change graph element appearance; they are not Preview renderers.

Use direct Graph API mutations when your plugin applies one particular style as part of its own command. Implement a transformer when users should choose a new reusable mapping from Gephi's Appearance panel.

Add `graph-api` for direct styling. Add `appearance-api` and `org-openide-util-lookup` when contributing a transformer.

## Apply an appearance from your own command

For a fixed operation, change the visible elements under the appropriate graph lock:

```java
Graph graph = graphModel.getGraphVisible();
graph.writeLock();
try {
for (Node node : graph.getNodes()) {
node.setColor(new Color(31, 119, 180));
node.setSize(12f);
}
} finally {
graph.writeUnlock();
}
```

Resolve `graphModel` for the current workspace when the operation starts. Use the visible graph when the command promises to style the filtered result, and the complete graph only when it explicitly promises to style all elements. This path applies a style; it does not add a new mapping to the Appearance panel.

## Choose the mapping type

- `SimpleTransformer<E>` applies one configured style to every eligible element.
- `RankingTransformer<E>` receives a numeric value and its normalized position from 0 to 1.
- `PartitionTransformer<E>` receives a categorical value and its partition configuration.

All three also implement `Transformer`, whose `isNode()` and `isEdge()` methods declare their target.

## A ranking transformer

This service maps a normalized number to node opacity:

```java
@ServiceProvider(service = Transformer.class)
public final class RankingNodeOpacityTransformer
implements RankingTransformer<Node> {
private float minimum = 0.2f;
private float maximum = 1f;

@Override
public void transform(
Node node, Ranking ranking,
Number value, float normalizedValue) {
float opacity = minimum
+ normalizedValue * (maximum - minimum);
Color color = node.getColor();
node.setColor(new Color(
color.getRed(), color.getGreen(), color.getBlue(),
Math.round(opacity * 255f)));
}

@Override public boolean isNode() { return true; }
@Override public boolean isEdge() { return false; }

public float getMinimum() { return minimum; }
public void setMinimum(float value) { minimum = clamp(value); }
public float getMaximum() { return maximum; }
public void setMaximum(float value) { maximum = clamp(value); }
private float clamp(float value) {
return Math.max(0f, Math.min(1f, value));
}
}
```

The Appearance engine supplies the value and normalized value. Do not scan the graph inside `transform()`; it is called once per element. Decide how missing values behave in the associated function/UI and keep the transformer itself quick.

## Make it visible in the Appearance panel

A transformer service performs the operation. A matching singleton `TransformerUI<T>` describes how it appears and configures the current `Function`:

- `getTransformerClass()` returns the exact transformer class.
- `getCategory()` selects or defines the visual category.
- `getPanel(Function)` returns controls for the current ranking, partition, or simple function.
- Name, description, icon, and registration `position` control presentation.
- Optional control buttons add compact actions; `onApply(Function)` reacts after application.

Register it separately:

```java
@ServiceProvider(service = TransformerUI.class, position = 2000)
public final class RankingNodeOpacityUI
implements TransformerUI<RankingNodeOpacityTransformer> {
// Implement the TransformerUI contract and return
// RankingNodeOpacityTransformer.class from getTransformerClass().
}
```

Because `TransformerUI` is a singleton, store no workspace-specific function in a field after the panel is done with it. Configure the transformer attached to the supplied `Function`, following the current built-in Appearance UIs for the exact function-to-transformer access pattern.

## Ranking and partition design

For ranking, validate the endpoints and define interpolation clearly. Normalized input can still encounter a zero-range column; do not divide by the raw range yourself when Gephi already supplies `normalizedValue`.

For partitions, choose a deterministic value-to-style mapping and provide a stable fallback for null or newly appearing categories. Do not use `toString()` as a persistent category identity unless the underlying type guarantees it.

## Appearance checklist

- Node/edge applicability is correct.
- The transformer implements exactly the simple, ranking, or partition contract it needs.
- Per-element work is constant-time and has no graph-wide scans.
- Minimum/maximum and missing values have explicit behavior.
- A matching `TransformerUI` is registered and does not retain workspace state.
- Changes are tested in Overview, Preview, and export where those visual properties should carry through.

Use Gephi 0.11.2's [AppearanceAPI](https://github.com/gephi/gephi/tree/v0.11.2/modules/AppearanceAPI) for contracts and [AppearancePlugin](https://github.com/gephi/gephi/tree/v0.11.2/modules/AppearancePlugin) for built-in color and size transformers.
157 changes: 157 additions & 0 deletions gephi-desktop/docs/Plugins/Desktop_UI_and_Events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
---
id: Desktop_UI_and_Events
title: Desktop UI and events
sidebar_position: 4
---

Choose a desktop extension by how users should encounter it. A one-shot operation belongs in an action; direct graph-canvas interaction belongs in a tool; persistent controls or results belong in a `TopComponent`. Keep the calculation behind that UI in a separate class.

## Pick the right surface

| User interaction | Recommended mechanism |
| --- | --- |
| Click a menu or toolbar command | NetBeans Platform action annotations |
| Click or drag nodes in Overview | Gephi `Tool` and event-listener SPIs |
| Keep settings/results visible | NetBeans `TopComponent` |
| Act when a workspace changes | `WorkspaceListener` |
| Run once after the whole UI is ready | `ModuleInstall` plus `WindowManager.invokeWhenUIReady` |
| Change a Data Laboratory row, cell, or column | A Data Laboratory manipulator |

Do not create a full window for a command that needs no persistent state. Conversely, do not hide an interactive workflow behind a chain of modal dialogs.

## Add a menu action

NetBeans annotations generate registration metadata at compile time:

```java
@ActionID(category = "Tools", id = "org.example.plugin.CountNodesAction")
@ActionRegistration(displayName = "Count nodes")
@ActionReference(path = "Menu/Plugins", position = 100)
public final class CountNodesAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent event) {
GraphModel model = Lookup.getDefault()
.lookup(GraphController.class)
.getGraphModel();

if (model == null) {
DialogDisplayer.getDefault().notify(
new NotifyDescriptor.Message("Open a workspace first."));
return;
}

int count = model.getGraphVisible().getNodeCount();
DialogDisplayer.getDefault().notify(
new NotifyDescriptor.Message("Visible nodes: " + count));
}
}
```

Add the NetBeans modules that own `org.openide.awt`, `org.openide.dialogs`, and `org.openide.util` packages. Use bundle keys for production display text. An action listener runs on the EDT: if the work is more than a quick lookup, launch a [long task](./Graph_API_and_Threads.md#keep-swing-responsive).

## Add a persistent window

A `TopComponent` is a dockable NetBeans window. The annotations below register it under **Window** and place it in an existing mode:

```java
@TopComponent.Description(
preferredID = "MyResultsTopComponent",
persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "filtersmode", openAtStartup = false)
@ActionID(category = "Window", id = "org.example.plugin.MyResultsTopComponent")
@ActionReference(path = "Menu/Window")
@TopComponent.OpenActionRegistration(
displayName = "My results",
preferredID = "MyResultsTopComponent"
)
public final class MyResultsTopComponent extends TopComponent {
public MyResultsTopComponent() {
setName("My results");
setLayout(new BorderLayout());
add(buildContent(), BorderLayout.CENTER);
}
}
```

Treat the window as a view, not as the data model:

- Resolve the current workspace when Run is clicked or when a workspace event arrives.
- Store results with the workspace that produced them; clear stale results on `disable()` or `close()`.
- Start observers or timers in `componentOpened()` and destroy them in `componentClosed()`.
- Do not start a graph-wide scan in the constructor.
- Use `SwingUtilities.invokeLater` for UI updates arriving from other threads.

Generated `.form` files are optional. Hand-written Swing panels are often easier to review and merge; GUI-builder forms are useful for complex fixed layouts.

## Add a graph interaction tool

A tool appears in the Overview toolbar and can receive canvas or node events. Implement `org.gephi.tools.spi.Tool`, register it as a service, and provide a `ToolUI`:

```java
@ServiceProvider(service = Tool.class)
public final class InspectTool implements Tool {
private final ToolUI ui = new InspectToolUI();

@Override public void select() { }
@Override public void unselect() { }

@Override
public ToolEventListener[] getListeners() {
return new ToolEventListener[] {
(NodeClickEventListener) nodes -> {
inspect(nodes);
return false;
}
};
}

@Override public ToolUI getUI() { return ui; }

@Override
public ToolSelectionType getSelectionType() {
return ToolSelectionType.SELECTION;
}
}
```

`ToolUI` supplies the name, description, icon, ordering position, and optional properties-bar panel. Event listeners should capture the event and return quickly. If inspection is expensive, copy stable node IDs and delegate the work; do not retain arbitrary node arrays indefinitely across graph changes.

Use public `ToolsAPI` and `VisualizationAPI` types. Older examples sometimes call internal visualization controllers directly; prefer public services in 0.11.2 so the plugin is not tied to an implementation.

## Capture lifecycle events

Use `WorkspaceListener` for project context; its complete pattern is shown in [Graph access and long tasks](./Graph_API_and_Threads.md#react-to-workspace-changes). Register it with `@ServiceProvider(service = WorkspaceListener.class)`.

For a single action after application startup, extend `ModuleInstall` and register the class in `manifest.mf`:

```text
OpenIDE-Module-Install: org/example/plugin/Installer.class
```

```java
public final class Installer extends ModuleInstall {
@Override
public void restored() {
WindowManager.getDefault().invokeWhenUIReady(() -> {
// Initialize UI integration only; do not perform heavy work here.
});
}
}
```

Startup hooks should be rare. Lazy initialization on first use makes Gephi start faster and avoids work for users who never open the plugin.

## Internationalization and accessibility

Place display text in `Bundle.properties` and retrieve it with `NbBundle`. Give controls accessible names and tooltips, associate each label with its input, preserve keyboard navigation, and never communicate state through color alone. User-visible errors should explain what happened and what the user can do next.

## UI review checklist

- The entry point is discoverable in the expected Gephi area.
- Controls are disabled when no workspace or valid selection exists.
- Run cannot be started twice; Cancel is available for long work.
- The window remains correct when users switch, close, or create workspaces.
- No internal implementation API is used where a public API exists.
- All listeners and workers have a clear owner and cleanup point.
- UI state is changed only on the EDT.
Loading