// ============================================
// Privacy-Preserving Data Flow Animation
// ============================================
{
// IEEE Color palette
const colors = {
navy: "#2C3E50",
teal: "#16A085",
orange: "#E67E22",
gray: "#7F8C8D",
lightGray: "#ECF0F1",
white: "#FFFFFF",
green: "#27AE60",
purple: "#9B59B6",
red: "#E74C3C",
yellow: "#F1C40F",
darkTeal: "#0E6655"
};
// Privacy techniques configuration
const techniques = [
{
id: "anonymization",
name: "Data Anonymization",
shortName: "Anonymization",
description: "k-anonymity & l-diversity",
color: colors.navy,
icon: "person_off",
detail: "Removes or generalizes identifiers so each record is indistinguishable from k-1 others",
steps: ["Raw Data", "Remove Direct IDs", "Generalize Quasi-IDs", "Verify k-anonymity", "Output"],
privacyBase: 0.7,
utilityBase: 0.8
},
{
id: "differential",
name: "Differential Privacy",
shortName: "Differential Privacy",
description: "Calibrated noise injection",
color: colors.teal,
icon: "blur_on",
detail: "Adds mathematical noise to query results, providing provable privacy guarantees",
steps: ["Raw Data", "Define Sensitivity", "Calculate Noise", "Add Laplace/Gaussian", "Output"],
privacyBase: 0.85,
utilityBase: 0.65
},
{
id: "homomorphic",
name: "Homomorphic Encryption",
shortName: "Homomorphic Enc.",
description: "Compute on encrypted data",
color: colors.orange,
icon: "enhanced_encryption",
detail: "Enables computation on ciphertext, producing encrypted results without decryption",
steps: ["Raw Data", "Encrypt Data", "Encrypted Compute", "Encrypted Result", "Decrypt Output"],
privacyBase: 0.95,
utilityBase: 0.9
},
{
id: "mpc",
name: "Secure Multi-Party Computation",
shortName: "MPC",
description: "Joint computation, private inputs",
color: colors.purple,
icon: "groups",
detail: "Multiple parties jointly compute a function without revealing their individual inputs",
steps: ["Party Inputs", "Secret Sharing", "Distributed Compute", "Combine Shares", "Output"],
privacyBase: 0.9,
utilityBase: 0.85
},
{
id: "masking",
name: "Data Masking",
shortName: "Masking",
description: "Pseudonymization & tokenization",
color: colors.green,
icon: "visibility_off",
detail: "Replaces sensitive data with realistic substitutes while preserving format",
steps: ["Raw Data", "Identify Sensitive", "Generate Tokens", "Apply Masking", "Output"],
privacyBase: 0.6,
utilityBase: 0.95
}
];
// Layout dimensions
const width = 700;
const height = 450;
const margin = { top: 60, right: 30, bottom: 40, left: 30 };
const stageWidth = (width - margin.left - margin.right) / 5;
const dataRowHeight = 22;
// State
let selectedTechnique = techniques[0];
let privacyLevel = 0.5;
let isAnimating = false;
let animationStep = -1;
// Sample data records
const rawData = [
{ name: "Alice Smith", age: 34, zip: "10001", salary: 75000, diagnosis: "Diabetes" },
{ name: "Bob Jones", age: 28, zip: "10002", salary: 82000, diagnosis: "Asthma" },
{ name: "Carol White", age: 45, zip: "10001", salary: 91000, diagnosis: "Diabetes" },
{ name: "David Brown", age: 31, zip: "10003", salary: 68000, diagnosis: "None" }
];
// Create container
const container = d3.create("div")
.style("font-family", "system-ui, -apple-system, sans-serif");
// Create SVG
const svg = container.append("svg")
.attr("viewBox", `0 0 ${width} ${height}`)
.attr("width", "100%")
.attr("height", height)
.style("background", "linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%)")
.style("border-radius", "12px")
.style("box-shadow", "0 4px 20px rgba(0,0,0,0.1)");
// Definitions
const defs = svg.append("defs");
// Gradients
techniques.forEach(tech => {
const grad = defs.append("linearGradient")
.attr("id", `grad-${tech.id}`)
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "0%").attr("y2", "100%");
grad.append("stop").attr("offset", "0%").attr("stop-color", tech.color).attr("stop-opacity", 0.9);
grad.append("stop").attr("offset", "100%").attr("stop-color", tech.color).attr("stop-opacity", 0.7);
});
// Privacy gradient (red to green)
const privacyGrad = defs.append("linearGradient")
.attr("id", "privacy-gradient")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%");
privacyGrad.append("stop").attr("offset", "0%").attr("stop-color", colors.red);
privacyGrad.append("stop").attr("offset", "50%").attr("stop-color", colors.yellow);
privacyGrad.append("stop").attr("offset", "100%").attr("stop-color", colors.green);
// Utility gradient (green to red - inverse)
const utilityGrad = defs.append("linearGradient")
.attr("id", "utility-gradient")
.attr("x1", "0%").attr("y1", "0%")
.attr("x2", "100%").attr("y2", "0%");
utilityGrad.append("stop").attr("offset", "0%").attr("stop-color", colors.red);
utilityGrad.append("stop").attr("offset", "50%").attr("stop-color", colors.yellow);
utilityGrad.append("stop").attr("offset", "100%").attr("stop-color", colors.green);
// Glow filter
const glow = defs.append("filter")
.attr("id", "glow")
.attr("x", "-50%").attr("y", "-50%")
.attr("width", "200%").attr("height", "200%");
glow.append("feGaussianBlur").attr("stdDeviation", "3").attr("result", "blur");
glow.append("feMerge")
.selectAll("feMergeNode")
.data(["blur", "SourceGraphic"])
.enter().append("feMergeNode")
.attr("in", d => d);
// Title
const titleGroup = svg.append("g").attr("class", "title");
titleGroup.append("text")
.attr("x", width / 2)
.attr("y", 28)
.attr("text-anchor", "middle")
.attr("font-size", "18px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Privacy-Preserving Data Flow");
const subtitleText = titleGroup.append("text")
.attr("class", "subtitle")
.attr("x", width / 2)
.attr("y", 46)
.attr("text-anchor", "middle")
.attr("font-size", "12px")
.attr("fill", colors.gray);
// Processing stages area
const stagesGroup = svg.append("g")
.attr("class", "stages")
.attr("transform", `translate(${margin.left}, ${margin.top + 20})`);
// Stage boxes
const stageBoxes = stagesGroup.selectAll(".stage-box")
.data([0, 1, 2, 3, 4])
.enter().append("g")
.attr("class", "stage-box")
.attr("transform", (d, i) => `translate(${i * stageWidth + 5}, 0)`);
stageBoxes.append("rect")
.attr("class", "stage-rect")
.attr("width", stageWidth - 10)
.attr("height", 140)
.attr("rx", 8)
.attr("fill", colors.white)
.attr("stroke", colors.lightGray)
.attr("stroke-width", 1.5);
stageBoxes.append("text")
.attr("class", "stage-label")
.attr("x", (stageWidth - 10) / 2)
.attr("y", 18)
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("font-weight", "bold")
.attr("fill", colors.navy);
// Connection arrows between stages
const arrows = stagesGroup.selectAll(".arrow")
.data([0, 1, 2, 3])
.enter().append("g")
.attr("class", "arrow")
.attr("transform", (d, i) => `translate(${(i + 1) * stageWidth - 3}, 70)`);
arrows.append("path")
.attr("d", "M0,0 L8,0 L5,-4 M8,0 L5,4")
.attr("stroke", colors.gray)
.attr("stroke-width", 2)
.attr("fill", "none");
// Data display area in each stage
const dataDisplays = stageBoxes.append("g")
.attr("class", "data-display")
.attr("transform", "translate(5, 30)");
// Animated data packet
const dataPacket = stagesGroup.append("g")
.attr("class", "data-packet")
.style("opacity", 0);
dataPacket.append("rect")
.attr("x", -20)
.attr("y", -12)
.attr("width", 40)
.attr("height", 24)
.attr("rx", 6)
.attr("fill", colors.teal)
.attr("filter", "url(#glow)");
dataPacket.append("text")
.attr("text-anchor", "middle")
.attr("y", 4)
.attr("font-size", "10px")
.attr("fill", colors.white)
.attr("font-weight", "bold")
.text("DATA");
// Meters section
const metersY = margin.top + 175;
const meterWidth = 200;
const meterHeight = 16;
const metersGroup = svg.append("g")
.attr("class", "meters")
.attr("transform", `translate(${width / 2 - meterWidth - 30}, ${metersY})`);
// Privacy meter
const privacyMeter = metersGroup.append("g").attr("class", "privacy-meter");
privacyMeter.append("text")
.attr("x", 0)
.attr("y", -5)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Privacy Protection");
privacyMeter.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", meterWidth)
.attr("height", meterHeight)
.attr("rx", 4)
.attr("fill", colors.lightGray)
.attr("stroke", colors.gray)
.attr("stroke-width", 1);
const privacyFill = privacyMeter.append("rect")
.attr("class", "privacy-fill")
.attr("x", 1)
.attr("y", 1)
.attr("width", 0)
.attr("height", meterHeight - 2)
.attr("rx", 3)
.attr("fill", "url(#privacy-gradient)");
const privacyValue = privacyMeter.append("text")
.attr("class", "privacy-value")
.attr("x", meterWidth + 8)
.attr("y", 12)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.navy);
// Utility meter
const utilityMeter = metersGroup.append("g")
.attr("class", "utility-meter")
.attr("transform", `translate(${meterWidth + 60}, 0)`);
utilityMeter.append("text")
.attr("x", 0)
.attr("y", -5)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.navy)
.text("Data Utility");
utilityMeter.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", meterWidth)
.attr("height", meterHeight)
.attr("rx", 4)
.attr("fill", colors.lightGray)
.attr("stroke", colors.gray)
.attr("stroke-width", 1);
const utilityFill = utilityMeter.append("rect")
.attr("class", "utility-fill")
.attr("x", 1)
.attr("y", 1)
.attr("width", 0)
.attr("height", meterHeight - 2)
.attr("rx", 3)
.attr("fill", "url(#utility-gradient)");
const utilityValue = utilityMeter.append("text")
.attr("class", "utility-value")
.attr("x", meterWidth + 8)
.attr("y", 12)
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", colors.navy);
// Trade-off indicator
const tradeoffY = metersY + 35;
const tradeoffGroup = svg.append("g")
.attr("class", "tradeoff")
.attr("transform", `translate(${width / 2}, ${tradeoffY})`);
tradeoffGroup.append("text")
.attr("class", "tradeoff-label")
.attr("text-anchor", "middle")
.attr("font-size", "10px")
.attr("fill", colors.gray);
// Technique detail panel
const detailY = height - 80;
const detailGroup = svg.append("g")
.attr("class", "detail-panel")
.attr("transform", `translate(${margin.left}, ${detailY})`);
detailGroup.append("rect")
.attr("class", "detail-bg")
.attr("width", width - margin.left - margin.right)
.attr("height", 65)
.attr("rx", 8)
.attr("fill", colors.navy)
.attr("opacity", 0.95);
detailGroup.append("text")
.attr("class", "detail-title")
.attr("x", 15)
.attr("y", 22)
.attr("font-size", "13px")
.attr("font-weight", "bold")
.attr("fill", colors.white);
detailGroup.append("text")
.attr("class", "detail-desc")
.attr("x", 15)
.attr("y", 42)
.attr("font-size", "11px")
.attr("fill", colors.lightGray);
detailGroup.append("text")
.attr("class", "detail-info")
.attr("x", 15)
.attr("y", 56)
.attr("font-size", "10px")
.attr("fill", colors.teal);
// Transform data based on technique and privacy level
function transformData(technique, level, step) {
const data = JSON.parse(JSON.stringify(rawData));
if (step === 0) return data; // Raw data
switch(technique.id) {
case "anonymization":
return data.map((d, i) => {
if (step >= 1) d.name = "*****";
if (step >= 2) {
d.age = Math.floor(d.age / (5 + level * 10)) * (5 + Math.floor(level * 10)) + "-" +
(Math.floor(d.age / (5 + level * 10)) * (5 + Math.floor(level * 10)) + 4 + Math.floor(level * 10));
d.zip = d.zip.substring(0, 3 - Math.floor(level * 2)) + "**";
}
if (step >= 3) d.salary = level > 0.5 ? "****" : Math.round(d.salary / 10000) * 10000 + "";
return d;
});
case "differential":
return data.map((d, i) => {
const noise = (Math.random() - 0.5) * level * 20;
if (step >= 1) d.sensitivity = "High";
if (step >= 2) d.noise = (noise > 0 ? "+" : "") + noise.toFixed(1);
if (step >= 3) {
d.age = Math.round(d.age + noise);
d.salary = Math.round(d.salary + noise * 5000);
}
if (step >= 4) { delete d.sensitivity; delete d.noise; }
return d;
});
case "homomorphic":
return data.map((d, i) => {
if (step >= 1) {
d.name = "Enc(" + d.name.substring(0, 3) + "...)";
d.salary = "Enc(" + d.salary + ")";
}
if (step >= 2) d.operation = "SUM()";
if (step >= 3) d.result = "Enc(316000)";
if (step >= 4) {
d.name = "[Decrypted]";
d.salary = step === 4 ? "Sum: 316000" : d.salary;
delete d.operation;
delete d.result;
}
return d;
});
case "mpc":
return data.map((d, i) => {
if (step >= 1) d.party = "P" + (i + 1);
if (step >= 2) {
d.share1 = "s" + (i + 1) + "a";
d.share2 = "s" + (i + 1) + "b";
}
if (step >= 3) d.partialResult = "r" + (i + 1);
if (step >= 4) {
d.finalResult = i === 0 ? "Avg: 79000" : "-";
delete d.share1;
delete d.share2;
delete d.partialResult;
}
return d;
});
case "masking":
const fakeNames = ["Jane Doe", "John Roe", "Mary Moe", "Pete Poe"];
return data.map((d, i) => {
if (step >= 1) d.sensitive = "name, zip";
if (step >= 2) d.token = "TKN" + (1000 + i);
if (step >= 3) {
d.name = fakeNames[i];
d.zip = (10000 + Math.floor(Math.random() * 90000)) + "";
}
if (step >= 4) { delete d.sensitive; delete d.token; }
return d;
});
}
return data;
}
// Render data in stage
function renderStageData(stageIndex, data, technique) {
const display = dataDisplays.filter((d, i) => i === stageIndex);
display.selectAll("*").remove();
if (!data || data.length === 0) return;
const record = data[0];
const keys = Object.keys(record).slice(0, 4);
keys.forEach((key, i) => {
const y = i * dataRowHeight;
display.append("text")
.attr("x", 0)
.attr("y", y + 12)
.attr("font-size", "8px")
.attr("fill", colors.gray)
.text(key.substring(0, 8) + ":");
let value = record[key];
if (typeof value === "number") value = value.toLocaleString();
if (typeof value === "string" && value.length > 12) value = value.substring(0, 12) + "..";
display.append("text")
.attr("x", stageWidth - 20)
.attr("y", y + 12)
.attr("text-anchor", "end")
.attr("font-size", "8px")
.attr("font-weight", "bold")
.attr("fill", technique.color)
.text(value);
});
}
// Update visualization
function updateVisualization() {
const tech = selectedTechnique;
// Update subtitle
subtitleText.text(`Technique: ${tech.name} - ${tech.description}`);
// Update stage labels
stageBoxes.selectAll(".stage-label")
.data(tech.steps)
.text(d => d);
// Update stage box colors
stageBoxes.selectAll(".stage-rect")
.transition().duration(300)
.attr("stroke", (d, i) => i === animationStep ? tech.color : colors.lightGray)
.attr("stroke-width", (d, i) => i === animationStep ? 3 : 1.5);
// Render data for each stage up to current step
for (let i = 0; i <= 4; i++) {
const data = i <= animationStep ? transformData(tech, privacyLevel, i) : null;
renderStageData(i, data, tech);
}
// Calculate and update meters
const privacyScore = tech.privacyBase + (privacyLevel * 0.3) * (1 - tech.privacyBase);
const utilityScore = tech.utilityBase * (1 - privacyLevel * 0.4);
privacyFill.transition().duration(500)
.attr("width", (meterWidth - 2) * privacyScore);
privacyValue.text(Math.round(privacyScore * 100) + "%");
utilityFill.transition().duration(500)
.attr("width", (meterWidth - 2) * utilityScore);
utilityValue.text(Math.round(utilityScore * 100) + "%");
// Update trade-off label
const tradeoffText = privacyScore > utilityScore
? "High Privacy, Reduced Utility"
: privacyScore < utilityScore - 0.1
? "High Utility, Moderate Privacy"
: "Balanced Privacy-Utility";
tradeoffGroup.select(".tradeoff-label").text(tradeoffText);
// Update detail panel
detailGroup.select(".detail-bg")
.transition().duration(300)
.attr("fill", tech.color);
detailGroup.select(".detail-title").text(tech.name);
detailGroup.select(".detail-desc").text(tech.detail);
detailGroup.select(".detail-info").text(
`Privacy: ${Math.round(privacyScore * 100)}% | Utility: ${Math.round(utilityScore * 100)}% | Steps: ${tech.steps.length}`
);
}
// Run animation
function runAnimation() {
if (isAnimating) return;
isAnimating = true;
animationStep = -1;
const tech = selectedTechnique;
const stepDuration = 800;
// Reset data displays
dataDisplays.selectAll("*").remove();
// Animate packet through stages
function animateStep(step) {
if (step > 4) {
// Animation complete
dataPacket.transition().duration(300).style("opacity", 0);
isAnimating = false;
return;
}
animationStep = step;
updateVisualization();
// Move packet
const x = step * stageWidth + stageWidth / 2;
dataPacket
.style("opacity", 1)
.transition().duration(stepDuration / 2)
.attr("transform", `translate(${x}, 70)`)
.on("end", () => {
// Flash effect
dataPacket.select("rect")
.transition().duration(100)
.attr("fill", tech.color)
.transition().duration(100)
.attr("fill", colors.teal);
setTimeout(() => animateStep(step + 1), stepDuration / 2);
});
}
animateStep(0);
}
// Reset animation
function reset() {
isAnimating = false;
animationStep = -1;
dataPacket.style("opacity", 0);
updateVisualization();
}
// Control panel
const controlPanel = container.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "15px")
.style("margin-top", "15px")
.style("padding", "15px")
.style("background", `linear-gradient(135deg, ${colors.navy} 0%, #1a252f 100%)`)
.style("border-radius", "12px")
.style("align-items", "flex-start");
// Technique selector section
const techSection = controlPanel.append("div")
.style("flex", "2")
.style("min-width", "300px");
techSection.append("div")
.style("color", colors.lightGray)
.style("font-size", "11px")
.style("font-weight", "bold")
.style("margin-bottom", "8px")
.text("PRIVACY TECHNIQUE:");
const techButtons = techSection.append("div")
.style("display", "flex")
.style("flex-wrap", "wrap")
.style("gap", "6px");
techniques.forEach(tech => {
techButtons.append("button")
.attr("class", "tech-btn")
.attr("data-id", tech.id)
.style("padding", "8px 12px")
.style("border", "none")
.style("border-radius", "6px")
.style("font-size", "11px")
.style("font-weight", "600")
.style("cursor", "pointer")
.style("color", colors.white)
.style("background", tech.id === selectedTechnique.id
? `linear-gradient(135deg, ${tech.color} 0%, ${colors.darkTeal} 100%)`
: colors.gray)
.style("opacity", tech.id === selectedTechnique.id ? "1" : "0.7")
.style("transition", "all 0.2s")
.text(tech.shortName)
.on("click", function() {
selectedTechnique = tech;
reset();
// Update button styles
techButtons.selectAll("button")
.style("background", colors.gray)
.style("opacity", "0.7");
d3.select(this)
.style("background", `linear-gradient(135deg, ${tech.color} 0%, ${colors.darkTeal} 100%)`)
.style("opacity", "1");
})
.on("mouseenter", function() {
if (tech.id !== selectedTechnique.id) {
d3.select(this).style("opacity", "0.9");
}
})
.on("mouseleave", function() {
if (tech.id !== selectedTechnique.id) {
d3.select(this).style("opacity", "0.7");
}
});
});
// Privacy level slider section
const sliderSection = controlPanel.append("div")
.style("flex", "1")
.style("min-width", "180px");
sliderSection.append("div")
.style("color", colors.lightGray)
.style("font-size", "11px")
.style("font-weight", "bold")
.style("margin-bottom", "8px")
.text("PRIVACY LEVEL:");
const sliderContainer = sliderSection.append("div")
.style("display", "flex")
.style("align-items", "center")
.style("gap", "10px");
sliderContainer.append("span")
.style("color", colors.lightGray)
.style("font-size", "10px")
.text("Low");
const slider = sliderContainer.append("input")
.attr("type", "range")
.attr("min", "0")
.attr("max", "100")
.attr("value", privacyLevel * 100)
.style("flex", "1")
.style("cursor", "pointer")
.style("accent-color", colors.teal)
.on("input", function() {
privacyLevel = this.value / 100;
updateVisualization();
});
sliderContainer.append("span")
.style("color", colors.lightGray)
.style("font-size", "10px")
.text("High");
// Action buttons section
const actionSection = controlPanel.append("div")
.style("flex", "1")
.style("min-width", "150px");
actionSection.append("div")
.style("color", colors.lightGray)
.style("font-size", "11px")
.style("font-weight", "bold")
.style("margin-bottom", "8px")
.text("ACTIONS:");
const actionButtons = actionSection.append("div")
.style("display", "flex")
.style("gap", "8px");
const buttonStyle = `
padding: 10px 18px;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 600;
cursor: pointer;
color: white;
transition: transform 0.1s;
`;
actionButtons.append("button")
.attr("style", buttonStyle)
.style("background", `linear-gradient(135deg, ${colors.teal} 0%, ${colors.green} 100%)`)
.text("Run Transformation")
.on("click", runAnimation)
.on("mouseenter", function() { d3.select(this).style("transform", "scale(1.05)"); })
.on("mouseleave", function() { d3.select(this).style("transform", "scale(1)"); });
actionButtons.append("button")
.attr("style", buttonStyle)
.style("background", colors.gray)
.text("Reset")
.on("click", reset)
.on("mouseenter", function() { d3.select(this).style("transform", "scale(1.05)"); })
.on("mouseleave", function() { d3.select(this).style("transform", "scale(1)"); });
// Initial render
updateVisualization();
return container.node();
}