site,id,event,status,date
A,1,1,Complete,2017-05-02
A,1,2,Complete,2017-06-06
A,1,3,Unverified,2018-03-21
A,1,4,Incomplete,2019-02-24
A,1,5,Complete,2019-06-18
A,2,1,Complete,2019-01-23
A,2,2,Unverified,2019-03-10
A,2,3,Complete,2019-05-30
A,3,1,Incomplete,2017-07-07
A,3,2,Complete,2018-03-11
A,3,3,Complete,2019-02-21
A,3,4,Unverified,2019-04-05
A,4,1,Unverified,2019-05-13
A,4,2,Complete,2019-08-09
A,5,1,Complete,2017-11-23
A,5,2,Complete,2018-05-17
A,5,3,Complete,2018-05-26
A,5,4,Complete,2019-02-05
B,1,1,Complete,2017-04-16
B,1,2,Complete,2017-11-26
B,1,3,Unverified,2018-03-22
B,1,4,Complete,2018-07-15
B,1,5,Complete,2019-11-05
B,2,1,Complete,2018-09-11
B,2,2,Complete,2018-10-16
B,2,3,Complete,2019-03-08
B,2,3,Unverified,2019-06-22
B,2,4,Complete,2019-10-19
B,3,1,Complete,2017-10-16
B,3,2,Incomplete,2018-09-06
B,3,3,Complete,2019-07-27
B,3,4,Complete,2019-10-02
B,4,1,Unverified,2017-04-21
B,4,2,Complete,2017-11-21
B,4,3,Complete,2018-03-12
B,4,4,Complete,2018-04-04
B,5,1,Unverified,2017-09-23
B,5,2,Complete,2018-01-19
B,5,3,Complete,2018-02-11
B,5,4,Complete,2019-06-26
C,1,1,Complete,2017-04-12
C,1,2,Complete,2018-08-20
C,1,3,Complete,2018-10-02
C,1,4,Unverified,2018-11-16
C,1,5,Complete,2019-06-16
C,2,1,Complete,2018-07-22
C,2,2,Complete,2019-06-13
C,2,3,Complete,2019-11-10
C,2,4,Incomplete,2019-11-12
C,3,1,Complete,2017-03-20
C,3,2,Complete,2018-01-12
C,3,3,Complete,2018-03-08
C,3,4,Complete,2019-09-24
C,4,1,Complete,2017-03-09
C,4,2,Complete,2017-07-13
C,4,3,Complete,2019-10-30
C,5,1,Complete,2018-07-29
C,5,2,Complete,2018-09-20
C,5,3,Unverified,2018-12-25
C,5,4,Complete,2019-05-26
C,5,5,Incomplete,2019-11-17
Study Participant Monitoring POC
I've used this format to monitor study participant progress in clinical trial settings. Each dot represents a participation phase like intake questionnaires, treatment, followup surveys etc. Color encoding can represent the status of each phase; for example, a red dot may mean that the study participant did not complete a milestone. Yellow may represent incomplete data. Of course, you are welcome to customize to suit your needs!
var margin = { top: 20, right: 20, bottom: 20, left: 20 };
var width = 500 - margin.left - margin.right;
var height = 150
var x = d3.scaleLinear()
.range([0, width - 90])
.domain([0.5, 5.5]);
var y = d3.scaleLinear().range([height - 40, 0]).domain([0, 6]);
var xAxis = d3.axisBottom(x).ticks(5);
var yAxis = d3.axisLeft(y).ticks(5);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var mydata = d3.csvParse(d3.select("pre#data").text());
var parseTime = d3.timeParse("%Y-%m-%d");
var formatDate = d3.timeFormat("%Y-%m-%d");
var startDate = parseTime("2017-01-01");
var endDate = parseTime("2019-12-30");
var xSlider = d3.scaleTime()
.domain([startDate, endDate])
.range([0, 300])
.clamp(true);
mydata.forEach(function (d) {
d.id = +d.id;
d.event = +d.event;
d.date = parseTime(d.date);
});
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
var slider = d3.select("#slider")
.append("svg")
.attr("class", "slider")
.attr("width", 370)
.attr("height", 60)
.attr("transform", "translate(" + 50 + "," + 580 + ")");
slider.append("line")
.attr("class", "track")
.attr("x1", xSlider.range()[0])
.attr("x2", xSlider.range()[1])
.select(function () {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-inset")
.select(function () {
return this.parentNode.appendChild(this.cloneNode(true));
})
.attr("class", "track-overlay")
.call(d3.drag()
.on("start.interrupt", function () { slider.interrupt(); })
.on("start drag", function () {
hue(xSlider.invert(d3.event.x));
}));
var label = slider.append("text")
.attr("class", "label")
.attr("text-anchor", "middle")
.attr("font-size", 12)
.text(formatDate(startDate))
.attr("transform", "translate(0," + (-15) + ")")
var handle = slider.insert("circle", ".track-overlay")
.attr("class", "handle")
.attr("r", 9);
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
var container = d3.select('#figurecontainer')
var createGraph = function () {
var panelData = d3.nest()
.key(function (d) { return d.site; })
.entries(mydata);
var myGraph = container.selectAll(".facetClass")
.data(panelData)
.enter().append("svg")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "facetClass")
.attr("width", width - margin.left - margin.right)
.attr("height", height)
.each(function (d) {
var facet = d3.select(this)
.append("g");
facet.append("rect")
.attr("x", margin.left)
.attr("y", margin.top)
.attr("width", width - 40)
.attr("height", height - margin.top - margin.bottom)
.style('opacity', 0.7)
.style('fill', '#FFFFFF');
facet.append("rect")
.attr("x", width - 30 - margin.left - margin.right)
.attr("width", 30)
.attr("y", margin.top)
.attr("height", height - margin.top - margin.bottom)
.style('opacity', 0.1)
.style('fill', "black");
facet.append("g")
.attr("transform", "translate(" + margin.left + "," +
margin.bottom + ")")
.call(yAxis);
facet.append("g")
.attr("transform", "translate(" + margin.left + "," +
130 + ")")
.call(xAxis);
facet.append("text")
.attr("x", height / 2)
.attr("y", 60 - width)
.attr("transform", function (d) { return "rotate(90)" })
.style("text-anchor", "end")
.text(function (d) { return d.key; });
});
};
var update = function (whichDate) {
var filteredData = mydata.filter(function (d) {
return d.date <= whichDate
});
var panelDotsData = d3.nest()
.key(function (d) { return d.site; })
.entries(filteredData);
var myFacets = d3.selectAll(".facetClass")
.data(panelDotsData);
var myDots = myFacets.selectAll(".dot")
.data(function (d) { return d.values });
var myDotsEnter = myDots.enter()
.append("circle")
.attr("class", "dot");
myDots.exit().remove();
var myDotsMerge = myDotsEnter.merge(myDots);
myDotsMerge.attr("r", 8)
.attr("cx", function (d) { return x(d.id); })
.attr("cy", function (d) { return y(d.event); })
.attr("transform", "translate(" + margin.left + "," +
margin.bottom + ")")
.style("stroke", "black")
.style("fill", function (d) {
if (d.status === "Complete") { return "darkgreen" }
if (d.status === "Unverified") { return "gold" }
if (d.status === "Incomplete") { return "red" }
});
};
/////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
createGraph();
handle.attr("cx", xSlider(endDate));
label.attr("x", xSlider(endDate)).text(formatDate(endDate));
update(endDate);
function hue(h) {
handle.attr("cx", xSlider(h));
label.attr("x", xSlider(h)).text(formatDate(h));
console.log(h);
update(h);
}