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
2 changes: 1 addition & 1 deletion config/install/rl.settings.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
debug_mode: false
enable_event_log: true
event_log_max_rows: 100000
chart_line_threshold: 10
chart_line_threshold: 9
15 changes: 5 additions & 10 deletions css/rl-charts.css
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,18 @@
gap: 0.75em;
}

.rl-presets,
.rl-axes {
.rl-filter-group {
display: flex;
align-items: center;
gap: 0.4em;
}

.rl-presets strong,
.rl-axes strong {
.rl-filter-group strong {
color: #555;
white-space: nowrap;
}

.rl-preset-select,
.rl-axis-select {
.rl-filter-select {
padding: 0.25em 0.5em;
background: #fff;
border: 1px solid #ced4da;
Expand All @@ -114,13 +111,11 @@
cursor: pointer;
}

.rl-preset-select:hover,
.rl-axis-select:hover {
.rl-filter-select:hover {
border-color: #adb5bd;
}

.rl-preset-select:focus,
.rl-axis-select:focus {
.rl-filter-select:focus {
border-color: #0d6efd;
outline: none;
box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.25);
Expand Down
57 changes: 35 additions & 22 deletions js/rl-plotly-charts.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@
function initPlotlyCharts(data) {
const config = getResponsiveConfig();

// Y-axis metric: 'score' (Bayesian) or 'rate' (raw)
const metric = data.metric || 'score';
const metricLabel = metric === 'score' ? 'Conversion Score' : 'Conversion Rate';
const chartTitle = metric === 'score'
? 'Learning-adjusted Conversion Rate (Conversion Score) Over Time'
: 'Conversion Rate Over Time';

const defaultLayout = {
paper_bgcolor: 'rgba(255,255,255,1)',
plot_bgcolor: 'rgba(255,255,255,1)',
Expand All @@ -94,14 +101,14 @@
let numArms = 0;
if (data.lineChartData && data.lineChartData.arms) {
numArms = data.lineChartData.arms.length;
} else if (data.surface3d && data.surface3d.zMatrix) {
numArms = data.surface3d.zMatrix.length;
} else if (data.surface3d && data.surface3d.zMatrixScore) {
numArms = data.surface3d.zMatrixScore.length;
}

// Chart selection based on arm count (threshold from config or default 10):
// 1-threshold arms: 2D line chart
// threshold+1 arms: 3D Posterior Landscape
const lineChartThreshold = data.chartLineThreshold || 10;
const lineChartThreshold = data.chartLineThreshold || 9;
const showLineChart = numArms >= 1 && numArms <= lineChartThreshold;
const showLandscape = numArms > lineChartThreshold;

Expand All @@ -116,7 +123,7 @@
surface3dEl.parentElement.parentElement.style.display = showLandscape ? 'block' : 'none';
}

// 1. 2D Line Chart - Conversion Rate Over Time (up to threshold arms)
// 1. 2D Line Chart (up to threshold arms)
if (showLineChart && data.lineChartData && data.lineChartData.arms && data.lineChartData.arms.length > 0 && lineChartEl) {
try {
const traces2d = [];
Expand All @@ -133,7 +140,8 @@
const yValues = [];
arm.data.forEach(function(point) {
xValues.push(point.x);
yValues.push(point.y);
// Use score or rate based on metric setting
yValues.push(metric === 'score' ? point.score : point.rate);
});

traces2d.push({
Expand All @@ -146,7 +154,7 @@
color: arm.color,
width: 2
},
hovertemplate: '<b>' + armLabel + '</b><br>' + xAxisLabel + ': %{x}<br>Rate: %{y:.1f}%<extra></extra>'
hovertemplate: '<b>' + armLabel + '</b><br>' + xAxisLabel + ': %{x}<br>' + metricLabel + ': %{y:.1f}%<extra></extra>'
});
}

Expand All @@ -170,12 +178,12 @@

Plotly.newPlot('rl-plotly-2d-lines', traces2d, Object.assign({}, defaultLayout, {
title: {
text: 'Conversion Rate Over Time',
text: chartTitle,
font: { size: config.titleSize }
},
xaxis: xAxisConfig,
yaxis: {
title: { text: 'Conversion Rate (%)', font: { size: config.axisTitleSize } },
title: { text: metricLabel + ' (%)', font: { size: config.axisTitleSize } },
tickfont: { size: config.tickSize },
gridcolor: 'rgba(0,0,0,0.1)',
rangemode: 'tozero'
Expand Down Expand Up @@ -206,15 +214,16 @@
}

// 2. 3D Posterior Landscape (loss-landscape style) - threshold+1 arms
if (showLandscape && data.surface3d && data.surface3d.zMatrix && data.surface3d.zMatrix.length > 0 && surface3dEl) {
const zMatrix = metric === 'score' ? data.surface3d.zMatrixScore : data.surface3d.zMatrixRate;
if (showLandscape && data.surface3d && zMatrix && zMatrix.length > 0 && surface3dEl) {
try {
const landscapeNumArms = data.surface3d.zMatrix.length;
const landscapeNumArms = zMatrix.length;
const numTimePoints = data.surface3d.xValues.length;

// Get current (latest) conversion rate for each arm to sort
// Get current (latest) value for each arm to sort
const armRates = [];
for (let i = 0; i < landscapeNumArms; i++) {
const lastRate = data.surface3d.zMatrix[i][numTimePoints - 1] || 0;
const lastRate = zMatrix[i][numTimePoints - 1] || 0;
armRates.push({ index: i, rate: lastRate });
}
// Sort by rate ASCENDING (lowest rate = lowest index = front, highest rate = back)
Expand All @@ -226,7 +235,7 @@
const armLabels = data.surface3d.armLabels || [];
for (let i = 0; i < armRates.length; i++) {
const origIdx = armRates[i].index;
sortedZMatrix.push(data.surface3d.zMatrix[origIdx]);
sortedZMatrix.push(zMatrix[origIdx]);
sortedLabels.push(armLabels[origIdx] || ('Variant #' + origIdx));
}

Expand All @@ -245,11 +254,11 @@

for (let ti = 0; ti < numTimePoints; ti++) {
const xValue = data.surface3d.xValues[ti];
const rate = sortedZMatrix[ai][ti];
const val = sortedZMatrix[ai][ti];
// Use custom label if available for time-based axes
const xDisplay = (data.xLabels && data.xLabels[xValue]) ? data.xLabels[xValue] : xValue;
// Build complete hover text for each point
hoverRow.push('<b>' + fullLabel + '</b><br>' + xAxisLabel3d + ': ' + xDisplay + '<br>Rate: ' + rate.toFixed(1) + '%');
hoverRow.push('<b>' + fullLabel + '</b><br>' + xAxisLabel3d + ': ' + xDisplay + '<br>' + metricLabel + ': ' + val.toFixed(1) + '%');
}
hoverTextData.push(hoverRow);
}
Expand All @@ -260,10 +269,14 @@
tickfont: { size: config.tickSize }
};

// Show arm labels on Y-axis for small number of arms
if (landscapeNumArms <= lineChartThreshold) {
// Show arm labels on Y-axis when 15 or fewer variants for readability
// Use shorter labels for 3D axis (max 25 chars) to ensure proper alignment
if (landscapeNumArms <= 15) {
const shortLabels = sortedLabels.map(function(label) {
return truncateLabel(label, 25);
});
yAxisConfig.tickvals = armIndices;
yAxisConfig.ticktext = truncatedLabels;
yAxisConfig.ticktext = shortLabels;
yAxisConfig.tickangle = 0;
}

Expand Down Expand Up @@ -346,17 +359,17 @@
z: 100
},
colorbar: {
title: { text: 'Conversion Rate', side: 'right', font: { size: config.axisTitleSize } },
title: { text: metricLabel, side: 'right', font: { size: config.axisTitleSize } },
thickness: config.colorbarThickness,
len: config.colorbarLen
}
}], Object.assign({}, defaultLayout, {
title: { text: 'Conversion Rate Over Time', font: { size: config.titleSize } },
title: { text: chartTitle, font: { size: config.titleSize } },
scene: {
xaxis: xAxis3dConfig,
yaxis: yAxisConfig,
zaxis: {
title: { text: 'Conversion Rate (%)', font: { size: config.axisTitleSize } },
title: { text: metricLabel + ' (%)', font: { size: config.axisTitleSize } },
tickfont: { size: config.tickSize }
},
camera: {
Expand All @@ -373,7 +386,7 @@
if (landscapeNumArms < totalArmsAll) {
const tipEl = surface3dEl.parentElement.querySelector('.rl-chart-tip');
if (tipEl) {
tipEl.innerHTML = '<strong>Tip:</strong> Showing top ' + landscapeNumArms + ' active variants out of ' + totalArmsAll + ' total. Taller/brighter = better conversion rate.';
tipEl.innerHTML = '<strong>Tip:</strong> Showing top ' + landscapeNumArms + ' active variants out of ' + totalArmsAll + ' total. Taller/brighter = better ' + metricLabel.toLowerCase() + '.';
}
}
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion rl.install
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ function rl_update_10001() {
function rl_update_10002() {
$config = \Drupal::configFactory()->getEditable('rl.settings');
if ($config->get('chart_line_threshold') === NULL) {
$config->set('chart_line_threshold', 10);
$config->set('chart_line_threshold', 9);
}
$config->save();

Expand Down
Loading