I have made a graph component in CytoscapeJS and want to have a map as an overlay. Basically I want to represent the nodes based on their co-ordinates on Map (using LeafletJs). I have looked into the plugin (cytoscape-mapbox-gl by zakjan) and cytoscape-leaf extension (using this currently).
I am getting this error:
Uncaught TypeError: obj.attachEvent is not a function Uncaught TypeError: Cannot read properties of undefined (reading 'lat') Uncaught Error: Map container is already initialized.
I only want to integrate CytoscapeJs with LeafletJS. I don't want to add any tile layer.
Followed all the steps on the Cytoscape-leaf plugin documentation. I have registered the function, created the instance, added the co-ordinates in the data object,rendered the Map component alongside cytoscape component, Added the lat,lng field in the data object for cytoscape node(Node Position).
cytoscape.use( leaflet );
const map = useRef();
data: {
id: ele.nodeAddr,
label: `IP-${ele.nodeAddr}`,
icon:Server,
type:'parentNode',
status:ele.status,
lat:19.5,
lng:72.8777,
}
const options = {
container: map.current,
// the data field for latitude
latitude: 'lat',
// the data field for longitude
longitude: 'lng'
};
const leaf = cy.leaflet(options);
<div
ref={divRef}
style={{
border: "1px solid",
backgroundColor: "#f5f6fe",
height: '600px',
}}
>
<div ref={map}></div>
My answer is independent of your code and your intended plugins
you want to use with cytoscape
. It may not answer your question, but it works to some extent that I consider it a good start to use cytoscapeJS within leaflet.
One issue that I found, it wont work with browsers on iOS
or ipadOS
. I am sorry about that. Help is welcome to solve the issues why Webkit based browsers on iOS/ipadOS do not handle cytoscape's graph like Chrome browsers on android/windows.
Code:
const base_dc = 0.027589;
const lon_ext = 0.5436;
const lat_ext = 0.5436;
const lonmin = 100.3257;
const latmin = 13.4978;
const lon_cor = lon_ext * base_dc;
const lonmax = lonmin + lon_ext;
const lonmid = (lonmin + lonmax) / 2;
const lonmax2 = lonmax + lon_cor;
const latmax = latmin + lat_ext;
const latmid = (latmin + latmax) / 2;
const latlng = { lat: latmin, lng: lonmin };
const latlng2 = { lat: latmax, lng: lonmax2 };
const clatlng = { lat: latmid, lng: lonmid };
const cbottom = { lat: latmin, lng: lonmid };
var zoom = 9;
const myRenderer = L.canvas({ padding: 0.0 }); //svg or canvas
/* Cytoscape global vars */
// must match SVG viewbox w h.
// and width, height of #cyOnSvg DIV
const cy_size = 1600;
const params = {
width: cy_size,
height: cy_size,
latmin: latmin,
latmax: latmax,
lonmin: lonmin,
lonmax: lonmax2
};
// Update CSS to use new cy_size
let root = document.documentElement;
root.style.setProperty("--svg-width", cy_size + "px");
root.style.setProperty("--svg-height", cy_size + "px");
/* choice of maps */
var mymap = L.map("mapid", {
renderer: myRenderer,
preferCanvas: false
}).setView(clatlng, zoom);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "Map data © OpenStreetMap contributors"
}).addTo(mymap);
const svg_pin =
'<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" fill="firebrick"></path></svg>';
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const svgpin_Icon = L.icon({
iconUrl: svgpin_Url,
iconSize: [24, 24],
iconAnchor: [12, 24],
popupAnchor: [0, -22]
});
var marker2 = L.marker(latlng2, {
renderer: myRenderer,
icon: svgpin_Icon
//draggable: true,
//autoPan: true
}).addTo(mymap);
marker2.bindPopup("<b>Control_UR</b>").openPopup();
marker2.openPopup();
var marker0 = L.marker(latlng, {
renderer: myRenderer,
icon: svgpin_Icon
}).addTo(mymap);
marker0.bindPopup("<b>Control_LL</b>").openPopup();
marker0.openPopup();
/* Embed SVG Element in the web page */
let svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElem.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgElem.setAttribute("id", "Svg0");
svgElem.setAttribute("preserveAspectRatio", "xMinYMax slice");
svgElem.setAttribute("viewBox", `0 0 ${cy_size} ${cy_size}`);
/* SVGOverlay with foreignObject-div-text */
svgElem.innerHTML = `<foreignObject x="0" y="0" width="100%" height="100%">
<div id="cyOnSvg" style="background-color:rgba(200,200,200,0.25);padding:0;"></div>
<div id="upperlefttext">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
</foreignObject>`;
// Note: lonmax2 (=lonmax + correction)
const svgElementBounds = [
[latmin, lonmin],
[latmax, lonmax2]
];
const svgobj = L.svgOverlay(svgElem, svgElementBounds, {
renderer: myRenderer,
zIndex: 15,
opacity: 0.65,
interactive: false
}).addTo(mymap);
//svgobj.bindPopup("SVG vector layer");
//var draggable = new L.Draggable(svgElem);
//draggable.enable(); //make draggable (must set=> interactive: true)
/* SVG OK */
/* set events */
mymap.on("click", onMapClick);
//svgobj.on("click", onSvgClick);
/* other useful settings */
mymap.scrollWheelZoom.disable();
const popup = L.popup(); //leaflet popup object
function onMapClick(e) {
popup.setLatLng(e.latlng).setContent(e.latlng.toString()).openOn(mymap);
}
/* -------cytoscape-------- */
function lnglat2xy(lon, lat, pars) {
//let w = pars.width;
//let h = pars.height;
let L = pars.lonmax - pars.lonmin; //lonmax2-lonmin
let B = pars.latmax - pars.latmin;
let y = ((B - (lat - pars.latmin)) * pars.height) / B;
let x = ((lon - pars.lonmin) * pars.width) / L;
return { x: x, y: y };
}
const lngMidLatMid = lnglat2xy(
(lonmin + lonmax2) / 2,
(latmin + latmax) / 2,
params
);
//console.log("lngMidLatMid: "+lngMidLatMid.x+"; "+lngMidLatMid.y);
const bkk = lnglat2xy(100.5348327, 13.7567441, params);
const bda = lnglat2xy(100.4094999, 13.7108552, params);
const hoc = lnglat2xy(100.6076988, 13.5676449, params);
const han = lnglat2xy(100.7522551, 13.6982529, params);
const nay = lnglat2xy(100.4081876, 13.8923587, params);
var cy = cytoscape({
container: document.querySelector("#cyOnSvg"),
elements: {
nodes: [
{
data: { id: "LL", name: "LowerLeft" },
classes: "controlpoint",
position: { x: 0, y: cy_size }
},
{
data: { id: "UL", name: "UpperLeft" },
classes: "controlpoint",
position: { x: 0, y: 0 }
},
{
data: { id: "UR", name: "UpperRight" },
classes: "controlpoint",
position: { x: cy_size, y: 0 }
},
{
data: { id: "LR", name: "LowerRight" },
classes: "controlpoint",
position: { x: cy_size, y: cy_size }
},
/* Nodes with (long,lat) coordinates */
{ data: { id: "bkk", name: "A8" }, position: { x: bkk.x, y: bkk.y } },
{ data: { id: "bda", name: "BL38" }, position: { x: bda.x, y: bda.y } },
{ data: { id: "hoc", name: "E23" }, position: { x: hoc.x, y: hoc.y } },
{ data: { id: "han", name: "A1" }, position: { x: han.x, y: han.y } },
{ data: { id: "nay", name: "PP01" }, position: { x: nay.x, y: nay.y } },
],
edges: [
{
data: { id: "LLUR", source: "LL", target: "UR" },
classes: "controlline"
},
{
data: { id: "ULLR", source: "UL", target: "LR" },
classes: "controlline"
},
{
data: { id: "bkk_bda", source: "bkk", target: "bda" },
classes: "edge"
},
{
data: { id: "bkk_han", source: "bkk", target: "han" },
classes: "edge"
},
{
data: { id: "bkk_hoc", source: "bkk", target: "hoc" },
classes: "edge"
},
{
data: { id: "bkk_nay", source: "bkk", target: "nay" },
classes: "edge"
},
]
},
style: [
{
selector: "node",
style: {
shape: "hexagon",
width: "50px",
height: "50px",
"background-color": "blue",
label: "data(name)",
opacity: 1,
"text-background-color": "yellow"
}
},
{
selector: ".controlline",
style: {
width: "2px",
"line-color": "black",
opacity: 1
}
},
{
selector: ".edge",
style: {
width: "3px",
"line-color": "red",
opacity: 1
}
},
],
layout: {
name: "preset"
}
});
cy.pan({ x: 0.0, y: 0.0 }); //match TOP-LEFT corner
cy.fit(cy.$("#LLUR"));
:root {
--svg-width: 1600px;
--svg-height: 1600px;
}
#cyOnSvg{
/* foreignObject, and canvas */
position: absolute;
width: 100%; //5%;
height: 100%; //10%;
top: 0;
left: 0;
background-color: lightgray;
overflow: visible;
margin: 0;
padding: 0;
}
#Svg0{
/* svg elem */
position: absolute;
width: var(--svg-width);
height: var(--svg-height);
top: 0;
left: 0;
display: block;
background-color: lightyellow;
/*border: 0.5px solid gray;
border-style: dashed;*/
overflow: visible;
margin: 0;
padding: 0;
}
#mapid {
height: 480px;
width: 480px;
}
#lorem {
width: var(--svg-width);
position: absolute;
background-color: gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.9.2/cytoscape.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-browser/0.1.0/jquery.browser.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin="" />
<div id="mapid"></div>