Skip to content

Instantly share code, notes, and snippets.

@ZoeLeBlanc
Last active August 16, 2018 20:24
Show Gist options
  • Save ZoeLeBlanc/ac87b65e609d7a3ca03c290ee7e7aaa4 to your computer and use it in GitHub Desktop.
Save ZoeLeBlanc/ac87b65e609d7a3ca03c290ee7e7aaa4 to your computer and use it in GitHub Desktop.
Brush & Zoom : ITS Learning Behavior
license: mit

The Visualization

This work is an exploration of the Brush and Zoom Feature using data from students solving math problems. Scroll within in the main graph to zoom in and out. You can keep track of your place within the graph by looking at the reference line below.

The Data Set

This data set that shows a summary of student behavior during mathematical problem solving within an intelligent tutoring system (ITS). The system records behavior such as time to solve each problem, number of attempts, number of hints shown, and correctness. This data set also has corresponding MCAS scores for each student (MCAS is a state-wide standardized mathematics test for students in Massachusetts).

Data Sources

To access original data from Feng, Heffernan, and Koedinger (2009) see here.

Code Sources

For brushing techniques, forked from EfratVil's block: Brush & Zoom line chart

Original line graph inspired by adry34160's block: 2 - Multiple line graphs with labels

forked from thulse's block: Brush & Zoom : ITS Learning Behavior

countbin counts original_count correct% correct MCAS_real MCAS DA_avg_time DA_avg_time_seconds DA_avg_attempt DA_avg_hint
1 0-20 20 0.38441994 3.844199404 28.47938144 2.847938144 2.37438634 142.4631804 1.538521479 0.505040532
2 21-40 40 0.406960369 4.069603686 31.38979118 3.138979118 1.503143038 90.1885823 1.537992353 0.432268707
3 41-60 60 0.396034224 3.960342236 28.8 2.88 1.011873105 60.7123863 1.527538651 0.420759201
4 61-80 80 0.474455986 4.744559862 32.19230769 3.219230769 0.815621771 48.93730626 1.5089773 0.43865895
5 81-106 100 0.384303569 3.843035693 24.14285714 2.414285714 0.583969106 35.03814635 1.427711578 0.80733423
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.line1 {
fill: none;
stroke: #ffa81e;
stroke-width: 2px;
}
.line2 {
fill: none;
stroke: #cc4e00;
stroke-width: 2px;
}
.line3 {
fill: none;
stroke: #00d161;
stroke-width: 2px;
}
.line4 {
fill: none;
stroke: #29c5f9;
stroke-width: 2px;
}
.line5 {
fill: none;
stroke: #4377f8;
stroke-width: 2px;
}
.zoom {
cursor: move;
fill: none;
pointer-events: all;
}
.tooltip text {
text-anchor: middle;
text-shadow: 0 1px 0 #fff, 1px 0 0 #fff, 0 -1px 0 #fff, -1px 0 0 #fff;
}
</style>
<script src="legend.js"></script>
<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 110, left: 40},
margin2 = {top: 430, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom,
height2 = +svg.attr("height") - margin2.top - margin2.bottom;
var x = d3.scaleLinear().range([0, width]),
x2 = d3.scaleLinear().range([0, width]),
y = d3.scaleLinear().range([height, 0]);
y2 = d3.scaleLinear().range([height2, 0]);
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([[0, 0], [width, height2]])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed);
var line1 = d3.line()
.x(function (d) { return x(d.original_count); })
.y(function (d) { return y(d.MCAS); });
var line1a = d3.line()
.x(function (d) { return x2(d.original_count); })
.y(function (d) { return y2(d.MCAS); });
var line2 = d3.line()
.x(function (d) { return x(d.original_count); })
.y(function (d) { return y(d.correct); });
var line2a = d3.line()
.x(function (d) { return x2(d.original_count); })
.y(function (d) { return y2(d.correct); });
var line3 = d3.line()
.x(function (d) { return x(d.original_count); })
.y(function (d) { return y(d.DA_avg_time); });
var line3a = d3.line()
.x(function (d) { return x2(d.original_count); })
.y(function (d) { return y2(d.DA_avg_time); });
var line4 = d3.line()
.x(function (d) { return x(d.original_count); })
.y(function (d) { return y(d.DA_avg_attempt); });
var line4a = d3.line()
.x(function (d) { return x2(d.original_count); })
.y(function (d) { return y2(d.DA_avg_attempt); });
var line5 = d3.line()
.x(function (d) { return x(d.original_count); })
.y(function (d) { return y(d.DA_avg_hint); });
var line5a = d3.line()
.x(function (d) { return x2(d.original_count); })
.y(function (d) { return y2(d.DA_avg_hint); });
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width)
.attr("height", height)
.attr("x", 0)
.attr("y", 0);
//legend
// const colorValue = d => d.estimated_temp;
// const colorLabel = 'Temperature (C)';
// const g = svg.append('g')
// .attr('transform', `translate(${margin.left},${margin.top})`);
// const xAxisG = g.append('g')
// .attr('transform', `translate(0, ${innerHeight})`);
// const yAxisG = g.append('g');
// const colorLegendG = g.append('g')
// .attr('transform', `translate(${innerWidth + 60}, 150)`);
// colorLegendG.append('text')
// .attr('class', 'legend-label')
// .attr('x', -30)
// .attr('y', -40)
// .text(colorLabel);
//other legend
// function legendDemo() {
// sampleCategoricalData = ["Correct","MCAS", "Time", "Attempts", "Hints"]
// sampleOrdinal = d3.scale.category20().domain(sampleCategoricalData);
// verticalLegend = d3.svg.legend().labelFormat("none").cellPadding(5).orientation("vertical").units("Things in a List").cellWidth(25).cellHeight(18).inputScale(sampleOrdinal).cellStepping(10);
// d3.select("svg").append("g").attr("transform", "translate(50,140)").attr("class", "legend").call(verticalLegend);
// }
//creating line variables
var Line_chart1 = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
var Line_chart2 = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
var Line_chart3 = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
var Line_chart4 = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
var Line_chart5 = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("clip-path", "url(#clip)");
//focus and context for zooming
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
//getting data
d3.csv("DataViz_MCAS.csv", type, function (error, data) {
if (error) throw error;
x.domain(d3.extent(data, function(d) { return d.original_count; }));
y.domain([0, d3.max(data, function (d) { return d.correct+1; })]);
x2.domain(x.domain());
y2.domain(y.domain());
function transition(path) {
path.transition()
.duration(2000)
.attrTween("stroke-dasharray", tweenDash);
}
function tweenDash() {
var l = this.getTotalLength(),
i = d3.interpolateString("0," + l, l + "," + l);
return function (t) { return i(t); };
}
//functions
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
var tooltip = focus.append("g")
.attr("transform", "translate(-100,-100)")
.attr("class", "tooltip");
tooltip.append("circle")
.attr("r", 3.5);
tooltip.append("text")
.attr("y", -10);
Line_chart1.append("path")
.datum(data)
.attr("class", "line1")
.attr("d", line1a)
.call(transition);
context.append("path")
.datum(data)
.attr("class", "line1")
.attr("d", line1a);
Line_chart2.append("path")
.datum(data)
.attr("class", "line2")
.attr("d", line2a);
context.append("path")
.datum(data)
.attr("class", "line2")
.attr("d", line2a);
Line_chart3.append("path")
.datum(data)
.attr("class", "line3")
.attr("d", line3a);
context.append("path")
.datum(data)
.attr("class", "line3")
.attr("d", line3a);
Line_chart4.append("path")
.datum(data)
.attr("class", "line4")
.attr("d", line4a);
context.append("path")
.datum(data)
.attr("class", "line4")
.attr("d", line4a);
Line_chart5.append("path")
.datum(data)
.attr("class", "line5")
.attr("d", line5a);
context.append("path")
.datum(data)
.attr("class", "line5")
.attr("d", line5a);
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr("width", width)
.attr("height", height)
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(zoom);
console.log(data);
});
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
Line_chart1.select(".line1").attr("d", line1);
Line_chart2.select(".line2").attr("d", line2);
Line_chart3.select(".line3").attr("d", line3);
Line_chart4.select(".line4").attr("d", line4);
Line_chart5.select(".line5").attr("d", line5);
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(width / (s[1] - s[0]))
.translate(-s[0], 0));
}
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
Line_chart1.select(".line1").attr("d", line1);
Line_chart2.select(".line2").attr("d", line2);
Line_chart3.select(".line3").attr("d", line3);
Line_chart4.select(".line4").attr("d", line4);
Line_chart5.select(".line5").attr("d", line5);
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
function type(d) {
d.original_count = +d.original_count;
d.correct = +d.correct;
d.MCAS = +d.MCAS;
d.DA_avg_time = +d.DA_avg_time;
d.DA_avg_attempt = +d.DA_avg_attempt;
d.DA_avg_hint = +d.DA_avg_hint;
return d;
}
//Text label for the X Axis
svg.append("text")
.attr("x", width/2)
.attr("y", height + height2+15)
.style("text-anchor", "middle")
.text("Number of Problems");
//text for line labels
svg.append("text")
// .attr("transform", "translate(" + (width+3) + "," + y(data.correct) + ")")
.attr ("dx", "3em")
.attr("dy", "8em")
.attr("text-anchor", "start")
.style("fill", "#cc4e00")
.text("Correctness");
svg.append("text")
// .attr("transform", "translate(" + (width+3) + "," + y(data.MCAS) + ")")
.attr("dx", "3em")
.attr("dy", "12em")
.attr("text-anchor", "start")
.style("fill", "#ffa81e")
.text("MCAS Score");
svg.append("text")
// .attr("transform", "translate(" + (width+3) + "," + y(data.DA_avg_time) + ")")
.attr("dx", "3em")
.attr("dy", "14.5em")
.attr("text-anchor", "start")
.style("fill", "#00d161")
.text("Time");
svg.append("text")
// .attr("transform", "translate(" + (width+3) + "," + y(data.DA_avg_attempt) + ")")
.attr("dx", "3em")
.attr("dy", "17.5em")
.attr("text-anchor", "start")
.style("fill", "#29c5f9")
.text("Errors");
svg.append("text")
// .attr("transform", "translate(" + (width+3) + "," + y(data.DA_avg_hint) + ")")
.attr("dx", "3em")
.attr("dy", "21.5em")
.attr("text-anchor", "start")
.style("fill", "#4377f8")
.text("Hints");
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment