<!-- Demo layout: controls + results table + bar chart history -->
<div class="panel">
<div class="controls">
<label>
<span class="lbl">Privacy budget ε = <strong id="eps-val">1.0</strong></span>
<input id="eps" type="range" min="0.1" max="3" step="0.1" value="1.0">
</label>
<label>
<span class="lbl">Failure probability δ = <strong id="delta-val">1e-5</strong></span>
<input id="delta" type="range" min="1" max="5" step="1" value="3">
</label>
<button id="btn-privatize" type="button">Privatize</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="result-area" class="result-area" aria-live="polite"></div>
<canvas id="chart" width="380" height="200" aria-label="Bar chart comparing Gaussian and Laplace privatized means over recent runs"></canvas>
<p class="hint">Each click adds calibrated noise to the true mean. Blue bars show Gaussian results; red bars show Laplace results. The dashed line is the true mean. More runs show the spread of each mechanism.</p>
</div>
/* Panel styles for the Gaussian vs Laplace demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.panel { max-width: 420px; margin: 0 auto; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
label { display: flex; flex-direction: column; gap: .2rem; }
.lbl { font-size: .85rem; color: #444; }
input[type=range] { width: 100%; accent-color: #1d3557; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result-area { font-size: .9rem; min-height: 5rem; background: #f4f7fa;
border-radius: 8px; padding: .7rem; margin-bottom: .6rem; }
.row { display: flex; justify-content: space-between; padding: .15rem 0;
border-bottom: 1px solid #e0e7ef; }
.row:last-child { border-bottom: none; font-weight: 600; }
.tag-g { color: #1a5276; }
.tag-l { color: #6e2f0a; }
canvas { display: block; border-radius: 8px; background: #f4f7fa;
margin-bottom: .6rem; }
.hint { font-size: .8rem; color: #666; line-height: 1.4; margin: 0; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Box-Muller transform — generates standard normal random variates.
function randNormal() {
let u = 0, v2 = 0;
while (u === 0) u = Math.random();
while (v2 === 0) v2 = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v2);
}
// Laplace variate via inverse CDF — generates a Laplace(0, b) variate via inverse CDF.
function randLaplace(b) {
const u = Math.random() - 0.5;
return -b * Math.sign(u) * Math.log(1 - 2 * Math.abs(u));
}
// Fixed dataset — 20 values drawn once, fixed for reproducibility of the demo.
const N = 20;
const DATASET = [72,45,88,31,60,95,14,67,53,82,41,76,29,58,90,37,64,48,71,55];
const TRUE_MEAN = DATASET.reduce((a, b) => a + b, 0) / N;
// L1 and L2 sensitivity for mean query on [0,100] values — each value in [0,100]; removing one record shifts mean by at most 100/N.
const L1_SENS = 100 / N;
const L2_SENS = 100 / N;
const epsSlider = document.getElementById('eps');
const deltaSlider = document.getElementById('delta');
const epsVal = document.getElementById('eps-val');
const deltaVal = document.getElementById('delta-val');
const resultArea = document.getElementById('result-area');
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// Delta slider encoding — delta slider maps 1..5 → 1e-1..1e-5.
function sliderToDelta(v) { return Math.pow(10, -parseInt(v, 10)); }
epsSlider.addEventListener('input', () => { epsVal.textContent = parseFloat(epsSlider.value).toFixed(1); });
deltaSlider.addEventListener('input', () => { deltaVal.textContent = '1e-' + deltaSlider.value; });
let history = [];
function privatize() {
const eps = parseFloat(epsSlider.value);
const delta = sliderToDelta(deltaSlider.value);
// Gaussian noise std-dev calibrated to (epsilon, delta)-DP — calibrate sigma to L2 sensitivity.
const sigma = L2_SENS * Math.sqrt(2 * Math.log(1.25 / delta)) / eps;
// Laplace scale calibrated to epsilon-DP — calibrate b to L1 sensitivity.
const b = L1_SENS / eps;
const gaussNoise = randNormal() * sigma;
const laplaceNoise = randLaplace(b);
const gaussResult = TRUE_MEAN + gaussNoise;
const laplaceResult = TRUE_MEAN + laplaceNoise;
history.push({ eps, delta, gaussResult, laplaceResult, sigma, b });
if (history.length > 8) history.shift();
// Render the results table
resultArea.innerHTML = `
<div class="row"><span>True mean</span><span>${TRUE_MEAN.toFixed(2)}</span></div>
<div class="row"><span class="tag-g">Gaussian result (σ=${sigma.toFixed(2)})</span><span>${gaussResult.toFixed(2)}</span></div>
<div class="row"><span class="tag-l">Laplace result (b=${b.toFixed(2)})</span><span>${laplaceResult.toFixed(2)}</span></div>
<div class="row"><span>Gaussian absolute error</span><span>${Math.abs(gaussNoise).toFixed(2)}</span></div>
<div class="row"><span>Laplace absolute error</span><span>${Math.abs(laplaceNoise).toFixed(2)}</span></div>
`;
drawChart();
}
function drawChart() {
// Bar chart of Gaussian vs Laplace results over history — bar chart of Gaussian vs Laplace results over history.
const W = canvas.width, H = canvas.height;
const PAD = 30;
ctx.clearRect(0, 0, W, H);
if (history.length === 0) return;
const allVals = history.flatMap(h => [h.gaussResult, h.laplaceResult, TRUE_MEAN]);
const lo = Math.min(...allVals) - 5;
const hi = Math.max(...allVals) + 5;
const scaleY = (v) => PAD + (H - 2 * PAD) * (1 - (v - lo) / (hi - lo));
// Dashed true-mean reference line — dashed line at true mean.
ctx.save();
ctx.strokeStyle = '#999';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath(); ctx.moveTo(PAD, scaleY(TRUE_MEAN)); ctx.lineTo(W - PAD, scaleY(TRUE_MEAN)); ctx.stroke();
ctx.restore();
const bw = Math.max(6, Math.floor((W - 2 * PAD) / (history.length * 2.5)));
history.forEach((h, i) => {
const cx = PAD + (i + 0.5) * ((W - 2 * PAD) / history.length);
// Blue bar for Gaussian result — blue bar for Gaussian result.
ctx.fillStyle = '#1a5276';
const gY = scaleY(h.gaussResult);
const baseY = scaleY(TRUE_MEAN);
ctx.fillRect(cx - bw - 1, Math.min(gY, baseY), bw, Math.abs(gY - baseY) || 1);
// Orange bar for Laplace result — orange bar for Laplace result.
ctx.fillStyle = '#c0392b';
const lY = scaleY(h.laplaceResult);
ctx.fillRect(cx + 1, Math.min(lY, baseY), bw, Math.abs(lY - baseY) || 1);
});
// Small legend — small legend.
ctx.font = '10px system-ui';
ctx.fillStyle = '#1a5276'; ctx.fillRect(PAD, H - 16, 10, 10);
ctx.fillStyle = '#333'; ctx.fillText('Gaussian', PAD + 14, H - 7);
ctx.fillStyle = '#c0392b'; ctx.fillRect(PAD + 80, H - 16, 10, 10);
ctx.fillStyle = '#333'; ctx.fillText('Laplace', PAD + 94, H - 7);
}
function resetDemo() {
history = [];
resultArea.innerHTML = '<span style="color:#888">Click Privatize to run the mechanisms.</span>';
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
document.getElementById('btn-privatize').addEventListener('click', privatize);
document.getElementById('btn-reset').addEventListener('click', resetDemo);
// Initialize demo on page load
resetDemo();
<!-- Demo layout: controls + results table + bar chart history -->
<div class="panel">
<div class="controls">
<label>
<span class="lbl">Privacy budget ε = <strong id="eps-val">1.0</strong></span>
<input id="eps" type="range" min="0.1" max="3" step="0.1" value="1.0">
</label>
<label>
<span class="lbl">Failure probability δ = <strong id="delta-val">1e-5</strong></span>
<input id="delta" type="range" min="1" max="5" step="1" value="3">
</label>
<button id="btn-privatize" type="button">Privatize</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="result-area" class="result-area" aria-live="polite"></div>
<canvas id="chart" width="380" height="200" aria-label="Bar chart comparing Gaussian and Laplace privatized means over recent runs"></canvas>
<p class="hint">Each click adds calibrated noise to the true mean. Blue bars show Gaussian results; red bars show Laplace results. The dashed line is the true mean. More runs show the spread of each mechanism.</p>
</div>
/* Panel styles for the Gaussian vs Laplace demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.panel { max-width: 420px; margin: 0 auto; }
.controls { display: flex; flex-direction: column; gap: .6rem; margin-bottom: .8rem; }
label { display: flex; flex-direction: column; gap: .2rem; }
.lbl { font-size: .85rem; color: #444; }
input[type=range] { width: 100%; accent-color: #1d3557; }
button { font: 600 14px system-ui; padding: .45rem .9rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 8px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.result-area { font-size: .9rem; min-height: 5rem; background: #f4f7fa;
border-radius: 8px; padding: .7rem; margin-bottom: .6rem; }
.row { display: flex; justify-content: space-between; padding: .15rem 0;
border-bottom: 1px solid #e0e7ef; }
.row:last-child { border-bottom: none; font-weight: 600; }
.tag-g { color: #1a5276; }
.tag-l { color: #6e2f0a; }
canvas { display: block; border-radius: 8px; background: #f4f7fa;
margin-bottom: .6rem; }
.hint { font-size: .8rem; color: #666; line-height: 1.4; margin: 0; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Box-Muller transform — generates standard normal random variates.
function randNormal() {
let u = 0, v2 = 0;
while (u === 0) u = Math.random();
while (v2 === 0) v2 = Math.random();
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v2);
}
// Laplace variate via inverse CDF — generates a Laplace(0, b) variate via inverse CDF.
function randLaplace(b) {
const u = Math.random() - 0.5;
return -b * Math.sign(u) * Math.log(1 - 2 * Math.abs(u));
}
// Fixed dataset — 20 values drawn once, fixed for reproducibility of the demo.
const N = 20;
const DATASET = [72,45,88,31,60,95,14,67,53,82,41,76,29,58,90,37,64,48,71,55];
const TRUE_MEAN = DATASET.reduce((a, b) => a + b, 0) / N;
// L1 and L2 sensitivity for mean query on [0,100] values — each value in [0,100]; removing one record shifts mean by at most 100/N.
const L1_SENS = 100 / N;
const L2_SENS = 100 / N;
const epsSlider = document.getElementById('eps');
const deltaSlider = document.getElementById('delta');
const epsVal = document.getElementById('eps-val');
const deltaVal = document.getElementById('delta-val');
const resultArea = document.getElementById('result-area');
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
// Delta slider encoding — delta slider maps 1..5 → 1e-1..1e-5.
function sliderToDelta(v) { return Math.pow(10, -parseInt(v, 10)); }
epsSlider.addEventListener('input', () => { epsVal.textContent = parseFloat(epsSlider.value).toFixed(1); });
deltaSlider.addEventListener('input', () => { deltaVal.textContent = '1e-' + deltaSlider.value; });
let history = [];
function privatize() {
const eps = parseFloat(epsSlider.value);
const delta = sliderToDelta(deltaSlider.value);
// Gaussian noise std-dev calibrated to (epsilon, delta)-DP — calibrate sigma to L2 sensitivity.
const sigma = L2_SENS * Math.sqrt(2 * Math.log(1.25 / delta)) / eps;
// Laplace scale calibrated to epsilon-DP — calibrate b to L1 sensitivity.
const b = L1_SENS / eps;
const gaussNoise = randNormal() * sigma;
const laplaceNoise = randLaplace(b);
const gaussResult = TRUE_MEAN + gaussNoise;
const laplaceResult = TRUE_MEAN + laplaceNoise;
history.push({ eps, delta, gaussResult, laplaceResult, sigma, b });
if (history.length > 8) history.shift();
// Render the results table
resultArea.innerHTML = `
<div class="row"><span>True mean</span><span>${TRUE_MEAN.toFixed(2)}</span></div>
<div class="row"><span class="tag-g">Gaussian result (σ=${sigma.toFixed(2)})</span><span>${gaussResult.toFixed(2)}</span></div>
<div class="row"><span class="tag-l">Laplace result (b=${b.toFixed(2)})</span><span>${laplaceResult.toFixed(2)}</span></div>
<div class="row"><span>Gaussian absolute error</span><span>${Math.abs(gaussNoise).toFixed(2)}</span></div>
<div class="row"><span>Laplace absolute error</span><span>${Math.abs(laplaceNoise).toFixed(2)}</span></div>
`;
drawChart();
}
function drawChart() {
// Bar chart of Gaussian vs Laplace results over history — bar chart of Gaussian vs Laplace results over history.
const W = canvas.width, H = canvas.height;
const PAD = 30;
ctx.clearRect(0, 0, W, H);
if (history.length === 0) return;
const allVals = history.flatMap(h => [h.gaussResult, h.laplaceResult, TRUE_MEAN]);
const lo = Math.min(...allVals) - 5;
const hi = Math.max(...allVals) + 5;
const scaleY = (v) => PAD + (H - 2 * PAD) * (1 - (v - lo) / (hi - lo));
// Dashed true-mean reference line — dashed line at true mean.
ctx.save();
ctx.strokeStyle = '#999';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.beginPath(); ctx.moveTo(PAD, scaleY(TRUE_MEAN)); ctx.lineTo(W - PAD, scaleY(TRUE_MEAN)); ctx.stroke();
ctx.restore();
const bw = Math.max(6, Math.floor((W - 2 * PAD) / (history.length * 2.5)));
history.forEach((h, i) => {
const cx = PAD + (i + 0.5) * ((W - 2 * PAD) / history.length);
// Blue bar for Gaussian result — blue bar for Gaussian result.
ctx.fillStyle = '#1a5276';
const gY = scaleY(h.gaussResult);
const baseY = scaleY(TRUE_MEAN);
ctx.fillRect(cx - bw - 1, Math.min(gY, baseY), bw, Math.abs(gY - baseY) || 1);
// Orange bar for Laplace result — orange bar for Laplace result.
ctx.fillStyle = '#c0392b';
const lY = scaleY(h.laplaceResult);
ctx.fillRect(cx + 1, Math.min(lY, baseY), bw, Math.abs(lY - baseY) || 1);
});
// Small legend — small legend.
ctx.font = '10px system-ui';
ctx.fillStyle = '#1a5276'; ctx.fillRect(PAD, H - 16, 10, 10);
ctx.fillStyle = '#333'; ctx.fillText('Gaussian', PAD + 14, H - 7);
ctx.fillStyle = '#c0392b'; ctx.fillRect(PAD + 80, H - 16, 10, 10);
ctx.fillStyle = '#333'; ctx.fillText('Laplace', PAD + 94, H - 7);
}
function resetDemo() {
history = [];
resultArea.innerHTML = '<span style="color:#888">Click Privatize to run the mechanisms.</span>';
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
document.getElementById('btn-privatize').addEventListener('click', privatize);
document.getElementById('btn-reset').addEventListener('click', resetDemo);
// Initialize demo on page load
resetDemo();
Comments
Loading comments...