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
1 change: 1 addition & 0 deletions public/example_templates/netjsonmap.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
right: 15px;
}
}

</style>
</head>
<body>
Expand Down
70 changes: 69 additions & 1 deletion src/css/netjsongraph-theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
background-color: #fff;
}

.njg-container .switch-wrap input[type="checkbox"]:checked + label {
.njg-container .switch-wrap input[type="checkbox"]:checked+label {
background: grey;
}

Expand Down Expand Up @@ -87,3 +87,71 @@
color: #000;
}
}

/* Dark Mode Styles */
.dark-mode .njg-container .njg-sideBar {
background-color: #1e1e1e;
color: #e0e0e0;
}

.dark-mode .njg-container .njg-tooltip {
background: #1e1e1e !important;
color: #e0e0e0 !important;
}

.dark-mode .njg-container .njg-tooltip-key,
.dark-mode .njg-container .njg-tooltip-value {
color: #e0e0e0;
}

.dark-mode .njg-container .njg-searchBtn {
background-color: #333;
color: #fff;
}

.dark-mode .njg-container .switch-wrap label {
background: #333;
border-color: #555;
}

.dark-mode .njg-container .switch-wrap label::before {
background-color: #e0e0e0;
}

.njg-themeControl {
width: auto;
height: 34px;
padding: 0 12px;
background-color: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 10px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
color: #333;
transition: background-color 0.3s, color 0.3s;
}

.njg-themeControl:hover {
background-color: #f5f5f5;
}

.dark-mode .njg-themeControl {
background-color: #1e1e1e;
color: #e0e0e0;
}

.dark-mode .njg-themeControl:hover {
background-color: #2c2c2c;
}

@media only screen and (max-width: 850px) {
.dark-mode .njg-container .njg-sideBar {
background: rgba(30, 30, 30, 0.96);
color: #e0e0e0;
}
}
10 changes: 10 additions & 0 deletions src/js/netjsongraph.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ const NetJSONGraphDefaultConfig = {
},
},
],
mapTileConfigDark: [
{
urlTemplate: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png",
options: {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, &copy; <a href="https://carto.com/attributions">CARTO</a>',
subdomains: "abcd",
},
},
],
geoOptions: {
style: {
fillColor: "#1566a9",
Expand Down
58 changes: 57 additions & 1 deletion src/js/netjsongraph.gui.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,57 @@ class NetJSONGraphGUI {
return selectIconContainer;
}

createThemeControl() {
const themeControl = document.createElement("div");
themeControl.setAttribute("class", "njg-themeControl");

const icon = document.createElement("span");
icon.innerHTML = "🌓";
icon.setAttribute("aria-label", "Toggle Dark Mode");
icon.style.marginRight = "5px";

const text = document.createElement("span");
text.innerHTML = "Toggle Theme";

themeControl.appendChild(icon);
themeControl.appendChild(text);

// Check initial state
try {
const savedTheme = localStorage.getItem("map_theme");
if (savedTheme === "dark") {
this.self.el.classList.add("dark-mode");
}
} catch (e) {
console.debug("LocalStorage access denied or not available");
}

themeControl.onclick = () => {
this.self.el.classList.toggle("dark-mode");
const isDark = this.self.el.classList.contains("dark-mode");
try {
localStorage.setItem("map_theme", isDark ? "dark" : "light");
} catch (e) {
console.debug("LocalStorage access denied or not available");
}

// If map is active, re-render to update tiles
if (this.self.config.render === this.self.utils.mapRender) {
this.self.utils.render();
}
};

// Append to controls if exists, otherwise create a container
if (this.controls) {
this.controls.appendChild(themeControl);
} else {
const controls = this.createControls();
this.controls = controls;
controls.appendChild(themeControl);
}
return themeControl;
}

createSideBar() {
const sideBar = document.createElement("div");
sideBar.setAttribute("class", "njg-sideBar");
Expand Down Expand Up @@ -280,8 +331,13 @@ class NetJSONGraphGUI {

init() {
this.sideBar = this.createSideBar();
// Always create theme control for now, or make it configurable
this.createThemeControl();

if (this.self.config.switchMode) {
this.controls = this.createControls();
if (!this.controls) {
this.controls = this.createControls();
}
this.renderModeSelector = this.createRenderModeSelector();
}
}
Expand Down
130 changes: 84 additions & 46 deletions src/js/netjsongraph.render.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,9 +407,15 @@ class NetJSONGraphRender {
}),
];

const isDarkMode = self.utils.isDarkMode(self);
const tiles =
isDarkMode && configs.mapTileConfigDark
? configs.mapTileConfigDark
: configs.mapTileConfig;

return {
leaflet: {
tiles: configs.mapTileConfig,
tiles,
mapOptions: configs.mapOptions,
},
series,
Expand Down Expand Up @@ -493,7 +499,29 @@ class NetJSONGraphRender {
// eslint-disable-next-line no-underscore-dangle
self.leaflet = self.echarts._api.getCoordinateSystems()[0].getLeaflet();
// eslint-disable-next-line no-underscore-dangle
self.leaflet._zoomAnimated = false;
if (self.leaflet) {
self.leaflet._zoomAnimated = false;
}

// Manually update tile layers because echarts-leaflet doesn't handle tile updates on setOption
const isDarkMode = self.utils.isDarkMode(self);

const targetTiles =
isDarkMode && self.config.mapTileConfigDark
? self.config.mapTileConfigDark
: self.config.mapTileConfig;

if (self.leaflet) {
self.leaflet.eachLayer((layer) => {
if (layer instanceof L.TileLayer) {
self.leaflet.removeLayer(layer);
}
});

targetTiles.forEach((tile) => {
L.tileLayer(tile.urlTemplate, tile.options).addTo(self.leaflet);
});
}

self.config.geoOptions = self.utils.deepMergeObj(
{
Expand Down Expand Up @@ -541,12 +569,12 @@ class NetJSONGraphRender {
}
}

if (bounds && bounds.isValid()) {
if (bounds && bounds.isValid() && self.leaflet) {
self.leaflet.fitBounds(bounds, {padding: [20, 20]});
}
}

if (self.leaflet.getZoom() < self.config.showLabelsAtZoomLevel) {
if (self.leaflet && self.leaflet.getZoom() < self.config.showLabelsAtZoomLevel) {
self.echarts.setOption({
series: [
{
Expand All @@ -564,48 +592,53 @@ class NetJSONGraphRender {
});
}

self.leaflet.on("zoomend", () => {
const currentZoom = self.leaflet.getZoom();
const showLabel = currentZoom >= self.config.showLabelsAtZoomLevel;
self.echarts.setOption({
series: [
{
id: "geo-map",
label: {
show: showLabel,
},
emphasis: {
if (self.leaflet) {
self.leaflet.on("zoomend", () => {
const currentZoom = self.leaflet.getZoom();
const showLabel = currentZoom >= self.config.showLabelsAtZoomLevel;
self.echarts.setOption({
series: [
{
id: "geo-map",
label: {
show: showLabel,
},
emphasis: {
label: {
show: showLabel,
},
},
},
},
],
});
],
});

// Zoom in/out buttons disabled only when it is equal to min/max zoomlevel
// Manually handle zoom control state to ensure correct behavior with float zoom levels
const minZoom = self.leaflet.getMinZoom();
const maxZoom = self.leaflet.getMaxZoom();
const zoomIn = document.querySelector(".leaflet-control-zoom-in");
const zoomOut = document.querySelector(".leaflet-control-zoom-out");
// Zoom in/out buttons disabled only when it is equal to min/max zoomlevel
// Manually handle zoom control state to ensure correct behavior with float zoom levels
const minZoom = self.leaflet.getMinZoom();
const maxZoom = self.leaflet.getMaxZoom();
const zoomIn = document.querySelector(".leaflet-control-zoom-in");
const zoomOut = document.querySelector(".leaflet-control-zoom-out");

if (zoomIn && zoomOut) {
if (Math.round(currentZoom) >= maxZoom) {
zoomIn.classList.add("leaflet-disabled");
} else {
zoomIn.classList.remove("leaflet-disabled");
}

if (zoomIn && zoomOut) {
if (Math.round(currentZoom) >= maxZoom) {
zoomIn.classList.add("leaflet-disabled");
} else {
zoomIn.classList.remove("leaflet-disabled");
if (Math.round(currentZoom) <= minZoom) {
zoomOut.classList.add("leaflet-disabled");
} else {
zoomOut.classList.remove("leaflet-disabled");
}
}
});
}


if (Math.round(currentZoom) <= minZoom) {
zoomOut.classList.add("leaflet-disabled");
} else {
zoomOut.classList.remove("leaflet-disabled");
}
}
});

self.leaflet.on("moveend", async () => {
if (self.leaflet) {
self.leaflet.on("moveend", async () => {
const bounds = self.leaflet.getBounds();
const removeBBoxData = () => {
const removeNodes = new Set(self.bboxData.nodes);
Expand Down Expand Up @@ -655,14 +688,15 @@ class NetJSONGraphRender {
removeBBoxData();
}
});
}
if (
self.config.clustering &&
self.config.clusteringThreshold < JSONData.nodes.length
) {
let {clusters, nonClusterNodes, nonClusterLinks} = self.utils.makeCluster(self);

// Only show clusters if we're below the disableClusteringAtLevel
if (self.leaflet.getZoom() > self.config.disableClusteringAtLevel) {
if (self.leaflet && self.leaflet.getZoom() > self.config.disableClusteringAtLevel) {
clusters = [];
nonClusterNodes = JSONData.nodes;
nonClusterLinks = JSONData.links;
Expand All @@ -687,18 +721,21 @@ class NetJSONGraphRender {
params.data.cluster
) {
// Zoom into the clicked cluster instead of expanding it
const currentZoom = self.leaflet.getZoom();
const targetZoom = Math.min(currentZoom + 2, self.leaflet.getMaxZoom());
self.leaflet.setView(
[params.data.value[1], params.data.value[0]],
targetZoom,
);
const currentZoom = self.leaflet ? self.leaflet.getZoom() : 0;
const targetZoom = Math.min(currentZoom + 2, self.leaflet ? self.leaflet.getMaxZoom() : 18);
if (self.leaflet) {
self.leaflet.setView(
[params.data.value[1], params.data.value[0]],
targetZoom,
);
}
}
});

// Ensure zoom handler consistently applies the same clustering logic
self.leaflet.on("zoomend", () => {
if (self.leaflet.getZoom() < self.config.disableClusteringAtLevel) {
if (self.leaflet) {
self.leaflet.on("zoomend", () => {
if (self.leaflet.getZoom() < self.config.disableClusteringAtLevel) {
const nodeData = self.utils.makeCluster(self);
clusters = nodeData.clusters;
nonClusterNodes = nodeData.nonClusterNodes;
Expand All @@ -720,6 +757,7 @@ class NetJSONGraphRender {
}
});
}
}

self.utils.setupHashChangeHandler(self);
self.event.emit("onLoad");
Expand Down
Loading
Loading