Remove unaccessible summary graph a->b functions and add y-axis
fixes CNVS-25736 test plan: - Go to quiz statistics page - Access summary graph via keyboard - Ensure that KO users do not have less functionality than other users with the graph - Ensure that y-axis correctly demonstrates the data presented Change-Id: Ib971a9e4061f65562f21ef60796afdc046224b89 Reviewed-on: https://gerrit.instructure.com/70073 Tested-by: Jenkins Reviewed-by: Ryan Taylor <rtaylor@instructure.com> QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dana Danger <dana@instructure.com>
This commit is contained in:
parent
5e8215b249
commit
a506ec9245
|
@ -51,24 +51,13 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
.y.axis {
|
||||
fill: $muteFg;
|
||||
|
||||
.x.brush {
|
||||
fill: rgba($contrastColor, 0.1);
|
||||
}
|
||||
|
||||
text.brush-stats {
|
||||
fill: $primaryFg;
|
||||
transition: opacity 0.25s ease 0;
|
||||
|
||||
&.unused {
|
||||
opacity: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text.brush-stats.unused {
|
||||
opacity: 1;
|
||||
line, path {
|
||||
fill: none;
|
||||
stroke-width: 1px;
|
||||
stroke: $muteFg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,21 +3,14 @@ define(function(require) {
|
|||
var React = require('react');
|
||||
var ChartMixin = require('../../mixins/chart');
|
||||
var d3 = require('d3');
|
||||
var _ = require('lodash');
|
||||
var I18n = require('i18n!quiz_statistics.summary');
|
||||
var max = d3.max;
|
||||
var sum = d3.sum;
|
||||
var throttle = _.throttle;
|
||||
|
||||
var MARGIN_T = 0;
|
||||
var MARGIN_R = 18;
|
||||
var MARGIN_B = 60;
|
||||
var MARGIN_L = 34;
|
||||
var CHART_BRUSHING_TIP_LABEL = I18n.t(
|
||||
'chart_brushing_tip',
|
||||
'Tip: you can focus a specific segment of the chart by making a ' +
|
||||
'selection using your cursor.'
|
||||
);
|
||||
|
||||
var ScorePercentileChart = React.createClass({
|
||||
mixins: [ ChartMixin.mixin ],
|
||||
|
@ -36,13 +29,13 @@ define(function(require) {
|
|||
width: 960,
|
||||
height: 220,
|
||||
barPadding: 0.25,
|
||||
minBarHeight: 1
|
||||
minBarHeight: 1,
|
||||
numTicks: 5
|
||||
};
|
||||
},
|
||||
|
||||
createChart: function(node, props) {
|
||||
var svg, width, height, x, xAxis;
|
||||
var brush, brushCaption, onBrushed;
|
||||
var barContainer;
|
||||
|
||||
width = props.width - MARGIN_L - MARGIN_R;
|
||||
|
@ -53,12 +46,20 @@ define(function(require) {
|
|||
x = d3.scale.ordinal().rangeRoundBands([0, width], props.barPadding, 0);
|
||||
x.domain(d3.range(0, 101, 1));
|
||||
|
||||
this.y = d3.scale.linear().range([height, 0]);
|
||||
|
||||
xAxis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.orient("bottom")
|
||||
.tickValues(d3.range(0, 101, 10))
|
||||
.tickFormat(function(d) { return d + '%'; });
|
||||
|
||||
this.yAxis = d3.svg.axis()
|
||||
.scale(this.y)
|
||||
.orient("left")
|
||||
.outerTickSize(0)
|
||||
.ticks(props.numTicks);
|
||||
|
||||
svg = d3.select(node)
|
||||
.attr('role', 'document')
|
||||
.attr('aria-role', 'document')
|
||||
|
@ -77,41 +78,16 @@ define(function(require) {
|
|||
.attr('transform', "translate(5," + height + ")")
|
||||
.call(xAxis);
|
||||
|
||||
barContainer = svg.append('g');
|
||||
|
||||
brushCaption = svg
|
||||
.append('text')
|
||||
.attr('class', 'brush-stats unused')
|
||||
.attr('text-anchor', 'left')
|
||||
this.yAxisContainer = svg.append('g')
|
||||
.attr('class', 'y axis')
|
||||
.attr('aria-hidden', true)
|
||||
.attr('dy', '.35em')
|
||||
.attr('y', height + 40)
|
||||
.attr('x', 0)
|
||||
.text(CHART_BRUSHING_TIP_LABEL);
|
||||
.call(this.yAxis);
|
||||
|
||||
onBrushed = throttle(this.onBrushed, 100, {
|
||||
leading: false,
|
||||
trailing: true
|
||||
});
|
||||
|
||||
brush = d3.svg.brush()
|
||||
.x(x)
|
||||
.clamp(true)
|
||||
.on("brush", onBrushed);
|
||||
|
||||
this.brushContainer = svg.append("g")
|
||||
.attr("class", "x brush")
|
||||
.call(brush);
|
||||
|
||||
this.brushContainer.selectAll("rect")
|
||||
.attr("y", props.minBarHeight)
|
||||
.attr("height", height + props.minBarHeight);
|
||||
barContainer = svg.append('g');
|
||||
|
||||
this.x = x;
|
||||
this.height = height;
|
||||
this.barContainer = barContainer;
|
||||
this.brushCaption = brushCaption;
|
||||
this.brush = brush;
|
||||
|
||||
this.updateChart(svg, props);
|
||||
|
||||
|
@ -132,15 +108,12 @@ define(function(require) {
|
|||
}));
|
||||
|
||||
this.renderBars(this.barContainer, props);
|
||||
|
||||
if (!this.brush.empty()) {
|
||||
this.onBrushed();
|
||||
}
|
||||
},
|
||||
|
||||
renderBars: function(svg, props) {
|
||||
var height, x, y, bars;
|
||||
var highest;
|
||||
var step;
|
||||
var visibilityThreshold;
|
||||
var data = this.chartData;
|
||||
|
||||
|
@ -148,12 +121,22 @@ define(function(require) {
|
|||
highest = max(data);
|
||||
|
||||
x = this.x;
|
||||
y = this.y
|
||||
|
||||
y = d3.scale.linear()
|
||||
.range([0, highest])
|
||||
.rangeRound([height, 0]);
|
||||
y.range([0, highest])
|
||||
.rangeRound([height, 0])
|
||||
.domain([0, highest]);
|
||||
|
||||
y.domain([0, highest]);
|
||||
step = -Math.ceil((highest + 1) / props.numTicks)
|
||||
|
||||
this.yAxis.tickValues(d3.range(highest, 0, step))
|
||||
.tickFormat(function(d){return Math.floor(d)});
|
||||
|
||||
this.yAxisContainer.call(this.yAxis).selectAll('text').attr('dy', '.8em');
|
||||
this.yAxisContainer
|
||||
.selectAll('line')
|
||||
.attr('y1', '.5')
|
||||
.attr('y2', '.5');
|
||||
|
||||
visibilityThreshold = Math.max(highest / 100, props.minBarHeight);
|
||||
|
||||
|
@ -170,7 +153,7 @@ define(function(require) {
|
|||
bars.transition()
|
||||
.delay(props.animeDelay)
|
||||
.duration(props.animeDuration)
|
||||
.attr('y', function(d) { return y(d) + visibilityThreshold; })
|
||||
.attr('y', function(d) { return y(d) - visibilityThreshold; })
|
||||
.attr('height', function(d) {
|
||||
return height - y(d) + visibilityThreshold;
|
||||
});
|
||||
|
@ -233,90 +216,8 @@ define(function(require) {
|
|||
return set;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* Update the focus caption with the number of students who are allocated to
|
||||
* the "brushed" percentile range (which could be just a single point as
|
||||
* well).
|
||||
*/
|
||||
onBrushed: function() {
|
||||
var i, a, b;
|
||||
var studentCount;
|
||||
var message;
|
||||
|
||||
var brush = this.brush;
|
||||
var brushCaption = this.brushCaption;
|
||||
var data = this.chartData;
|
||||
|
||||
var range = this.x.range();
|
||||
var extent = brush.extent();
|
||||
|
||||
// invert the brush extent against the X scale and locate the percentiles
|
||||
// we're focusing, a and b:
|
||||
for (i = 0; i < range.length; ++i) {
|
||||
if (a === undefined && range[i] > extent[0]) {
|
||||
a = Math.max(i-1, 0);
|
||||
}
|
||||
|
||||
if (b === undefined && range[i] > extent[1]) {
|
||||
b = Math.max(i-1, 0);
|
||||
}
|
||||
|
||||
if (a !== undefined && b !== undefined) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if at this point we still didn't find the end percentile, it means
|
||||
// the brush extends beyond the last percentile (100%) so just choose that
|
||||
if (b === undefined) {
|
||||
b = range.length - 1;
|
||||
|
||||
if (a === undefined) { // single-point brush
|
||||
a = b;
|
||||
}
|
||||
}
|
||||
|
||||
if (a - b === 0) { // single percentile
|
||||
studentCount = data[a] || 0;
|
||||
|
||||
message = I18n.t('students_who_got_a_certain_score', {
|
||||
zero: 'No students have received a score of %{score}%.',
|
||||
one: 'One student has received a score of %{score}%.',
|
||||
other: '%{count} students have received a score of %{score}%.',
|
||||
}, {
|
||||
count: studentCount,
|
||||
score: a
|
||||
});
|
||||
|
||||
// redraw the brush to cover the percentile bar:
|
||||
this.brush.extent([ range[a], range[b] + this.x.rangeBand() ]);
|
||||
this.brush(this.brushContainer);
|
||||
}
|
||||
else { // percentile range
|
||||
studentCount = 0;
|
||||
|
||||
for (i = a; i <= b; ++i) {
|
||||
studentCount += data[i] || 0;
|
||||
}
|
||||
|
||||
message = I18n.t('students_who_scored_in_a_range', {
|
||||
zero: 'No students have scored between %{start} and %{end}%.',
|
||||
one: 'One student has scored between %{start} and %{end}%.',
|
||||
other: '%{count} students have scored between %{start} and %{end}%.',
|
||||
}, {
|
||||
count: studentCount,
|
||||
start: a,
|
||||
end: b
|
||||
});
|
||||
}
|
||||
|
||||
brushCaption.text(message).attr('class', 'brush-stats');
|
||||
},
|
||||
|
||||
render: ChartMixin.defaults.render
|
||||
});
|
||||
|
||||
return ScorePercentileChart;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -52,10 +52,10 @@ define(function(require) {
|
|||
});
|
||||
|
||||
testRects([
|
||||
{ i: 15, x: 136, y: 92, h: 92 },
|
||||
{ i: 25, x: 226, y: 92, h: 92 },
|
||||
{ i: 44, x: 397, y: 92, h: 92 },
|
||||
{ i: 59, x: 532, y: 2, h: 182 },
|
||||
{ i: 15, x: 136, y: 88, h: 92 },
|
||||
{ i: 25, x: 226, y: 88, h: 92 },
|
||||
{ i: 44, x: 397, y: 88, h: 92 },
|
||||
{ i: 59, x: 532, y: -2, h: 182 },
|
||||
], done);
|
||||
});
|
||||
|
||||
|
@ -69,8 +69,8 @@ define(function(require) {
|
|||
});
|
||||
|
||||
testRects([
|
||||
{ i: 15, x: 136, y: 2, h: 182 },
|
||||
{ i: 25, x: 226, y: 182, h: 2 },
|
||||
{ i: 15, x: 136, y: -2, h: 182 },
|
||||
{ i: 25, x: 226, y: 178, h: 2 },
|
||||
], function updatePropsAnotherTime() {
|
||||
setProps({
|
||||
scores: {
|
||||
|
@ -80,8 +80,8 @@ define(function(require) {
|
|||
});
|
||||
|
||||
testRects([
|
||||
{ i: 15, x: 136, y: 2, h: 182 },
|
||||
{ i: 25, x: 226, y: 2, h: 182 },
|
||||
{ i: 15, x: 136, y: -2, h: 182 },
|
||||
{ i: 25, x: 226, y: -2, h: 182 },
|
||||
], done);
|
||||
});
|
||||
});
|
||||
|
@ -95,8 +95,8 @@ define(function(require) {
|
|||
});
|
||||
|
||||
testRects([
|
||||
{ i: 0, x: 1, y: 146, h: 38 },
|
||||
{ i: 100, x: 901, y: 2, h: 182 },
|
||||
{ i: 0, x: 1, y: 142, h: 38 },
|
||||
{ i: 100, x: 901, y: -2, h: 182 },
|
||||
], done);
|
||||
});
|
||||
|
||||
|
@ -108,4 +108,4 @@ define(function(require) {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue