Skip to content

Commit 409224d

Browse files
committed
add selection feature
Select a node or link while holding the CTRL key.
1 parent b6b186f commit 409224d

File tree

4 files changed

+198
-19
lines changed

4 files changed

+198
-19
lines changed

src/js/netjsongraph.config.js

+60
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,39 @@ const NetJSONGraphDefaultConfig = {
8585
legendHoverLink: true,
8686
emphasis: {
8787
focus: "none",
88+
scale: 2,
8889
lineStyle: {
8990
color: "#3acc38",
9091
opacity: 1,
9192
},
93+
itemStyle: {
94+
//color: "#3acc38",
95+
color: {
96+
type: "radial",
97+
x: 0.5,
98+
y: 0.5,
99+
r: 0.5,
100+
colorStops: [
101+
{
102+
offset: 0,
103+
color: "#ffebc4",
104+
},
105+
{
106+
offset: 0.5,
107+
color: "#ffebc4",
108+
},
109+
{
110+
offset: 0.51,
111+
color: "#ffffff33",
112+
},
113+
{
114+
offset: 1,
115+
color: "#ffffff33",
116+
},
117+
],
118+
opacity: 1,
119+
},
120+
},
92121
},
93122
nodeStyle: {
94123
color: "#ffebc4",
@@ -172,6 +201,37 @@ const NetJSONGraphDefaultConfig = {
172201
nodeStyle: {
173202
color: "#1566a9",
174203
},
204+
emphasis: {
205+
focus: "none",
206+
scale: 2,
207+
itemStyle: {
208+
color: {
209+
type: "radial",
210+
x: 0.5,
211+
y: 0.5,
212+
r: 0.5,
213+
colorStops: [
214+
{
215+
offset: 0,
216+
color: "#1566a9",
217+
},
218+
{
219+
offset: 0.5,
220+
color: "#1566a9",
221+
},
222+
{
223+
offset: 0.51,
224+
color: "green",
225+
},
226+
{
227+
offset: 1,
228+
color: "green",
229+
},
230+
],
231+
opacity: 1,
232+
},
233+
},
234+
},
175235
nodeSize: "17",
176236
},
177237
linkConfig: {

src/js/netjsongraph.core.js

+110
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,115 @@
11
import NetJSONGraphDefaultConfig from "./netjsongraph.config";
22
import NetJSONGraphUpdate from "./netjsongraph.update";
33

4+
class Selection {
5+
constructor() {
6+
this.selected = new Set();
7+
}
8+
9+
getSetId(item) {
10+
if (item.node) {
11+
// map node
12+
return item.node.id;
13+
} else if (item.link) {
14+
// map link
15+
return (item.link.source + "=>" + item.link.target);
16+
} else if (item.id) {
17+
// graph node
18+
return item.id;
19+
} else {
20+
// graph link
21+
return (item.source + "=>" + item.target);
22+
}
23+
}
24+
25+
isSelected(item) {
26+
let id = this.getSetId(item);
27+
return this.selected.has(id);
28+
}
29+
30+
toggleSelection(item) {
31+
let id = this.getSetId(item);
32+
33+
if (this.selected.has(id)) {
34+
this.selected.delete(id)
35+
return false;
36+
} else {
37+
this.selected.add(id);
38+
return true;
39+
}
40+
}
41+
42+
changeSelection(echarts, params) {
43+
const multiSelectKey = (window.event.ctrlKey || window.event.metaKey);
44+
const isSelectionEmpty = (this.selected.size === 0);
45+
46+
if (multiSelectKey) {
47+
if (this.toggleSelection(params.data)) {
48+
echarts.dispatchAction(
49+
{ type: 'highlight', seriesIndex: params.seriesIndex, dataType: params.dataType, dataIndex: params.dataIndex}
50+
)
51+
} else {
52+
echarts.dispatchAction(
53+
{ type: 'downplay', seriesIndex: params.seriesIndex, dataType: params.dataType, dataIndex: params.dataIndex}
54+
)
55+
}
56+
} else if (!isSelectionEmpty) {
57+
const option = echarts.getOption();
58+
let nodeData = [];
59+
let linkData = [];
60+
if (option.leaflet) {
61+
// map data
62+
nodeData = option.leaflet[0].mapOptions.nodeConfig.data;
63+
linkData = option.leaflet[0].mapOptions.linkConfig.data;
64+
} else {
65+
// graph data
66+
nodeData = option.series[0].nodes;
67+
linkData = option.series[0].links;
68+
}
69+
const nodeIndexes = nodeData.map((node, index) => index);
70+
const linkIndexes = linkData.map((link, index) => index);
71+
72+
// downplay all items
73+
echarts.dispatchAction(
74+
{ type: 'downplay', seriesIndex: 0, batch: [
75+
{dataType: "node", dataIndex: nodeIndexes},
76+
{dataType: "edge", dataIndex: linkIndexes},
77+
]
78+
}
79+
)
80+
81+
this.selected.clear();
82+
}
83+
}
84+
85+
// called when switching between graph/map view
86+
highlightSelected(echarts) {
87+
const option = echarts.getOption();
88+
let nodeData = [];
89+
let linkData = [];
90+
if (option.leaflet) {
91+
// map data
92+
nodeData = option.leaflet[0].mapOptions.nodeConfig.data;
93+
linkData = option.leaflet[0].mapOptions.linkConfig.data;
94+
} else {
95+
// graph data
96+
nodeData = option.series[0].nodes;
97+
linkData = option.series[0].links;
98+
}
99+
100+
const nodeIndexes = nodeData.map((node, index) => this.isSelected(node) ? index : -1).filter((index) => index !== -1);
101+
const linkIndexes = linkData.map((link, index) => this.isSelected(link) ? index : -1).filter((index) => index !== -1);
102+
103+
echarts.dispatchAction(
104+
{ type: 'highlight', seriesIndex: 0, batch: [
105+
{dataType: "node", dataIndex: nodeIndexes},
106+
{dataType: "edge", dataIndex: linkIndexes},
107+
]
108+
}
109+
)
110+
}
111+
}
112+
4113
class NetJSONGraph {
5114
/**
6115
* @constructor
@@ -10,6 +119,7 @@ class NetJSONGraph {
10119
*/
11120
constructor(JSONParam) {
12121
this.utils = new NetJSONGraphUpdate();
122+
this.selection = new Selection();
13123
this.config = {...NetJSONGraphDefaultConfig};
14124
this.JSONParam = this.utils.isArray(JSONParam) ? JSONParam : [JSONParam];
15125
}

src/js/netjsongraph.js

+4
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class NetJSONGraph {
115115
* @returns {Object} - The graph configuration.
116116
*/
117117
onLoad() {
118+
this.utils.echartsSetEventHandler(this);
119+
118120
if (this.config.metadata && this.type === "netjson") {
119121
this.gui.createMetaInfoContainer(this.graph);
120122
this.utils.updateMetadata.call(this);
@@ -151,6 +153,8 @@ class NetJSONGraph {
151153
document.querySelector(".leaflet-control-zoom").style.display =
152154
"block";
153155
}
156+
157+
this.selection.highlightSelected(this.echarts);
154158
};
155159
}
156160
this.utils.hideLoading.call(this);

src/js/netjsongraph.render.js

+24-19
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,30 @@ echarts.use([
3232
]);
3333

3434
class NetJSONGraphRender {
35+
echartsSetEventHandler(self) {
36+
self.echarts.on(
37+
"click",
38+
(params) => {
39+
self.selection.changeSelection(self.echarts, params);
40+
41+
const clickElement = self.config.onClickElement.bind(self);
42+
if (params.componentSubType === "graph") {
43+
return clickElement(
44+
params.dataType === "edge" ? "link" : "node",
45+
params.data,
46+
);
47+
}
48+
if (params.componentSubType === "graphGL") {
49+
return clickElement("node", params.data);
50+
}
51+
return params.componentSubType === "lines"
52+
? clickElement("link", params.data.link)
53+
: !params.data.cluster && clickElement("node", params.data.node);
54+
},
55+
{passive: true},
56+
);
57+
}
58+
3559
/**
3660
* @function
3761
* @name echartsSetOption
@@ -95,25 +119,6 @@ class NetJSONGraphRender {
95119
);
96120

97121
echartsLayer.setOption(self.utils.deepMergeObj(commonOption, customOption));
98-
echartsLayer.on(
99-
"click",
100-
(params) => {
101-
const clickElement = configs.onClickElement.bind(self);
102-
if (params.componentSubType === "graph") {
103-
return clickElement(
104-
params.dataType === "edge" ? "link" : "node",
105-
params.data,
106-
);
107-
}
108-
if (params.componentSubType === "graphGL") {
109-
return clickElement("node", params.data);
110-
}
111-
return params.componentSubType === "lines"
112-
? clickElement("link", params.data.link)
113-
: !params.data.cluster && clickElement("node", params.data.node);
114-
},
115-
{passive: true},
116-
);
117122

118123
return echartsLayer;
119124
}

0 commit comments

Comments
 (0)