Commit 9a9a3241 by Tim Krones

Allow to display and interact with exercises in the LMS.

parent 6cb67fc6
/* CSS for VectorDrawXBlock */ /* CSS for VectorDrawXBlock */
.vectordraw_block .count { .vectordraw_block {
display: inline-block;
}
.vectordraw_block .jxgboard {
float: left;
border: 2px solid #1f628d;
}
.vectordraw_block .jxgboard .JXGtext {
pointer-events: none; /* prevents cursor from turning into caret when over a label */
}
.vectordraw_block .menu {
float: left;
width: 160px;
margin-left: 15px;
}
.vectordraw_block .menu .controls {
margin-bottom: 20px;
font-size: 0;
}
.vectordraw_block .menu .controls select {
width: 160px;
margin-bottom: 8px;
font-size: 18px;
}
.vectordraw_block .menu .controls button {
display: inline-block;
background-color: #3498db;
border-radius: 5px;
box-shadow: 0 1px 3px #666;
color: #fff;
font-size: 14px;
padding: 5px 10px 5px 10px;
margin: 4px 0;
border: 2px solid #1f628d;
text-decoration: none;
width: 160px;
}
.vectordraw_block .menu .controls button:hover {
background: #3cb0fd;
background-image: -webkit-linear-gradient(top, #3cb0fd, #3498db);
background-image: -moz-linear-gradient(top, #3cb0fd, #3498db);
background-image: -ms-linear-gradient(top, #3cb0fd, #3498db);
background-image: -o-linear-gradient(top, #3cb0fd, #3498db);
background-image: linear-gradient(to bottom, #3cb0fd, #3498db);
text-decoration: none;
}
vectordraw_block .menu .controls button.undo,
vectordraw_block .menu .controls button.redo {
width: 78px;
}
.vectordraw_block .menu .controls button.undo {
margin-right: 4px;
}
.vectordraw_block .menu .vector-properties {
padding: 10px;
border: 1px solid #1f628d;
font-size: 16px;
line-height: 1.25;
}
.vectordraw_block .menu .vector-properties h3 {
font-size: 16px;
margin: 0 0 5px;
}
.vectordraw_block .menu .vector-properties .vector-prop-bold {
font-weight: bold; font-weight: bold;
} }
.vectordraw_block p { .vectordraw_block .menu .vector-prop-slope {
cursor: pointer; display: none;
} }
<div class="vectordraw_block"> <div class="vectordraw_block">
<p>VectorDrawXBlock: count is now
<span class='count'>{self.count}</span> (click me to increment). <div id="vectordraw" />
</p>
</div> </div>
/* Javascript for VectorDrawXBlock. */ /* Javascript for VectorDrawXBlock. */
function VectorDrawXBlock(runtime, element) { function VectorDrawXBlock(runtime, element, init_args) {
'use strict';
function updateCount(result) { var VectorDraw = function(element_id, settings) {
$('.count', element).text(result.count); this.board = null;
this.dragged_vector = null;
this.drawMode = false;
this.history_stack = {undo: [], redo: []};
this.settings = this.sanitizeSettings(settings);
this.element = $('#' + element_id);
this.element.on('click', '.reset', this.reset.bind(this));
this.element.on('click', '.add-vector', this.addElementFromList.bind(this));
this.element.on('click', '.undo', this.undo.bind(this));
this.element.on('click', '.redo', this.redo.bind(this));
// Prevents default image drag and drop actions in some browsers.
this.element.on('mousedown', '.jxgboard image', function(evt) { evt.preventDefault(); });
this.render();
};
VectorDraw.prototype.sanitizeSettings = function(settings) {
// Fill in defaults at top level of settings.
var default_settings = {
width: 550,
height: 400,
axis: false,
background: null,
bounding_box_size: 10,
show_navigation: false,
show_vector_properties: true,
add_vector_label: 'Add Selected Force',
vector_properties_label: 'Vector Properties',
vectors: [],
points: [],
expected_result: {},
custom_checks: [],
unit_vector_ratio: 1
};
_.defaults(settings, default_settings);
var width_scale = settings.width / settings.height,
box_size = settings.bounding_box_size;
settings.bounding_box = [-box_size*width_scale, box_size, box_size*width_scale, -box_size];
// Fill in defaults for vectors.
var default_vector = {
type: 'vector',
render: false,
length_factor: 1,
length_units: '',
base_angle: 0,
style: {}
};
var default_vector_style = {
pointSize: 1,
pointColor: 'red',
width: 4,
color: "blue",
label: null,
labelColor: 'black'
};
settings.vectors.forEach(function(vector) {
_.defaults(vector, default_vector);
_.defaults(vector.style, default_vector_style);
});
// Fill in defaults for points.
var default_point = {
fixed: true, // Default to true for backwards compatibility.
render: true,
style: {}
};
var default_point_style = {
size: 1,
withLabel: false,
color: 'pink',
showInfoBox: false
};
settings.points.forEach(function(point) {
_.defaults(point, default_point);
_.defaults(point.style, default_point_style);
point.style.name = point.name;
point.style.fixed = point.fixed;
point.style.strokeColor = point.style.color;
point.style.fillColor = point.style.color;
delete point.style.color;
});
return settings;
};
VectorDraw.prototype.template = _.template([
'<div class="jxgboard" style="width:<%= width %>px; height:<%= height %>px;" />',
'<div class="menu">',
' <div class="controls">',
' <select>',
' <% vectors.forEach(function(vec, idx) { %>',
' <option value="vector-<%= idx %>"><%= vec.description %></option>',
' <% }) %>',
' <% points.forEach(function(point, idx) { if (!point.fixed) { %>',
' <option value="point-<%= idx %>"><%= point.description %></option>',
' <% }}) %>',
' </select>',
' <button class="add-vector"><%= add_vector_label %></button>',
' <button class="reset">Reset</button>',
' <button class="undo" title="Undo"><span class="fa fa-undo" /></button>',
' <button class="redo" title="redo"><span class="fa fa-repeat" /></button>',
' </div>',
' <% if (show_vector_properties) { %>',
' <div class="vector-properties">',
' <h3><%= vector_properties_label %></h3>',
' <div class="vector-prop-name">',
' name: <span class="value vector-prop-bold">-</span>',
' </div>',
' <div class="vector-prop-length">',
' length: <span class="value">-</span>',
' </div>',
' <div class="vector-prop-angle">',
' angle: <span class="value">-</span>&deg;',
' </div>',
' <div class="vector-prop-slope">',
' slope: <span class="value">-</span>',
' </div>',
' </div>',
' <% } %>',
'</div>'
].join('\n'));
VectorDraw.prototype.render = function() {
this.element.html(this.template(this.settings));
// Assign the jxgboard element a random unique ID,
// because JXG.JSXGraph.initBoard needs it.
this.element.find('.jxgboard').prop('id', _.uniqueId('jxgboard'));
this.createBoard();
};
VectorDraw.prototype.createBoard = function() {
var id = this.element.find('.jxgboard').prop('id'),
self = this;
this.board = JXG.JSXGraph.initBoard(id, {
keepaspectratio: true,
boundingbox: this.settings.bounding_box,
axis: this.settings.axis,
showCopyright: false,
showNavigation: this.settings.show_navigation
});
function getImageRatio(bg, callback) {
$('<img/>').attr('src', bg.src).load(function(){
//technically it's inverse of ratio, but we need it to calculate height
var ratio = this.height / this.width;
callback(bg, ratio);
});
}
function drawBackground(bg, ratio) {
var height = (bg.height) ? bg.height : bg.width * ratio;
var coords = (bg.coords) ? bg.coords : [-bg.width/2, -height/2];
self.board.create('image', [bg.src, coords, [bg.width, height]], {fixed: true});
}
if (this.settings.background) {
if (this.settings.background.height) {
drawBackground(this.settings.background);
}
else {
getImageRatio(this.settings.background, drawBackground);
}
}
this.settings.points.forEach(function(point, idx) {
if (point.render) {
this.renderPoint(idx);
}
}, this);
this.settings.vectors.forEach(function(vec, idx) {
if (vec.render) {
this.renderVector(idx);
} }
}, this);
var handlerUrl = runtime.handlerUrl(element, 'increment_count'); this.board.on('down', this.onBoardDown.bind(this));
this.board.on('move', this.onBoardMove.bind(this));
this.board.on('up', this.onBoardUp.bind(this));
};
$('p', element).click(function(eventObject) { VectorDraw.prototype.renderPoint = function(idx, coords) {
$.ajax({ var point = this.settings.points[idx];
type: "POST", var coords = coords || point.coords;
url: handlerUrl, var board_object = this.board.elementsByName[point.name];
data: JSON.stringify({"hello": "world"}), if (board_object) {
success: updateCount // If the point is already rendered, only update its coordinates.
board_object.setPosition(JXG.COORDS_BY_USER, coords);
return;
}
this.board.create('point', coords, point.style);
if (!point.fixed) {
// Disable the <option> element corresponding to point.
var option = this.getMenuOption('point', idx);
option.prop('disabled', true).prop('selected', false);
}
};
VectorDraw.prototype.removePoint = function(idx) {
var point = this.settings.points[idx];
var object = this.board.elementsByName[point.name];
if (object) {
this.board.removeAncestors(object);
// Enable the <option> element corresponding to point.
var option = this.getMenuOption('point', idx);
option.prop('disabled', false);
}
};
VectorDraw.prototype.getVectorCoordinates = function(vec) {
var coords = vec.coords;
if (!coords) {
var tail = vec.tail || [0, 0];
var length = 'length' in vec ? vec.length : 5;
var angle = 'angle' in vec ? vec.angle : 30;
var radians = angle * Math.PI / 180;
var tip = [
tail[0] + Math.cos(radians) * length,
tail[1] + Math.sin(radians) * length
];
coords = [tail, tip];
}
return coords;
};
VectorDraw.prototype.getVectorStyle = function(vec) {
//width, color, size of control point, label (which should be a JSXGraph option)
var default_style = {
pointSize: 1,
pointColor: 'red',
width: 4,
color: "blue",
label: null,
labelColor: 'black'
};
return _.extend(default_style, vec.style);
};
VectorDraw.prototype.renderVector = function(idx, coords) {
var vec = this.settings.vectors[idx];
coords = coords || this.getVectorCoordinates(vec);
// If this vector is already rendered, only update its coordinates.
var board_object = this.board.elementsByName[vec.name];
if (board_object) {
board_object.point1.setPosition(JXG.COORDS_BY_USER, coords[0]);
board_object.point2.setPosition(JXG.COORDS_BY_USER, coords[1]);
return;
}
var style = vec.style;
var tail = this.board.create('point', coords[0], {
name: vec.name,
size: style.pointSize,
fillColor: style.pointColor,
strokeColor: style.pointColor,
withLabel: false,
fixed: (vec.type === 'arrow' | vec.type === 'vector'),
showInfoBox: false
});
var tip = this.board.create('point', coords[1], {
name: style.label || vec.name,
size: style.pointSize,
fillColor: style.pointColor,
strokeColor: style.pointColor,
withLabel: true,
showInfoBox: false
}); });
// Not sure why, but including labelColor in attributes above doesn't work,
// it only works when set explicitly with setAttribute.
tip.setAttribute({labelColor: style.labelColor});
var line_type = (vec.type === 'vector') ? 'arrow' : vec.type;
var line = this.board.create(line_type, [tail, tip], {
name: vec.name,
strokeWidth: style.width,
strokeColor: style.color
}); });
tip.label.setAttribute({fontsize: 18, highlightStrokeColor: 'black'});
// Disable the <option> element corresponding to vector.
var option = this.getMenuOption('vector', idx);
option.prop('disabled', true).prop('selected', false);
return line;
};
VectorDraw.prototype.removeVector = function(idx) {
var vec = this.settings.vectors[idx];
var object = this.board.elementsByName[vec.name];
if (object) {
this.board.removeAncestors(object);
// Enable the <option> element corresponding to vector.
var option = this.getMenuOption('vector', idx);
option.prop('disabled', false);
}
};
VectorDraw.prototype.getMenuOption = function(type, idx) {
return this.element.find('.menu option[value=' + type + '-' + idx + ']');
};
VectorDraw.prototype.getSelectedElement = function() {
var selector = this.element.find('.menu select').val();
if (selector) {
selector = selector.split('-');
return {
type: selector[0],
idx: parseInt(selector[1])
};
}
return {};
};
VectorDraw.prototype.addElementFromList = function() {
this.pushHistory();
var selected = this.getSelectedElement();
if (selected.type === 'vector') {
this.updateVectorProperties(this.renderVector(selected.idx));
} else {
this.renderPoint(selected.idx);
}
};
VectorDraw.prototype.reset = function() {
this.pushHistory();
JXG.JSXGraph.freeBoard(this.board);
this.render();
};
VectorDraw.prototype.pushHistory = function() {
var state = this.getState();
var previous_state = _.last(this.history_stack.undo);
if (!_.isEqual(state, previous_state)) {
this.history_stack.undo.push(state);
this.history_stack.redo = [];
}
};
VectorDraw.prototype.undo = function() {
var curr_state = this.getState();
var undo_state = this.history_stack.undo.pop();
if (undo_state && !_.isEqual(undo_state, curr_state)) {
this.history_stack.redo.push(curr_state);
this.setState(undo_state);
}
};
VectorDraw.prototype.redo = function() {
var state = this.history_stack.redo.pop();
if (state) {
this.history_stack.undo.push(this.getState());
this.setState(state);
}
};
VectorDraw.prototype.getMouseCoords = function(evt) {
var i = evt[JXG.touchProperty] ? 0 : undefined;
var c_pos = this.board.getCoordsTopLeftCorner(evt, i);
var abs_pos = JXG.getPosition(evt, i);
var dx = abs_pos[0] - c_pos[0];
var dy = abs_pos[1] - c_pos[1];
return new JXG.Coords(JXG.COORDS_BY_SCREEN, [dx, dy], this.board);
};
VectorDraw.prototype.getVectorForObject = function(obj) {
if (obj instanceof JXG.Line) {
return obj;
}
if (obj instanceof JXG.Text) {
return this.getVectorForObject(obj.element);
}
if (obj instanceof JXG.Point) {
return _.find(obj.descendants, function (d) { return (d instanceof JXG.Line); });
}
return null;
};
VectorDraw.prototype.getVectorSettingsByName = function(name) {
return _.find(this.settings.vectors, function(vec) {
return vec.name === name;
});
};
VectorDraw.prototype.updateVectorProperties = function(vector) {
var vec_settings = this.getVectorSettingsByName(vector.name);
var x1 = vector.point1.X(),
y1 = vector.point1.Y(),
x2 = vector.point2.X(),
y2 = vector.point2.Y();
var length = vec_settings.length_factor * Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2));
var slope = (y2-y1)/(x2-x1);
var angle = ((Math.atan2(y2-y1, x2-x1)/Math.PI*180) - vec_settings.base_angle) % 360;
if (angle < 0) {
angle += 360;
}
$('.vector-prop-name .value', this.element).html(vector.point2.name); // labels are stored as point2 names
$('.vector-prop-angle .value', this.element).html(angle.toFixed(2));
if (vector.elType !== "line") {
$('.vector-prop-length', this.element).show();
$('.vector-prop-length .value', this.element).html(length.toFixed(2) + ' ' + vec_settings.length_units);
$('.vector-prop-slope', this.element).hide();
}
else {
$('.vector-prop-length', this.element).hide();
if (this.settings.show_slope_for_lines) {
$('.vector-prop-slope', this.element).show();
$('.vector-prop-slope .value', this.element).html(slope.toFixed(2));
}
}
};
VectorDraw.prototype.isVectorTailDraggable = function(vector) {
return vector.elType !== 'arrow';
};
VectorDraw.prototype.canCreateVectorOnTopOf = function(el) {
// If the user is trying to drag the arrow of an existing vector, we should not create a new vector.
if (el instanceof JXG.Line) {
return false;
}
// If this is tip/tail of a vector, it's going to have a descendant Line - we should not create a new vector
// when over the tip. Creating on top of the tail is allowed for plain vectors but not for segments.
// If it doesn't have a descendant Line, it's a point from settings.points - creating a new vector is allowed.
if (el instanceof JXG.Point) {
var vector = this.getVectorForObject(el);
if (!vector) {
return el.getProperty('fixed');
} else if (el === vector.point1 && !this.isVectorTailDraggable(vector)) {
return true;
} else {
return false;
}
}
return true;
};
VectorDraw.prototype.objectsUnderMouse = function(coords) {
var filter = function(el) {
return !(el instanceof JXG.Image) && el.hasPoint(coords.scrCoords[1], coords.scrCoords[2]);
};
return _.filter(_.values(this.board.objects), filter);
};
VectorDraw.prototype.onBoardDown = function(evt) {
this.pushHistory();
// Can't create a vector if none is selected from the list.
var selected = this.getSelectedElement();
var coords = this.getMouseCoords(evt);
var targetObjects = this.objectsUnderMouse(coords);
if (selected.idx && (!targetObjects || _.all(targetObjects, this.canCreateVectorOnTopOf.bind(this)))) {
var point_coords = [coords.usrCoords[1], coords.usrCoords[2]];
if (selected.type === 'vector') {
this.drawMode = true;
this.dragged_vector = this.renderVector(selected.idx, [point_coords, point_coords]);
} else {
this.renderPoint(selected.idx, point_coords);
}
}
else {
this.drawMode = false;
var vectorPoint = _.find(targetObjects, this.getVectorForObject.bind(this));
if (vectorPoint) {
this.dragged_vector = this.getVectorForObject(vectorPoint);
this.dragged_vector.point1.setProperty({fixed: false});
this.updateVectorProperties(this.dragged_vector);
}
}
};
VectorDraw.prototype.onBoardMove = function(evt) {
if (this.drawMode) {
var coords = this.getMouseCoords(evt);
this.dragged_vector.point2.moveTo(coords.usrCoords);
}
if (this.dragged_vector) {
this.updateVectorProperties(this.dragged_vector);
}
};
VectorDraw.prototype.onBoardUp = function(evt) {
this.drawMode = false;
if (this.dragged_vector && !this.isVectorTailDraggable(this.dragged_vector)) {
this.dragged_vector.point1.setProperty({fixed: true});
}
this.dragged_vector = null;
};
VectorDraw.prototype.getVectorCoords = function(name) {
var object = this.board.elementsByName[name];
if (object) {
return {
tail: [object.point1.X(), object.point1.Y()],
tip: [object.point2.X(), object.point2.Y()]
};
}
};
VectorDraw.prototype.getState = function() {
var vectors = {}, points = {};
this.settings.vectors.forEach(function(vec) {
var coords = this.getVectorCoords(vec.name);
if (coords) {
vectors[vec.name] = coords;
}
}, this);
this.settings.points.forEach(function(point) {
var obj = this.board.elementsByName[point.name];
if (obj) {
points[point.name] = [obj.X(), obj.Y()];
}
}, this);
return {vectors: vectors, points: points};
};
VectorDraw.prototype.setState = function(state) {
this.settings.vectors.forEach(function(vec, idx) {
var vec_state = state.vectors[vec.name];
if (vec_state) {
this.renderVector(idx, [vec_state.tail, vec_state.tip]);
} else {
this.removeVector(idx);
}
}, this);
this.settings.points.forEach(function(point, idx) {
var point_state = state.points[point.name];
if (point_state) {
this.renderPoint(idx, point_state);
} else {
this.removePoint(idx);
}
}, this);
this.board.update();
};
$(function ($) { $(function ($) {
/* Here's where you'd do things on page load. */ /* Here's where you'd do things on page load. */
var vectordraw = new VectorDraw('vectordraw', init_args);
}); });
} }
"""TO-DO: Write a description of what this XBlock is.""" """TO-DO: Write a description of what this XBlock is."""
import json
import pkg_resources import pkg_resources
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, Integer from xblock.fields import Scope, Boolean, Integer, String
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin
class VectorDrawXBlock(XBlock): loader = ResourceLoader(__name__)
class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
""" """
TO-DO: document what your XBlock does. TO-DO: document what your XBlock does.
""" """
...@@ -21,6 +27,185 @@ class VectorDrawXBlock(XBlock): ...@@ -21,6 +27,185 @@ class VectorDrawXBlock(XBlock):
help="A simple counter, to show something happening", help="A simple counter, to show something happening",
) )
# Content
display_name = String(
display_name="Title (display name)",
help="Title to display",
default="Vector Drawing",
scope=Scope.content
)
width = Integer(
display_name="Width",
help="The width of the board in pixels",
default=550,
scope=Scope.content
)
height = Integer(
display_name="",
help="The height of the board in pixels",
default=400,
scope=Scope.content
)
bounding_box_size = Integer(
display_name="",
help=(
"Defines the bounding box height of the graph area. "
"The bounding box width is calculated from the width/height ratio."
),
default=10,
scope=Scope.content
)
axis = Boolean(
display_name="Show axis",
help="Show the graph axis",
default=False,
scope=Scope.content
)
show_navigation = Boolean(
display_name="Show navigation",
help="Show navigation arrows and zoom controls",
default=False,
scope=Scope.content
)
show_vector_properties = Boolean(
display_name="Show vector properties",
help="Show box detailing vector properties",
default=True,
scope=Scope.content
)
show_slope_for_lines = Boolean(
display_name="Show slope for lines",
help="If True, slope will be shown for line objects.",
default=False,
scope=Scope.content
)
add_vector_label = String(
display_name="Add vector label",
help="Label for button that allows to add vectors to the board",
default="Add Selected Force",
scope=Scope.content
)
vector_properties_label = String(
display_name="Vector properties label",
help="Label for box that displays vector properties",
default="Vector Properties",
scope=Scope.content
)
background_url = String(
display_name="Background URL",
help="URL for background image",
default="",
scope=Scope.content
)
background_width = Integer(
display_name="Background width",
help="Width of background image",
default=0,
scope=Scope.content
)
background_height = Integer(
display_name="Background height",
help="Height of background image",
default=0,
scope=Scope.content
)
vectors = String(
display_name="Vectors",
help=(
"List of vectors to use for the exercise. "
"You must specify it as an array of entries where each entry represents an individual vector."
),
default="[]",
multiline_editor=True,
resettable_editor=False,
scope=Scope.content
)
points = String(
display_name="Points",
help=(
"List of points to be drawn on the board for reference, or to be placed by the student."
"You must specify it as an array of entries where each entry represents an individual point."
),
default="[]",
multiline_editor=True,
resettable_editor=False,
scope=Scope.content
)
expected_result = String(
display_name="Expected result",
help=(
"Defines vector properties for grading. "
"Vectors omitted from this setting are ignored when grading."
),
default="{}",
multiline_editor=True,
resettable_editor=False,
scope=Scope.content
)
custom_checks = String(
display_name="Custom checks",
help=(
'List of custom checks to use for grading. '
'This is needed when grading is more complex and cannot be defined in terms of "Expected results" only.'
),
default="[]",
multiline_editor=True,
resettable_editor=False,
scope=Scope.content
)
editable_fields = (
'display_name',
'width',
'height',
'bounding_box_size',
'axis',
'show_navigation',
'show_vector_properties',
'show_slope_for_lines',
'add_vector_label',
'vector_properties_label',
'background_url',
'background_width',
'background_height',
'vectors',
'points',
'expected_result',
'custom_checks'
)
@property
def background(self):
return {
'src': self.background_url,
'width': self.background_width,
'height': self.background_height,
}
@property
def vectors_json(self):
return json.loads(self.vectors)
@property
def points_json(self):
return json.loads(self.points)
def resource_string(self, path): def resource_string(self, path):
"""Handy helper for getting resources from our kit.""" """Handy helper for getting resources from our kit."""
data = pkg_resources.resource_string(__name__, path) data = pkg_resources.resource_string(__name__, path)
...@@ -32,12 +217,29 @@ class VectorDrawXBlock(XBlock): ...@@ -32,12 +217,29 @@ class VectorDrawXBlock(XBlock):
The primary view of the VectorDrawXBlock, shown to students The primary view of the VectorDrawXBlock, shown to students
when viewing courses. when viewing courses.
""" """
html = self.resource_string("static/html/vectordraw.html") fragment = Fragment()
frag = Fragment(html.format(self=self)) fragment.add_content(loader.render_template('static/html/vectordraw.html', context))
frag.add_css(self.resource_string("static/css/vectordraw.css")) fragment.add_css(self.resource_string('static/css/vectordraw.css'))
frag.add_javascript(self.resource_string("static/js/src/vectordraw.js")) fragment.add_javascript_url("//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.98/jsxgraphcore.js")
frag.initialize_js('VectorDrawXBlock') fragment.add_javascript(self.resource_string("static/js/src/vectordraw.js"))
return frag fragment.initialize_js(
'VectorDrawXBlock',
{
'width': self.width,
'height': self.height,
'bounding_box_size': self.bounding_box_size,
'axis': self.axis,
'show_navigation': self.show_navigation,
'show_vector_properties': self.show_vector_properties,
'show_slope_for_lines': self.show_slope_for_lines,
'add_vector_label': self.add_vector_label,
'vector_properties_label': self.vector_properties_label,
'background': self.background,
'vectors': self.vectors_json,
'points': self.points_json,
}
)
return fragment
# TO-DO: change this handler to perform your own actions. You may need more # TO-DO: change this handler to perform your own actions. You may need more
# than one handler, or you may not need any handlers at all. # than one handler, or you may not need any handlers at all.
......
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