Last active
August 29, 2015 14:11
-
-
Save avimoondra/6fc394c17f81a8c8f2b5 to your computer and use it in GitHub Desktop.
Blocking
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"> | |
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta charset="utf-8"> | |
</head> | |
<body> | |
<style type="text/css"> | |
#svgWrapper { | |
display: inline-block; | |
} | |
#panelWrapper { | |
width: 200px; | |
border: solid #808080 1px; | |
padding: 10px; | |
display: inline-block; | |
position: fixed; | |
margin-top: 10px; | |
} | |
.separator { | |
margin-bottom: 10px; | |
height: 1px; | |
background-color: #808080; | |
} | |
svg .patient.red { | |
fill: #F05133; | |
} | |
svg .patient.blue { | |
fill: #569BBD; | |
} | |
svg .patient_number { | |
font-size: 10px; | |
} | |
svg .surrounding_rect { | |
fill-opacity: 0; | |
stroke: black; | |
} | |
</style> | |
<div class="row"> | |
<div id="svgWrapper"> | |
<svg></svg> | |
</div> | |
<div id="panelWrapper"> | |
<div> | |
<p> Blocking in a randomized experiment. </p> | |
<div class="separator"></div> | |
<p> <b>Step 1.</b> Identify and divide patients into low-risk and high-risk blocks: | |
<button id="button_create_blocks" type="button" class="btn btn-primary btn-xs"> Create Blocks </button> | |
</p> | |
<p> <b>Step 2.</b> Evenly separate each block of patients into the treatment groups using randomization: | |
<button id="button_assign_groups" type="button" class="btn btn-primary btn-xs disabled"> Assign Groups </button> | |
</p> | |
<div class="separator"></div> | |
<div>This strategy ensures an equal representation of patients in each treatment group from both the low-risk and high-risk categories, so patient risk does not bias the outcome of the experiment. | |
<button id="button_reset" class="btn btn-primary btn-xs disabled"> Reset </button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script type="text/javascript"> | |
// svg | |
height = 1; | |
width = height * 1.8; | |
domain = [0, width]; | |
range = [0, 700]; | |
svg_scale = d3.scale.linear().domain(domain).range(range); | |
var svg = d3.select("svg").attr("width", svg_scale(width)).attr("height", svg_scale(height)); | |
var svg_width = svg.attr("width"); | |
var svg_height = svg.attr("height"); | |
// colors | |
blue = "#569BBD"; | |
green = "#4C721D"; | |
yellow = "#F4DC00"; | |
red = "#F05133"; | |
black = "#000000"; | |
gray = "#808080"; | |
lgray = "#D9D9D9"; | |
colors = [blue, red]; | |
color_names = ["blue","red"]; | |
// generate data | |
datapoint_radius = svg_scale(0.0075); | |
start_x = 0.10; | |
start_y = 0.25; | |
num_columns_in_block = 6 | |
spacing = 0.06; | |
init_rect_start_x = start_x - spacing; | |
init_rect_start_y = start_y - spacing; | |
init_rect_width = (num_columns_in_block + 1) * spacing; | |
init_rect_height = Math.ceil(54/num_columns_in_block + 1) * spacing; | |
dataset = []; | |
blues = []; | |
reds = []; | |
counter = 0; | |
var sample_without_replacement = function(arr, num_samples){ | |
samples = [] | |
for(i = 0; i < num_samples; i++){ | |
var index = Math.floor(Math.random()*arr.length); | |
samples.push(arr.splice(index, 1)[0]); | |
} | |
return samples; | |
} | |
num_patients = 54 | |
interval = []; | |
for(var i = 0; i < num_patients; i++) { interval.push(i) } | |
rand_indices_for_blues = sample_without_replacement(interval, num_patients/colors.length); | |
for(var y = 0; y < 9; y++){ | |
for(var x = 0; x < 6; x++){ | |
rand_index = rand_indices_for_blues.indexOf(counter) >= 0 ? 0 : 1; | |
counter++; | |
datapoint = { | |
"cx": svg_scale(start_x + x*spacing), | |
"cy": svg_scale(start_y + y*spacing), | |
"label": counter + "", | |
"color": colors[rand_index], | |
"class": "patient " + color_names[rand_index] | |
}; | |
if(~datapoint["class"].indexOf("blue")){ | |
blues.push(datapoint); | |
} else { | |
reds.push(datapoint); | |
} | |
} | |
} | |
start_x = 0.65 | |
start_y = 0.15 | |
num_columns_in_block = 7 | |
spacing = 0.06; | |
for(var i = 0; i < blues.length; i++){ | |
blues[i]["cx_block"] = svg_scale(start_x + (i % num_columns_in_block)*spacing); | |
blues[i]["cy_block"] = svg_scale(start_y + Math.ceil((i+1)/num_columns_in_block)*spacing); | |
} | |
blue_block_start_x = start_x - spacing; | |
blue_block_start_y = start_y - spacing / 2; | |
blue_block_rect_height = Math.ceil(blues.length/num_columns_in_block + 1) * spacing; | |
blue_block_rect_width = (num_columns_in_block + 1) * spacing | |
start_y = start_y + 0.45; | |
for(var i = 0; i < reds.length; i++){ | |
reds[i]["cx_block"] = svg_scale(start_x + (i % num_columns_in_block)*spacing); | |
reds[i]["cy_block"] = svg_scale(start_y + Math.ceil((i+1)/num_columns_in_block)*spacing); | |
} | |
red_block_start_x = start_x - spacing; | |
red_block_start_y = start_y - spacing / 2; | |
red_block_rect_height = Math.ceil(reds.length/num_columns_in_block + 1) * spacing; | |
red_block_rect_width = (num_columns_in_block + 1) * spacing; | |
dataset = blues.concat(reds); | |
var init = function(){ | |
svg.selectAll("*").remove(); | |
svg.append("rect") | |
.attr("x", svg_scale(init_rect_start_x)) | |
.attr("y", svg_scale(init_rect_start_y)) | |
.attr("width", svg_scale(init_rect_width)) | |
.attr("height", svg_scale(init_rect_height)) | |
.classed("surrounding_rect", true); | |
svg.append("text") | |
.text("Numbered Patients") | |
.attr("x", svg_scale(init_rect_start_x + init_rect_width / 2)) | |
.attr("y", svg_scale(init_rect_start_y - spacing / 3)) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle"); | |
var patients = svg.selectAll("g") | |
.data(dataset) | |
.enter() | |
.append("g") | |
.classed("patient_wrapper", true) | |
.classed("blue",function(d){ | |
if(~d["class"].indexOf("blue")){ | |
return true; | |
} | |
return false; | |
}) | |
.classed("red",function(d){ | |
if(~d["class"].indexOf("red")){ | |
return true; | |
} | |
return false; | |
}); | |
patients.append("circle") | |
.attr("cx", function(d, i) { | |
return d["cx"]; | |
}) | |
.attr("cy", function(d, i) { | |
return d["cy"]; | |
}) | |
.attr("r", datapoint_radius) | |
.attr("class", function(d, i){ | |
return d["class"]; | |
}); | |
patients.append("text") | |
.text(function(d){ | |
return d["label"]; | |
}) | |
.attr("x",function(d){ | |
return d["cx"]; | |
}) | |
.attr("y",function(d){ | |
return d["cy"] - 5; | |
}) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle") | |
.attr("class", "patient_number"); | |
}; | |
function clone(selector) { | |
var node = d3.select(selector).node(); | |
return d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling)); | |
} | |
function cloneAll(selector) { | |
var nodes = d3.selectAll(selector); | |
nodes.each(function(d, i) { | |
nodes[0][i] = this.parentNode.insertBefore(this.cloneNode(true), this.nextSibling); | |
}); | |
return nodes; | |
} | |
var create_blocks = function(){ | |
svg.append("rect") | |
.attr("x", svg_scale(red_block_start_x)) | |
.attr("y", svg_scale(red_block_start_y + spacing/2)) | |
.attr("width", svg_scale(red_block_rect_width)) | |
.attr("height", svg_scale(red_block_rect_height)) | |
.classed("surrounding_rect", true); | |
svg.append("rect") | |
.attr("x", svg_scale(blue_block_start_x)) | |
.attr("y", svg_scale(blue_block_start_y + spacing / 2)) | |
.attr("width", svg_scale(blue_block_rect_width)) | |
.attr("height", svg_scale(blue_block_rect_height)) | |
.classed("surrounding_rect", true); | |
svg.append("text") | |
.text("High-risk patients") | |
.attr("x", svg_scale(blue_block_start_x + blue_block_rect_width / 2)) | |
.attr("y", svg_scale(blue_block_start_y + spacing / 3)) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle"); | |
svg.append("text") | |
.text("Low-risk patients") | |
.attr("x", svg_scale(red_block_start_x + red_block_rect_width / 2)) | |
.attr("y", svg_scale(red_block_start_y + spacing / 3)) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle"); | |
patient_blocks = cloneAll("g, .patient_wrapper") | |
.classed("patient_wrapper", false) | |
.classed("block_patient_wrapper", true) | |
.data(dataset); | |
patient_blocks.transition() | |
.attr("transform",function(d){ | |
return "translate(" + (d["cx_block"] - d["cx"]) + ", " + (d["cy_block"] - d["cy"]) + ")"; | |
}); | |
} | |
var assign_groups = function(){ | |
start_treat_x = 1.3; | |
start_treat_y = 0.15; | |
start_control_x = start_treat_x; | |
start_control_y = start_treat_y + 0.45 | |
spacing = 0.06; | |
num_columns_in_block = 7; | |
treat_counter = 0; | |
control_counter = 0; | |
num_patients = 27 | |
interval = []; | |
for(var i = 0; i < num_patients; i++) { interval.push(i) } | |
rand_indices_for_treat = sample_without_replacement(interval, Math.floor(num_patients/colors.length) + Math.round(Math.random())); | |
for(var i = 0; i < blues.length; i++){ | |
if (rand_indices_for_treat.indexOf(i) >= 0) { | |
blues[i]["cx_group"] = svg_scale(start_treat_x + (treat_counter % num_columns_in_block)*spacing); | |
blues[i]["cy_group"] = svg_scale(start_treat_y + Math.ceil((treat_counter+1)/num_columns_in_block)*spacing); | |
treat_counter++; | |
} else { | |
blues[i]["cx_group"] = svg_scale(start_control_x + (control_counter % num_columns_in_block)*spacing); | |
blues[i]["cy_group"] = svg_scale(start_control_y + Math.ceil((control_counter+1)/num_columns_in_block)*spacing); | |
control_counter++; | |
} | |
} | |
interval = []; | |
for(var i = 0; i < num_patients; i++) { interval.push(i) } | |
rand_indices_for_treat = sample_without_replacement(interval, Math.floor(num_patients/colors.length) + Math.round(Math.random())); | |
for(var i = 0; i < reds.length; i++){ | |
if (rand_indices_for_treat.indexOf(i) >= 0) { | |
reds[i]["cx_group"] = svg_scale(start_treat_x + (treat_counter % num_columns_in_block)*spacing); | |
reds[i]["cy_group"] = svg_scale(start_treat_y + Math.ceil((treat_counter+1)/num_columns_in_block)*spacing); | |
treat_counter++; | |
} else { | |
reds[i]["cx_group"] = svg_scale(start_control_x + (control_counter % num_columns_in_block)*spacing); | |
reds[i]["cy_group"] = svg_scale(start_control_y + Math.ceil((control_counter+1)/num_columns_in_block)*spacing); | |
control_counter++; | |
} | |
} | |
treat_group_start_x = start_treat_x - spacing; | |
treat_group_start_y = start_treat_y - spacing / 2; | |
treat_group_rect_height = Math.ceil(treat_counter/num_columns_in_block + 1) * spacing; | |
treat_group_rect_width = (num_columns_in_block + 1) * spacing | |
control_group_start_x = start_control_x - spacing | |
control_group_start_y = start_control_y - spacing / 2; | |
control_group_rect_height = Math.ceil(control_counter/num_columns_in_block + 1) * spacing; | |
control_group_rect_width = (num_columns_in_block + 1) * spacing | |
svg.append("rect") | |
.attr("x", svg_scale(treat_group_start_x)) | |
.attr("y", svg_scale(treat_group_start_y + spacing / 2)) | |
.attr("width", svg_scale(treat_group_rect_width)) | |
.attr("height", svg_scale(treat_group_rect_height)) | |
.classed("surrounding_rect", true); | |
svg.append("text") | |
.text("Treatment") | |
.attr("x", svg_scale(treat_group_start_x + treat_group_rect_width / 2)) | |
.attr("y", svg_scale(treat_group_start_y + spacing / 3)) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle"); | |
svg.append("rect") | |
.attr("x", svg_scale(control_group_start_x)) | |
.attr("y", svg_scale(control_group_start_y + spacing / 2)) | |
.attr("width", svg_scale(control_group_rect_width)) | |
.attr("height", svg_scale(control_group_rect_height)) | |
.classed("surrounding_rect", true); | |
svg.append("text") | |
.text("Control") | |
.attr("x", svg_scale(control_group_start_x + control_group_rect_width / 2)) | |
.attr("y", svg_scale(control_group_start_y + spacing / 3)) | |
.attr("font-family","sans-serif") | |
.attr("text-anchor","middle"); | |
red_patient_blocks = cloneAll(".red.block_patient_wrapper") | |
.classed("block_patient_wrapper", false) | |
.classed("tr_patient_wrapper",true) | |
.data(reds) | |
blue_patient_blocks = cloneAll(".blue.block_patient_wrapper") | |
.classed("block_patient_wrapper", false) | |
.classed("tr_patient_wrapper",true) | |
.data(blues) | |
delay_duration = 100; | |
blue_patient_blocks.transition() | |
.delay(function(d, i) { return i / blue_patient_blocks.length * delay_duration}) | |
.attr("transform",function(d){ | |
return "translate(" + (d["cx_group"] - d["cx"]) + ", " + (d["cy_group"] - d["cy"]) + ")"; | |
}); | |
setTimeout(function() { | |
red_patient_blocks.transition() | |
.delay(function(d, i) { return i / red_patient_blocks.length * delay_duration}) | |
.attr("transform",function(d){ | |
return "translate(" + (d["cx_group"] - d["cx"]) + ", " + (d["cy_group"] - d["cy"]) + ")"; | |
}) | |
}, delay_duration * (blues.length+1)); | |
}; | |
d3.select("#button_create_blocks").on("click", function(){ | |
create_blocks(); | |
d3.select("#button_create_blocks").classed("disabled", true) | |
d3.select("#button_assign_groups").classed("disabled", false); | |
}); | |
d3.select("#button_assign_groups").on("click", function(){ | |
assign_groups(); | |
d3.select("#button_assign_groups").classed("disabled", true) | |
d3.select("#button_reset").classed("disabled", false); | |
}); | |
d3.select("#button_reset").on("click", function(){ | |
init(); | |
d3.select("#button_reset").classed("disabled", true) | |
d3.select("#button_create_blocks").classed("disabled", false); | |
}); | |
init(); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment