Skip to content

[fix] Prevent Overlapping of Clusters in netjsongraph.js #171 #349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 82 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
38a34d4
Refactor CSS for consistency and clarity
cestercian Mar 1, 2025
5ad2983
Merge branch 'master' into master
cestercian Mar 5, 2025
f314caa
Create netjson-cluster-overlap.html
cestercian Mar 12, 2025
09005af
Update netjsongraph.util.js
cestercian Mar 12, 2025
02682ae
Merge branch 'master' of https://github.com/cestercian/netjsongraph.js
cestercian Mar 12, 2025
c02b4ad
Revert "Create netjson-cluster-overlap.html"
cestercian Mar 12, 2025
659b2d6
Reapply "Create netjson-cluster-overlap.html"
cestercian Mar 12, 2025
61fe412
Revert "Reapply "Create netjson-cluster-overlap.html""
cestercian Mar 12, 2025
ff949d7
Reapply "Reapply "Create netjson-cluster-overlap.html""
cestercian Mar 19, 2025
d62d97b
Update index.html
cestercian Mar 19, 2025
a84b1fe
Create netjson-cluster-overlap.html
cestercian Mar 19, 2025
cc0722d
Update netjson-cluster-overlap.html
cestercian Mar 20, 2025
ffc2a0c
Update index.html
cestercian Mar 20, 2025
61a9d91
Update index.html
cestercian Mar 22, 2025
87e82af
Merge branch 'master' into master
cestercian Mar 22, 2025
d3c5c82
Update netjson-cluster-overlap.html
cestercian Mar 22, 2025
882b22c
Update netjson-cluster-overlap.html
cestercian Mar 22, 2025
4579f8b
Update netjson-cluster-overlap.html
cestercian Mar 22, 2025
fc182c7
Update netjson-cluster-overlap.html
cestercian Mar 22, 2025
3f38efd
Update netjson-cluster-overlap.html
cestercian Mar 23, 2025
09f2814
Merge branch 'master' into master
nemesifier Mar 24, 2025
3c0e099
Update netjson-cluster-overlap.html
cestercian Mar 25, 2025
4deeb11
Update netjson-cluster-overlap.html
cestercian Mar 26, 2025
9a23460
Update netjson-cluster-overlap.html
cestercian Mar 26, 2025
9477fe2
Create clusterUtils.js
cestercian Mar 26, 2025
7c2359a
Update netjson-cluster-overlap.html
cestercian Mar 26, 2025
68d9cf9
Update README.md
cestercian Mar 26, 2025
662f6f8
Update README.md
cestercian Mar 26, 2025
52592f6
Update netjson-cluster-overlap.html
cestercian Mar 27, 2025
04679a3
Update .gitignore
cestercian Mar 27, 2025
1ac4463
Update netjson-cluster-overlap.html
cestercian Mar 27, 2025
f445098
Update netjsongraph.config.js
cestercian Mar 27, 2025
20ff2fa
Update netjson-cluster-overlap.html
cestercian Mar 27, 2025
0370da1
Merge branch 'master' into master
cestercian Apr 2, 2025
2206e70
[fix] Netjsongraph.js
cestercian Apr 3, 2025
47213da
[fix] Formatting
cestercian Apr 3, 2025
baa2a04
[fix] netjson-cluster-overlap.html
cestercian Apr 10, 2025
4d70d61
[fix] Netjson cluster overlap.html
cestercian Apr 10, 2025
1b42afd
Merge branch 'master' of https://github.com/cestercian/netjsongraph.js
cestercian Apr 10, 2025
3db782c
[fix] Netjson-cluster-overlap.html
cestercian Apr 10, 2025
1bbc178
Merge branch 'master' into master
cestercian Apr 11, 2025
3b1ad94
Update netjson-clustering.html
cestercian Apr 11, 2025
a287629
[fix] Clustering.html
cestercian Apr 11, 2025
8f8a394
[fix] Index.html
cestercian Apr 11, 2025
73d96c5
[fix] Gitignore
cestercian Apr 11, 2025
ce4313d
[fix] Readme.md
cestercian Apr 11, 2025
32d052c
Merge branch 'master' of https://github.com/cestercian/netjsongraph.js
cestercian Apr 11, 2025
9986a39
[fix] Merge master into feature-branch
cestercian Apr 11, 2025
26e5f67
Merge branch 'master' of https://github.com/cestercian/netjsongraph.js
cestercian Apr 11, 2025
3077a5c
[fix] Readme
cestercian Apr 11, 2025
664e8d4
[fix] Readme
cestercian Apr 12, 2025
8106dbd
[fix] Readme
cestercian Apr 12, 2025
63aaa20
[fix] Netjson-clustering.html
cestercian Apr 12, 2025
455aeb1
Merge branch 'master' into master
cestercian Apr 15, 2025
f4451e8
[fix] Netjson-clustering.html
cestercian Apr 17, 2025
737e393
Update netjsongraph.render.js
cestercian Apr 22, 2025
1656537
Update netjsongraph.config.js
cestercian Apr 22, 2025
c60ad0a
[fix] Netjsongraph.render.js
cestercian Apr 22, 2025
f9a5441
[fix] Netjsongraph.js
cestercian Apr 22, 2025
ce05edf
[fix] Netjsongraph.js
cestercian Apr 22, 2025
81a44b5
[fix] Netjsongraph.spec.js
cestercian Apr 22, 2025
6b6a604
[fix] Netjsongraph.spec.js
cestercian Apr 22, 2025
122fc12
[fix] Netjsongraph.js
cestercian Apr 22, 2025
71e004d
[fix] Netjsongraph.render.js
cestercian Apr 23, 2025
be2e4c6
Revert "[fix] Netjsongraph.render.js"
cestercian Apr 23, 2025
572155f
[fix] Netjsongraph.render.js
cestercian Apr 23, 2025
fd5b529
[change] Netjsongraph.js
cestercian Apr 23, 2025
46390b0
[fix] Formatting
cestercian Apr 23, 2025
d8e7ec6
[fix] Netjson-clustering.html
cestercian Apr 24, 2025
6b60948
[fix] Netjson-clustering.html
cestercian Apr 24, 2025
ab8af74
[change] Removed Comments
cestercian Apr 24, 2025
3a8fd56
[fix] Prettier
cestercian Apr 24, 2025
401de7f
[fix] Netjsongraph.config.js
cestercian Apr 26, 2025
09bc435
[fix] Prettier
cestercian Apr 26, 2025
f7acfcc
[fix] Netjsongraph.render.test.js
cestercian Apr 26, 2025
273f5ac
[fix] Netjsongraph.render.test.js
cestercian Apr 26, 2025
fd0e1bb
Revert "[fix] Netjsongraph.render.test.js"
cestercian Apr 26, 2025
8369683
[fix] Netjsongraph.render.test.js
cestercian Apr 26, 2025
f37ac79
[fix] Eslint
cestercian Apr 26, 2025
08681ff
Merge branch 'master' into master
cestercian May 3, 2025
a79eeec
[fix] Cluster-utlis.js
cestercian May 5, 2025
12b3437
[fix] Netjsongraph.render.js
cestercian May 5, 2025
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
102 changes: 101 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,70 @@ For map, you need to configure `mapOptions`. The [`mapOptions`](https://leafletj

You can also customize some global properties with [`echartsOption`](https://echarts.apache.org/en/option.html) in echarts.

## Cluster Utilities

The library includes utilities for handling cluster visualization and preventing overlap in map views. These utilities are available in the `lib/js/clusterUtils.js` module.

### Functions

#### `preventClusterOverlap()`

Identifies clusters at the same location and arranges them in a circular pattern to prevent overlap. This is particularly useful when you have multiple clusters at the same geographic location, such as nodes with different statuses at the same coordinates.

```javascript
import {preventClusterOverlap} from "../../lib/js/clusterUtils.js";

// Call the function to arrange overlapping clusters
preventClusterOverlap();
```

#### `setupClusterOverlapPrevention(leafletMap)`

Sets up event listeners for cluster overlap prevention. This function automatically applies the `preventClusterOverlap()` function when map events occur that might cause clusters to overlap.

```javascript
import {setupClusterOverlapPrevention} from "../../lib/js/clusterUtils.js";

// Get the Leaflet map instance
const leafletMap = map.map;

// Set up overlap prevention
setupClusterOverlapPrevention(leafletMap);
```

### Usage Example

To use the cluster utilities in your project:

```javascript
// Import the cluster utilities
import {
preventClusterOverlap,
setupClusterOverlapPrevention,
} from "../../lib/js/clusterUtils.js";

// Initialize your map
const map = new NetJSONGraph(data, {
render: "map",
clustering: true,
clusteringThreshold: 1,
clusterRadius: 40,
clusteringAttribute: "status", // Cluster by status
});

map.render();

// Get the Leaflet map instance
const leafletMap = map.map;

// Set up cluster overlap prevention when the map is loaded
window.addEventListener("load", () => {
setupClusterOverlapPrevention(leafletMap);
});
```

See the [Cluster Overlap Example](https://openwisp.github.io/netjsongraph.js/examples/netjson-cluster-overlap.html) for a complete demonstration.

### API Introduction

#### Core
Expand Down Expand Up @@ -789,7 +853,7 @@ Using array files to append data step by step at start.
Similiar to the first method, but easier.
[ Append data using arrays demo](https://openwisp.github.io/netjsongraph.js/examples/netjsonmap-appendData2.html)

The demo shows the clustering of nodes.
The demo shows how to handle overlapping clusters with different statuses.
[ Clustering demo](https://openwisp.github.io/netjsongraph.js/examples/netjson-clustering.html)

### Upgrading from 0.1.x versions to 0.2.x
Expand Down Expand Up @@ -855,3 +919,39 @@ Refer to the [Arguments section](#arguments) section for more details.
### License

[BSD 3-Clause License](https://github.com/interop-dev/netjsongraph.js/blob/master/LICENSE).

#### Cluster Overlap Prevention

To prevent visual clutter when multiple clusters occupy the same geographic coordinates, NetJSONGraph.js can automatically arrange them in a circular layout. This is particularly useful when you have nodes with different statuses in the same location.

You can enable this feature in two ways:

1. Using the `clusterOverlapPrevention` option:

```javascript
const graph = new NetJSONGraph("./data/your_data.json", {
render: "map",
clustering: true,
clusterOverlapPrevention: true,
// ... other options
});
```

2. Or manually using the utility functions:

```javascript
import {setupClusterOverlapPrevention} from "../../lib/js/clusterUtils.js";

// Initialize your map
const map = new NetJSONGraph(data, {
render: "map",
clustering: true,
// ... other options
});
map.render();

// Set up cluster overlap prevention
setupClusterOverlapPrevention(map.map);
```

See the [Cluster Overlap Example](./examples/netjson-clustering.html) for a complete demonstration.
207 changes: 152 additions & 55 deletions public/example_templates/netjson-clustering.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<title>netjsongraph.js: basic example</title>
<title>NetJSON Cluster Overlap Example</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
Expand All @@ -14,6 +14,11 @@
<link href="../lib/css/netjsongraph-theme.css" rel="stylesheet" />
<link href="../lib/css/netjsongraph.css" rel="stylesheet" />
<style type="text/css">
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
}
#legend h4 {
margin: 10px 0;
text-align: center;
Expand All @@ -30,7 +35,6 @@
border-radius: 8px;
color: #000;
font-family: Arial, sans-serif;
font-family: sans-serif;
font-size: 14px;
z-index: 1000;
user-select: text;
Expand All @@ -44,101 +48,194 @@
width: 16px;
margin-right: 5px;
}
#legend .link-up,
#legend .link-down {
.status-ok,
.status-problem,
.status-critical {
display: inline-block;
height: 5px;
border-bottom-width: 6px;
border-bottom-style: solid;
width: 15px;
height: 15px;
margin-right: 5px;
border-radius: 50%;
}
#legend .link-up {
color: #3acc38;
margin-right: 10px;
.status-ok {
background-color: #1ba619;
}
#legend .link-down {
color: red;
margin-right: 10px;
.status-problem {
background-color: #ffa500;
}

@media only screen and (max-width: 850px) {
#legend {
right: 15px;
}
.status-critical {
background-color: #c92517;
}
</style>
</head>
<body>
<script type="text/javascript">
const map = new NetJSONGraph("../assets/data/netjsonmap.json", {
<script type="text/javascript" src="/netjsongraph.min.js"></script>
<script type="module">
import {
preventClusterOverlap,
setupClusterOverlapPrevention,
} from "../../src/js/cluster-utils.js";

function generateNodesForStatus(
status,
baseLat,
baseLng,
minNodes,
maxNodes,
startId,
) {
const nodes = [];
const numNodes =
Math.floor(Math.random() * (maxNodes - minNodes + 1)) + minNodes;
let currentId = startId;

for (let i = 0; i < numNodes; i++) {
nodes.push({
id: `${currentId++}`,
name: `${status.charAt(0).toUpperCase() + status.slice(1)} Node ${i + 1}`,
// Slightly offset location for each node within the group
location: {
lat: baseLat + (Math.random() - 0.5) * 0.001,
lng: baseLng + (Math.random() - 0.5) * 0.001,
},
properties: {status: status},
});
}
return {nodes, nextId: currentId};
}

let nextNodeId = 1;
const criticalNodesResult = generateNodesForStatus(
"critical",
45.4642,
9.19,
7,
15,
nextNodeId,
);
nextNodeId = criticalNodesResult.nextId;
const okNodesResult = generateNodesForStatus(
"ok",
45.4742,
9.18,
12,
22,
nextNodeId,
);
nextNodeId = okNodesResult.nextId;
const problemNodesResult = generateNodesForStatus(
"problem",
45.4842,
9.17,
5,
10,
nextNodeId,
);

const allNodes = [
...criticalNodesResult.nodes,
...okNodesResult.nodes,
...problemNodesResult.nodes,
];

const testData = {
type: "NetworkGraph",
label: "Cluster Overlap Test with Variable Nodes",
nodes: allNodes,
links: [], // Keep links empty for this example
};

const map = new NetJSONGraph(testData, {
render: "map",
clustering: true,
clusteringThreshold: 50,
// set map initial state.
clusteringThreshold: 2,
clusterRadius: 80,
disableClusteringAtLevel: 18,
clusteringAttribute: "status",
mapOptions: {
center: [46.86764405052012, 19.675998687744144],
zoom: 4,
center: [45.4642, 9.19],
zoom: 12,
minZoom: 3,
maxZoom: 18,
nodeConfig: {
label: {
offset: [0, -10],
},
},
},
linkCategories: [
nodeCategories: [
{
name: "down",
linkStyle: {
color: "#c92517",
width: 5,
name: "ok",
nodeStyle: {
color: "#1ba619",
},
},
{
name: "problem",
nodeStyle: {
color: "#ffa500",
},
emphasis: {
linkStyle: {
color: "#FD0101",
opacity: 1,
},
},
{
name: "critical",
nodeStyle: {
color: "#c92517",
},
},
],
// Convert to internal json format
prepareData: (data) => {
data.nodes.map((node) => {
data.nodes.forEach((node) => {
node.label = node.name;
node.properties = Object.assign(node.properties || {}, {
node.properties = {
...node.properties,
location: node.location,
});
});

data.links.map((link) => {
link.properties = link.properties || {};
if (link.properties.status) {
link.category = link.properties.status;
};

if (node.properties && node.properties.status) {
const status = node.properties.status.toLowerCase();
// Map known statuses to categories
if (status === 'ok' || status === 'problem' || status === 'critical') {
node.category = status;
} else {
// Default others to unknown (though this example only generates known ones)
node.category = "unknown";
}
} else {
// Default nodes without status to unknown
node.category = "unknown";
}
});
},
});

const createLegend = (key, name) => {
const createLegend = (key, className) => {
const legendItem = document.createElement("p");
const legendIcon = document.createElement("span");

legendIcon.setAttribute("class", name);

legendIcon.setAttribute("class", className);
legends.appendChild(legendItem);
legendItem.appendChild(legendIcon);

legendItem.innerHTML += key;
return legendItem;
};

const legends = document.createElement("div");
const legendsHeader = document.createElement("h4");
legends.setAttribute("id", "legend");
legendsHeader.innerHTML = "Legend";
legendsHeader.innerHTML = "Node Status";
legends.appendChild(legendsHeader);
legends.appendChild(createLegend("Up", "link-up"));
legends.appendChild(createLegend("Down", "link-down"));

legends.appendChild(createLegend("OK", "status-ok"));
legends.appendChild(createLegend("Problem", "status-problem"));
legends.appendChild(createLegend("Critical", "status-critical"));
document.body.appendChild(legends);

map.render();
try {
map.render();
const leafletMap = map.map;

setupClusterOverlapPrevention(map.map);
} catch (e) {
console.error("Error initializing NetJSONGraph:", e);
}
</script>
</body>
</html>
Loading
Loading