Commit addea00c by Tim Krones

Merge pull request #82 from open-craft/plot-overlays

Step Builder: Support for additional overlays for plot blocks
parents 21326b8c 8af9fa2b
......@@ -26,6 +26,8 @@ from lazy.lazy import lazy
from xblock.core import XBlock
from xblock.fields import String, Scope
from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import (
StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin, XBlockWithPreviewMixin
......@@ -244,12 +246,89 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
'average_claims': self.average_claims,
}
@property
def allowed_nested_blocks(self):
"""
Returns a list of allowed nested XBlocks. Each item can be either
* An XBlock class
* A NestedXBlockSpec
If XBlock class is used it is assumed that this XBlock is enabled and allows multiple instances.
NestedXBlockSpec allows explicitly setting disabled/enabled state,
disabled reason (if any) and single/multiple instances.
"""
return [PlotOverlayBlock]
@lazy
def overlay_ids(self):
"""
Get the usage_ids of all of this XBlock's children that are overlays.
"""
return [
_normalize_id(child_id) for child_id in self.children if
child_isinstance(self, child_id, PlotOverlayBlock)
]
@lazy
def overlays(self):
"""
Get the overlay children of this block.
"""
return [self.runtime.get_block(overlay_id) for overlay_id in self.overlay_ids]
@lazy
def overlay_data(self):
if not self.claims:
return []
overlay_data = []
claims = self.claims.split('\n')
for index, overlay in enumerate(self.overlays):
claims_json = []
if overlay.claim_data:
claim_data = overlay.claim_data.split('\n')
for claim, data in zip(claims, claim_data):
claim = claim.split(', ')[0]
r1, r2 = data.split(', ')
claims_json.append([claim, int(r1), int(r2)])
claims_json = json.dumps(claims_json)
overlay_data.append({
'plot_label': overlay.plot_label,
'point_color': overlay.point_color,
'description': overlay.description,
'citation': overlay.citation,
'claims_json': claims_json,
'position': index,
})
return overlay_data
@lazy
def claims_display(self):
if not self.claims:
return []
claims = []
for claim in self.claims.split('\n'):
claim, q1, q2 = claim.split(', ')
claims.append([claim, q1, q2])
return claims
def author_preview_view(self, context):
return Fragment(
context['self'] = self
fragment = Fragment()
fragment.add_content(loader.render_template('templates/html/plot_preview.html', context))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/plot-preview.css'))
if self.overlay_ids:
fragment.add_content(
u"<p>{}</p>".format(
_(u"This block displays a plot that summarizes answers to scale questions.")
)
)
_(u"In addition to the default and average overlays the plot includes the following overlays:")
))
for overlay in self.overlays:
overlay_fragment = self._render_child_fragment(overlay, context, view='mentoring_view')
fragment.add_frag_resources(overlay_fragment)
fragment.add_content(overlay_fragment.content)
return fragment
def mentoring_view(self, context):
return self.student_view(context)
......@@ -266,3 +345,115 @@ class PlotBlock(StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/plot.js'))
fragment.initialize_js('PlotBlock')
return fragment
def author_edit_view(self, context):
"""
Add some HTML to the author view that allows authors to add child blocks.
"""
context['wrap_children'] = {
'head': u'<div class="mentoring">',
'tail': u'</div>'
}
fragment = super(PlotBlock, self).author_edit_view(context)
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/plot_edit.js'))
fragment.initialize_js('PlotEdit')
return fragment
@XBlock.needs('i18n')
class PlotOverlayBlock(StudioEditableXBlockMixin, XBlockWithPreviewMixin, XBlock):
"""
XBlock that represents a user-defined overlay for a plot block.
"""
CATEGORY = 'sb-plot-overlay'
STUDIO_LABEL = _(u"Plot Overlay")
# Settings
display_name = String(
display_name=_("Overlay title"),
default="Overlay",
scope=Scope.content
)
plot_label = String(
display_name=_("Plot label"),
help=_("Label for button that allows to toggle visibility of this overlay"),
default="",
scope=Scope.content
)
point_color = String(
display_name=_("Point color"),
help=_("Point color to use for this overlay"),
default="",
scope=Scope.content
)
description = String(
display_name=_("Description"),
help=_("Description of this overlay (optional)"),
default="",
scope=Scope.content
)
citation = String(
display_name=_("Citation"),
help=_("Source of data belonging to this overlay (optional)"),
default="",
scope=Scope.content
)
claim_data = String(
display_name=_("Claim data"),
help=_(
'Claim data to include in this overlay. '
'Each line defines a tuple of the form "q1, q2", '
'where "q1" is the value associated with the first scale or rating question, '
'and "q2" is the value associated with the second scale or rating question. '
'Note that data will be associated with claims in the order that they are defined in the parent plot.'
),
default="",
multiline_editor=True,
resettable_editor=False
)
editable_fields = (
"plot_label", "point_color", "description", "citation", "claim_data"
)
def validate_field_data(self, validation, data):
"""
Validate this block's field data.
"""
super(PlotOverlayBlock, self).validate_field_data(validation, data)
def add_error(msg):
validation.add(ValidationMessage(ValidationMessage.ERROR, msg))
if not data.plot_label.strip():
add_error(_(u"No plot label set. Button for toggling visibility of this overlay will not have a label."))
if not data.point_color.strip():
add_error(_(u"No point color set. This overlay will not work correctly."))
# If parent plot is associated with one or more claims, prompt user to add claim data
parent = self.get_parent()
if parent.claims.strip() and not data.claim_data.strip():
add_error(_(u"No claim data provided. This overlay will not work correctly."))
def author_preview_view(self, context):
return self.student_view(context)
def mentoring_view(self, context):
context = context.copy() if context else {}
context['hide_header'] = True
return self.author_preview_view(context)
def student_view(self, context):
context['self'] = self
fragment = Fragment()
fragment.add_content(loader.render_template('templates/html/overlay.html', context))
fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/overlay.css'))
return fragment
.sb-plot-overlay {
margin-bottom: 10px;
}
.italic {
font-style: italic;
}
.sb-plot table {
width: 100%;
margin-top: 1em;
margin-bottom: 1em;
border: 2px solid #999;
}
.sb-plot thead {
border-bottom: 2px solid #999;
background-color: #ddd;
font-weight: bold;
}
.sb-plot tr:nth-child(even) {
background-color: #eee;
}
.sb-plot td {
border-left: 1px solid #999;
padding: 5px;
}
.sb-plot {
overflow: auto;
}
.quadrants label {
font-weight: bold;
}
.overlays {
float: right;
width: 40%;
}
.overlays input {
margin-top: 10px;
margin-right: 5px;
}
.quadrants input, .overlays input {
background-color: rgb(204, 204, 204);
}
.plot-info {
margin-top: 15px;
}
.plot-info-header {
font-weight: bold;
}
......@@ -15,6 +15,7 @@
}
/* Custom appearance for our "Add" buttons */
.xblock[data-block-type=sb-plot] .add-xblock-component .new-component .new-component-type .add-xblock-component-button,
.xblock[data-block-type=sb-review-step] .add-xblock-component .new-component .new-component-type .add-xblock-component-button,
.xblock[data-block-type=sb-step] .add-xblock-component .new-component .new-component-type .add-xblock-component-button,
.xblock[data-block-type=step-builder] .add-xblock-component .new-component .new-component-type .add-xblock-component-button,
......@@ -25,6 +26,8 @@
line-height: 30px;
}
.xblock[data-block-type=sb-plot] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled,
.xblock[data-block-type=sb-plot] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled:hover,
.xblock[data-block-type=sb-review-step] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled,
.xblock[data-block-type=sb-review-step] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled:hover,
.xblock[data-block-type=sb-step] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled,
......
function PlotBlock(runtime, element) {
// jQuery helpers
jQuery.fn.isEmpty = function() {
return !$.trim($(this).html());
};
jQuery.fn.isHidden = function() {
// Don't use jQuery :hidden selector here;
// this is necessary to ensure that result is independent of parent visibility
return $(this).css('display') === 'none';
};
jQuery.fn.isVisible = function() {
// Don't use jQuery :visible selector here;
// this is necessary to ensure that result is independent of parent visibility
return $(this).css('display') !== 'none';
};
// Plot
// Define margins
......@@ -58,7 +76,8 @@ function PlotBlock(runtime, element) {
var defaultButton = $('.plot-default', element),
averageButton = $('.plot-average', element),
quadrantsButton = $('.plot-quadrants', element);
quadrantsButton = $('.plot-quadrants', element),
overlayButtons = $('input.plot-overlay', element);
// Claims
......@@ -80,7 +99,7 @@ function PlotBlock(runtime, element) {
// Event handlers
function toggleOverlay(claims, color, klass, refresh) {
var selector = "." + klass,
var selector = buildSelector(klass),
selection = svgContainer.selectAll(selector);
if (selection.empty()) {
showOverlay(selection, claims, color, klass);
......@@ -92,6 +111,14 @@ function PlotBlock(runtime, element) {
}
}
function buildSelector(klass) {
var classes = klass.split(' ');
if (classes.length === 1) {
return "." + klass;
}
return '.' + classes.join('.');
}
function showOverlay(selection, claims, color, klass) {
selection
.data(claims)
......@@ -127,6 +154,29 @@ function PlotBlock(runtime, element) {
}
}
function toggleOverlayInfo(klass, hide) {
var plotInfo = $('.plot-info', element),
selector = buildSelector(klass),
overlayInfo = plotInfo.children(selector);
if (hide || overlayInfo.isVisible()) {
overlayInfo.hide();
var overlayInfos = plotInfo.children('.plot-overlay'),
hidePlotInfo = true;
overlayInfos.each(function() {
var overlayInfo = $(this);
hidePlotInfo = hidePlotInfo && (overlayInfo.isHidden() || overlayInfo.isEmpty());
});
if (hidePlotInfo) {
plotInfo.hide();
}
} else {
overlayInfo.show();
if (!overlayInfo.isEmpty() && !plotInfo.isVisible()) {
plotInfo.show();
}
}
}
function toggleQuadrantLabels() {
var selection = svgContainer.selectAll(".quadrant-label"),
quadrantLabelsOn = quadrantsButton.val() === 'On';
......@@ -168,7 +218,7 @@ function PlotBlock(runtime, element) {
toggleBorderColor(this, defaultColor, refresh);
});
averageButton.on('click', function(event) {
averageButton.on('click', function() {
toggleOverlay(averageClaims, averageColor, 'claim-average');
toggleBorderColor(this, averageColor);
});
......@@ -177,9 +227,30 @@ function PlotBlock(runtime, element) {
toggleQuadrantLabels();
});
overlayButtons.each(function(index) {
var overlayButton = $(this),
claims = overlayButton.data('claims'),
color = overlayButton.data('point-color'),
klass = overlayButton.attr('class');
overlayButton.on('click', function() {
toggleOverlay(claims, color, klass);
toggleBorderColor(this, color);
toggleOverlayInfo(klass);
});
// Hide overlay info initially
toggleOverlayInfo(klass, 'hide');
});
// Quadrant labels are off initially; color of button for toggling them should reflect this
quadrantsButton.css("border-color", "red");
// Hide plot info initially
$('.plot-info', element).hide();
// API
var dataXHR;
......
function PlotEdit(runtime, element) {
'use strict';
StudioContainerXBlockWithNestedXBlocksMixin(runtime, element);
ProblemBuilderUtil.transformClarifications(element);
}
{% load i18n %}
<div class="sb-plot-overlay">
{% if self.plot_label and self.point_color %}
<h3 style="color: {{ self.point_color }};">{{ self.plot_label }} {% trans "Overlay" %}</h3>
{% endif %}
<p>
<strong>{% trans "Description:" %}</strong>
{% if self.description %}
{{ self.description }}
{% else %}
<span class="italic">{% trans "No description provided" %}</span>
{% endif %}
</p>
<p>
<strong>{% trans "Source:" %}</strong>
{% if self.citation %}
{{ self.citation }}
{% else %}
<span class="italic">{% trans "No citation provided" %}</span>
{% endif %}
</p>
<p>
<strong>{% trans "Data:" %}</strong>
{% if self.claim_data %}
{{ self.claim_data }}
{% else %}
<span class="italic">{% trans "No data provided" %}</span>
{% endif %}
</p>
</div>
{% load i18n %}
<div class="sb-plot">
<div class="quadrants">
<label>
Quadrant labels
{% trans "Quadrant labels" %}
<input type="button"
class="plot-quadrants"
data-q1-label="{{ self.q1_label }}"
......@@ -14,7 +15,7 @@
</div>
<div class="overlays">
<h3>Compare your plot to others!</h3>
<h3>{% trans "Compare your plot to others!" %}</h3>
<input type="button"
class="plot-default"
......@@ -30,6 +31,40 @@
data-overlay-on="false"
value="Average"
/>
{% for overlay in self.overlay_data %}
<input type="button"
class="plot-overlay plot-overlay-{{ overlay.position }}"
data-claims="{{ overlay.claims_json }}"
data-point-color="{{ overlay.point_color }}"
data-overlay-on="false"
value="{{ overlay.plot_label }}"
/>
{% endfor %}
<div class="plot-info">
<p class="plot-info-header">{% trans "Plot info" %}</p>
{% for overlay in self.overlay_data %}
<div class="plot-overlay plot-overlay-{{ overlay.position }}">
{% if overlay.description or overlay.citation %}
<p class="overlay-plot-label" style="color: {{ overlay.point_color }};">{{ overlay.plot_label }}</p>
{% if overlay.description %}
<p class="overlay-description">
<strong>{% trans "Description:" %}</strong> {{ overlay.description }}
</p>
{% endif %}
{% if overlay.citation %}
<p class="overlay-citation">
<strong>{% trans "Source:" %}</strong> {{ overlay.citation }}
</p>
{% endif %}
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
{% load i18n %}
<div class="sb-plot">
<p>{{ self.display_name }}</p>
{% if self.claims %}
<p>{% trans "This block displays a plot that summarizes responses to the following claims:" %}</p>
<table>
<thead>
<tr>
<td>{% trans "Claim" %}</td>
<td>{% trans "Question 1" %}</td>
<td>{% trans "Question 2" %}</td>
</tr>
</thead>
<tbody>
{% for claim in self.claims_display %}
<tr>
<td>{{ claim.0 }}</td>
<td>{{ claim.1 }}</td>
<td>{{ claim.2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans "This block displays a plot that summarizes responses to a set of claims." %}</p>
{% endif %}
</div>
......@@ -6,6 +6,28 @@ from .base_test import CORRECT, INCORRECT, PARTIAL, MentoringAssessmentBaseTest,
from .test_dashboard import MockSubmissionsAPI
class HTMLColors(object):
GREEN = 'rgba(0, 128, 0, 1)'
BLUE = 'rgba(0, 0, 255, 1)'
RED = 'rgba(255, 0, 0, 1)'
GREY = 'rgba(237, 237, 237, 1)'
PURPLE = 'rgba(128, 0, 128, 1)'
ORANGE = 'rgba(255, 165, 0, 1)'
CORAL = 'rgba(255, 127, 80, 1)'
CORNFLOWERBLUE = 'rgba(100, 149, 237, 1)'
OLIVE = 'rgba(128, 128, 0, 1)'
CRIMSON = 'rgba(220, 20, 60, 1)'
class PointColors(object):
ORANGE = 'rgb(255, 165, 0)'
PURPLE = 'rgb(128, 0, 128)'
CORAL = 'rgb(255, 127, 80)'
CORNFLOWERBLUE = 'rgb(100, 149, 237)'
OLIVE = 'rgb(128, 128, 0)'
CRIMSON = 'rgb(220, 20, 60)'
class ExtendedMockSubmissionsAPI(MockSubmissionsAPI):
def get_all_submissions(self, course_key_str, block_id, block_type):
return (
......@@ -587,6 +609,27 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
return plot_controls
def additional_plot_controls(self, step_builder):
class Namespace(object):
pass
additional_plot_controls = Namespace()
additional_plot_controls.teacher_button = step_builder.find_element_by_css_selector(
"input.plot-overlay.plot-overlay-0"
)
additional_plot_controls.researchers_button = step_builder.find_element_by_css_selector(
"input.plot-overlay.plot-overlay-1"
)
additional_plot_controls.sheldon_button = step_builder.find_element_by_css_selector(
"input.plot-overlay.plot-overlay-2"
)
additional_plot_controls.yoda_button = step_builder.find_element_by_css_selector(
"input.plot-overlay.plot-overlay-3"
)
return additional_plot_controls
def plot_empty(self, step_builder):
points = step_builder.find_elements_by_css_selector("circle")
self.assertEquals(points, [])
......@@ -601,43 +644,33 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
]
if hidden:
self.assertEquals(quadrant_labels, [])
# rgba(255, 0, 0, 1): "red"
self.assertTrue(all(bc == 'rgba(255, 0, 0, 1)' for bc in quadrants_button_border_colors))
self.assertTrue(all(bc == HTMLColors.RED for bc in quadrants_button_border_colors))
else:
self.assertEquals(len(quadrant_labels), 4)
self.assertEquals(set(label.text for label in quadrant_labels), set(labels))
# rgba(0, 128, 0, 1): "green"
self.assertTrue(all(bc == 'rgba(0, 128, 0, 1)' for bc in quadrants_button_border_colors))
def click_default_button(
self, plot_controls, overlay_on, color_on='rgba(0, 128, 0, 1)', color_off='rgba(237, 237, 237, 1)'
):
plot_controls.default_button.click()
default_button_border_colors = [
plot_controls.default_button.value_of_css_property('border-top-color'),
plot_controls.default_button.value_of_css_property('border-right-color'),
plot_controls.default_button.value_of_css_property('border-bottom-color'),
plot_controls.default_button.value_of_css_property('border-left-color'),
self.assertTrue(all(bc == HTMLColors.GREEN for bc in quadrants_button_border_colors))
def click_overlay_button(self, overlay_button, overlay_on, color_on=None, color_off=HTMLColors.GREY):
overlay_button.click()
button_border_colors = [
overlay_button.value_of_css_property('border-top-color'),
overlay_button.value_of_css_property('border-right-color'),
overlay_button.value_of_css_property('border-bottom-color'),
overlay_button.value_of_css_property('border-left-color'),
]
if overlay_on:
self.assertTrue(all(bc == color_on for bc in default_button_border_colors))
self.assertTrue(all(bc == color_on for bc in button_border_colors))
else:
self.assertTrue(all(bc == color_off for bc in default_button_border_colors))
self.assertTrue(all(bc == color_off for bc in button_border_colors))
def click_average_button(
self, plot_controls, overlay_on, color_on='rgba(0, 0, 255, 1)', color_off='rgba(237, 237, 237, 1)'
):
plot_controls.average_button.click()
average_button_border_colors = [
plot_controls.average_button.value_of_css_property('border-top-color'),
plot_controls.average_button.value_of_css_property('border-right-color'),
plot_controls.average_button.value_of_css_property('border-bottom-color'),
plot_controls.average_button.value_of_css_property('border-left-color'),
]
if overlay_on:
self.assertTrue(all(bc == color_on for bc in average_button_border_colors))
else:
self.assertTrue(all(bc == color_off for bc in average_button_border_colors))
def click_default_button(self, plot_controls, overlay_on, color_on=HTMLColors.GREEN):
self.click_overlay_button(plot_controls.default_button, overlay_on, color_on)
def click_average_button(self, plot_controls, overlay_on, color_on=HTMLColors.BLUE):
self.click_overlay_button(plot_controls.average_button, overlay_on, color_on)
def check_button_label(self, button, expected_value):
self.assertEquals(button.get_attribute('value'), expected_value)
def test_empty_plot(self):
step_builder, controls = self.load_assessment_scenario("step_builder_plot_defaults.xml", {})
......@@ -655,6 +688,9 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
self.plot_empty(step_builder)
# Obtain references to plot controls
plot_controls = self.plot_controls(step_builder)
# Check button labels
self.check_button_label(plot_controls.default_button, "yours")
self.check_button_label(plot_controls.average_button, "Average")
# Check if plot is empty (default overlay off, average overlay off)
self.click_default_button(plot_controls, overlay_on=False)
self.plot_empty(step_builder)
......@@ -678,7 +714,8 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
for overlay in overlays:
# Check if correct number of points is present
points = step_builder.find_elements_by_css_selector(overlay['selector'])
selector = 'circle' + overlay['selector']
points = step_builder.find_elements_by_css_selector(selector)
self.assertEquals(len(points), overlay['num_points'])
# Check point colors
point_colors = [
......@@ -718,11 +755,14 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
# Step 2: Plot
# Obtain references to plot controls
plot_controls = self.plot_controls(step_builder)
# Check button labels
self.check_button_label(plot_controls.default_button, "Custom plot label")
self.check_button_label(plot_controls.average_button, "Average")
# Overlay data
default_overlay = {
'selector': '.claim-default',
'num_points': 2,
'point_color': 'rgb(255, 165, 0)', # orange
'point_color': PointColors.ORANGE,
'titles': ['2 + 2 = 5: 1, 5', 'The answer to everything is 42: 5, 1'],
'positions': [
('20', '396'), # Values computed according to xScale and yScale (cf. plot.js)
......@@ -732,7 +772,7 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
average_overlay = {
'selector': '.claim-average',
'num_points': 2,
'point_color': 'rgb(128, 0, 128)', # purple
'point_color': PointColors.PURPLE,
'titles': ['2 + 2 = 5: 1, 5', 'The answer to everything is 42: 5, 1'],
'positions': [
('20', '396'), # Values computed according to xScale and yScale (cf. plot.js)
......@@ -743,7 +783,7 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
self.check_overlays(step_builder, total_num_points=2, overlays=[default_overlay])
# Check if plot shows correct overlay(s) (default overlay on, average overlay on)
self.click_average_button(plot_controls, overlay_on=True, color_on='rgba(128, 0, 128, 1)') # purple
self.click_average_button(plot_controls, overlay_on=True, color_on=HTMLColors.PURPLE)
self.check_overlays(step_builder, 4, overlays=[default_overlay, average_overlay])
# Check if plot shows correct overlay(s) (default overlay off, average overlay on)
......@@ -755,7 +795,7 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
self.plot_empty(step_builder)
# Check if plot shows correct overlay(s) (default overlay on, average overlay off)
self.click_default_button(plot_controls, overlay_on=True, color_on='rgba(255, 165, 0, 1)') # orange
self.click_default_button(plot_controls, overlay_on=True, color_on=HTMLColors.ORANGE)
self.check_overlays(step_builder, 2, overlays=[default_overlay])
# Check quadrant labels
......@@ -765,3 +805,319 @@ class StepBuilderTest(MentoringAssessmentBaseTest):
step_builder, plot_controls, hidden=False,
labels=['Custom Q1 label', 'Custom Q2 label', 'Custom Q3 label', 'Custom Q4 label']
)
def check_display_status(self, element, hidden):
if hidden:
display_status = element.value_of_css_property('display')
self.assertEquals(display_status, 'none')
else:
# self.wait_until_visible(element)
display_status = element.value_of_css_property('display')
self.assertEquals(display_status, 'block')
def check_plot_info(self, step_builder, hidden, visible_overlays=[], hidden_overlays=[]):
# Check if plot info is present and visible
plot_info = step_builder.find_element_by_css_selector(".plot-info")
self.check_display_status(plot_info, hidden)
# Check if info about visible overlays is present and visible
for overlay in visible_overlays:
overlay_info = plot_info.find_element_by_css_selector(overlay['selector'])
self.check_display_status(overlay_info, hidden=False)
description = overlay['description']
citation = overlay['citation']
if description is not None or citation is not None:
overlay_plot_label = overlay_info.find_element_by_css_selector('.overlay-plot-label')
self.assertEquals(overlay_plot_label.text, overlay['plot_label'])
text_color = overlay_plot_label.value_of_css_property('color')
self.assertEquals(text_color, overlay['plot_label_color'])
if description is not None:
overlay_description = overlay_info.find_element_by_css_selector('.overlay-description')
self.assertEquals(overlay_description.text, 'Description: ' + description)
if citation is not None:
overlay_citation = overlay_info.find_element_by_css_selector('.overlay-citation')
self.assertEquals(overlay_citation.text, 'Source: ' + citation)
# Check if info about hidden overlays is hidden
for overlay in hidden_overlays:
overlay_info = plot_info.find_element_by_css_selector(overlay['selector'])
self.check_display_status(overlay_info, hidden=True)
def test_plot_overlays(self):
step_builder, controls = self.load_assessment_scenario("step_builder_plot_overlays.xml", {})
# Step 1: Questions
# Provide first rating
self.answer_rating_question(1, 1, step_builder, "How much do you agree?", "1 - Disagree")
# Provide second rating
self.answer_rating_question(1, 2, step_builder, "How important do you think this is?", "5 - Very important")
# Advance
self.submit_and_go_to_next_step(controls)
# Step 2: Questions
# Provide first rating
self.answer_rating_question(2, 1, step_builder, "How much do you agree?", "5 - Agree")
# Provide second rating
self.answer_rating_question(2, 2, step_builder, "How important do you think this is?", "1 - Not important")
# Advance
self.submit_and_go_to_next_step(controls, last=True)
# Step 2: Plot
# Obtain references to plot controls
additional_plot_controls = self.additional_plot_controls(step_builder)
# Check button labels
self.check_button_label(additional_plot_controls.teacher_button, "Teacher")
self.check_button_label(additional_plot_controls.researchers_button, "Researchers")
self.check_button_label(additional_plot_controls.sheldon_button, "Sheldon Cooper")
self.check_button_label(additional_plot_controls.yoda_button, "Yoda")
# Overlay data
default_overlay = {
'selector': '.claim-default',
'num_points': 2,
'point_color': PointColors.ORANGE,
'titles': ['2 + 2 = 5: 1, 5', 'The answer to everything is 42: 5, 1'],
'positions': [
('4', '380'), # Values computed according to xScale and yScale (cf. plot.js)
('20', '396'), # Values computed according to xScale and yScale (cf. plot.js)
],
}
teacher_overlay = {
'selector': '.plot-overlay.plot-overlay-0',
'num_points': 2,
'point_color': PointColors.CORAL,
'titles': ['2 + 2 = 5: 2, 3', 'The answer to everything is 42: 4, 2'],
'positions': [
('8', '388'), # Values computed according to xScale and yScale (cf. plot.js)
('16', '392'), # Values computed according to xScale and yScale (cf. plot.js)
],
'plot_label': 'Teacher',
'plot_label_color': HTMLColors.CORAL,
'description': None,
'citation': None,
}
researchers_overlay = {
'selector': '.plot-overlay.plot-overlay-1',
'num_points': 2,
'point_color': PointColors.CORNFLOWERBLUE,
'titles': ['2 + 2 = 5: 4, 4', 'The answer to everything is 42: 1, 5'],
'positions': [
('16', '384'), # Values computed according to xScale and yScale (cf. plot.js)
('4', '380'), # Values computed according to xScale and yScale (cf. plot.js)
],
'plot_label': 'Researchers',
'plot_label_color': HTMLColors.CORNFLOWERBLUE,
'description': 'Responses of leading researchers in the field',
'citation': None,
}
sheldon_overlay = {
'selector': '.plot-overlay.plot-overlay-2',
'num_points': 2,
'point_color': PointColors.OLIVE,
'titles': ['2 + 2 = 5: 3, 5', 'The answer to everything is 42: 2, 4'],
'positions': [
('12', '380'), # Values computed according to xScale and yScale (cf. plot.js)
('8', '384'), # Values computed according to xScale and yScale (cf. plot.js)
],
'plot_label': 'Sheldon Cooper',
'plot_label_color': HTMLColors.OLIVE,
'description': None,
'citation': 'The Big Bang Theory',
}
yoda_overlay = {
'selector': '.plot-overlay.plot-overlay-3',
'num_points': 2,
'point_color': PointColors.CRIMSON,
'titles': ['2 + 2 = 5: 1, 2', 'The answer to everything is 42: 3, 3'],
'positions': [
('4', '392'), # Values computed according to xScale and yScale (cf. plot.js)
('12', '388'), # Values computed according to xScale and yScale (cf. plot.js)
],
'plot_label': 'Yoda',
'plot_label_color': HTMLColors.CRIMSON,
'description': 'Powerful you have become, the dark side I sense in you.',
'citation': 'Star Wars',
}
# Check if plot shows correct overlay(s) initially (default overlay on, additional overlays off)
self.check_overlays(
step_builder, 2, overlays=[default_overlay]
)
self.check_plot_info(
step_builder,
hidden=True,
visible_overlays=[],
hidden_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay, yoda_overlay]
)
# Turn on additional overlays one by one.
# - Check if plot shows correct overlay(s)
# - Check if block displays correct info about plot
# "Teacher" on
self.click_overlay_button(
additional_plot_controls.teacher_button, overlay_on=True, color_on=HTMLColors.CORAL
)
self.check_overlays(
step_builder, 4, overlays=[default_overlay, teacher_overlay]
)
self.check_plot_info(
step_builder,
hidden=True, # "Teacher" overlay has no description/citation,
# so plot info as a whole should stay hidden
visible_overlays=[teacher_overlay],
hidden_overlays=[researchers_overlay, sheldon_overlay, yoda_overlay]
)
# "Researchers" on
self.click_overlay_button(
additional_plot_controls.researchers_button, overlay_on=True, color_on=HTMLColors.CORNFLOWERBLUE
)
self.check_overlays(
step_builder, 6, overlays=[default_overlay, teacher_overlay, researchers_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[teacher_overlay, researchers_overlay],
hidden_overlays=[sheldon_overlay, yoda_overlay]
)
# "Sheldon Cooper" on
self.click_overlay_button(
additional_plot_controls.sheldon_button, overlay_on=True, color_on=HTMLColors.OLIVE
)
self.check_overlays(
step_builder, 8, overlays=[default_overlay, teacher_overlay, researchers_overlay, sheldon_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay],
hidden_overlays=[yoda_overlay]
)
# "Yoda" on
self.click_overlay_button(
additional_plot_controls.yoda_button, overlay_on=True, color_on=HTMLColors.CRIMSON
)
self.check_overlays(
step_builder,
10,
overlays=[default_overlay, teacher_overlay, researchers_overlay, sheldon_overlay, yoda_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay, yoda_overlay],
hidden_overlays=[]
)
# Turn off additional overlays one by one.
# - Check if plot shows correct overlay(s)
# - Check if block displays correct info about plot
# "Yoda" off
self.click_overlay_button(additional_plot_controls.yoda_button, overlay_on=False)
self.check_overlays(
step_builder, 8, overlays=[default_overlay, teacher_overlay, researchers_overlay, sheldon_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay],
hidden_overlays=[yoda_overlay]
)
# "Sheldon Cooper" off
self.click_overlay_button(additional_plot_controls.sheldon_button, overlay_on=False)
self.check_overlays(
step_builder, 6, overlays=[default_overlay, teacher_overlay, researchers_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[teacher_overlay, researchers_overlay],
hidden_overlays=[sheldon_overlay, yoda_overlay]
)
# "Researchers" off
self.click_overlay_button(additional_plot_controls.researchers_button, overlay_on=False)
self.check_overlays(
step_builder, 4, overlays=[default_overlay, teacher_overlay]
)
self.check_plot_info(
step_builder,
hidden=True, # "Teacher" overlay has no description/citation,
# so plot info should be hidden at this point
visible_overlays=[teacher_overlay],
hidden_overlays=[researchers_overlay, sheldon_overlay, yoda_overlay]
)
# "Teacher" off
self.click_overlay_button(additional_plot_controls.teacher_button, overlay_on=False)
self.check_overlays(
step_builder, 2, overlays=[default_overlay]
)
self.check_plot_info(
step_builder,
hidden=True,
visible_overlays=[],
hidden_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay, yoda_overlay]
)
# When deactivating an overlay that has no description/citation,
# visibility of information about remaining overlays that are currently active
# should not be affected:
# "Yoda" on:
self.click_overlay_button(
additional_plot_controls.yoda_button, overlay_on=True, color_on=HTMLColors.CRIMSON
)
self.check_overlays(
step_builder, 4, overlays=[default_overlay, yoda_overlay]
)
self.check_plot_info(
step_builder,
hidden=False, # Plot info becomes visible
visible_overlays=[yoda_overlay],
hidden_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay]
)
# "Teacher" on:
self.click_overlay_button(
additional_plot_controls.teacher_button, overlay_on=True, color_on=HTMLColors.CORAL
)
self.check_overlays(
step_builder, 6, overlays=[default_overlay, yoda_overlay, teacher_overlay]
)
self.check_plot_info(
step_builder,
hidden=False,
visible_overlays=[yoda_overlay, teacher_overlay],
hidden_overlays=[researchers_overlay, sheldon_overlay]
)
# "Teacher" off:
self.click_overlay_button(additional_plot_controls.teacher_button, overlay_on=False)
self.check_overlays(
step_builder, 4, overlays=[default_overlay, yoda_overlay]
)
self.check_plot_info(
step_builder,
hidden=False, # Plot info stays visible
visible_overlays=[yoda_overlay],
hidden_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay],
)
# "Yoda" off:
self.click_overlay_button(additional_plot_controls.yoda_button, overlay_on=False)
self.check_overlays(
step_builder, 2, overlays=[default_overlay]
)
self.check_plot_info(
step_builder,
hidden=True, # Last remaining overlay with description/citation deactivated;
# plot info now hidden
visible_overlays=[],
hidden_overlays=[teacher_overlay, researchers_overlay, sheldon_overlay, yoda_overlay]
)
<step-builder url_name="step-builder" display_name="Step Builder">
<sb-step display_name="First step">
<pb-rating name="rating_1_1"
low="Disagree"
high="Agree"
question="How much do you agree?"
correct_choices='["1", "2", "3", "4","5"]'>
</pb-rating>
<pb-rating name="rating_1_2"
low="Not important"
high="Very important"
question="How important do you think this is?"
correct_choices='["1", "2", "3", "4","5"]'>
</pb-rating>
</sb-step>
<sb-step display_name="Second step">
<pb-rating name="rating_2_1"
low="Disagree"
high="Agree"
question="How much do you agree?"
correct_choices='["1", "2", "3", "4","5"]'>
</pb-rating>
<pb-rating name="rating_2_2"
low="Not important"
high="Very important"
question="How important do you think this is?"
correct_choices='["1", "2", "3", "4","5"]'>
</pb-rating>
</sb-step>
<sb-step display_name="Last step">
<sb-plot plot_label="Custom plot label"
point_color_default="orange"
point_color_average="purple"
q1_label="Custom Q1 label"
q2_label="Custom Q2 label"
q3_label="Custom Q3 label"
q4_label="Custom Q4 label"
claims="2 + 2 = 5, rating_1_1, rating_1_2&#10;The answer to everything is 42, rating_2_1, rating_2_2">
<sb-plot-overlay plot_label="Teacher"
point_color="coral"
claim_data="2, 3&#10;4, 2">
</sb-plot-overlay>
<sb-plot-overlay plot_label="Researchers"
point_color="cornflowerblue"
description="Responses of leading researchers in the field"
claim_data="4, 4&#10;1, 5">
</sb-plot-overlay>
<sb-plot-overlay plot_label="Sheldon Cooper"
point_color="rgb(128, 128, 0)"
citation="The Big Bang Theory"
claim_data="3, 5&#10;2, 4">
</sb-plot-overlay>
<sb-plot-overlay plot_label="Yoda"
point_color="#dc143c"
description="Powerful you have become, the dark side I sense in you."
citation="Star Wars"
claim_data="1, 2&#10;3, 3">
</sb-plot-overlay>
</sb-plot>
</sb-step>
<sb-review-step></sb-review-step>
</step-builder>
......@@ -46,6 +46,7 @@ BLOCKS = [
'sb-review-step = problem_builder.step:ReviewStepBlock',
'sb-plot = problem_builder.plot:PlotBlock',
'sb-plot-overlay = problem_builder.plot:PlotOverlayBlock',
'pb-table = problem_builder.table:MentoringTableBlock',
'pb-column = problem_builder.table:MentoringTableColumn',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment