Skip to content
Merged
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
63 changes: 44 additions & 19 deletions css/comparison.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,35 @@
*/

/* ========== 比较矩阵容器 ========== */
/* Wrapper to handle full-width layout */
.comparison-matrix-wrapper {
width: calc(100% + 2 * var(--spacing-xl));
margin: 0 calc(0px - var(--spacing-xl));
display: flex;
flex-direction: column;
/* Default: Center the matrix when it fits in the screen */
align-items: center;
}

/* When the matrix overflows the screen, JS adds this class to switch alignment */
.comparison-matrix-wrapper.align-left {
align-items: flex-start;
}

.comparison-matrix-container {
/* Allow the matrix to scroll horizontally on smaller screens while
still centering the content when space permits. */
width: 100%;
max-width: 100%;
margin: var(--spacing-lg) auto;
/* Container fits its content */
width: fit-content;
max-width: none;
/* Margin for vertical spacing only; horizontal alignment handled by wrapper */
margin: var(--spacing-lg) 0;

padding: clamp(var(--spacing-lg), 3vw, var(--spacing-xl));
background: var(--bg-light);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
overflow-x: auto;
overscroll-behavior-x: contain;
box-sizing: border-box;
overflow-x: visible;
}

.matrix-title {
Expand All @@ -30,14 +46,16 @@
background: var(--bg-white);
border-radius: var(--radius-md);
border: 1px solid var(--border-color);
/* Ensure title limits width to container */
max-width: 100%;
}

/* ========== 矩阵网格 ========== */
.comparison-matrix-grid {
display: grid;
gap: var(--spacing-sm);
/* remove large vertical margins that can cause visual overflow; center horizontally */
margin: 0 auto;
/* Margin no longer needed for centering, handled by wrapper flex */
margin: 0;
background: var(--bg-white);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
Expand Down Expand Up @@ -143,7 +161,8 @@
.matrix-col-label {
height: var(--matrix-label-size, 40px);
line-height: var(--matrix-label-size, 40px);
writing-mode: horizontal-tb; /* keep column labels horizontal */
writing-mode: horizontal-tb;
/* keep column labels horizontal */
}

/* Row labels: make them match the column label size (width) and display
Expand Down Expand Up @@ -341,8 +360,13 @@
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
from {
opacity: 0;
}

to {
opacity: 1;
}
}

.group-modal-content {
Expand All @@ -364,6 +388,7 @@
transform: translateY(-50px);
opacity: 0;
}

to {
transform: translateY(0);
opacity: 1;
Expand Down Expand Up @@ -503,11 +528,11 @@
.comparison-matrix-grid {
gap: var(--spacing-sm);
}

.matrix-cell {
min-height: 150px;
}

.matrix-row-label,
.matrix-col-label {
font-size: var(--font-xs);
Expand All @@ -519,26 +544,26 @@
.comparison-matrix-container {
padding: 10px;
}

.matrix-title {
font-size: var(--font-xl);
}

.comparison-matrix-grid {
gap: var(--spacing-xs);
padding: 10px;
}

.matrix-cell {
min-height: 120px;
}

.group-modal-content {
width: 95%;
margin: 10% auto;
padding: var(--spacing-xl);
}

.group-modal-header {
font-size: var(--font-2xl);
}
Expand Down
97 changes: 87 additions & 10 deletions js/renderers/comparison-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@

const VALID_LAYOUTS = new Set(['radial', 'tree', 'packing']);
const PACK_EPSILON = 1e-6;

// Matrix alignment threshold: when sidebar is visible and the matrix fills less than
// this percentage of available width, left-align it to avoid a large empty band on the left.
// Value of 0.92 (92%) provides a good balance - matrices that are reasonably wide stay
// centered, while narrow matrices align left to improve visual appearance.
const MATRIX_FILL_RATIO_THRESHOLD = 0.92;

function resolveReverseColorsFlag() {
if (typeof colorSchemeReversed !== 'undefined') return !!colorSchemeReversed;
Expand Down Expand Up @@ -338,9 +344,9 @@
if (pSort && pSort !== 'none') {
root.sort((a, b) => {
if (pSort === 'name') return d3.ascending(a.data.name, b.data.name);
const valA = (typeof a._agg === 'number') ? a._agg : (a.value || 0);
const valB = (typeof b._agg === 'number') ? b._agg : (b.value || 0);
return pSort === 'value-asc' ? valA - valB : valB - valA;
const valA = (typeof a._agg === 'number') ? a._agg : (a.value || 0);
const valB = (typeof b._agg === 'number') ? b._agg : (b.value || 0);
return pSort === 'value-asc' ? valA - valB : valB - valA;
});
}

Expand Down Expand Up @@ -476,6 +482,13 @@
}

if (!vizContainer) return;

// Clean up any existing ResizeObserver before clearing content
const cleanupStore = resolveComparisonRendererStore();
if (cleanupStore && typeof cleanupStore.disconnectResizeObserver === 'function') {
try { cleanupStore.disconnectResizeObserver(); } catch (_) { }
}

vizContainer.innerHTML = '';

// 包裹面板与顶部栏(仅普通/内联模式使用)
Expand Down Expand Up @@ -1254,23 +1267,28 @@

const treatments = [...new Set(comparisons.flatMap(c => [c.treatment_1, c.treatment_2]))];
const n = treatments.length;

// Create a wrapper to handle the breakout layout (negative margins) and centering
const matrixWrapper = document.createElement('div');
matrixWrapper.className = 'comparison-matrix-wrapper';

const matrixContainer = document.createElement('div');
matrixContainer.className = 'comparison-matrix-container';
matrixContainer.id = 'comparison-matrix';
// Make the matrix container size to its inner grid so exports capture full width.
try {
// set inline width to max-content to ensure render/export code sees correct size
matrixContainer.style.width = 'max-content';
matrixContainer.style.maxWidth = 'none';
// keep centering via margin (CSS also sets this)
matrixContainer.style.margin = 'var(--spacing-lg) auto';
matrixContainer.style.margin = 'var(--spacing-lg) 0';
} catch (_) { }

const title = document.createElement('div');
title.className = 'matrix-title';
title.textContent = `Treatment Comparison Matrix (${n} groups, ${comparisons.length} comparisons)`;
matrixContainer.appendChild(title);

// Append container to wrapper
matrixWrapper.appendChild(matrixContainer);

const matrixGrid = document.createElement('div');
matrixGrid.className = 'comparison-matrix-grid';
matrixGrid.id = 'comparison-matrix-grid';
Expand All @@ -1296,7 +1314,7 @@
label.style.zIndex = '1';
matrixGrid.appendChild(label);
}

const cornerCell = createEmptyCell();
cornerCell.style.pointerEvents = 'none';
cornerCell.style.zIndex = '0';
Expand Down Expand Up @@ -1327,7 +1345,7 @@
matrixGrid.appendChild(createEmptyCell());
}
}

// Append row label at the end of the row
matrixGrid.appendChild(rowLabel);
}
Expand All @@ -1340,7 +1358,59 @@
matrixContainer.appendChild(legend);
}

vizContainer.appendChild(matrixContainer);
vizContainer.appendChild(matrixWrapper);

// Adaptive Alignment Logic:
// Center the matrix when it fits in the screen (default).
// Align left (flex-start) when it overflows to avoid left-side clipping.
const resizeObserver = new ResizeObserver(() => {
if (!matrixContainer || !matrixWrapper) return;
const containerWidth = matrixContainer.offsetWidth;
// IMPORTANT: use the actual width of the visualization area (right pane),
// not window.innerWidth. Otherwise, when the sidebar is visible (or DevTools
// shrinks the viewport), we may incorrectly keep centering and the left side
// gets hidden behind the sidebar due to z-index stacking.
let availableWidth = 0;
try {
availableWidth = (vizContainer && vizContainer.getBoundingClientRect)
? vizContainer.getBoundingClientRect().width
: 0;
} catch (_) { availableWidth = 0; }
if (!availableWidth || !Number.isFinite(availableWidth)) {
try {
availableWidth = matrixWrapper.parentElement
? matrixWrapper.parentElement.getBoundingClientRect().width
: 0;
} catch (_) { availableWidth = 0; }
}
if (!availableWidth || !Number.isFinite(availableWidth)) {
availableWidth = window.innerWidth;
}

// Alignment policy:
// - Sidebar visible: prefer left alignment when the matrix is narrow to avoid
// a large empty band on the left (esp. when users shrink panel width).
// - Sidebar collapsed: centering is usually preferable; only left-align when
// overflowing.
const appBodyEl = document.querySelector('.app-body');
const sidebarCollapsed = !!(appBodyEl && appBodyEl.classList && appBodyEl.classList.contains('sidebar-collapsed'));

const fillRatio = availableWidth > 0 ? (containerWidth / availableWidth) : 1;
const shouldAlignLeft = sidebarCollapsed
? (containerWidth > availableWidth)
: ((containerWidth > availableWidth) || (fillRatio < MATRIX_FILL_RATIO_THRESHOLD));

if (shouldAlignLeft) {
matrixWrapper.classList.add('align-left');
} else {
matrixWrapper.classList.remove('align-left');
}
});
resizeObserver.observe(matrixContainer);

// Store the observer reference for cleanup when view changes
matrixStore.setResizeObserver(resizeObserver);

if (typeof window.requestLayoutPanelContextSync === 'function') {
try { window.requestLayoutPanelContextSync(); } catch (_) { }
}
Expand Down Expand Up @@ -1484,6 +1554,13 @@
: document.getElementById('viz-container');

if (!vizContainer) return;

// Clean up any existing ResizeObserver before clearing content
const cleanupStore = resolveComparisonRendererStore();
if (cleanupStore && typeof cleanupStore.disconnectResizeObserver === 'function') {
try { cleanupStore.disconnectResizeObserver(); } catch (_) { }
}

vizContainer.innerHTML = '';

// Remember current focused item so redraws in matrix mode keep this view
Expand Down
19 changes: 19 additions & 0 deletions js/state/comparison-renderer-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
let svg = null;
let zoom = null;
let stats = null;
let resizeObserver = null;

return {
getSvg() {
Expand All @@ -25,12 +26,30 @@
svg = null;
zoom = null;
stats = null;
this.disconnectResizeObserver();
},
getStats() {
return stats;
},
setStats(value) {
stats = value || null;
},
getResizeObserver() {
return resizeObserver;
},
setResizeObserver(value) {
this.disconnectResizeObserver();
resizeObserver = value || null;
},
disconnectResizeObserver() {
if (resizeObserver) {
try {
resizeObserver.disconnect();
} catch (e) {
console.warn('Failed to disconnect ResizeObserver:', e);
}
resizeObserver = null;
}
}
};
})();
Expand Down
Loading