diff --git a/inst/htmlwidgets/neurosurface/src/ColorMap.js b/inst/htmlwidgets/neurosurface/src/ColorMap.js
index 7472323..c3d37de 100644
--- a/inst/htmlwidgets/neurosurface/src/ColorMap.js
+++ b/inst/htmlwidgets/neurosurface/src/ColorMap.js
@@ -1,5 +1,6 @@
import colormap from 'colormap';
import { EventEmitter } from './EventEmitter.js';
+import { debugLog } from './debug.js';
class ColorMap extends EventEmitter {
constructor(colors, options = {}) {
@@ -18,7 +19,7 @@ class ColorMap extends EventEmitter {
setRange(range) {
if (Array.isArray(range) && range.length === 2 && range.every(v => typeof v === 'number')) {
this.range = range;
- console.log('ColorMap: Emitting rangeChanged event', this.range);
+ debugLog('ColorMap: Emitting rangeChanged event', this.range);
this.emit('rangeChanged', this.range);
} else {
this.range = [0, 1];
@@ -28,7 +29,7 @@ class ColorMap extends EventEmitter {
setThreshold(threshold) {
if (Array.isArray(threshold) && threshold.length === 2 && threshold.every(v => typeof v === 'number')) {
this.threshold = threshold;
- console.log('ColorMap: Emitting thresholdChanged event', this.threshold);
+ debugLog('ColorMap: Emitting thresholdChanged event', this.threshold);
this.emit('thresholdChanged', this.threshold);
} else {
this.threshold = [0, 0];
diff --git a/inst/htmlwidgets/neurosurface/src/EventEmitter.js b/inst/htmlwidgets/neurosurface/src/EventEmitter.js
index 61d1c05..5f67439 100644
--- a/inst/htmlwidgets/neurosurface/src/EventEmitter.js
+++ b/inst/htmlwidgets/neurosurface/src/EventEmitter.js
@@ -1,9 +1,13 @@
export class EventEmitter {
constructor() {
- this._events = {};
+ // Use an object without a prototype to avoid prototype pollution
+ this._events = Object.create(null);
}
on(event, listener) {
+ if (typeof listener !== 'function') {
+ throw new TypeError('listener must be a function');
+ }
if (!this._events[event]) {
this._events[event] = [];
}
@@ -11,9 +15,21 @@ export class EventEmitter {
return () => this.removeListener(event, listener);
}
+ once(event, listener) {
+ if (typeof listener !== 'function') {
+ throw new TypeError('listener must be a function');
+ }
+ const wrapped = (...args) => {
+ this.removeListener(event, wrapped);
+ listener(...args);
+ };
+ return this.on(event, wrapped);
+ }
+
emit(event, ...args) {
if (this._events[event]) {
- this._events[event].forEach((listener) => listener(...args));
+ // Copy listeners to avoid issues if the array is modified during emit
+ [...this._events[event]].forEach((listener) => listener(...args));
}
}
@@ -22,6 +38,17 @@ export class EventEmitter {
this._events[event] = this._events[event].filter(
(listener) => listener !== listenerToRemove
);
+ if (this._events[event].length === 0) {
+ delete this._events[event];
+ }
+ }
+ }
+
+ removeAllListeners(event) {
+ if (event) {
+ delete this._events[event];
+ } else {
+ this._events = Object.create(null);
}
}
-}
\ No newline at end of file
+}
diff --git a/inst/htmlwidgets/neurosurface/src/NeuroSurfaceViewer.js b/inst/htmlwidgets/neurosurface/src/NeuroSurfaceViewer.js
index 8215e61..54fcbc5 100644
--- a/inst/htmlwidgets/neurosurface/src/NeuroSurfaceViewer.js
+++ b/inst/htmlwidgets/neurosurface/src/NeuroSurfaceViewer.js
@@ -3,6 +3,7 @@ import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls
import { ColorMappedNeuroSurface, VertexColoredNeuroSurface } from './classes.js';
import { Pane } from 'tweakpane';
import * as EssentialsPlugin from '@tweakpane/plugin-essentials';
+import { debugLog } from './debug.js';
export class NeuroSurfaceViewer {
constructor(container, width, height, config = {}, viewpoint = 'lateral') {
@@ -265,9 +266,9 @@ export class NeuroSurfaceViewer {
umat = new THREE.Matrix4().identity();
}
- console.log('Setting viewpoint to:', viewpoint);
- console.log('Current viewpoint state:', this.viewpointState);
- console.log("umat", umat);
+ debugLog('Setting viewpoint to:', viewpoint);
+ debugLog('Current viewpoint state:', this.viewpointState);
+ debugLog('umat', umat);
// Calculate the bounding box of all surfaces
const box = new THREE.Box3();
@@ -334,7 +335,7 @@ export class NeuroSurfaceViewer {
}
updateIntensityRange() {
- console.log('NeuroSurfaceViewer: Updating intensity range', [this.intensityRange.range.min, this.intensityRange.range.max]);
+ debugLog('NeuroSurfaceViewer: Updating intensity range', [this.intensityRange.range.min, this.intensityRange.range.max]);
this.surfaces.forEach(surface => {
if (surface instanceof ColorMappedNeuroSurface) {
surface.colorMap.setRange([this.intensityRange.range.min, this.intensityRange.range.max]);
@@ -344,7 +345,7 @@ export class NeuroSurfaceViewer {
}
updateThresholdRange() {
- console.log('NeuroSurfaceViewer: Updating threshold range', [this.thresholdRange.range.min, this.thresholdRange.range.max]);
+ debugLog('NeuroSurfaceViewer: Updating threshold range', [this.thresholdRange.range.min, this.thresholdRange.range.max]);
this.surfaces.forEach(surface => {
if (surface instanceof ColorMappedNeuroSurface) {
surface.colorMap.setThreshold([this.thresholdRange.range.min, this.thresholdRange.range.max]);
@@ -361,7 +362,7 @@ export class NeuroSurfaceViewer {
}
addSurface(surface, id) {
- console.log('Adding surface:', surface, 'with id:', id);
+ debugLog('Adding surface:', surface, 'with id:', id);
this.surfaces.set(id, surface);
if (!surface.mesh) {
console.warn('Surface mesh not created. Creating now.');
@@ -370,7 +371,7 @@ export class NeuroSurfaceViewer {
this.scene.add(surface.mesh);
if (surface instanceof ColorMappedNeuroSurface) {
- console.log('Updating data range for ColorMappedNeuroSurface');
+ debugLog('Updating data range for ColorMappedNeuroSurface');
this.updateDataRange(surface.data);
this.updateRangeControls();
}
@@ -481,7 +482,7 @@ export class NeuroSurfaceViewer {
this.updateRangeControls();
surface.updateColors();
this.render();
- console.log(`Updated data for surface with id: ${id}`);
+ debugLog(`Updated data for surface with id: ${id}`);
} else {
console.warn(`No ColorMappedNeuroSurface found with id: ${id}`);
}
@@ -492,7 +493,7 @@ export class NeuroSurfaceViewer {
if (surface && surface instanceof VertexColoredNeuroSurface) {
surface.setColors(colors);
this.render();
- console.log(`Updated colors for surface with id: ${id}`);
+ debugLog(`Updated colors for surface with id: ${id}`);
} else {
console.warn(`No VertexColoredNeuroSurface found with id: ${id}`);
}
diff --git a/inst/htmlwidgets/neurosurface/src/classes.js b/inst/htmlwidgets/neurosurface/src/classes.js
index b4df5d6..7b5c729 100644
--- a/inst/htmlwidgets/neurosurface/src/classes.js
+++ b/inst/htmlwidgets/neurosurface/src/classes.js
@@ -1,5 +1,6 @@
import * as THREE from 'three';
import ColorMap from './ColorMap.js';
+import { debugLog } from './debug.js';
export class SurfaceGeometry {
constructor(vertices, faces, hemi) {
@@ -8,10 +9,10 @@ export class SurfaceGeometry {
this.hemi = hemi;
this.mesh = null;
- console.log("SurfaceGeometry constructor called");
- console.log("Vertices:", this.vertices.length);
- console.log("Faces:", this.faces.length);
- console.log("Hemi:", this.hemi);
+ debugLog('SurfaceGeometry constructor called');
+ debugLog('Vertices:', this.vertices.length);
+ debugLog('Faces:', this.faces.length);
+ debugLog('Hemi:', this.hemi);
this.createMesh();
}
@@ -28,8 +29,8 @@ export class SurfaceGeometry {
});
this.mesh = new THREE.Mesh(geometry, material);
- console.log("SurfaceGeometry construction complete");
- console.log("Mesh:", this.mesh);
+ debugLog('SurfaceGeometry construction complete');
+ debugLog('Mesh:', this.mesh);
}
}
@@ -195,17 +196,17 @@ export class ColorMappedNeuroSurface extends NeuroSurface {
// Set up new listeners
this.rangeListener = this.colorMap.on('rangeChanged', (range) => {
- console.log('ColorMappedNeuroSurface: Received rangeChanged event', range);
+ debugLog('ColorMappedNeuroSurface: Received rangeChanged event', range);
this.irange = range;
this.updateColors();
});
this.thresholdListener = this.colorMap.on('thresholdChanged', (threshold) => {
- console.log('ColorMappedNeuroSurface: Received thresholdChanged event', threshold);
+ debugLog('ColorMappedNeuroSurface: Received thresholdChanged event', threshold);
this.threshold = threshold;
this.updateColors();
});
this.alphaListener = this.colorMap.on('alphaChanged', (alpha) => {
- console.log('ColorMappedNeuroSurface: Received alphaChanged event', alpha);
+ debugLog('ColorMappedNeuroSurface: Received alphaChanged event', alpha);
this.config.alpha = alpha;
this.updateColors();
});
@@ -239,11 +240,11 @@ export class ColorMappedNeuroSurface extends NeuroSurface {
}
updateColors() {
- console.log('Updating colors. Mesh:', !!this.mesh, 'ColorMap:', !!this.colorMap);
+ debugLog('Updating colors. Mesh:', !!this.mesh, 'ColorMap:', !!this.colorMap);
if (!this.mesh || !this.colorMap) {
console.warn('Mesh or ColorMap not initialized in updateColors');
- console.log('Mesh:', this.mesh);
- console.log('ColorMap:', this.colorMap);
+ debugLog('Mesh:', this.mesh);
+ debugLog('ColorMap:', this.colorMap);
return;
}
@@ -251,10 +252,10 @@ export class ColorMappedNeuroSurface extends NeuroSurface {
const componentsPerColor = 4; // Always use RGBA
const colors = new Float32Array(vertexCount * componentsPerColor);
- console.log("threshold", this.threshold);
- console.log("irange", this.irange);
- console.log("alpha", this.config.alpha);
- console.log("data", this.data);
+ debugLog('threshold', this.threshold);
+ debugLog('irange', this.irange);
+ debugLog('alpha', this.config.alpha);
+ debugLog('data', this.data);
const baseSurfaceColor = new THREE.Color(this.config.color);
diff --git a/inst/htmlwidgets/neurosurface/src/debug.js b/inst/htmlwidgets/neurosurface/src/debug.js
new file mode 100644
index 0000000..a5b1b52
--- /dev/null
+++ b/inst/htmlwidgets/neurosurface/src/debug.js
@@ -0,0 +1,9 @@
+export let DEBUG = false;
+export function setDebug(value) {
+ DEBUG = value;
+}
+export function debugLog(...args) {
+ if (DEBUG) {
+ console.log(...args);
+ }
+}
diff --git a/inst/htmlwidgets/neurosurface/src/index.js b/inst/htmlwidgets/neurosurface/src/index.js
index 25d7fda..d504198 100644
--- a/inst/htmlwidgets/neurosurface/src/index.js
+++ b/inst/htmlwidgets/neurosurface/src/index.js
@@ -1,6 +1,7 @@
import * as THREE from 'three';
import { NeuroSurfaceViewer } from './NeuroSurfaceViewer';
import { SurfaceGeometry, NeuroSurface, ColorMappedNeuroSurface, VertexColoredNeuroSurface } from './classes';
+import { debugLog, setDebug } from './debug.js';
// Export the classes so they're available to the widget
export {
@@ -9,7 +10,9 @@ export {
NeuroSurface,
ColorMappedNeuroSurface,
VertexColoredNeuroSurface,
- THREE
+ THREE,
+ debugLog,
+ setDebug
};
// Optionally, you can also attach these to the global window object
@@ -21,8 +24,9 @@ if (typeof window !== 'undefined') {
NeuroSurface,
ColorMappedNeuroSurface,
VertexColoredNeuroSurface,
- THREE
+ THREE,
+ debugLog,
+ setDebug
};
}
-
-console.log('Neurosurface module initialized');
+debugLog('Neurosurface module initialized');
diff --git a/inst/htmlwidgets/surfwidget.js b/inst/htmlwidgets/surfwidget.js
index 7c52539..5df50f3 100644
--- a/inst/htmlwidgets/surfwidget.js
+++ b/inst/htmlwidgets/surfwidget.js
@@ -8,31 +8,42 @@ HTMLWidgets.widget({
var viewer;
var surfaceId = 'main'; // Default ID for single surface
- return {
+ function cleanup() {
+ if (viewer && typeof viewer.dispose === 'function') {
+ viewer.dispose();
+ viewer = null;
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ }
+ }
+
+ var instance = {
renderValue: function(x) {
- if (!viewer) {
- console.log("Creating NeuroSurfaceViewer");
- viewer = new neurosurface.NeuroSurfaceViewer(el, width, height, {
- ...x.config,
- cmap: x.cmap,
- rotationSpeed: 2.5, // Increase rotation speed
- initialZoom: 2.5 // Increase initial zoom
- }, x.viewpoint);
-
- // Use the new methods to set rotation speed and initial zoom
- //viewer.setRotationSpeed(2.5);
- //viewer.setInitialZoom(2.5);
- }
+ // dispose a previous viewer before creating a new one
+ cleanup();
+
+ neurosurface.debugLog('Creating NeuroSurfaceViewer');
+ viewer = new neurosurface.NeuroSurfaceViewer(el, width, height, {
+ ...x.config,
+ cmap: x.cmap,
+ rotationSpeed: 2.5, // Increase rotation speed
+ initialZoom: 2.5 // Increase initial zoom
+ }, x.viewpoint);
+
+ // Use the new methods to set rotation speed and initial zoom
+ //viewer.setRotationSpeed(2.5);
+ //viewer.setInitialZoom(2.5);
try {
- console.log("Creating SurfaceGeometry");
+ neurosurface.debugLog('Creating SurfaceGeometry');
var geometry = new neurosurface.SurfaceGeometry(x.vertices, x.faces, x.hemi);
- console.log("SurfaceGeometry created:", geometry);
+ neurosurface.debugLog('SurfaceGeometry created:', geometry);
var surface;
if (x.cmap) {
- console.log("Creating ColorMappedNeuroSurface");
+ neurosurface.debugLog('Creating ColorMappedNeuroSurface');
surface = new neurosurface.ColorMappedNeuroSurface(
geometry,
x.indices,
@@ -41,7 +52,7 @@ HTMLWidgets.widget({
{ irange: x.irange, thresh: x.thresh, alpha: x.alpha, ...x.config }
);
} else if (x.vertexColors) {
- console.log("Creating VertexColoredNeuroSurface");
+ neurosurface.debugLog('Creating VertexColoredNeuroSurface');
surface = new neurosurface.VertexColoredNeuroSurface(
geometry,
x.indices,
@@ -52,15 +63,15 @@ HTMLWidgets.widget({
throw new Error("Neither color map nor vertex colors provided");
}
- console.log("Surface created:", surface);
- console.log("Adding surface to viewer");
+ neurosurface.debugLog('Surface created:', surface);
+ neurosurface.debugLog('Adding surface to viewer');
viewer.addSurface(surface, surfaceId);
viewer.animate();
} catch (error) {
console.error("Error in renderValue:", error);
}
- },
+ },
resize: function(width, height) {
if (viewer) {
@@ -95,7 +106,18 @@ HTMLWidgets.widget({
setZoom: function(zoom) {
if (viewer) viewer.setInitialZoom(zoom);
- }
+ },
+
+ // Clean up viewer resources
+ destroy: cleanup
};
+ el.surfwidget = instance;
+ return instance;
+ },
+
+ onBeforeDestroy: function(el) {
+ if (el && el.surfwidget && typeof el.surfwidget.destroy === 'function') {
+ el.surfwidget.destroy();
+ }
}
});