Skip to content

Commit 66db175

Browse files
committed
adding pastel option, fixing plddt color
1 parent 1b1da83 commit 66db175

3 files changed

Lines changed: 225 additions & 55 deletions

File tree

py2Dmol/resources/pseudo_3D_viewer.html

Lines changed: 103 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@
7575
flex-shrink: 0;
7676
font-size: 12px; /* Ensure label font size matches */
7777
}
78-
/* Style for the shadow/outline checkbox labels */
78+
/* Style for the shadow/outline/pastel checkbox labels */
7979
.toggle-item label[for="shadowEnabledCheckbox"],
80-
.toggle-item label[for="outlineEnabledCheckbox"] {
80+
.toggle-item label[for="outlineEnabledCheckbox"],
81+
.toggle-item label[for="rotationCheckbox"] {
8182
width: auto; /* Let it be auto-sized */
8283
margin-left: 5px; /* Add space next to the slider */
8384
}
@@ -216,27 +217,30 @@
216217
<!-- Right Column -->
217218
<div id="rightPanelContainer">
218219

219-
<!-- Color Select -->
220-
<div class="control-group">
221-
<label for="colorSelect">Color</label>
222-
<select id="colorSelect">
223-
<option value="auto">Auto</option>
224-
<option value="plddt">pLDDT</option>
225-
<option value="rainbow">Rainbow</option>
226-
<option value="chain">Chain</option>
227-
</select>
228-
</div>
229-
230-
<!-- Options -->
231-
<div id="optionsContainer" class="control-group">
232-
<div class="toggle-item">
233-
<input type="checkbox" id="rotationCheckbox">
234-
<label for="rotationCheckbox">Rotate</label>
220+
<!-- Style Group -->
221+
<div id="styleAppearanceContainer" class="control-group">
222+
<label>Style</label>
223+
<!-- Color Select -->
224+
<div class="toggle-item">
225+
<label for="colorSelect" style="width: 50px;">Color:</label>
226+
<select id="colorSelect" style="flex-grow: 1;">
227+
<option value="auto">Auto</option>
228+
<option value="plddt">pLDDT</option>
229+
<option value="rainbow">Rainbow</option>
230+
<option value="chain">Chain</option>
231+
</select>
232+
</div>
233+
<!-- Pastel -->
234+
<div class="toggle-item">
235+
<label for="pastelSlider" style="width: 50px;">Pastel:</label>
236+
<input type="range" id="pastelSlider" min="0" max="1" value="0.25" step="0.05">
235237
</div>
238+
<!-- Width -->
236239
<div class="toggle-item">
237-
<label for="lineWidthSlider">Width:</label>
240+
<label for="lineWidthSlider" style="width: 50px;">Width:</label>
238241
<input type="range" id="lineWidthSlider" min="1" max="5" value="3" step="0.5">
239242
</div>
243+
<!-- Shadow -->
240244
<div class="toggle-item">
241245
<input type="checkbox" id="shadowEnabledCheckbox">
242246
<label for="shadowEnabledCheckbox">Shadow</label>
@@ -248,7 +252,16 @@
248252
</div>
249253
</div>
250254

251-
<!-- Trajectory -->
255+
<!-- View Group -->
256+
<div id="viewContainer" class="control-group">
257+
<label>View</label>
258+
<div class="toggle-item">
259+
<input type="checkbox" id="rotationCheckbox">
260+
<label for="rotationCheckbox">Rotate</label>
261+
</div>
262+
</div>
263+
264+
<!-- Trajectory Group -->
252265
<div id="trajectoryContainer" class="control-group">
253266
<label for="trajectorySelect">Trajectory</label>
254267
<select id="trajectorySelect">
@@ -289,19 +302,30 @@
289302
const pymolColors = ["#33ff33","#00ffff","#ff33cc","#ffff00","#ff9999","#e5e5e5","#7f7fff","#ff7f00","#7fff7f","#199999","#ff007f","#ffdd5e","#8c3f99","#b2b2b2","#007fff","#c4b200","#8cb266","#00bfbf","#b27f7f","#fcd1a5","#ff7f7f","#ffbfdd","#7fffff","#ffff7f","#00ff7f","#337fcc","#d8337f","#bfff3f","#ff7fff","#d8d8ff","#3fffbf","#b78c4c","#339933","#66b2b2","#ba8c84","#84bf00","#b24c66","#7f7f7f","#3f3fa5","#a5512b"];
290303
function hexToRgb(hex) { if (!hex || typeof hex !== 'string') { return {r: 128, g: 128, b: 128}; } const r = parseInt(hex.slice(1,3), 16); const g = parseInt(hex.slice(3,5), 16); const b = parseInt(hex.slice(5,7), 16); return {r, g, b}; }
291304
function hsvToRgb(h, s, v) { const c = v * s; const x = c * (1 - Math.abs((h / 60) % 2 - 1)); const m = v - c; let r, g, b; if (h < 60) { r = c; g = x; b = 0; } else if (h < 120) { r = x; g = c; b = 0; } else if (h < 180) { r = 0; g = c; b = x; } else if (h < 240) { r = 0; g = x; b = c; } else if (h < 300) { r = x; g = 0; b = c; } else { r = c; g = 0; b = x; } return { r: Math.round((r + m) * 255), g: Math.round((g + m) * 255), b: Math.round((b + m) * 255) }; }
305+
306+
// N-term (blue) to C-term (red)
292307
function getRainbowColor(value, min, max) {
293308
if (max - min < 1e-6) return hsvToRgb(240, 1.0, 1.0); // Default to blue
294309
let normalized = (value - min) / (max - min);
295310
normalized = Math.max(0, Math.min(1, normalized));
296-
const hue = 240 * (1 - normalized); // Reversed: 0 -> 240 (blue), 1 -> 0 (red)
311+
const hue = 240 * (1 - normalized); // 0 -> 240 (blue), 1 -> 0 (red)
297312
return hsvToRgb(hue, 1.0, 1.0);
298313
}
299-
function getPlddtColor(plddt) { return getRainbowColor(plddt, 50, 90); }
314+
315+
// 50 (red) to 90 (blue)
316+
function getPlddtRainbowColor(value, min, max) {
317+
if (max - min < 1e-6) return hsvToRgb(0, 1.0, 1.0); // Default to red
318+
let normalized = (value - min) / (max - min);
319+
normalized = Math.max(0, Math.min(1, normalized));
320+
const hue = 240 * normalized; // 0 -> 0 (red), 1 -> 240 (blue)
321+
return hsvToRgb(hue, 1.0, 1.0);
322+
}
323+
324+
function getPlddtColor(plddt) { return getPlddtRainbowColor(plddt, 50, 90); }
300325
function getChainColor(chainIndex) { if (chainIndex < 0) chainIndex = 0; return hexToRgb(pymolColors[chainIndex % pymolColors.length]); }
301326

302327
// ============================================================================
303328
// PSEUDO-3D RENDERER
304-
// ============================================================================
305329
class Pseudo3DRenderer {
306330
constructor(canvas) {
307331
this.canvas = canvas;
@@ -317,7 +341,8 @@
317341
default_rotate: false,
318342
hide_controls: false,
319343
autoplay: false,
320-
hide_box: false
344+
hide_box: false,
345+
default_pastel: 0.25
321346
};
322347

323348
// Current render state
@@ -336,6 +361,7 @@
336361
// Set defaults from config, with fallback
337362
this.shadowEnabled = (typeof config.default_shadow === 'boolean') ? config.default_shadow : true;
338363
this.outlineEnabled = (typeof config.default_outline === 'boolean') ? config.default_outline : true;
364+
this.pastelLevel = (typeof config.default_pastel === 'number') ? config.default_pastel : 0.25;
339365

340366
this.isTransparent = false; // Default to white background
341367
this.resolvedAutoColor = 'rainbow'; // Default 'auto' to rainbow
@@ -389,6 +415,7 @@
389415
this.lineWidthSlider = null;
390416
this.shadowEnabledCheckbox = null;
391417
this.outlineEnabledCheckbox = null;
418+
this.pastelSlider = null;
392419

393420
this.setupInteraction();
394421
}
@@ -403,6 +430,19 @@
403430
const dy = touch1.clientY - touch2.clientY;
404431
return Math.sqrt(dx * dx + dy * dy);
405432
}
433+
434+
_applyPastel(rgb) {
435+
if (this.pastelLevel <= 0) {
436+
return rgb;
437+
}
438+
// Apply pastel transformation (mix with white)
439+
const mix = this.pastelLevel;
440+
return {
441+
r: Math.round(rgb.r * (1 - mix) + 255 * mix),
442+
g: Math.round(rgb.g * (1 - mix) + 255 * mix),
443+
b: Math.round(rgb.b * (1 - mix) + 255 * mix)
444+
};
445+
}
406446

407447
setupInteraction() {
408448
// Add inertia logic
@@ -568,7 +608,7 @@
568608
}
569609

570610
// Set UI controls from main script
571-
setUIControls(controlsContainer, playButton, frameSlider, frameCounter, trajectorySelect, speedSelect, rotationCheckbox, lineWidthSlider, shadowEnabledCheckbox, outlineEnabledCheckbox) {
611+
setUIControls(controlsContainer, playButton, frameSlider, frameCounter, trajectorySelect, speedSelect, rotationCheckbox, lineWidthSlider, shadowEnabledCheckbox, outlineEnabledCheckbox, pastelSlider) {
572612
this.controlsContainer = controlsContainer;
573613
this.playButton = playButton;
574614
this.frameSlider = frameSlider;
@@ -579,6 +619,7 @@
579619
this.lineWidthSlider = lineWidthSlider;
580620
this.shadowEnabledCheckbox = shadowEnabledCheckbox;
581621
this.outlineEnabledCheckbox = outlineEnabledCheckbox;
622+
this.pastelSlider = pastelSlider;
582623

583624
this.lineWidth = parseFloat(this.lineWidthSlider.value); // Read default from slider
584625
this.autoRotate = this.rotationCheckbox.checked; // Read default from checkbox
@@ -625,6 +666,18 @@
625666
this.render();
626667
});
627668
}
669+
670+
if (this.pastelSlider) {
671+
this.pastelSlider.addEventListener('input', (e) => {
672+
this.pastelLevel = parseFloat(e.target.value);
673+
// Force-recalculate both color arrays
674+
this.colors = this._calculateSegmentColors();
675+
this.plddtColors = this._calculatePlddtColors();
676+
if (!this.isPlaying) { // Only render if not playing
677+
this.render();
678+
}
679+
});
680+
}
628681

629682
// Prevent canvas drag from interfering with slider
630683
const handleSliderChange = (e) => {
@@ -654,7 +707,7 @@
654707
// Also prevent canvas drag when interacting with other controls
655708
const allControls = [this.playButton, this.trajectorySelect, this.speedSelect,
656709
this.rotationCheckbox, this.lineWidthSlider,
657-
this.shadowEnabledCheckbox, this.outlineEnabledCheckbox];
710+
this.shadowEnabledCheckbox, this.outlineEnabledCheckbox, this.pastelSlider];
658711
allControls.forEach(control => {
659712
if (control) {
660713
control.addEventListener('mousedown', (e) => {
@@ -810,6 +863,7 @@
810863
this.lineWidthSlider.disabled = !enabled;
811864
if (this.shadowEnabledCheckbox) this.shadowEnabledCheckbox.disabled = !enabled;
812865
if (this.outlineEnabledCheckbox) this.outlineEnabledCheckbox.disabled = !enabled;
866+
if (this.pastelSlider) this.pastelSlider.disabled = !enabled;
813867
this.canvas.style.cursor = enabled ? 'grab' : 'wait';
814868
}
815869

@@ -1109,27 +1163,28 @@
11091163
}
11101164
}
11111165

1112-
const grey = {r: 128, g: 128, b: 128};
1113-
11141166
return this.segmentIndices.map(segInfo => {
1167+
const grey = {r: 128, g: 128, b: 128};
1168+
let color;
11151169
const i = segInfo.origIndex;
11161170
const type = segInfo.type;
11171171

11181172
if (type === 'L') {
11191173
// Ligands are grey unless in plddt mode
1120-
return grey;
1174+
color = grey;
11211175
}
11221176
// plddt mode is handled in render()
1123-
if (effectiveColorMode === 'chain') {
1177+
else if (effectiveColorMode === 'chain') {
11241178
const chainId = this.chains[i] || 'A';
11251179
const chainIndex = chainIndexMap.get(chainId);
1126-
return getChainColor(chainIndex !== undefined ? chainIndex : 0);
1180+
color = getChainColor(chainIndex !== undefined ? chainIndex : 0);
11271181
}
11281182
else { // rainbow
11291183
const scale = this.chainRainbowScales[segInfo.chainId];
1130-
if (scale) { return getRainbowColor(segInfo.colorIndex, scale.min, scale.max); }
1131-
else { return grey; }
1184+
if (scale) { color = getRainbowColor(segInfo.colorIndex, scale.min, scale.max); }
1185+
else { color = grey; }
11321186
}
1187+
return this._applyPastel(color);
11331188
});
11341189
}
11351190

@@ -1138,23 +1193,24 @@
11381193
const m = this.segmentIndices.length;
11391194
if (m === 0) return [];
11401195

1141-
const grey = {r: 128, g: 128, b: 128};
11421196
const colors = new Array(m);
11431197

11441198
for (let i = 0; i < m; i++) {
11451199
const segInfo = this.segmentIndices[i];
11461200
const atomIdx = segInfo.origIndex;
11471201
const type = segInfo.type;
1202+
let color;
11481203

11491204
if (type === 'L') {
11501205
const plddt1 = (this.plddts[atomIdx] !== null && this.plddts[atomIdx] !== undefined) ? this.plddts[atomIdx] : 50;
1151-
colors[i] = getPlddtColor(plddt1);
1206+
color = getPlddtColor(plddt1);
11521207
} else {
11531208
const plddt1 = (this.plddts[atomIdx] !== null && this.plddts[atomIdx] !== undefined) ? this.plddts[atomIdx] : 50;
11541209
const plddt2_idx = (segInfo.idx2 < this.coords.length) ? segInfo.idx2 : segInfo.idx1;
11551210
const plddt2 = (this.plddts[plddt2_idx] !== null && this.plddts[plddt2_idx] !== undefined) ? this.plddts[plddt2_idx] : 50;
1156-
colors[i] = getPlddtColor((plddt1 + plddt2) / 2);
1211+
color = getPlddtColor((plddt1 + plddt2) / 2);
11571212
}
1213+
colors[i] = this._applyPastel(color);
11581214
}
11591215
return colors;
11601216
}
@@ -1602,11 +1658,12 @@
16021658
default_shadow: true,
16031659
default_outline: true,
16041660
default_width: 3.0,
1605-
default_rotate: false,
1606-
hide_controls: false,
1607-
autoplay: false,
1608-
hide_box: false
1609-
};
1661+
default_rotate: false,
1662+
hide_controls: false,
1663+
autoplay: false,
1664+
hide_box: false,
1665+
default_pastel: 0.25
1666+
};
16101667

16111668
// 2. Setup Canvas
16121669
const canvas = document.getElementById('canvas');
@@ -1645,17 +1702,20 @@
16451702
const speedSelect = document.getElementById('speedSelect');
16461703
const rotationCheckbox = document.getElementById('rotationCheckbox');
16471704
const lineWidthSlider = document.getElementById('lineWidthSlider');
1705+
const pastelSlider = document.getElementById('pastelSlider');
16481706

1649-
// Set defaults for width and rotate
1707+
// Set defaults for width, rotate, and pastel
16501708
lineWidthSlider.value = window.renderer.lineWidth;
16511709
rotationCheckbox.checked = window.renderer.autoRotate;
1710+
pastelSlider.value = window.renderer.pastelLevel; // Set default from renderer
16521711

16531712
// Pass ALL controls to the renderer
16541713
window.renderer.setUIControls(
16551714
controlsContainer, playButton,
16561715
frameSlider, frameCounter, trajectorySelect,
16571716
speedSelect, rotationCheckbox, lineWidthSlider,
1658-
shadowEnabledCheckbox, outlineEnabledCheckbox
1717+
shadowEnabledCheckbox, outlineEnabledCheckbox,
1718+
pastelSlider
16591719
);
16601720

16611721
// Handle new UI config options

0 commit comments

Comments
 (0)