Skip to content

Instantly share code, notes, and snippets.

@avimoondra
Last active August 29, 2015 14:11
Show Gist options
  • Save avimoondra/6fc394c17f81a8c8f2b5 to your computer and use it in GitHub Desktop.
Save avimoondra/6fc394c17f81a8c8f2b5 to your computer and use it in GitHub Desktop.
Blocking
<!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