More details... In the extension I currently have two measures. The first measure is used to calculate quantiles for the dataset as a whole and then to translate the quantiles into a box plot.
The second measure is used to plot the individual data points on top of the box plot.
The set analysis and overall data structure appear correct when I look at the data in a table with the appropriate selections. In the visualization, however, initially everything appears correctly, but when I initiate a selection for Student the box plot calculated from Measure 1 disappears and only the data points for the selected student remain. I'm creating the extension in d3 and the code is included below.
Thanks in advance for any help.
define(["jquery", "./d3.v4.min", "./initialProperties", "./properties", "text!./CCP_Grouped_Multi.css"],
function ($, d3, initProps, props, cssContent) {
"use strict"
$('<style>').html(cssContent).appendTo('head');
return {
initialProperties: initProps,
definition: props,
support: {
snapshot: true,
export: false,
exportData: false
},
paint: function ($element, layout) {
var hypercube = layout.cube1;
console.log(hypercube);
var margin = {
top: 20,
right: 20,
bottom: 60,
left: 40
},
width = $element.width() - margin.left - margin.right,
height = $element.height() - margin.top - margin.bottom,
center = height / 2,
offset = 50;
var id = "ID_D3" + layout.qInfo.qId;
$element.attr("id", id); //assign to $element the attribute id set as "ID_D3"
d3.select("#" + id).html(null); //clear div so that it doesn't re-render with each run
var dataset = layout.qHyperCube.qDataPages[0].qMatrix.map(function (d) {
return {
"d_1": d[0].qText, //Domain
"d_2": d[1].qText, //Student
"d_3": d[2].qNum, //Box plot measure (=Aggr({1<Student=>} NODISTINCT Max(Final_Score), Student, Domain)
"d_4": d[3].qNum //Data points measure (=Aggr(NODISTINCT Max(Final_Score), Student, Domain)
}
});
//Calculate distribution components (Adjust with d3.nest)
var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
.key(function (d) { return d.d_1; })
.rollup(function (g) {
return {
"min": d3.quantile(g, 0, g => g.d_3),
"q1": d3.quantile(g, .25, g => g.d_3),
"med": d3.quantile(g, .5, g => g.d_3),
"q3": d3.quantile(g, .75, g => g.d_3),
"max": d3.quantile(g, 1, g => g.d_3)
}
})
.entries(dataset)
console.log("sumstat:", sumstat)
//Create svg object
var svg = d3.select("#" + id)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// Show the X scale (Need to find consistent way to translate the position: currently shifts at resizing)
var x = d3.scaleLinear()
.domain([0, 1])
.range([0, width])
svg
.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).tickValues([0, 1]).tickFormat(d3.format(".0%")));
// Add X axis label:
svg
.append("text")
.style("text-anchor", "middle")
.attr("x", width / 2)
.attr("y", height + margin.bottom - 15)
.text("Range");
// Show the Y scale
var y = d3.scaleBand()
.range([height, 0])
.domain(["CM", "CR", "HX", "PE"])
.padding(.4);
svg.append("g")
.call(d3.axisLeft(y).tickSize(0))
.select(".domain").remove()
// Show the boxes (shape outlines commented out)
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("y", function (d) { return y(d.key); })
.attr("x", function (d) { return (x(d.value.min)) })
.attr("width", function (d) { return (x(d.value.q1) - x(d.value.min)) })
.attr("height", center / 4)
// .attr("height", y.bandwidth())
// .attr("stroke", "gray")
.style("fill", "#EEEEEE")
.text("Minimum");
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("y", function (d) { return y(d.key); })
.attr("x", function (d) { return (x(d.value.q1)) })
.attr("width", function (d) { return (x(d.value.med) - x(d.value.q1)) })
.attr("height", center / 4)
// .attr("height", y.bandwidth())
// .attr("stroke", "gray")
.style("fill", "#CCCCCC");
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("y", function (d) { return y(d.key); })
.attr("x", function (d) { return (x(d.value.med)) })
.attr("width", function (d) { return (x(d.value.q3) - x(d.value.med)) })
.attr("height", center / 4)
// .attr("height", y.bandwidth())
// .attr("stroke", "gray")
.style("fill", "#999999");
svg
.selectAll("boxes")
.data(sumstat)
.enter()
.append("rect")
.attr("y", function (d) { return y(d.key); })
.attr("x", function (d) { return (x(d.value.q3)) })
.attr("width", function (d) { return (x(d.value.max) - x(d.value.q3)) })
.attr("height", center / 4)
// .attr("height", y.bandwidth())
// .attr("stroke", "gray")
.style("fill", "#666666");
//Adding individual data points (Change to rectangle with a value representing student store +- 5% of max-min) also: Convert to d3.nest() and sub for dataset (key/value)
var color = d3.scaleLinear()
.domain([.5, 1])
.range(["#e7ecfc", "#1045e3"]);
svg
.selectAll("indPoints")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) { return (x(d.d_4)) })
.attr("cy", function (d) { return (y(d.d_1) + (y.bandwidth() - (center / 7))) }) //Will have to adjust for flexibility
.attr("r", 4)
.style("fill", function (d) { return (color(+d.d_4)) })
}
}
})