Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
xblock-vectordraw
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
xblock-vectordraw
Commits
aa7c1340
Commit
aa7c1340
authored
Oct 30, 2015
by
Tim Krones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add grading functionality.
parent
dd06d817
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
365 additions
and
10 deletions
+365
-10
vectordraw/grader.py
+229
-0
vectordraw/static/css/vectordraw.css
+27
-2
vectordraw/static/html/vectordraw.html
+12
-0
vectordraw/static/js/src/vectordraw.js
+64
-0
vectordraw/vectordraw.py
+33
-8
No files found.
vectordraw/grader.py
0 → 100644
View file @
aa7c1340
import
inspect
import
json
import
logging
import
math
log
=
logging
.
getLogger
(
__name__
)
## Built-in check functions
def
_errmsg
(
default_message
,
check
,
vectors
):
template
=
check
.
get
(
'errmsg'
,
default_message
)
vec
=
vectors
[
check
[
'vector'
]]
return
template
.
format
(
name
=
vec
.
name
,
tail_x
=
vec
.
tail
.
x
,
tail_y
=
vec
.
tail
.
y
,
tip_x
=
vec
.
tip
.
x
,
tip_y
=
vec
.
tip
.
y
,
length
=
vec
.
length
,
angle
=
vec
.
angle
)
def
_errmsg_point
(
default_message
,
check
,
point
):
template
=
check
.
get
(
'errmsg'
,
default_message
)
return
template
.
format
(
name
=
check
[
'point'
],
x
=
point
.
x
,
y
=
point
.
y
)
def
check_presence
(
check
,
vectors
):
if
check
[
'vector'
]
not
in
vectors
:
errmsg
=
check
.
get
(
'errmsg'
,
'You need to use the {name} vector.'
)
return
errmsg
.
format
(
name
=
check
[
'vector'
])
def
check_tail
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
expected
=
check
[
'expected'
]
dist
=
math
.
hypot
(
expected
[
0
]
-
vec
.
tail
.
x
,
expected
[
1
]
-
vec
.
tail
.
y
)
if
dist
>
tolerance
:
return
_errmsg
(
'Vector {name} does not start at correct point.'
,
check
,
vectors
)
def
check_tip
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
expected
=
check
[
'expected'
]
dist
=
math
.
hypot
(
expected
[
0
]
-
vec
.
tip
.
x
,
expected
[
1
]
-
vec
.
tip
.
y
)
if
dist
>
tolerance
:
return
_errmsg
(
'Vector {name} does not end at correct point.'
,
check
,
vectors
)
def
_check_coordinate
(
check
,
coord
):
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
expected
=
check
[
'expected'
]
return
abs
(
expected
-
coord
)
>
tolerance
def
check_tail_x
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
if
_check_coordinate
(
check
,
vec
.
tail
.
x
):
return
_errmsg
(
'Vector {name} does not start at correct point.'
,
check
,
vectors
)
def
check_tail_y
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
if
_check_coordinate
(
check
,
vec
.
tail
.
y
):
return
_errmsg
(
'Vector {name} does not start at correct point.'
,
check
,
vectors
)
def
check_tip_x
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
if
_check_coordinate
(
check
,
vec
.
tip
.
x
):
return
_errmsg
(
'Vector {name} does not end at correct point.'
,
check
,
vectors
)
def
check_tip_y
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
if
_check_coordinate
(
check
,
vec
.
tip
.
y
):
return
_errmsg
(
'Vector {name} does not end at correct point.'
,
check
,
vectors
)
def
_coord_delta
(
expected
,
actual
):
if
expected
==
'_'
:
return
0
else
:
return
expected
-
actual
def
_coords_within_tolerance
(
vec
,
expected
,
tolerance
):
for
expected_coords
,
vec_coords
in
([
expected
[
0
],
vec
.
tail
],
[
expected
[
1
],
vec
.
tip
]):
delta_x
=
_coord_delta
(
expected_coords
[
0
],
vec_coords
.
x
)
delta_y
=
_coord_delta
(
expected_coords
[
1
],
vec_coords
.
y
)
if
math
.
hypot
(
delta_x
,
delta_y
)
>
tolerance
:
return
False
return
True
def
check_coords
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
expected
=
check
[
'expected'
]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
if
not
_coords_within_tolerance
(
vec
,
expected
,
tolerance
):
return
_errmsg
(
'Vector {name} coordinates are not correct.'
,
check
,
vectors
)
def
check_segment_coords
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
expected
=
check
[
'expected'
]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
if
not
(
_coords_within_tolerance
(
vec
,
expected
,
tolerance
)
or
_coords_within_tolerance
(
vec
.
opposite
(),
expected
,
tolerance
)):
return
_errmsg
(
'Segment {name} coordinates are not correct.'
,
check
,
vectors
)
def
check_length
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
if
abs
(
vec
.
length
-
check
[
'expected'
])
>
tolerance
:
return
_errmsg
(
'The length of {name} is incorrect. Your length: {length:.1f}'
,
check
,
vectors
)
def
_angle_within_tolerance
(
vec
,
expected
,
tolerance
):
# Calculate angle between vec and identity vector with expected angle
# using the formula:
# angle = acos((A . B) / len(A)*len(B))
x
=
vec
.
tip
.
x
-
vec
.
tail
.
x
y
=
vec
.
tip
.
y
-
vec
.
tail
.
y
dot_product
=
x
*
math
.
cos
(
expected
)
+
y
*
math
.
sin
(
expected
)
angle
=
math
.
degrees
(
math
.
acos
(
dot_product
/
vec
.
length
))
return
abs
(
angle
)
<=
tolerance
def
check_angle
(
check
,
vectors
):
vec
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
2.0
)
expected
=
math
.
radians
(
check
[
'expected'
])
if
not
_angle_within_tolerance
(
vec
,
expected
,
tolerance
):
return
_errmsg
(
'The angle of {name} is incorrect. Your angle: {angle:.1f}'
,
check
,
vectors
)
def
check_segment_angle
(
check
,
vectors
):
# Segments are not directed, so we must check the angle between the segment and
# the vector that represents it, as well as its opposite vector.
vec
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
2.0
)
expected
=
math
.
radians
(
check
[
'expected'
])
if
not
(
_angle_within_tolerance
(
vec
,
expected
,
tolerance
)
or
_angle_within_tolerance
(
vec
.
opposite
(),
expected
,
tolerance
)):
return
_errmsg
(
'The angle of {name} is incorrect. Your angle: {angle:.1f}'
,
check
,
vectors
)
def
_dist_line_point
(
line
,
point
):
# Return the distance between the given line and point. The line is passed in as a Vector
# instance, the point as a Point instance.
direction_x
=
line
.
tip
.
x
-
line
.
tail
.
x
direction_y
=
line
.
tip
.
y
-
line
.
tail
.
y
determinant
=
(
point
.
x
-
line
.
tail
.
x
)
*
direction_y
-
(
point
.
y
-
line
.
tail
.
y
)
*
direction_x
return
abs
(
determinant
)
/
math
.
hypot
(
direction_x
,
direction_y
)
def
check_points_on_line
(
check
,
vectors
):
line
=
vectors
[
check
[
'vector'
]]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
points
=
check
.
get
(
'expected'
)
for
point
in
points
:
point
=
Point
(
point
[
0
],
point
[
1
])
if
_dist_line_point
(
line
,
point
)
>
tolerance
:
return
_errmsg
(
'The line {name} does not pass through the correct points.'
,
check
,
vectors
)
def
check_point_coords
(
check
,
points
):
point
=
points
[
check
[
'point'
]]
tolerance
=
check
.
get
(
'tolerance'
,
1.0
)
expected
=
check
.
get
(
'expected'
)
dist
=
math
.
hypot
(
expected
[
0
]
-
point
.
x
,
expected
[
1
]
-
point
.
y
)
if
dist
>
tolerance
:
return
_errmsg_point
(
'Point {name} is not at the correct location.'
,
check
,
point
)
class
Point
(
object
):
def
__init__
(
self
,
x
,
y
):
self
.
x
=
x
self
.
y
=
y
class
Vector
(
object
):
def
__init__
(
self
,
name
,
x1
,
y1
,
x2
,
y2
):
self
.
name
=
name
self
.
tail
=
Point
(
x1
,
y1
)
self
.
tip
=
Point
(
x2
,
y2
)
self
.
length
=
math
.
hypot
(
x2
-
x1
,
y2
-
y1
)
angle
=
math
.
degrees
(
math
.
atan2
(
y2
-
y1
,
x2
-
x1
))
if
angle
<
0
:
angle
+=
360
self
.
angle
=
angle
def
opposite
(
self
):
return
Vector
(
self
.
name
,
self
.
tip
.
x
,
self
.
tip
.
y
,
self
.
tail
.
x
,
self
.
tail
.
y
)
class
Grader
(
object
):
check_registry
=
{
'presence'
:
check_presence
,
'tail'
:
check_tail
,
'tip'
:
check_tip
,
'tail_x'
:
check_tail_x
,
'tail_y'
:
check_tail_y
,
'tip_x'
:
check_tip_x
,
'tip_y'
:
check_tip_y
,
'coords'
:
check_coords
,
'length'
:
check_length
,
'angle'
:
check_angle
,
'segment_angle'
:
check_segment_angle
,
'segment_coords'
:
check_segment_coords
,
'points_on_line'
:
check_points_on_line
,
'point_coords'
:
check_point_coords
,
}
def
__init__
(
self
,
success_message
=
'Test passed'
,
custom_checks
=
None
):
self
.
success_message
=
success_message
if
custom_checks
:
self
.
check_registry
.
update
(
custom_checks
)
def
grade
(
self
,
answer
):
check_data
=
dict
(
vectors
=
self
.
_get_vectors
(
answer
),
points
=
self
.
_get_points
(
answer
),
)
for
check
in
answer
[
'checks'
]:
check_data
[
'check'
]
=
check
check_fn
=
self
.
check_registry
[
check
[
'check'
]]
args
=
[
check_data
[
arg
]
for
arg
in
inspect
.
getargspec
(
check_fn
)
.
args
]
result
=
check_fn
(
*
args
)
if
result
:
return
{
'ok'
:
False
,
'msg'
:
result
}
return
{
'ok'
:
True
,
'msg'
:
self
.
success_message
}
def
cfn
(
self
,
e
,
ans
):
answer
=
json
.
loads
(
json
.
loads
(
ans
)[
'answer'
])
return
self
.
grade
(
answer
)
def
_get_vectors
(
self
,
answer
):
vectors
=
{}
for
name
,
props
in
answer
[
'vectors'
]
.
iteritems
():
tail
=
props
[
'tail'
]
tip
=
props
[
'tip'
]
vectors
[
name
]
=
Vector
(
name
,
tail
[
0
],
tail
[
1
],
tip
[
0
],
tip
[
1
])
return
vectors
def
_get_points
(
self
,
answer
):
return
{
name
:
Point
(
*
coords
)
for
name
,
coords
in
answer
[
'points'
]
.
iteritems
()}
vectordraw/static/css/vectordraw.css
View file @
aa7c1340
/* CSS for VectorDrawXBlock */
.vectordraw_block
{
.vectordraw_block
,
.vectordraw_block
#vectordraw
{
display
:
inline-block
;
}
.vectordraw_block
.vectordraw-description
{
.vectordraw_block
.vectordraw-description
,
.vectordraw_block
#vectordraw
,
.vectordraw_block
.vectordraw-status
{
margin-bottom
:
1.5em
;
}
...
...
@@ -86,3 +89,25 @@ vectordraw_block .menu .controls button.redo {
.vectordraw_block
.menu
.vector-prop-slope
{
display
:
none
;
}
.vectordraw_block
.action
button
{
height
:
40px
;
margin-right
:
10px
;
font-weight
:
600
;
text-transform
:
uppercase
;
}
.vectordraw_block
.vectordraw-status
{
display
:
inline-block
;
width
:
100%
;
}
.vectordraw_block
.checkmark-correct
{
font-size
:
22pt
;
color
:
#629b2b
;
}
.vectordraw_block
.checkmark-incorrect
{
font-size
:
22pt
;
color
:
#ff0000
;
}
vectordraw/static/html/vectordraw.html
View file @
aa7c1340
...
...
@@ -10,4 +10,16 @@
<div
id=
"vectordraw"
/>
<div
class=
"vectordraw-status"
>
<span
class=
"correctness icon-2x"
></span>
<div
class=
"status-message"
></div>
</div>
<div
class=
"action"
>
<button
class=
"check"
>
<span
class=
"check-label"
>
Check
</span>
<span
class=
"sr"
>
your answer
</span>
</button>
</div>
</div>
vectordraw/static/js/src/vectordraw.js
View file @
aa7c1340
...
...
@@ -545,11 +545,75 @@ function VectorDrawXBlock(runtime, element, init_args) {
this
.
board
.
update
();
};
var
checkHandlerUrl
=
runtime
.
handlerUrl
(
element
,
'check_answers'
);
var
checkXHR
;
function
getInput
(
vectordraw
)
{
var
input
=
vectordraw
.
getState
();
// Transform the expected_result setting into a list of checks.
var
checks
=
[];
_
.
each
(
vectordraw
.
settings
.
expected_result
,
function
(
answer
,
name
)
{
var
presence_check
=
{
vector
:
name
,
check
:
'presence'
};
if
(
'presence_errmsg'
in
answer
)
{
presence_check
.
errmsg
=
answer
.
presence_errmsg
;
}
checks
.
push
(
presence_check
);
[
'tail'
,
'tail_x'
,
'tail_y'
,
'tip'
,
'tip_x'
,
'tip_y'
,
'coords'
,
'length'
,
'angle'
,
'segment_angle'
,
'segment_coords'
,
'points_on_line'
].
forEach
(
function
(
prop
)
{
if
(
prop
in
answer
)
{
var
check
=
{
vector
:
name
,
check
:
prop
,
expected
:
answer
[
prop
]};
if
(
prop
+
'_tolerance'
in
answer
)
{
check
.
tolerance
=
answer
[
prop
+
'_tolerance'
];
}
if
(
prop
+
'_errmsg'
in
answer
)
{
check
.
errmsg
=
answer
[
prop
+
'_errmsg'
];
}
checks
.
push
(
check
);
}
});
});
input
.
checks
=
checks
.
concat
(
vectordraw
.
settings
.
custom_checks
);
return
input
;
}
function
updateStatus
(
data
)
{
var
correctness
=
$
(
'.correctness'
,
element
);
if
(
data
.
result
.
ok
)
{
correctness
.
removeClass
(
'checkmark-incorrect fa fa-times'
);
correctness
.
addClass
(
'checkmark-correct fa fa-check'
);
}
else
{
correctness
.
removeClass
(
'checkmark-correct fa fa-check'
);
correctness
.
addClass
(
'checkmark-incorrect fa fa-times'
);
}
$
(
'.status-message'
,
element
).
text
(
data
.
result
.
msg
);
}
function
checkAnswers
(
vectordraw
)
{
if
(
checkXHR
)
{
checkXHR
.
abort
();
}
var
state
=
getInput
(
vectordraw
);
checkXHR
=
$
.
post
(
checkHandlerUrl
,
JSON
.
stringify
(
state
))
.
success
(
function
(
response
)
{
console
.
log
(
JSON
.
stringify
(
response
));
updateStatus
(
response
);
});
}
$
(
function
(
$
)
{
/* Here's where you'd do things on page load. */
var
vectordraw
=
new
VectorDraw
(
'vectordraw'
,
init_args
);
$
(
'.action .check'
,
element
).
on
(
'click'
,
function
(
e
)
{
checkAnswers
(
vectordraw
);
});
});
}
vectordraw/vectordraw.py
View file @
aa7c1340
"""TO-DO: Write a description of what this XBlock is."""
import
json
import
logging
import
pkg_resources
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
Boolean
,
Integer
,
String
from
xblock.fields
import
Scope
,
Boolean
,
Float
,
Integer
,
String
from
xblock.fragment
import
Fragment
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
from
.grader
import
Grader
loader
=
ResourceLoader
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
class
VectorDrawXBlock
(
StudioEditableXBlockMixin
,
XBlock
):
"""
...
...
@@ -179,6 +184,13 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
scope
=
Scope
.
content
)
weight
=
Float
(
display_name
=
"Weight"
,
default
=
1
,
scope
=
Scope
.
settings
,
enforce_type
=
True
)
editable_fields
=
(
'display_name'
,
'description'
,
...
...
@@ -200,6 +212,8 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'custom_checks'
)
has_score
=
True
@property
def
background
(
self
):
return
{
...
...
@@ -216,6 +230,10 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
def
points_json
(
self
):
return
json
.
loads
(
self
.
points
)
@property
def
expected_result_json
(
self
):
return
json
.
loads
(
self
.
expected_result
)
def
resource_string
(
self
,
path
):
"""Handy helper for getting resources from our kit."""
data
=
pkg_resources
.
resource_string
(
__name__
,
path
)
...
...
@@ -230,6 +248,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
context
[
'self'
]
=
self
fragment
=
Fragment
()
fragment
.
add_content
(
loader
.
render_template
(
'static/html/vectordraw.html'
,
context
))
fragment
.
add_css_url
(
"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.3.0/css/font-awesome.min.css"
)
fragment
.
add_css
(
self
.
resource_string
(
'static/css/vectordraw.css'
))
fragment
.
add_javascript_url
(
"//cdnjs.cloudflare.com/ajax/libs/jsxgraph/0.98/jsxgraphcore.js"
)
fragment
.
add_javascript
(
self
.
resource_string
(
"static/js/src/vectordraw.js"
))
...
...
@@ -248,6 +267,7 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
'background'
:
self
.
background
,
'vectors'
:
self
.
vectors_json
,
'points'
:
self
.
points_json
,
'expected_result'
:
self
.
expected_result_json
}
)
return
fragment
...
...
@@ -255,15 +275,20 @@ class VectorDrawXBlock(StudioEditableXBlockMixin, XBlock):
# 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.
@XBlock.json_handler
def
increment_count
(
self
,
data
,
suffix
=
''
):
def
check_answers
(
self
,
data
,
suffix
=
''
):
"""
An example handler, which increments the data.
Check student's answers
"""
# Just to show data coming in...
assert
data
[
'hello'
]
==
'world'
self
.
count
+=
1
return
{
"count"
:
self
.
count
}
grader
=
Grader
()
result
=
grader
.
grade
(
data
)
# Publish grade data
score
=
1
if
result
[
"ok"
]
else
0
self
.
runtime
.
publish
(
self
,
'grade'
,
dict
(
value
=
score
,
max_value
=
1
))
return
{
"message"
:
"Success!"
,
"data"
:
data
,
"result"
:
result
,
}
# TO-DO: change this to create the scenarios you'd like to see in the
# workbench while developing your XBlock.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment