first import book

This commit is contained in:
Johann Dreo 2015-03-03 15:56:44 +01:00
commit dfd9c869d5
233 changed files with 47797 additions and 0 deletions

1
book/modules/__init__.py Normal file
View file

@ -0,0 +1 @@

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,3 @@
from .parsons import *

View file

@ -0,0 +1,237 @@
# Copyright (C) 2012 Michael Hewner
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'hewner'
import re
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from luther.sphinx.assess import Assessment
def setup(app):
app.add_directive('parsonsprob',ParsonsProblem)
app.add_stylesheet('parsons.css')
app.add_stylesheet('lib/prettify.css')
# includes parsons specific javascript headers
# parsons-noconflict reverts underscore and
# jquery to their original versions
app.add_javascript('lib/jquery.min.js')
app.add_javascript('lib/jquery-ui.min.js')
app.add_javascript('lib/prettify.js')
app.add_javascript('lib/underscore-min.js')
app.add_javascript('lib/lis.js')
app.add_javascript('parsons.js')
app.add_javascript('parsons-noconflict.js')
class ParsonsProblem(Assessment):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
has_content = True
def run(self):
"""
Instructions for solving the problem should be written and then a line with -----
signals the beginning of the code. If you want more than one line in a single
code block, seperate your code blocks with =====.
Both the instructions sections and code blocks are optional. If you don't include any
=====, the code will assume you want each line to be its own code block.
Example:
.. parsonsprob:: unqiue_problem_id_here
Solve my really cool parsons problem...if you can.
-----
def findmax(alist):
=====
if len(alist) == 0:
return None
=====
curmax = alist[0]
for item in alist:
=====
if item &gt; curmax:
=====
curmax = item
=====
return curmax
"""
template_values = {}
template_values['qnumber'] = self.getNumber()
template_values['unique_id'] = self.lineno
template_values['instructions'] = ""
code = self.content
if '-----' in self.content:
index = self.content.index('-----')
template_values['instructions'] = "\n".join(self.content[:index])
code = self.content[index + 1:]
if '=====' in code:
template_values['code'] = self.parse_multiline_parsons(code);
else:
template_values['code'] = "\n".join(code)
template_values['divid'] = self.arguments[0]
TEMPLATE = '''
<div class='parsons alert alert-warning'>
%(qnumber)s: %(instructions)s<br /><br />
<div style="clear:left;"></div>
<div id="parsons-orig-%(unique_id)s" style="display:none;">%(code)s</div>
<div id="parsons-sortableTrash-%(unique_id)s" class="sortable-code"></div>
<div id="parsons-sortableCode-%(unique_id)s" class="sortable-code"></div>
<div style="clear:left;"></div>
<input type="button" class='btn btn-success' id="checkMe%(unique_id)s" value="Check Me"/>
<input type="button" class='btn btn-default' id="reset%(unique_id)s" value="Reset"/>
<div id="parsons-message-%(unique_id)s"></div>
</div>
<script>
$pjQ(document).ready(function(){
var msgBox = $("#parsons-message-%(unique_id)s");
msgBox.hide();
var displayErrors = function (fb) {
if(fb.errors.length > 0) {
var hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableCode-%(unique_id)s");
msgBox.fadeIn(500);
msgBox.attr('class','alert alert-danger');
msgBox.html(fb.errors[0]);
logBookEvent({'event':'parsons', 'act':hash, 'div_id':'%(divid)s'});
} else {
logBookEvent({'event':'parsons', 'act':'yes', 'div_id':'%(divid)s'});
msgBox.fadeIn(100);
msgBox.attr('class','alert alert-success');
msgBox.html("Perfect!")
}
}
$(window).load(function() {
// set min width and height
var sortableul = $("#ul-parsons-sortableCode-%(unique_id)s");
var trashul = $("#ul-parsons-sortableTrash-%(unique_id)s");
var sortableHeight = sortableul.height();
var sortableWidth = sortableul.width();
var trashWidth = trashul.width();
var trashHeight = trashul.height();
var minHeight = Math.max(trashHeight,sortableHeight);
var minWidth = Math.max(trashWidth, sortableWidth);
trashul.css("min-height",minHeight + "px");
sortableul.css("min-height",minHeight + "px");
sortableul.height(minHeight);
trashul.css("min-width",minWidth + "px");
sortableul.css("min-width",minWidth + "px");
});
var pp_%(unique_id)s = new ParsonsWidget({
'sortableId': 'parsons-sortableCode-%(unique_id)s',
'trashId': 'parsons-sortableTrash-%(unique_id)s',
'max_wrong_lines': 1,
'solution_label': 'Drop blocks here',
'feedback_cb' : displayErrors
});
pp_%(unique_id)s.init($pjQ("#parsons-orig-%(unique_id)s").text());
pp_%(unique_id)s.shuffleLines();
if(localStorage.getItem('%(divid)s') && localStorage.getItem('%(divid)s-trash')) {
try {
var solution = localStorage.getItem('%(divid)s');
var trash = localStorage.getItem('%(divid)s-trash');
pp_%(unique_id)s.createHTMLFromHashes(solution,trash);
pp_%(unique_id)s.getFeedback();
} catch(err) {
var text = "An error occured restoring old %(divid)s state. Error: ";
console.log(text + err.message);
}
}
$pjQ("#reset%(unique_id)s").click(function(event){
event.preventDefault();
pp_%(unique_id)s.shuffleLines();
// set min width and height
var sortableul = $("#ul-parsons-sortableCode-%(unique_id)s");
var trashul = $("#ul-parsons-sortableTrash-%(unique_id)s");
var sortableHeight = sortableul.height();
var sortableWidth = sortableul.width();
var trashWidth = trashul.width();
var trashHeight = trashul.height();
var minHeight = Math.max(trashHeight,sortableHeight);
var minWidth = Math.max(trashWidth, sortableWidth);
trashul.css("min-height",minHeight + "px");
sortableul.css("min-height",minHeight + "px");
trashul.css("min-width",minWidth + "px");
sortableul.css("min-width",minWidth + "px");
msgBox.hide();
});
$pjQ("#checkMe%(unique_id)s").click(function(event){
event.preventDefault();
var hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableCode-%(unique_id)s");
localStorage.setItem('%(divid)s',hash);
hash = pp_%(unique_id)s.getHash("#ul-parsons-sortableTrash-%(unique_id)s");
localStorage.setItem('%(divid)s-trash',hash);
pp_%(unique_id)s.getFeedback();
msgBox.fadeIn(100);
});
});
</script>
'''
self.assert_has_content()
return [nodes.raw('',TEMPLATE % template_values, format='html')]
def parse_multiline_parsons(self, lines):
current_block = []
results = []
for line in lines:
if(line == '====='):
results.append(self.convert_leading_whitespace_for_block(current_block))
current_block = []
else:
current_block.append(line)
results.append(self.convert_leading_whitespace_for_block(current_block))
return "\n".join(results)
def convert_leading_whitespace_for_block(self, block):
whitespaceMatcher = re.compile("^\s*")
initialWhitespace = whitespaceMatcher.match(block[0]).end()
result = block[0]
for line in block[1:]:
result += '\\n' # not a line break...the literal characters \n
result += line[initialWhitespace:]
return result

View file

View file

View file

@ -0,0 +1 @@
from .activecode import *

View file

@ -0,0 +1,290 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
import json
import os
# try:
# import conf
# version = conf.version
# staticserver = conf.staticserver
# except:
# version = '2.1.0'
# staticserver = 'runestonestatic.appspot.com'
def setup(app):
app.add_directive('activecode',ActiveCode)
app.add_directive('actex',ActiveExercise)
app.add_stylesheet('codemirror.css')
app.add_stylesheet('activecode.css')
app.add_javascript('jquery.highlight.js' )
app.add_javascript('bookfuncs.js' )
app.add_javascript('codemirror.js' )
app.add_javascript('python.js' )
app.add_javascript('javascript.js' )
app.add_javascript('activecode.js')
app.add_javascript('skulpt.min.js' )
app.add_javascript('skulpt-stdlib.js')
app.add_node(ActivcodeNode, html=(visit_ac_node, depart_ac_node))
app.connect('doctree-resolved',process_activcode_nodes)
app.connect('env-purge-doc', purge_activecodes)
START = '''
<div id="%(divid)s" lang="%(language)s" >
'''
EDIT1 = '''
<br/>
<div id="%(divid)s_code_div" style="display: %(hidecode)s">
<textarea cols="50" rows="12" id="%(divid)s_code" class="active_code", prefixcode="%(include)s" lang="%(language)s">
%(initialcode)s
</textarea>
</div>
<p class="ac_caption"><span class="ac_caption_text">%(caption)s (%(divid)s)</span> </p>
<button class='btn btn-success' id="%(divid)s_runb" onclick="runit('%(divid)s',this, %(include)s);">Run</button>
'''
UNHIDE='''
<button class='btn btn-default' id="%(divid)s_showb" onclick="$('#%(divid)s_code_div').toggle();cm_editors['%(divid)s_code'].refresh();$('#%(divid)s_saveb').toggle();$('#%(divid)s_loadb').toggle()">Show/Hide Code</button>
'''
GRADES = '''
<input type="button" class='btn btn-default ' id="gradeb" name="Show Feedback" value="Show Feedback" onclick="createGradeSummary('%(divid)s')"/>
'''
AUDIO = '''
<input type="button" class='btn btn-default ' id="audiob" name="Play Audio" value="Start Audio Tour" onclick="createAudioTourHTML('%(divid)s','%(argu)s','%(no_of_buttons)s','%(ctext)s')"/>
'''
EDIT2 = '''
<div id="cont"></div>
<button class="ac_opt btn btn-default" style="display: inline-block" id="%(divid)s_saveb" onclick="saveEditor('%(divid)s');">Save</button>
<button class="ac_opt btn btn-default" style="display: inline-block" id="%(divid)s_loadb" onclick="requestCode('%(divid)s');">Load</button>
<script>
if ('%(hidecode)s' == 'none') {
// a hack to preserve the inline-block display style. Toggle() will use display: block
// (instead of inline-block) if the previous display style was 'none'
$('#%(divid)s_saveb').toggle();
$('#%(divid)s_loadb').toggle();
}
</script>
'''
CANVAS = '''
<div style="text-align: center">
<canvas id="%(divid)s_canvas" class="ac-canvas" height="400" width="400" style="border-style: solid; display: none; text-align: center"></canvas>
</div>
'''
SUFF = '''<pre id="%(divid)s_suffix" style="display:none">%(suffix)s</pre>'''
PRE = '''
<pre id="%(divid)s_pre" class="active_out">
</pre>
'''
END = '''
</div>
'''
AUTO = '''
<script type="text/javascript">
$(document).ready(function() {
$(window).load(function() {
var runb = document.getElementById("%(divid)s_runb");
runit('%(divid)s',runb, %(include)s);
});
});
</script>
'''
#'
class ActivcodeNode(nodes.General, nodes.Element):
def __init__(self,content):
"""
Arguments:
- `self`:
- `content`:
"""
super(ActivcodeNode,self).__init__()
self.ac_components = content
# self for these functions is an instance of the writer class. For example
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
# The node that is passed as a parameter is an instance of our node class.
def visit_ac_node(self,node):
#print self.settings.env.activecodecounter
res = START
if 'above' in node.ac_components:
res += CANVAS
res += EDIT1
if 'tour_1' not in node.ac_components:
res += EDIT2
else:
res += AUDIO + EDIT2
if 'above' not in node.ac_components:
if 'nocanvas' not in node.ac_components:
res += CANVAS
if 'hidecode' not in node.ac_components:
node.ac_components['hidecode'] = 'block'
if node.ac_components['hidecode'] == 'none':
res += UNHIDE
if 'gradebutton' in node.ac_components:
res += GRADES
if 'suffix' in node.ac_components:
res += SUFF
if 'nopre' not in node.ac_components:
res += PRE
if 'autorun' in node.ac_components:
res += AUTO
res += END
res = res % node.ac_components
res = res.replace("u'","'") # hack: there must be a better way to include the list and avoid unicode strings
self.body.append(res)
def depart_ac_node(self,node):
''' This is called at the start of processing an activecode node. If activecode had recursive nodes
etc and did not want to do all of the processing in visit_ac_node any finishing touches could be
added here.
'''
pass
def process_activcode_nodes(app,env,docname):
pass
def purge_activecodes(app,env,docname):
pass
class ActiveCode(Directive):
required_arguments = 1
optional_arguments = 1
has_content = True
option_spec = {
'nocanvas':directives.flag,
'nopre':directives.flag,
'above':directives.flag, # put the canvas above the code
'autorun':directives.flag,
'caption':directives.unchanged,
'include':directives.unchanged,
'hidecode':directives.flag,
'language':directives.unchanged,
'tour_1':directives.unchanged,
'tour_2':directives.unchanged,
'tour_3':directives.unchanged,
'tour_4':directives.unchanged,
'tour_5':directives.unchanged
}
def run(self):
env = self.state.document.settings.env
# keep track of how many activecodes we have.... could be used to automatically make a unique id for them.
if not hasattr(env,'activecodecounter'):
env.activecodecounter = 0
env.activecodecounter += 1
self.options['divid'] = self.arguments[0]
if self.content:
if '====' in self.content:
idx = self.content.index('====')
source = "\n".join(self.content[:idx])
suffix = "\n".join(self.content[idx+1:])
else:
source = "\n".join(self.content)
suffix = "\n"
else:
source = '\n'
suffix = '\n'
self.options['initialcode'] = source
self.options['suffix'] = suffix
str=source.replace("\n","*nline*")
str0=str.replace("\"","*doubleq*")
str1=str0.replace("(","*open*")
str2=str1.replace(")","*close*")
str3=str2.replace("'","*singleq*")
self.options['argu']=str3
complete=""
no_of_buttons=0
okeys = self.options.keys()
for k in okeys:
if '_' in k:
x,label = k.split('_')
no_of_buttons=no_of_buttons+1
complete=complete+self.options[k]+"*atype*"
newcomplete=complete.replace("\"","*doubleq*")
self.options['ctext'] = newcomplete
self.options['no_of_buttons'] = no_of_buttons
if 'caption' not in self.options:
self.options['caption'] = ''
if 'include' not in self.options:
self.options['include'] = 'undefined'
else:
lst = self.options['include'].split(',')
lst = [x.strip() for x in lst]
self.options['include'] = lst
if 'hidecode' in self.options:
self.options['hidecode'] = 'none'
else:
self.options['hidecode'] = 'block'
if 'language' not in self.options:
self.options['language'] = 'python'
return [ActivcodeNode(self.options)]
class ActiveExercise(ActiveCode):
required_arguments = 1
optional_arguments = 0
has_content = True
def run(self):
self.options['hidecode'] = True
self.options['gradebutton'] = True
return super(ActiveExercise,self).run()
if __name__ == '__main__':
a = ActiveCode()

View file

@ -0,0 +1,2 @@
from .animation import *

View file

@ -0,0 +1,105 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('animation',Animation)
# app.add_stylesheet('video.css')
app.add_javascript('animationbase.js')
SRC = '''
<div id="%(divid)s">
<canvas id="%(divid)s_canvas" width="400" height="400" style="border:4px solid blue"></canvas>
<br />
<button onclick="%(divid)s_anim = %(divid)s_init('%(divid)s')">Initialize</button>
<button onclick="%(divid)s_anim.run('%(divid)s_anim')">Run</button>
<button onclick="%(divid)s_anim.stop()">Stop</button> </br>
<button onclick="%(divid)s_anim.begin()">Beginning</button>
<button onclick="%(divid)s_anim.forward()">Step Forward</button>
<button onclick="%(divid)s_anim.backward()">Step Backward</button>
<button onclick="%(divid)s_anim.end()">End</button>
<script type="text/javascript">
%(divid)s_init = function(divid)
{
var a = new Animator(new %(model)s(), new %(viewer)s(), divid)
a.init()
return a
}
</script>
</div>
'''
SCRIPTTAG = '''<script type="text/javascript" src="../_static/%s"></script>\n'''
class Animation(Directive):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = False
option_spec = {'modelfile':directives.unchanged,
'viewerfile':directives.unchanged,
'model':directives.unchanged,
'viewer':directives.unchanged
}
def run(self):
"""
process the video directive and generate html for output.
:param self:
:return:
"""
res = ''
self.options['divid'] = self.arguments[0]
if 'modelfile' in self.options:
res = res + SCRIPTTAG % self.options['modelfile']
if 'viewerfile' in self.options:
res = res + SCRIPTTAG % self.options['viewerfile']
res = res + SRC % self.options
return [nodes.raw('',res , format='html')]
source = '''
.. animation:: testanim
:modelfile: sortmodels.js
:viewerfile: sortviewers.js
:model: SortModel
:viewer: BarViewer
'''
if __name__ == '__main__':
from docutils.core import publish_parts
directives.register_directive('animation',Animation)
doc_parts = publish_parts(source,
settings_overrides={'output_encoding': 'utf8',
'initial_header_level': 2},
writer_name="html")
print doc_parts['html_body']

View file

@ -0,0 +1,204 @@
Animator = function(m, v, divid)
{
this.model = m
this.viewer = v
this.timer = null
this.cursor = -1
this.sc = document.getElementById(divid+"_canvas")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
this.script = this.model.init() //does the animation and sends script back
this.viewer.init(this.ctx)
}
Animator.prototype.getContext=function()
{
return this.ctx
}
Animator.prototype.incCursor=function()
{
if (this.cursor < this.script.length-1)
this.cursor = this.cursor + 1
}
Animator.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
Animator.prototype.getCursor=function()
{
return this.cursor
}
Animator.prototype.setCursor=function(newc)
{
this.cursor = newc
}
Animator.prototype.run = function(animobj)
{
if (this.timer == null)
this.timer = setInterval(animobj+".forward()",this.speed)
}
Animator.prototype.stop = function()
{
clearInterval(this.timer)
this.timer=null
}
Animator.prototype.forward = function()
{
this.incCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
if (this.getCursor() == this.script.length-1 && this.timer != null)
{
clearInterval(this.timer)
this.timer = null
}
}
Animator.prototype.backward = function()
{
this.decCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.end = function()
{
this.setCursor(this.script.length-1)
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.begin = function()
{
this.setCursor(0)
this.sc.width=this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.init = function()
{
this.setCursor(0)
this.sc.width = this.sc.width
this.viewer.render(this.script[0])
}
init1 = function()
{
a = new Animator(new BubbleSortModel(), new BarViewer())
a.init()
}
init2 = function()
{
a = new Animator(new BubbleSortModel(), new ScatterViewer())
a.init()
}
init3 = function()
{
a = new Animator(new BubbleSortModel(), new BoxViewer())
a.init()
}
init4 = function()
{
a = new Animator(new SelectionSortModel(), new BarViewer())
a.init()
}
init5 = function()
{
a = new Animator(new SelectionSortModel(), new ScatterViewer())
a.init()
}
init6 = function()
{
a = new Animator(new SelectionSortModel(), new BoxViewer())
a.init()
}
init7 = function()
{
a = new Animator(new InsertionSortModel(), new BarViewer())
a.init()
}
init8 = function()
{
a = new Animator(new InsertionSortModel(), new ScatterViewer())
a.init()
}
init9 = function()
{
a = new Animator(new InsertionSortModel(), new BoxViewer())
a.init()
}
init10 = function()
{
a = new Animator(new ShellSortModel(), new BarViewer())
a.init()
}
init11 = function()
{
a = new Animator(new ShellSortModel(), new ScatterViewer())
a.init()
}
init12 = function()
{
a = new Animator(new ShellSortModel(), new BoxViewer())
a.init()
}
init13 = function()
{
a = new Animator(new MergeSortModel(), new BarViewer())
a.init()
}
init14 = function()
{
a = new Animator(new MergeSortModel(), new ScatterViewer())
a.init()
}
init15 = function()
{
a = new Animator(new MergeSortModel(), new BoxViewer())
a.init()
}
init16 = function()
{
a = new Animator(new QuickSortModel(), new BarViewer())
a.init()
}
init17 = function()
{
a = new Animator(new QuickSortModel(), new ScatterViewer())
a.init()
}
init18 = function()
{
a = new Animator(new QuickSortModel(), new BoxViewer())
a.init()
}

View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<head>
<script type "text/javascript" src="animationrefactor.js"></script>
<html>
<body onload="">
<div id="ancan">
<canvas id="ancan_canvas" width="400" height="400" style="border:4px solid blue"></canvas>
<br />
<button onclick="ancan_anim = init1('ancan')">Initialize with BarView</button><button onclick="ancan_anim = init2('ancan')">Initialize with ListView</button><br/>
<button onclick="ancan_anim.run('ancan_anim')">Run</button>
<button onclick="ancan_anim.stop()">Stop</button> </br>
<button onclick="ancan_anim.begin()">Beginning</button>
<button onclick="ancan_anim.forward()">Step Forward</button>
<button onclick="ancan_anim.backward()">Step Backward</button>
<button onclick="ancan_anim.end()">End</button>
</div>
<script type="text/javascript">
init1 = function(divid)
{
var a = new Animator(new SortModel(), new BarViewer(), divid)
a.init()
return a
}
init2 = function(divid)
{
var a = new Animator(new SortModel(), new ListViewer(), divid)
a.init()
return a
}
</script>
</body>
</html>

View file

@ -0,0 +1,256 @@
DataItem = function(pos, h, col)
{
this.height = h
this.position = pos
this.color = col
}
DataItem.prototype.clone=function()
{
var newitem = new DataItem(this.position,this.height,this.color) //make a copy
return newitem
}
DataItem.prototype.getHeight=function()
{
return this.height
}
DataItem.prototype.getColor=function()
{
return this.color
}
DataItem.prototype.getPosition=function()
{
return this.position
}
DataItem.prototype.setHeight=function(newh)
{
this.height = newh
}
DataItem.prototype.setPosition=function(newp)
{
this.position = newp
}
DataItem.prototype.setColor=function(newc)
{
this.color = newc
}
SortModel = function() //construct the model
{
}
SortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 50
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var passnum=this.valuelist.length-1; passnum>0; passnum = passnum-1)
{
for (var i=0; i<passnum; i=i+1)
{
this.valuelist[i].setColor("red")
this.valuelist[i+1].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getHeight() > this.valuelist[i+1].getHeight())
{
var temp = this.valuelist[i]
this.valuelist[i] = this.valuelist[i+1]
this.valuelist[i+1] = temp
this.script.push(this.makescene())
}
this.valuelist[i].setColor("black")
this.valuelist[i+1].setColor("black")
this.script.push(this.makescene())
}
}
return this.script
}
SortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
BarViewer = function() //construct the view
{
}
BarViewer.prototype.init = function(c)
{
this.ctx = c
}
BarViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillRect(p*7 + 2,
this.ctx.canvas.height-ascene[p].height,
3,
ascene[p].height)
}
}
ListViewer = function() //contruct a list of numbers view
{
}
ListViewer.prototype.init = function(c)
{
this.ctx = c
}
ListViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].height, p*7 + 2,
this.ctx.canvas.height-ascene[p].height)
}
}
Animator = function(m, v, divid)
{
this.model = m
this.viewer = v
this.timer = null
this.cursor = -1
this.sc = document.getElementById(divid+"_canvas")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
this.script = this.model.init() //does the sort and sends script back
this.viewer.init(this.ctx)
}
Animator.prototype.getContext=function()
{
return this.ctx
}
Animator.prototype.incCursor=function()
{
if (this.cursor < this.script.length-1)
this.cursor = this.cursor + 1
}
Animator.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
Animator.prototype.getCursor=function()
{
return this.cursor
}
Animator.prototype.setCursor=function(newc)
{
this.cursor = newc
}
Animator.prototype.run = function(animobj)
{
if (this.timer == null)
this.timer = setInterval(animobj+".forward()",this.speed)
}
Animator.prototype.stop = function()
{
clearInterval(this.timer)
this.timer=null
}
Animator.prototype.forward = function()
{
this.incCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
if (this.getCursor() == this.script.length-1 && this.timer != null)
{
clearInterval(this.timer)
this.timer = null
}
}
Animator.prototype.backward = function()
{
this.decCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.end = function()
{
this.setCursor(this.script.length-1)
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.begin = function()
{
this.setCursor(0)
this.sc.width=this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.init = function()
{
this.setCursor(0)
this.sc.width = this.sc.width
this.viewer.render(this.script[0])
}

View file

@ -0,0 +1,51 @@
<html>
<head>
<title>charts</title>
<!-- <script type="text/javascript" src="https://www.google.com/jsapi"></script> -->
<!-- <script type="text/javascript" src="chart.js"></script> -->
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript" src="jqchart/jquery.gchart.js"></script>
<script type="text/javascript" src="jqchart/jquery.gchart.graphviz.js"></script>
<style>
#visualization { width: 800px; height: 400px; }
</style>
</head>
<body>
<div id="visualization"></div>
<script type="text/javascript">
// $("#visualization").gchart({type: 'graphviz', series: [$.gchart.series([20, 50, 30])]});
// label: '<f0> left | <f1> middle | <f2> right'
$('#visualization').gchart($.gchart.graphviz(true,
{
struct1: {label: '<f0> left |<f1> middle |<f2> right'},
struct2: {label: '<f0> one|<f1> two'},
struct3: {label: 'hello\nworld |{ b |{c|<here> d|e}| f}| g | h'}
},
{
'struct1:f1': ['struct2:f0'],
'struct2:f2': ['struct3:here']
},
{
node: {shape: 'record'}
}
));
// $('#visualization').gchart($.gchart.graphviz(true,
// {struct1: {label: "<f0> left |<f1> middle |<f2> right"},
// struct2: {label: "<f0> one|<f1> two"},
// struct3: {label: "hello\ world |{ b |{c|<here> d|e}| f}| g | h"}
// },
// {'struct1:f1': ['struct2:f0'],
// 'struct2:f2': ['struct3:here']},
// {node: {shape: 'record'}}))
</script>
</body>
</html>

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>jQuery Google Chart</title>
<style type="text/css">
#basicGChart { width: 450px; height: 300px }
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript" src="jquery.gchart.js"></script>
<script type="text/javascript">
$(function () {
$('#basicGChart').gchart({type: 'line', maxValue: 40,
title: 'Weather for|Brisbane, Australia', titleColor: 'green',
backgroundColor: $.gchart.gradient('horizontal', 'ccffff', 'ccffff00'),
series: [$.gchart.series('Max', [29.1, 28.9, 28.1, 26.3,
23.5, 21.2, 20.6, 21.7, 23.8, 25.6, 27.3, 28.6], 'red', 'ffcccc'),
$.gchart.series('Min', [20.9, 20.8, 19.5, 16.9,
13.8, 10.9, 9.5, 10.0, 12.5, 15.6, 18.0, 19.8], 'green'),
$.gchart.series('Rainfall', [157.7, 174.6, 138.5, 90.4,
98.8, 71.2, 62.6, 42.7, 34.9, 94.4, 96.5, 126.2], 'blue', 0, 200)],
axes: [$.gchart.axis('bottom', ['Jan', 'Feb', 'Mar', 'Apr',
'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 'black'),
$.gchart.axis('left', 0, 40, 'red', 'right'),
$.gchart.axis('left', ['C'], [50], 'red', 'right'),
$.gchart.axis('right', 0, 200, 50, 'blue', 'left'),
$.gchart.axis('right', ['mm'], [50], 'blue', 'left')],
legend: 'right'});
});
</script>
</head>
<body>
<h1>jQuery Google Chart Basics</h1>
<p>This page demonstrates the very basics of the
<a href="http://keith-wood.name/gChart.html">jQuery Google Chart plugin</a>.
It contains the minimum requirements for using the plugin and
can be used as the basis for your own experimentation.</p>
<p>For more detail see the <a href="http://keith-wood.name/gChartRef.html">documentation reference</a> page.</p>
<div id="basicGChart"></div>
</body>
</html>

View file

@ -0,0 +1,334 @@
/* http://keith-wood.name/gChart.html
Google Chart interface extensions for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($) { // Hide scope, no $ conflict
$.extend($.gchart._defaults, {
// Maps -------------------
mapLatLong: false, // True to use lat/long coords in mapArea
mapArea: null, // New maps: (number) pixel border all around or
// (number[4]) individual pixel borders or lat/long
// Original maps: the general area to show:
// world, africa, asia, europe, middle_east, south_america, usa
mapRegions: [], // List of country/state codes to plot
mapDefaultColor: 'bebebe', // The colour for non-plotted countries/states
mapColors: ['blue', 'red'], // The colour range for plotted countries/states
// QR Code ----------------
qrECLevel: null, // Error correction level: low, medium, quarter, high
qrMargin: null // Margin (squares) around QR code, default is 4
});
// New chart types: formula, map, mapOriginal, meter, qrCode, scatter, venn
$.extend($.gchart._chartTypes, {formula: 'tx', map: 'map', mapOriginal: 't',
meter: 'gom', qrCode: 'qr', scatter: 's', venn: 'v',
gom: 'gom', qr: 'qr', s: 's', t: 't', tx: 'tx', v: 'v'});
$.extend($.gchart._typeOptions, {map: 'map', qr: 'qr', t: 'map', tx: 'no'});
$.extend($.gchart._prototype.prototype, {
/* Latitude and longitude coordinates for the continents. */
mapAfrica: [-35, -20, 40, 55],
mapAsia: [-15, 40, 75, 180],
mapAustralia: [-45, 110, -10, 155],
mapEurope: [33, -25, 73, 50],
mapNorthAmerica: [5, -175, 75, -50],
mapSouthAmerica: [-55, -85, 15, -35],
/* Prepare options for a scatter chart.
@param values (number[][2/3]) the coordinates of the points: [0] is the x-coord,
[1] is the y-coord, [2] (optional) is the percentage size
@param minMax (number[2/4]) any minimum and maximum values for the axes (optional)
@param labels (string[]) the labels for the groups (optional)
@param colours (string[]) the colours for the labels (optional)
@param options (object) additional settings (optional)
@return (object) the configured options object */
scatter: function(values, minMax, labels, colours, options) {
if (!$.isArray(minMax)) {
options = minMax;
colours = null;
labels = null;
minMax = null;
}
else if (typeof minMax[0] != 'number') {
options = colours;
colours = labels;
labels = minMax;
minMax = null;
}
if (labels && !$.isArray(labels)) {
options = labels;
colours = null;
labels = null;
}
var series = [[], [], []];
for (var i = 0; i < values.length; i++) {
series[0][i] = values[i][0];
series[1][i] = values[i][1];
series[2][i] = values[i][2] || 100;
}
minMax = minMax || [];
options = options || {};
if (labels) {
options.extension = {chdl: labels.join('|')};
}
if (colours) {
colours = $.map(colours, function(v, i) {
return $.gchart.color(v);
});
$.extend(options.extension, {chco: colours.join('|')});
}
return $.extend({}, options,
{type: 'scatter', encoding: (minMax.length >= 2 ? 'scaled' : 'text'), series: [
(minMax.length >= 2 ? $.gchart.series(series[0], minMax[0], minMax[1]) :
$.gchart.series(series[0])),
(minMax.length >= 4 ? $.gchart.series(series[1],
(minMax[2] != null ? minMax[2] : minMax[0]), (minMax[3] != null ? minMax[3] : minMax[1])) :
$.gchart.series(series[1])), $.gchart.series(series[2])]});
},
/* Prepare options for a Venn diagram.
@param size1 (number) the relative size of the first circle
@param size2 (number) the relative size of the second circle
@param size3 (number) the relative size of the third circle
@param overlap12 (number) the overlap between circles 1 and 2
@param overlap13 (number) the overlap between circles 1 and 3
@param overlap23 (number) the overlap between circles 2 and 3
@param overlap123 (number) the overlap between all circles
@param options (object) additional settings (optional)
@return (object) the configured options object */
venn: function(size1, size2, size3, overlap12, overlap13, overlap23, overlap123, options) {
return $.extend({}, options || {}, {type: 'venn', series:
[$.gchart.series([size1, size2, size3, overlap12, overlap13, overlap23, overlap123])]});
},
/* Prepare options for a Google meter.
@param text (string or string[]) the text to show on the arrow (optional)
@param values (number or number[] or [] of these) the position(s) of the arrow(s)
@param maxValue (number) the maximum value for the meter (optional, default 100)
@param colours (string[]) the colours to use for the band (optional)
@param labels (string[]) labels appearing beneath the meter (optional)
@param styles (number[][4]) the styles of each series' arrows:
width, dash, space, arrow size (optional)
@param options (object) additional settings (optional)
@return (object) the configured options object */
meter: function(text, values, maxValue, colours, labels, styles, options) {
if (typeof text != 'string' && !$.isArray(text)) {
options = styles;
styles = labels;
labels = colours;
colours = maxValue;
maxValue = values;
values = text;
text = '';
}
if (typeof maxValue != 'number') {
options = styles;
styles = labels;
labels = colours;
colours = maxValue;
maxValue = null;
}
if (!$.isArray(colours)) {
options = styles;
styles = labels;
labels = colours;
colours = null;
}
if (!$.isArray(labels)) {
options = styles;
styles = labels;
labels = null;
}
if (!$.isArray(styles)) {
options = styles;
styles = null;
}
values = ($.isArray(values) ? values : [values]);
var multi = false;
for (var i = 0; i < values.length; i++) {
multi = multi || $.isArray(values[i]);
}
var ss = (multi ? [] : [$.gchart.series(values)]);
if (multi) {
for (var i = 0; i < values.length; i++) {
ss.push($.gchart.series($.isArray(values[i]) ? values[i] : [values[i]]));
}
}
values = ss;
if (colours) {
var cs = '';
$.each(colours, function(i, v) {
cs += ',' + $.gchart.color(v);
});
colours = cs.substr(1);
}
if (styles) {
var ls = ['', ''];
$.each(styles, function(i, v) {
v = ($.isArray(v) ? v : [v]);
ls[0] += '|' + $.gchart.color(v.slice(0, 3).join(','));
ls[1] += '|' + (v[3] || 15);
});
styles = ls[0].substr(1) + ls[1];
}
var axis = (labels && labels.length ? $.gchart.axis('y', labels) : null);
return $.extend({}, options || {}, {type: 'meter',
maxValue: maxValue || 100, series: values,
dataLabels: ($.isArray(text) ? text : [text || ''])},
(colours ? {extension: {chco: colours}} : {}),
(axis ? {axes: [axis]} : {}),
(styles ? {extension: {chls: styles}} : {}));
},
/* Prepare options for a map chart.
@param latLongArea (boolean) true to specify the area via latitude/longitude (optional)
@param mapArea (string) the region of the world to show (original map style) or
(number[4]) the pixel zoom or lat/long coordinates to show or
(number) all around pixel zoom (optional)
@param values (object) the countries/states to plot -
attributes are country/state codes and values
@param defaultColour (string) the colour for regions without values (optional)
@param colour (string or string[]) the starting colour or
gradient colours for rendering values (optional)
@param endColour (string) the ending colour for rendering values (optional)
@param options (object) additional settings (optional)
@return (object) the configured options object */
map: function(latLongArea, mapArea, values, defaultColour, colour, endColour, options) {
if (typeof latLongArea != 'boolean') {
options = endColour;
endColour = colour;
colour = defaultColour;
defaultColour = values;
values = mapArea;
mapArea = latLongArea;
latLongArea = false;
}
if (typeof mapArea == 'object' && !$.isArray(mapArea)) { // Optional mapArea
options = endColour;
endColour = colour;
colour = defaultColour;
defaultColour = values;
values = mapArea;
mapArea = null;
}
if (typeof defaultColour == 'object') {
options = defaultColour;
endColour = null;
colour = null;
defaultColour = null;
}
else if (typeof colour == 'object' && !$.isArray(colour)) {
options = colour;
endColour = null;
colour = null;
}
else if (typeof endColour == 'object') {
options = endColour;
endColour = null;
}
var mapRegions = [];
var data = [];
var i = 0;
for (var name in values) {
mapRegions[i] = name.replace(/_/g, '-');
data[i] = values[name];
i++;
}
if (typeof mapArea == 'number') {
mapArea = [mapArea, mapArea, mapArea, mapArea];
}
return $.extend({}, options || {},
{type: (typeof mapArea == 'string' ? 'mapOriginal' : 'map'),
mapLatLong: latLongArea, mapArea: mapArea, mapRegions: mapRegions,
mapDefaultColor: defaultColour || $.gchart._defaults.mapDefaultColor,
mapColors: ($.isArray(colour) ? colour : [colour || $.gchart._defaults.mapColors[0],
endColour || $.gchart._defaults.mapColors[1]]),
series: [$.gchart.series('', data)]});
},
/* Prepare options for generating a QR Code.
@param text (object) the QR code settings or
(string) the text to encode
@param encoding (string) the encoding scheme (optional)
@param ecLevel (string) the error correction level: l, m, q, h (optional)
@param margin (number) the margin around the code (optional)
@return (object) the configured options object */
qrCode: function(text, encoding, ecLevel, margin) {
var options = {};
if (typeof text == 'object') {
options = text;
}
else { // Individual fields
options = {dataLabels: [text], encoding: encoding,
qrECLevel: ecLevel, qrMargin: margin};
}
options.type = 'qrCode';
if (options.text) {
options.dataLabels = [options.text];
options.text = null;
}
return options;
},
/* Generate standard options for map charts.
@param options (object) the chart settings
@param labels (string) the concatenated labels for the chart
@return (string) the standard map chart options */
mapOptions: function(options, labels) {
var encoding = this['_' + options.encoding + 'Encoding'] || this['_textEncoding'];
var colours = '';
for (var i = 0; i < options.mapColors.length; i++) {
colours += ',' + $.gchart.color(options.mapColors[i]);
}
return (typeof options.mapArea == 'string' ? '&chtm=' + options.mapArea :
(options.mapArea ? (options.mapLatLong ? ':fixed=' : ':auto=') +
($.isArray(options.mapArea) ? options.mapArea.join(',') :
options.mapArea + ',' + options.mapArea + ',' + options.mapArea + ',' + options.mapArea) : '')) +
'&chd=' + encoding.apply($.gchart, [options]) +
(options.mapRegions && options.mapRegions.length ?
'&chld=' + options.mapRegions.join(typeof options.mapArea == 'string' ? '' : '|') : '') +
'&chco=' + $.gchart.color(options.mapDefaultColor) + colours;
},
/* Generate standard options for QR Code charts.
@param options (object) the chart settings
@param labels (string) the concatenated labels for the chart
@return (string) the standard QR Code chart options */
qrOptions: function(options, labels) {
return $.gchart._include('&choe=', options.encoding) +
(options.qrECLevel || options.qrMargin ?
'&chld=' + (options.qrECLevel ? options.qrECLevel.charAt(0) : 'l') +
(options.qrMargin != null ? '|' + options.qrMargin : '') : '') +
(labels ? '&chl=' + labels.substr(1) : '');
},
/* Generate standard options for charts that aren't really charts.
@param options (object) the chart settings
@param labels (string) the concatenated labels for the chart
@return (string) the standard non-chart options */
noOptions: function(options, labels) {
return '&chl=' + labels.substr(1);
},
/* Generate the options for chart size, including restriction for maps.
@param type (string) the encoded chart type
@param options (object) the chart settings
@return (string) the chart size options */
addSize: function(type, options) {
var maxSize = (type == 'map' || type == 't' ? 600 : 1000);
options.width = Math.max(10, Math.min(options.width, maxSize));
options.height = Math.max(10, Math.min(options.height, maxSize));
if (options.width * options.height > 300000) {
options.height = Math.floor(300000 / options.width);
}
return 'chs=' + options.width + 'x' + options.height;
}
});
})(jQuery);

View file

@ -0,0 +1,8 @@
/* http://keith-wood.name/gChart.html
Google Chart interface extensions for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($){$.extend($.gchart._defaults,{mapLatLong:false,mapArea:null,mapRegions:[],mapDefaultColor:'bebebe',mapColors:['blue','red'],qrECLevel:null,qrMargin:null});$.extend($.gchart._chartTypes,{formula:'tx',map:'map',mapOriginal:'t',meter:'gom',qrCode:'qr',scatter:'s',venn:'v',gom:'gom',qr:'qr',s:'s',t:'t',tx:'tx',v:'v'});$.extend($.gchart._typeOptions,{map:'map',qr:'qr',t:'map',tx:'no'});$.extend($.gchart._prototype.prototype,{mapAfrica:[-35,-20,40,55],mapAsia:[-15,40,75,180],mapAustralia:[-45,110,-10,155],mapEurope:[33,-25,73,50],mapNorthAmerica:[5,-175,75,-50],mapSouthAmerica:[-55,-85,15,-35],scatter:function(a,b,c,d,e){if(!$.isArray(b)){e=b;d=null;c=null;b=null}else if(typeof b[0]!='number'){e=d;d=c;c=b;b=null}if(c&&!$.isArray(c)){e=c;d=null;c=null}var f=[[],[],[]];for(var i=0;i<a.length;i++){f[0][i]=a[i][0];f[1][i]=a[i][1];f[2][i]=a[i][2]||100}b=b||[];e=e||{};if(c){e.extension={chdl:c.join('|')}}if(d){d=$.map(d,function(v,i){return $.gchart.color(v)});$.extend(e.extension,{chco:d.join('|')})}return $.extend({},e,{type:'scatter',encoding:(b.length>=2?'scaled':'text'),series:[(b.length>=2?$.gchart.series(f[0],b[0],b[1]):$.gchart.series(f[0])),(b.length>=4?$.gchart.series(f[1],(b[2]!=null?b[2]:b[0]),(b[3]!=null?b[3]:b[1])):$.gchart.series(f[1])),$.gchart.series(f[2])]})},venn:function(a,b,c,d,e,f,g,h){return $.extend({},h||{},{type:'venn',series:[$.gchart.series([a,b,c,d,e,f,g])]})},meter:function(a,b,c,d,e,f,g){if(typeof a!='string'&&!$.isArray(a)){g=f;f=e;e=d;d=c;c=b;b=a;a=''}if(typeof c!='number'){g=f;f=e;e=d;d=c;c=null}if(!$.isArray(d)){g=f;f=e;e=d;d=null}if(!$.isArray(e)){g=f;f=e;e=null}if(!$.isArray(f)){g=f;f=null}b=($.isArray(b)?b:[b]);var h=false;for(var i=0;i<b.length;i++){h=h||$.isArray(b[i])}var j=(h?[]:[$.gchart.series(b)]);if(h){for(var i=0;i<b.length;i++){j.push($.gchart.series($.isArray(b[i])?b[i]:[b[i]]))}}b=j;if(d){var k='';$.each(d,function(i,v){k+=','+$.gchart.color(v)});d=k.substr(1)}if(f){var l=['',''];$.each(f,function(i,v){v=($.isArray(v)?v:[v]);l[0]+='|'+$.gchart.color(v.slice(0,3).join(','));l[1]+='|'+(v[3]||15)});f=l[0].substr(1)+l[1]}var m=(e&&e.length?$.gchart.axis('y',e):null);return $.extend({},g||{},{type:'meter',maxValue:c||100,series:b,dataLabels:($.isArray(a)?a:[a||''])},(d?{extension:{chco:d}}:{}),(m?{axes:[m]}:{}),(f?{extension:{chls:f}}:{}))},map:function(a,b,c,d,e,f,g){if(typeof a!='boolean'){g=f;f=e;e=d;d=c;c=b;b=a;a=false}if(typeof b=='object'&&!$.isArray(b)){g=f;f=e;e=d;d=c;c=b;b=null}if(typeof d=='object'){g=d;f=null;e=null;d=null}else if(typeof e=='object'&&!$.isArray(e)){g=e;f=null;e=null}else if(typeof f=='object'){g=f;f=null}var h=[];var j=[];var i=0;for(var k in c){h[i]=k.replace(/_/g,'-');j[i]=c[k];i++}if(typeof b=='number'){b=[b,b,b,b]}return $.extend({},g||{},{type:(typeof b=='string'?'mapOriginal':'map'),mapLatLong:a,mapArea:b,mapRegions:h,mapDefaultColor:d||$.gchart._defaults.mapDefaultColor,mapColors:($.isArray(e)?e:[e||$.gchart._defaults.mapColors[0],f||$.gchart._defaults.mapColors[1]]),series:[$.gchart.series('',j)]})},qrCode:function(a,b,c,d){var e={};if(typeof a=='object'){e=a}else{e={dataLabels:[a],encoding:b,qrECLevel:c,qrMargin:d}}e.type='qrCode';if(e.text){e.dataLabels=[e.text];e.text=null}return e},mapOptions:function(a,b){var c=this['_'+a.encoding+'Encoding']||this['_textEncoding'];var d='';for(var i=0;i<a.mapColors.length;i++){d+=','+$.gchart.color(a.mapColors[i])}return(typeof a.mapArea=='string'?'&chtm='+a.mapArea:(a.mapArea?(a.mapLatLong?':fixed=':':auto=')+($.isArray(a.mapArea)?a.mapArea.join(','):a.mapArea+','+a.mapArea+','+a.mapArea+','+a.mapArea):''))+'&chd='+c.apply($.gchart,[a])+(a.mapRegions&&a.mapRegions.length?'&chld='+a.mapRegions.join(typeof a.mapArea=='string'?'':'|'):'')+'&chco='+$.gchart.color(a.mapDefaultColor)+d},qrOptions:function(a,b){return $.gchart._include('&choe=',a.encoding)+(a.qrECLevel||a.qrMargin?'&chld='+(a.qrECLevel?a.qrECLevel.charAt(0):'l')+(a.qrMargin!=null?'|'+a.qrMargin:''):'')+(b?'&chl='+b.substr(1):'')},noOptions:function(a,b){return'&chl='+b.substr(1)},addSize:function(a,b){var c=(a=='map'||a=='t'?600:1000);b.width=Math.max(10,Math.min(b.width,c));b.height=Math.max(10,Math.min(b.height,c));if(b.width*b.height>300000){b.height=Math.floor(300000/b.width)}return'chs='+b.width+'x'+b.height}})})(jQuery);

View file

@ -0,0 +1,8 @@
/* http://keith-wood.name/gChart.html
Google Chart interface extensions for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(o($){$.w($.7.O,{X:Y,r:6,C:[],P:\'1o\',B:[\'1p\',\'1q\'],D:6,E:6});$.w($.7.1r,{1s:\'Q\',z:\'z\',19:\'t\',Z:\'11\',12:\'F\',13:\'s\',14:\'v\',11:\'11\',F:\'F\',s:\'s\',t:\'t\',Q:\'Q\',v:\'v\'});$.w($.7.1t,{z:\'z\',F:\'F\',t:\'z\',Q:\'1u\'});$.w($.7.1v.1w,{1x:[-1a,-20,1b,1c],1y:[-15,1b,1d,1z],1A:[-1B,1C,-10,1D],1E:[1F,-25,1G,1e],1H:[5,-1I,1d,-1e],1J:[-1c,-1K,15,-1a],13:o(a,b,c,d,e){8(!$.n(b)){e=b;d=6;c=6;b=6}R 8(p b[0]!=\'16\'){e=d;d=c;c=b;b=6}8(c&&!$.n(c)){e=c;d=6;c=6}9 f=[[],[],[]];G(9 i=0;i<a.A;i++){f[0][i]=a[i][0];f[1][i]=a[i][1];f[2][i]=a[i][2]||1f}b=b||[];e=e||{};8(c){e.S={1L:c.H(\'|\')}}8(d){d=$.z(d,o(v,i){u $.7.I(v)});$.w(e.S,{17:d.H(\'|\')})}u $.w({},e,{J:\'13\',T:(b.A>=2?\'1M\':\'U\'),q:[(b.A>=2?$.7.q(f[0],b[0],b[1]):$.7.q(f[0])),(b.A>=4?$.7.q(f[1],(b[2]!=6?b[2]:b[0]),(b[3]!=6?b[3]:b[1])):$.7.q(f[1])),$.7.q(f[2])]})},14:o(a,b,c,d,e,f,g,h){u $.w({},h||{},{J:\'14\',q:[$.7.q([a,b,c,d,e,f,g])]})},Z:o(a,b,c,d,e,f,g){8(p a!=\'V\'&&!$.n(a)){g=f;f=e;e=d;d=c;c=b;b=a;a=\'\'}8(p c!=\'16\'){g=f;f=e;e=d;d=c;c=6}8(!$.n(d)){g=f;f=e;e=d;d=6}8(!$.n(e)){g=f;f=e;e=6}8(!$.n(f)){g=f;f=6}b=($.n(b)?b:[b]);9 h=Y;G(9 i=0;i<b.A;i++){h=h||$.n(b[i])}9 j=(h?[]:[$.7.q(b)]);8(h){G(9 i=0;i<b.A;i++){j.1N($.7.q($.n(b[i])?b[i]:[b[i]]))}}b=j;8(d){9 k=\'\';$.1g(d,o(i,v){k+=\',\'+$.7.I(v)});d=k.W(1)}8(f){9 l=[\'\',\'\'];$.1g(f,o(i,v){v=($.n(v)?v:[v]);l[0]+=\'|\'+$.7.I(v.1O(0,3).H(\',\'));l[1]+=\'|\'+(v[3]||15)});f=l[0].W(1)+l[1]}9 m=(e&&e.A?$.7.1P(\'y\',e):6);u $.w({},g||{},{J:\'Z\',1Q:c||1f,q:b,18:($.n(a)?a:[a||\'\'])},(d?{S:{17:d}}:{}),(m?{1R:[m]}:{}),(f?{S:{1S:f}}:{}))},z:o(a,b,c,d,e,f,g){8(p a!=\'1T\'){g=f;f=e;e=d;d=c;c=b;b=a;a=Y}8(p b==\'K\'&&!$.n(b)){g=f;f=e;e=d;d=c;c=b;b=6}8(p d==\'K\'){g=d;f=6;e=6;d=6}R 8(p e==\'K\'&&!$.n(e)){g=e;f=6;e=6}R 8(p f==\'K\'){g=f;f=6}9 h=[];9 j=[];9 i=0;G(9 k 1U c){h[i]=k.1V(/1h/g,\'-\');j[i]=c[k];i++}8(p b==\'16\'){b=[b,b,b,b]}u $.w({},g||{},{J:(p b==\'V\'?\'19\':\'z\'),X:a,r:b,C:h,P:d||$.7.O.P,B:($.n(e)?e:[e||$.7.O.B[0],f||$.7.O.B[1]]),q:[$.7.q(\'\',j)]})},12:o(a,b,c,d){9 e={};8(p a==\'K\'){e=a}R{e={18:[a],T:b,D:c,E:d}}e.J=\'12\';8(e.U){e.18=[e.U];e.U=6}u e},1W:o(a,b){9 c=1i[\'1h\'+a.T+\'1X\']||1i[\'1Y\'];9 d=\'\';G(9 i=0;i<a.B.A;i++){d+=\',\'+$.7.I(a.B[i])}u(p a.r==\'V\'?\'&1Z=\'+a.r:(a.r?(a.X?\':21=\':\':22=\')+($.n(a.r)?a.r.H(\',\'):a.r+\',\'+a.r+\',\'+a.r+\',\'+a.r):\'\'))+\'&23=\'+c.24($.7,[a])+(a.C&&a.C.A?\'&1j=\'+a.C.H(p a.r==\'V\'?\'\':\'|\'):\'\')+\'&17=\'+$.7.I(a.P)+d},26:o(a,b){u $.7.27(\'&28=\',a.T)+(a.D||a.E?\'&1j=\'+(a.D?a.D.29(0):\'l\')+(a.E!=6?\'|\'+a.E:\'\'):\'\')+(b?\'&1k=\'+b.W(1):\'\')},2a:o(a,b){u\'&1k=\'+b.W(1)},2b:o(a,b){9 c=(a==\'z\'||a==\'t\'?2c:2d);b.L=M.1l(10,M.1m(b.L,c));b.N=M.1l(10,M.1m(b.N,c));8(b.L*b.N>1n){b.N=M.2e(1n/b.L)}u\'2f=\'+b.L+\'x\'+b.N}})})(2g);',62,141,'||||||null|gchart|if|var||||||||||||||isArray|function|typeof|series|mapArea|||return||extend|||map|length|mapColors|mapRegions|qrECLevel|qrMargin|qr|for|join|color|type|object|width|Math|height|_defaults|mapDefaultColor|tx|else|extension|encoding|text|string|substr|mapLatLong|false|meter||gom|qrCode|scatter|venn||number|chco|dataLabels|mapOriginal|35|40|55|75|50|100|each|_|this|chld|chl|max|min|300000|bebebe|blue|red|_chartTypes|formula|_typeOptions|no|_prototype|prototype|mapAfrica|mapAsia|180|mapAustralia|45|110|155|mapEurope|33|73|mapNorthAmerica|175|mapSouthAmerica|85|chdl|scaled|push|slice|axis|maxValue|axes|chls|boolean|in|replace|mapOptions|Encoding|_textEncoding|chtm||fixed|auto|chd|apply||qrOptions|_include|choe|charAt|noOptions|addSize|600|1000|floor|chs|jQuery'.split('|'),0,{}))

View file

@ -0,0 +1,112 @@
/* http://keith-wood.name/gChart.html
Google Chart GraphViz extension for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($) { // Hide scope, no $ conflict
// New chart types: graphviz
$.extend($.gchart._chartTypes, {graphviz: 'gv', gv: 'gv'});
$.extend($.gchart._typeOptions, {gv: 'no'});
$.extend($.gchart._prototype.prototype, {
/* Prepare options for a GraphViz chart.
@param engine (string, optional) the graphing engine to use:
dot (default), neato, twopi, circo, fdp
@param options (object, optional) other options for the chart
@param directed (boolean, optional) true for directed graph, false for normal
@param nodes (string) the DOT representation of the nodes to graph or
(object) the graph nodes and their settings
@param edges (object, optional) the graph edges keyed from, with array of to
@param attrs (object, optional) other settings for the graph
@return (object) the configured options object */
graphviz: function(engine, options, directed, nodes, edges, attrs) {
if (arguments.length == 1) {
nodes = engine;
engine = 'dot';
}
var hadEngine = typeof engine == 'string';
if (!hadEngine) {
attrs = edges;
edges = nodes;
nodes = directed;
directed = options;
options = engine;
engine = 'dot';
}
if ((options && typeof options != 'object') || arguments.length == 2 ||
(arguments.length == 3 && hadEngine)) {
attrs = edges;
edges = nodes;
nodes = directed;
directed = options;
options = {};
}
if (typeof directed != 'boolean' && arguments.length > 1) {
attrs = edges;
edges = nodes;
nodes = directed;
directed = false;
}
options = options || {};
options.type = 'gv' + (engine != 'dot' ? ':' + engine : '');
options.dataLabels = [typeof nodes == 'string' ? nodes :
this._genGraph(directed, nodes, edges, attrs)];
return options;
},
/* Generate a graph definition.
@param directed (boolean, optional) true for directed graph, false for normal
@param nodes (object) the graph nodes and their settings
@param edges (object) the graph edges keyed from, with array of to
@param attrs (object, optional) other settings for the graph
@return (string) the graph definition */
_genGraph: function(directed, nodes, edges, attrs) {
attrs = attrs || {};
var gdef = (directed ? 'digraph' : 'graph') + '{';
var sep = '';
for (var n in attrs) {
gdef += sep + n;
var sep2 = '[';
for (var n2 in attrs[n]) {
gdef += sep2 + n2 + '=' + attrs[n][n2];
sep2 = ','
}
gdef += (sep2 != '[' ? ']' : '');
sep = ';';
}
for (var node in nodes || {}) {
gdef += sep + node;
var sep2 = '[';
for (var n in nodes[node]) {
gdef += sep2 + n + '=' + nodes[node][n];
sep2 = ','
}
gdef += (sep2 != '[' ? ']' : '');
sep = ';';
}
for (var edge in edges || {}) {
for (var n in edges[edge]) {
gdef += sep + edge + (directed ? '->' : '--') + edges[edge][n];
}
sep = ';';
}
gdef += '}';
return gdef;
},
/* Generate standard options for charts that aren't really charts.
@param options (object) the chart settings
@param labels (string) the concatenated labels for the chart
@return (string) the standard non-chart options */
noOptions: function(options, labels) {
return '&chl=' + labels.substr(1);
}
});
})(jQuery);

View file

@ -0,0 +1,8 @@
/* http://keith-wood.name/gChart.html
Google Chart GraphViz extension for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
(function($){$.extend($.gchart._chartTypes,{graphviz:'gv',gv:'gv'});$.extend($.gchart._typeOptions,{gv:'no'});$.extend($.gchart._prototype.prototype,{graphviz:function(a,b,c,d,e,f){if(arguments.length==1){d=a;a='dot'}var g=typeof a=='string';if(!g){f=e;e=d;d=c;c=b;b=a;a='dot'}if((b&&typeof b!='object')||arguments.length==2||(arguments.length==3&&g)){f=e;e=d;d=c;c=b;b={}}if(typeof c!='boolean'&&arguments.length>1){f=e;e=d;d=c;c=false}b=b||{};b.type='gv'+(a!='dot'?':'+a:'');b.dataLabels=[typeof d=='string'?d:this._genGraph(c,d,e,f)];return b},_genGraph:function(a,b,c,d){d=d||{};var e=(a?'digraph':'graph')+'{';var f='';for(var n in d){e+=f+n;var g='[';for(var h in d[n]){e+=g+h+'='+d[n][h];g=','}e+=(g!='['?']':'');f=';'}for(var i in b||{}){e+=f+i;var g='[';for(var n in b[i]){e+=g+n+'='+b[i][n];g=','}e+=(g!='['?']':'');f=';'}for(var j in c||{}){for(var n in c[j]){e+=f+j+(a?'->':'--')+c[j][n]}f=';'}e+='}';return e},noOptions:function(a,b){return'&chl='+b.substr(1)}})})(jQuery);

View file

@ -0,0 +1,8 @@
/* http://keith-wood.name/gChart.html
Google Chart GraphViz extension for jQuery v1.4.3.
See API details at http://code.google.com/apis/chart/.
Written by Keith Wood (kbwood{at}iinet.com.au) September 2008.
Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
Please attribute the author if you use it. */
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(7($){$.m($.o.u,{r:\'6\',6:\'6\'});$.m($.o.v,{6:\'w\'});$.m($.o.x.y,{r:7(a,b,c,d,e,f){8(9.k==1){d=a;a=\'p\'}0 g=l a==\'s\';8(!g){f=e;e=d;d=c;c=b;b=a;a=\'p\'}8((b&&l b!=\'z\')||9.k==2||(9.k==3&&g)){f=e;e=d;d=c;c=b;b={}}8(l c!=\'A\'&&9.k>1){f=e;e=d;d=c;c=B}b=b||{};b.C=\'6\'+(a!=\'p\'?\':\'+a:\'\');b.D=[l d==\'s\'?d:E.t(c,d,e,f)];q b},t:7(a,b,c,d){d=d||{};0 e=(a?\'F\':\'G\')+\'{\';0 f=\'\';4(0 n 5 d){e+=f+n;0 g=\'[\';4(0 h 5 d[n]){e+=g+h+\'=\'+d[n][h];g=\',\'}e+=(g!=\'[\'?\']\':\'\');f=\';\'}4(0 i 5 b||{}){e+=f+i;0 g=\'[\';4(0 n 5 b[i]){e+=g+n+\'=\'+b[i][n];g=\',\'}e+=(g!=\'[\'?\']\':\'\');f=\';\'}4(0 j 5 c||{}){4(0 n 5 c[j]){e+=f+j+(a?\'->\':\'--\')+c[j][n]}f=\';\'}e+=\'}\';q e},H:7(a,b){q\'&I=\'+b.J(1)}})})(K);',47,47,'var||||for|in|gv|function|if|arguments|||||||||||length|typeof|extend||gchart|dot|return|graphviz|string|_genGraph|_chartTypes|_typeOptions|no|_prototype|prototype|object|boolean|false|type|dataLabels|this|digraph|graph|noOptions|chl|substr|jQuery'.split('|'),0,{}))

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,138 @@
DataItem = function(pos, h, col)
{
this.value = h
this.position = pos
this.color = col
}
DataItem.prototype.clone=function()
{
var newitem = new DataItem(this.position,this.value,this.color) //make a copy
return newitem
}
DataItem.prototype.getValue=function()
{
return this.value
}
DataItem.prototype.getColor=function()
{
return this.color
}
DataItem.prototype.getPosition=function()
{
return this.position
}
DataItem.prototype.setValue=function(newh)
{
this.value = newh
}
DataItem.prototype.setPosition=function(newp)
{
this.position = newp
}
DataItem.prototype.setColor=function(newc)
{
this.color = newc
}
BinarySearchModel = function() //construct the model
{
}
BinarySearchModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 25
var initvalues=[25,30,46,55,60,78,90,95,101,110,122,134,145,150,166,175,187,200,205,213,240,255,267,299]
for (var i=0; i<howmany; i++)
{
var item = new DataItem(i,initvalues[i],"black")
this.valuelist.push(item)
}
this.script = new Array()
//this.script.push(this.makescene(this.valuelist))
this.binarySearch(this.valuelist,200)
this.script.push(this.makescene(this.valuelist))
return this.script
}
BinarySearchModel.prototype.binarySearch=function(alist, item)
{
var first = 0
var last = alist.length-1
var found = false
var oldmidpoint = null
while (first<=last && !found)
{ this.chunkcolor(first,last,"black")
this.script.push(this.makescene(this.valuelist))
if (oldmidpoint != null)
{alist[oldmidpoint].setColor("black")
this.script.push(this.makescene(this.valuelist))}
var midpoint = Math.floor((first + last)/2)
alist[midpoint].setColor("blue")
this.script.push(this.makescene(this.valuelist))
//alist[midpoint].setColor("black")
//this.script.push(this.makescene(this.valuelist))
if (alist[midpoint].getValue() == item)
{
found = true
alist[midpoint].setColor("green")
this.script.push(this.makescene(this.valuelist))
}
else
if (item < alist[midpoint].getValue())
{
last = midpoint-1
this.chunkcolor(first,midpoint-1,"red")
this.script.push(this.makescene(this.valuelist))
oldmidpoint = midpoint
//alist[midpoint].setColor("black")
//this.script.push(this.makescene(this.valuelist))
}
else
{
first = midpoint+1
this.chunkcolor(midpoint+1,last,"red")
this.script.push(this.makescene(this.valuelist))
oldmidpoint = midpoint
//alist[midpoint].setColor("black")
//this.script.push(this.makescene(this.valuelist))
}
}
return found
}
BinarySearchModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
BinarySearchModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}

View file

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="animationrefactor.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript" src="jqchart/jquery.gchart.js"></script>
<script type="text/javascript" src="jqchart/jquery.gchart.graphviz.js"></script>
<script type="text/javascript" src="animationrefactor.js"></script>
<script type="text/javascript" src="simpletree.js"></script>
</head>
<body>
<style>
#ancan_div { width: 800px; height: 400px; }
</style>
<div id="ancan">
<canvas id="ancan_canvas" width="400" height="400" style="border:4px solid blue"></canvas>
<div id="ancan_div" width="400" height="400"> </div>
<br />
<button onclick="ancan_anim = init1('ancan')">Initialize</button>
<button onclick="ancan_anim.run('ancan_anim')">Run</button>
<button onclick="ancan_anim.stop()">Stop</button> </br>
<button onclick="ancan_anim.begin()">Beginning</button>
<button onclick="ancan_anim.forward()">Step Forward</button>
<button onclick="ancan_anim.backward()">Step Backward</button>
<button onclick="ancan_anim.end()">End</button>
</div>
<script type="text/javascript">
init1 = function(divid)
{
var a = new Animator(new SimpleTreeModel(), new TreeViewer(), divid)
a.init()
return a
}
</script>
</body>
</html>

View file

@ -0,0 +1,63 @@
SimpleTreeModel = function() //construct the model
{
}
SimpleTreeModel.prototype.init = function(ctl)
{
var model = [
{ nodelist: {C_0: {color: 'blue', style: 'filled'},
H_0: {type: 's', shape: 'record', color: 'blue', label: 'foo'},
H_1: {type: 's'}, H_2: {type: 's'},
C_1: {type: 's'}, H_3: {type: 's'},
H_4: {type: 's'}, H_5: {type: 's'}},
edgelist: {C_0: ['H_0:f1', 'H_1', 'H_2', 'C_1'], C_1: ['H_3', 'H_4', 'H_5']},
params: {node: {shape: 'circle', color: 'red'}, edge: {color: 'blue'}}},
{ nodelist: {C_0: {},
H_0: {type: 's', shape: 'record', color: 'blue', label: 'foo', style: 'filled'},
H_1: {type: 's'}, H_2: {type: 's'},
C_1: {type: 's'}, H_3: {type: 's'},
H_4: {type: 's'}, H_5: {type: 's'}},
edgelist: {C_0: ['H_0:f1', 'H_1', 'H_2', 'C_1'], C_1: ['H_3', 'H_4', 'H_5']},
params: {node: {shape: 'circle', color: 'red'}, edge: {color: 'blue'}}},
{ nodelist: {C_0: {},
H_0: {type: 's', shape: 'record', label: 'foo'},
H_1: {type: 's', style: 'filled', color: 'blue'}, H_2: {type: 's'},
C_1: {type: 's'}, H_3: {type: 's'},
H_4: {type: 's'}, H_5: {type: 's'}},
edgelist: {C_0: ['H_0:f1', 'H_1', 'H_2', 'C_1'], C_1: ['H_3', 'H_4', 'H_5']},
params: {node: {shape: 'circle', color: 'red'}, edge: {color: 'blue'}}},
{ nodelist: {C_0: {},
H_0: {type: 's', shape: 'record', label: 'foo'},
H_1: {type: 's', style: 'filled', color: 'blue'}, H_2: {type: 's'},
C_1: {type: 's'}, H_3: {type: 's'},
H_4: {type: 's'}, H_5: {type: 's'}, B_1: {type: 's', color: 'blue', style: 'filled'}},
edgelist: {C_0: ['H_0:f1', 'H_1', 'H_2', 'C_1'], C_1: ['H_3', 'H_4', 'H_5'], H_1: ['B_1']},
params: {node: {shape: 'circle', color: 'red'}, edge: {color: 'blue'}}},
];
return model
}
TreeViewer = function() //construct the view
{
}
TreeViewer.prototype.init = function(c)
{
this.ctx = c
}
TreeViewer.prototype.render = function(ascene)
{
$('#ancan_div').attr('class','none')
$('#ancan_div').gchart($.gchart.graphviz(true, ascene.nodelist,
ascene.edgelist, ascene.params ))
}

View file

@ -0,0 +1,35 @@
<!DOCTYPE html>
<head>
<script type="text/javascript" src="sortmodels.js"></script>
<script type="text/javascript" src="sortviewers.js"></script>
<script type="text/javascript" src="animationbase.js"></script>
<html>
<body onload="">
<canvas id="ancan" width="400" height="400" style="border:4px solid blue"></canvas>
<br />
<button onclick="init1()">BubbleSort with BarView</button><button onclick="init2()">BubbleSort with ScatterView</button>
<button onclick="init3()">BubbleSort with BoxView</button><br/>
<button onclick="init4()">SelectionSort with BarView</button><button onclick="init5()">SelectionSort with ScatterView</button>
<button onclick="init6()">SelectionSort with BoxView</button><br/>
<button onclick="init7()">InsertionSort with BarView</button><button onclick="init8()">InsertionSort with ScatterView</button>
<button onclick="init9()">InsertionSort with BoxView</button><br/>
<button onclick="init10()">ShellSort with BarView</button><button onclick="init11()">ShellSort with ScatterView</button>
<button onclick="init12()">ShellSort with BoxView</button><br/>
<button onclick="init13()">MergeSort with BarView</button><button onclick="init14()">MergeSort with ScatterView</button>
<button onclick="init15()">MergeSort with BoxView</button><br/>
<button onclick="init16()">QuickSort with BarView</button><button onclick="init17()">QuickSort with ScatterView</button>
<button onclick="init18()">QuickSort with BoxView</button><br/>
<button onclick="a.run()">Run</button>
<button onclick="a.stop()">Stop</button> </br>
<button onclick="a.begin()">Beginning</button>
<button onclick="a.forward()">Step Forward</button>
<button onclick="a.backward()">Step Backward</button>
<button onclick="a.end()">End</button>
</body>
</html>

View file

@ -0,0 +1,22 @@
<!DOCTYPE html>
<head>
<script type "text/javascript" src="sortingdemo.js"></script>
<html>
<body onload="">
<canvas id="sortingcanvas" width="400" height="400" style="border:4px solid blue">
</canvas>
<br />
<button onclick="init()">Initialize</button><br/>
<button onclick="run()">Run</button>
<button onclick="stop()">Stop</button> </br>
<button onclick="begin()">Beginning</button>
<button onclick="forward()">Step Forward</button>
<button onclick="backward()">Step Backward</button>
<button onclick="end()">End</button>
</body>
</html>

View file

@ -0,0 +1,268 @@
BarList = function(hm)
{
this.howmany = hm
this.bars = new Array()
for (var i=0; i<this.howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
abar = new Bar(i,y,"black")
this.bars.push(abar)
}
}
BarList.prototype.size = function()
{
return this.howmany
}
BarList.prototype.show = function(c)
{
for (var idx=0; idx<this.howmany; idx++)
{
this.bars[idx].show(this.c)
}
}
Bar = function(pos, h, col)
{
this.height = h
this.position = pos
this.color = col
}
Bar.prototype.clone=function()
{
var newbar = new Bar() //make a copy
newbar.setHeight(this.getHeight())
newbar.setPosition(this.getPosition())
newbar.setColor(this.getColor())
return newbar
}
Bar.prototype.getHeight=function()
{
return this.height
}
Bar.prototype.setHeight=function(newh)
{
this.height = newh
}
Bar.prototype.getColor=function()
{
return this.color
}
Bar.prototype.getPosition=function()
{
return this.position
}
Bar.prototype.setPosition=function(newp)
{
this.position = newp
}
Bar.prototype.show = function(c,p)
{
c.fillStyle=this.color
c.fillRect(p*7 + 2, c.canvas.height-this.height, 3, this.height)
}
Bar.prototype.unshow = function(c,p)
{
c.clearRect(p*7 + 2, c.canvas.height-this.height, 3, this.height)
}
Bar.prototype.setColor=function(newc)
{
this.color = newc
}
SortingAnimation = function() //Insertion Sort Demo
{
this.timer = null
this.framelist = new Array()
this.cursor = -1
this.sc = document.getElementById("sortingcanvas")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
//commented out code does insertion sort, code below does bubble sort
/* for (var index=1; index < this.barlist.bars.length; index = index+1)
{
this.barlist.bars[index].setColor("blue")
this.snapshot()
this.barlist.bars[index].setColor("black")
this.snapshot()
var currentvalue = this.barlist.bars[index].clone()
var position = index
while (position>0 && (this.barlist.bars[position-1].getHeight() > currentvalue.getHeight()))
{
this.barlist.bars[position-1].setColor("red")
this.snapshot()
this.barlist.bars[position-1].setColor("black")
this.barlist.bars[position] = this.barlist.bars[position-1].clone()
//this.barlist.bars[position-1] = currentvalue
this.barlist.bars[position-1].setHeight(0)
this.snapshot()
position = position-1
}
this.barlist.bars[position] = currentvalue
this.barlist.bars[position].setColor("blue")
this.snapshot()
this.barlist.bars[position].setColor("black")
}
this.snapshot()*/
this.barlist = new BarList(50)
this.snapshot()
for (var passnum=this.barlist.bars.length-1; passnum>0; passnum = passnum-1)
{
for (var i=0; i<passnum; i=i+1)
{
this.barlist.bars[i].setColor("red")
this.barlist.bars[i+1].setColor("red")
this.snapshot()
if (this.barlist.bars[i].getHeight() > this.barlist.bars[i+1].getHeight())
{
var temp = this.barlist.bars[i]
this.barlist.bars[i] = this.barlist.bars[i+1]
this.barlist.bars[i+1] = temp
this.snapshot()
}
this.barlist.bars[i].setColor("black")
this.barlist.bars[i+1].setColor("black")
this.snapshot()
}
}
}
SortingAnimation.prototype.incCursor=function()
{
if (this.cursor < this.framelist.length-1)
this.cursor = this.cursor + 1
}
SortingAnimation.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
SortingAnimation.prototype.getCursor=function()
{
return this.cursor
}
SortingAnimation.prototype.setCursor=function(newc)
{
this.cursor = newc
}
SortingAnimation.prototype.render = function(framenum)
{
var currentframe = this.framelist[framenum]
this.sc.width = this.sc.width
for (var idx=0; idx<currentframe.length; idx++)
{
currentframe[idx].show(this.ctx,idx)
}
}
SortingAnimation.prototype.snapshot = function()
{
var newframe = new Array()
for (var idx=0; idx<this.barlist.bars.length; idx++)
{
var newbar = new Bar() //make a copy
newbar.setHeight(this.barlist.bars[idx].getHeight())
newbar.setPosition(this.barlist.bars[idx].getPosition())
newbar.setColor(this.barlist.bars[idx].getColor())
newframe.push(newbar)
}
this.framelist.push(newframe)
}
run = function()
{
if (sa.timer == null)
sa.timer = setInterval("forward()",sa.speed)
}
stop = function()
{
clearInterval(sa.timer)
sa.timer=null
}
forward = function()
{
sa.incCursor()
sa.render(sa.getCursor())
if (sa.getCursor() == sa.framelist.length-1 && sa.timer != null)
{
clearInterval(sa.timer)
sa.timer = null
}
}
backward = function()
{
sa.decCursor()
sa.render(sa.getCursor())
}
end = function()
{
sa.setCursor(sa.framelist.length-1)
sa.render(sa.getCursor())
}
begin = function()
{
sa.setCursor(0)
sa.render(sa.getCursor())
}
init = function()
{
sa = new SortingAnimation()
sa.snapshot()
sa.render(0)
}

View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<head>
<script type "text/javascript" src="sortingpackage.js"></script>
<html>
<body onload="">
<canvas id="ancan" width="400" height="400" style="border:4px solid blue"></canvas>
<br />
<button onclick="init1()">BubbleSort with BarView</button><button onclick="init2()">BubbleSort with ScatterView</button>
<button onclick="init3()">BubbleSort with BoxView</button><br/>
<button onclick="init4()">SelectionSort with BarView</button><button onclick="init5()">SelectionSort with ScatterView</button>
<button onclick="init6()">SelectionSort with BoxView</button><br/>
<button onclick="init7()">InsertionSort with BarView</button><button onclick="init8()">InsertionSort with ScatterView</button>
<button onclick="init9()">InsertionSort with BoxView</button><br/>
<button onclick="init10()">ShellSort with BarView</button><button onclick="init11()">ShellSort with ScatterView</button>
<button onclick="init12()">ShellSort with BoxView</button><br/>
<button onclick="init13()">MergeSort with BarView</button><button onclick="init14()">MergeSort with ScatterView</button>
<button onclick="init15()">MergeSort with BoxView</button><br/>
<button onclick="init16()">QuickSort with BarView</button><button onclick="init17()">QuickSort with ScatterView</button>
<button onclick="init18()">QuickSort with BoxView</button><br/>
<button onclick="a.run()">Run</button>
<button onclick="a.stop()">Stop</button> </br>
<button onclick="a.begin()">Beginning</button>
<button onclick="a.forward()">Step Forward</button>
<button onclick="a.backward()">Step Backward</button>
<button onclick="a.end()">End</button>
</body>
</html>

View file

@ -0,0 +1,870 @@
DataItem = function(pos, h, col)
{
this.height = h
this.position = pos
this.color = col
}
DataItem.prototype.clone=function()
{
var newitem = new DataItem(this.position,this.height,this.color) //make a copy
return newitem
}
DataItem.prototype.getHeight=function()
{
return this.height
}
DataItem.prototype.getColor=function()
{
return this.color
}
DataItem.prototype.getPosition=function()
{
return this.position
}
DataItem.prototype.setHeight=function(newh)
{
this.height = newh
}
DataItem.prototype.setPosition=function(newp)
{
this.position = newp
}
DataItem.prototype.setColor=function(newc)
{
this.color = newc
}
BubbleSortModel = function() //construct the model
{
}
BubbleSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var passnum=this.valuelist.length-1; passnum>0; passnum = passnum-1)
{
for (var i=0; i<passnum; i=i+1)
{
this.valuelist[i].setColor("red")
this.valuelist[i+1].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getHeight() > this.valuelist[i+1].getHeight())
{
var temp = this.valuelist[i]
this.valuelist[i] = this.valuelist[i+1]
this.valuelist[i+1] = temp
this.script.push(this.makescene())
}
this.valuelist[i].setColor("black")
this.valuelist[i+1].setColor("black")
this.script.push(this.makescene())
}
}
return this.script
}
BubbleSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
InsertionSortModel = function()
{
}
InsertionSortModel.prototype.init=function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var index=1; index < this.valuelist.length; index = index+1)
{
this.valuelist[index].setColor("blue")
this.script.push(this.makescene())
this.valuelist[index].setColor("black")
this.script.push(this.makescene())
var currentvalue = this.valuelist[index].clone()
var position = index
while (position>0 && (this.valuelist[position-1].getHeight() > currentvalue.getHeight()))
{
this.valuelist[position-1].setColor("red")
this.script.push(this.makescene())
this.valuelist[position-1].setColor("black")
this.valuelist[position] = this.valuelist[position-1].clone()
//this.barlist.bars[position-1] = currentvalue
this.valuelist[position-1].setHeight(0)
this.script.push(this.makescene())
position = position-1
}
this.valuelist[position] = currentvalue
this.valuelist[position].setColor("blue")
this.script.push(this.makescene())
this.valuelist[position].setColor("black")
}
this.script.push(this.makescene())
return this.script
}
InsertionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
SelectionSortModel = function() //construct the model
{
}
SelectionSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var fillslot=this.valuelist.length-1; fillslot>0; fillslot = fillslot-1)
{ var positionOfMax=0
this.valuelist[positionOfMax].setColor("yellow")
this.valuelist[fillslot].setColor("blue")
this.script.push(this.makescene())
for (var i=1; i<fillslot+1; i=i+1)
{
this.valuelist[i].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getHeight() > this.valuelist[positionOfMax].getHeight())
{
this.valuelist[positionOfMax].setColor("black")
positionOfMax = i
this.valuelist[i].setColor("yellow")
this.script.push(this.makescene())
}
else
{
this.valuelist[i].setColor("black")
this.script.push(this.makescene())
}
}
var temp = this.valuelist[fillslot]
this.valuelist[fillslot] = this.valuelist[positionOfMax]
this.valuelist[positionOfMax] = temp
this.script.push(this.makescene())
this.valuelist[fillslot].setColor("black")
this.script.push(this.makescene())
}
return this.script
}
SelectionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
ShellSortModel = function() //construct the model
{
}
ShellSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
var sublistcount = Math.floor(this.valuelist.length/2)
while (sublistcount > 0)
{
for (var startposition = 0; startposition < sublistcount;
startposition = startposition+1)
{
var gap = sublistcount
var start = startposition
this.valuelist[start].setColor("red")
for (var i=start+gap; i<this.valuelist.length; i = i + gap)
{
currentvalue = this.valuelist[i].clone()
currentvalue.setColor("red")
this.script.push(this.makescene())
position = i
while (position>=gap && this.valuelist[position-gap].getHeight()>currentvalue.getHeight())
{
this.valuelist[position] = this.valuelist[position-gap].clone()
this.valuelist[position-gap].setHeight(0)
position = position-gap
this.script.push(this.makescene())
}
this.valuelist[position]=currentvalue
this.script.push(this.makescene())
}
for (var clearidx=0; clearidx<this.valuelist.length; clearidx++)
this.valuelist[clearidx].setColor("black")
this.script.push(this.makescene())
}
this.script.push(this.makescene())
sublistcount = Math.floor(sublistcount/2)
}
this.script.push(this.makescene())
return this.script
}
ShellSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
MergeSortModel = function() //construct the model
{
}
MergeSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.domergesort(0,this.valuelist.length-1)
this.script.push(this.makescene(this.valuelist))
return this.script
}
MergeSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
MergeSortModel.prototype.domergesort = function(start,end)
{ len = end-start + 1
if (len>1)
{
var mid = start + Math.floor(len/2)
this.chunkcolor(start,mid-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,mid-1,"black")
this.domergesort(start,mid-1)
this.chunkcolor(mid,end,"blue")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(mid,end,"black")
this.domergesort(mid,end)
var i=start
var j=mid
var newlist = Array()
while (i<mid && j<=end)
{
if (this.valuelist[i].getHeight()<this.valuelist[j].getHeight())
{
newlist.push(this.valuelist[i])
i=i+1
}
else
{
newlist.push(this.valuelist[j])
j=j+1
}
}
while (i<mid)
{
newlist.push(this.valuelist[i])
i=i+1
}
while (j<=end)
{
newlist.push(this.valuelist[j])
j=j+1
}
this.copyback(newlist,start)
this.chunkcolor(start,end,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,end,"black")
}
}
MergeSortModel.prototype.copyback = function(original,i,j) //make copy from i to j excl
{
var newcopy = new Array()
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i=i+1
}
}
MergeSortModel.prototype.makecopy = function(original,i) //make copy to i
{
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i++
}
return newcopy
}
MergeSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
QuickSortModel = function() //construct the model
{
}
QuickSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.quickSort(this.valuelist)
this.script.push(this.makescene(this.valuelist))
return this.script
}
QuickSortModel.prototype.quickSort=function(alist)
{
this.quickSortHelper(alist,0,alist.length-1)
}
QuickSortModel.prototype.quickSortHelper=function(alist,first,last)
{
if (first<last)
{
var splitpoint = this.partition(alist,first,last)
this.chunkcolor(first,splitpoint-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(first,splitpoint-1,"black")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"black")
this.script.push(this.makescene(this.valuelist))
this.quickSortHelper(alist,first,splitpoint-1)
this.quickSortHelper(alist,splitpoint+1,last)
}
}
QuickSortModel.prototype.partition = function(alist,first,last)
{
var pivotvalue = alist[first].getHeight()
alist[first].setColor("red")
this.script.push(this.makescene(this.valuelist))
var leftmark = first+1
var rightmark = last
alist[leftmark].setColor("blue")
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))
var done = false
while (! done)
{
while (leftmark <= rightmark && alist[leftmark].getHeight() <= pivotvalue)
{
alist[leftmark].setColor("black")
leftmark = leftmark + 1
if (leftmark <= rightmark)
{
alist[leftmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
while (alist[rightmark].getHeight() >= pivotvalue && rightmark >= leftmark)
{
alist[rightmark].setColor("black")
rightmark = rightmark - 1
if (rightmark >= leftmark)
{
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
if (rightmark < leftmark)
done = true
else
{
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
this.script.push(this.makescene(this.valuelist))
alist[leftmark].setColor("black")
alist[rightmark].setColor("black")
}
}
var temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
alist[first].setColor("black")
alist[rightmark].setColor("red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(0,this.valuelist.length-1,"black")
this.script.push(this.makescene(this.valuelist))
return rightmark
}
QuickSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
QuickSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
BarViewer = function() //construct the view
{
}
BarViewer.prototype.init = function(c)
{
this.ctx = c
}
BarViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillRect(p*7 + 2,
this.ctx.canvas.height-ascene[p].height,
3,
ascene[p].height)
}
}
ScatterViewer = function() //contruct a list of numbers view
{
}
ScatterViewer.prototype.init = function(c)
{
this.ctx = c
}
ScatterViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].height, p*7 + 2,
this.ctx.canvas.height-ascene[p].height)
}
}
BoxViewer = function() //contruct an array of boxes view
{
}
BoxViewer.prototype.init = function(c)
{
this.ctx = c
}
BoxViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].height, p*25 + 3, 200)
this.ctx.strokeStyle = ascene[p].color
this.ctx.strokeRect(p*25+2,185,25,25)
}
}
Animator = function(m, v)
{
this.model = m
this.viewer = v
this.timer = null
this.cursor = -1
this.sc = document.getElementById("ancan")
this.ctx = this.sc.getContext("2d")
this.sc.width = this.sc.width
this.speed = 75
this.script = this.model.init() //does the sort and sends script back
this.viewer.init(this.ctx)
}
Animator.prototype.getContext=function()
{
return this.ctx
}
Animator.prototype.incCursor=function()
{
if (this.cursor < this.script.length-1)
this.cursor = this.cursor + 1
}
Animator.prototype.decCursor=function()
{
if (this.cursor > 0)
this.cursor = this.cursor -1
}
Animator.prototype.getCursor=function()
{
return this.cursor
}
Animator.prototype.setCursor=function(newc)
{
this.cursor = newc
}
Animator.prototype.run = function()
{
if (this.timer == null)
this.timer = setInterval("a.forward()",this.speed)
}
Animator.prototype.stop = function()
{
clearInterval(this.timer)
this.timer=null
}
Animator.prototype.forward = function()
{
this.incCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
if (this.getCursor() == this.script.length-1 && this.timer != null)
{
clearInterval(this.timer)
this.timer = null
}
}
Animator.prototype.backward = function()
{
this.decCursor()
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.end = function()
{
this.setCursor(this.script.length-1)
this.sc.width = this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.begin = function()
{
this.setCursor(0)
this.sc.width=this.sc.width
this.viewer.render(this.script[this.getCursor()])
}
Animator.prototype.init = function()
{
this.setCursor(0)
this.sc.width = this.sc.width
this.viewer.render(this.script[0])
}
init1 = function()
{
a = new Animator(new BubbleSortModel(), new BarViewer())
a.init()
}
init2 = function()
{
a = new Animator(new BubbleSortModel(), new ScatterViewer())
a.init()
}
init3 = function()
{
a = new Animator(new BubbleSortModel(), new BoxViewer())
a.init()
}
init4 = function()
{
a = new Animator(new SelectionSortModel(), new BarViewer())
a.init()
}
init5 = function()
{
a = new Animator(new SelectionSortModel(), new ScatterViewer())
a.init()
}
init6 = function()
{
a = new Animator(new SelectionSortModel(), new BoxViewer())
a.init()
}
init7 = function()
{
a = new Animator(new InsertionSortModel(), new BarViewer())
a.init()
}
init8 = function()
{
a = new Animator(new InsertionSortModel(), new ScatterViewer())
a.init()
}
init9 = function()
{
a = new Animator(new InsertionSortModel(), new BoxViewer())
a.init()
}
init10 = function()
{
a = new Animator(new ShellSortModel(), new BarViewer())
a.init()
}
init11 = function()
{
a = new Animator(new ShellSortModel(), new ScatterViewer())
a.init()
}
init12 = function()
{
a = new Animator(new ShellSortModel(), new BoxViewer())
a.init()
}
init13 = function()
{
a = new Animator(new MergeSortModel(), new BarViewer())
a.init()
}
init14 = function()
{
a = new Animator(new MergeSortModel(), new ScatterViewer())
a.init()
}
init15 = function()
{
a = new Animator(new MergeSortModel(), new BoxViewer())
a.init()
}
init16 = function()
{
a = new Animator(new QuickSortModel(), new BarViewer())
a.init()
}
init17 = function()
{
a = new Animator(new QuickSortModel(), new ScatterViewer())
a.init()
}
init18 = function()
{
a = new Animator(new QuickSortModel(), new BoxViewer())
a.init()
}

View file

@ -0,0 +1,601 @@
DataItem = function(pos, h, col)
{
this.value = h
this.position = pos
this.color = col
}
DataItem.prototype.clone=function()
{
var newitem = new DataItem(this.position,this.value,this.color) //make a copy
return newitem
}
DataItem.prototype.getValue=function()
{
return this.value
}
DataItem.prototype.getColor=function()
{
return this.color
}
DataItem.prototype.getPosition=function()
{
return this.position
}
DataItem.prototype.setValue=function(newh)
{
this.value = newh
}
DataItem.prototype.setPosition=function(newp)
{
this.position = newp
}
DataItem.prototype.setColor=function(newc)
{
this.color = newc
}
BubbleSortModel = function() //construct the model
{
}
BubbleSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var passnum=this.valuelist.length-1; passnum>0; passnum = passnum-1)
{
for (var i=0; i<passnum; i=i+1)
{
this.valuelist[i].setColor("red")
this.valuelist[i+1].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getValue() > this.valuelist[i+1].getValue())
{
var temp = this.valuelist[i]
this.valuelist[i] = this.valuelist[i+1]
this.valuelist[i+1] = temp
this.script.push(this.makescene())
}
this.valuelist[i].setColor("black")
this.valuelist[i+1].setColor("black")
this.script.push(this.makescene())
}
}
return this.script
}
BubbleSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
InsertionSortModel = function()
{
}
InsertionSortModel.prototype.init=function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var index=1; index < this.valuelist.length; index = index+1)
{
this.valuelist[index].setColor("blue")
this.script.push(this.makescene())
this.valuelist[index].setColor("black")
this.script.push(this.makescene())
var currentvalue = this.valuelist[index].clone()
var position = index
while (position>0 && (this.valuelist[position-1].getValue() > currentvalue.getValue()))
{
this.valuelist[position-1].setColor("red")
this.script.push(this.makescene())
this.valuelist[position-1].setColor("black")
this.valuelist[position] = this.valuelist[position-1].clone()
//this.barlist.bars[position-1] = currentvalue
this.valuelist[position-1].setValue(0)
this.script.push(this.makescene())
position = position-1
}
this.valuelist[position] = currentvalue
this.valuelist[position].setColor("blue")
this.script.push(this.makescene())
this.valuelist[position].setColor("black")
}
this.script.push(this.makescene())
return this.script
}
InsertionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
SelectionSortModel = function() //construct the model
{
}
SelectionSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
for (var fillslot=this.valuelist.length-1; fillslot>0; fillslot = fillslot-1)
{ var positionOfMax=0
this.valuelist[positionOfMax].setColor("yellow")
this.valuelist[fillslot].setColor("blue")
this.script.push(this.makescene())
for (var i=1; i<fillslot+1; i=i+1)
{
this.valuelist[i].setColor("red")
this.script.push(this.makescene())
if (this.valuelist[i].getValue() > this.valuelist[positionOfMax].getValue())
{
this.valuelist[positionOfMax].setColor("black")
positionOfMax = i
this.valuelist[i].setColor("yellow")
this.script.push(this.makescene())
}
else
{
this.valuelist[i].setColor("black")
this.script.push(this.makescene())
}
}
var temp = this.valuelist[fillslot]
this.valuelist[fillslot] = this.valuelist[positionOfMax]
this.valuelist[positionOfMax] = temp
this.script.push(this.makescene())
this.valuelist[fillslot].setColor("black")
this.script.push(this.makescene())
}
return this.script
}
SelectionSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
ShellSortModel = function() //construct the model
{
}
ShellSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene())
var sublistcount = Math.floor(this.valuelist.length/2)
while (sublistcount > 0)
{
for (var startposition = 0; startposition < sublistcount;
startposition = startposition+1)
{
var gap = sublistcount
var start = startposition
this.valuelist[start].setColor("red")
for (var i=start+gap; i<this.valuelist.length; i = i + gap)
{
currentvalue = this.valuelist[i].clone()
currentvalue.setColor("red")
this.script.push(this.makescene())
position = i
while (position>=gap && this.valuelist[position-gap].getValue()>currentvalue.getValue())
{
this.valuelist[position] = this.valuelist[position-gap].clone()
this.valuelist[position-gap].setValue(0)
position = position-gap
this.script.push(this.makescene())
}
this.valuelist[position]=currentvalue
this.script.push(this.makescene())
}
for (var clearidx=0; clearidx<this.valuelist.length; clearidx++)
this.valuelist[clearidx].setColor("black")
this.script.push(this.makescene())
}
this.script.push(this.makescene())
sublistcount = Math.floor(sublistcount/2)
}
this.script.push(this.makescene())
return this.script
}
ShellSortModel.prototype.makescene = function()
{
var newscene = new Array()
for (var idx=0; idx<this.valuelist.length; idx++)
{
var item = this.valuelist[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
MergeSortModel = function() //construct the model
{
}
MergeSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.domergesort(0,this.valuelist.length-1)
this.script.push(this.makescene(this.valuelist))
return this.script
}
MergeSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
MergeSortModel.prototype.domergesort = function(start,end)
{ len = end-start + 1
if (len>1)
{
var mid = start + Math.floor(len/2)
this.chunkcolor(start,mid-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,mid-1,"black")
this.domergesort(start,mid-1)
this.chunkcolor(mid,end,"blue")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(mid,end,"black")
this.domergesort(mid,end)
var i=start
var j=mid
var newlist = Array()
while (i<mid && j<=end)
{
if (this.valuelist[i].getValue()<this.valuelist[j].getValue())
{
newlist.push(this.valuelist[i])
i=i+1
}
else
{
newlist.push(this.valuelist[j])
j=j+1
}
}
while (i<mid)
{
newlist.push(this.valuelist[i])
i=i+1
}
while (j<=end)
{
newlist.push(this.valuelist[j])
j=j+1
}
this.copyback(newlist,start)
this.chunkcolor(start,end,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(start,end,"black")
}
}
MergeSortModel.prototype.copyback = function(original,i,j) //make copy from i to j excl
{
var newcopy = new Array()
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i=i+1
}
}
MergeSortModel.prototype.makecopy = function(original,i) //make copy to i
{
for (var idx=0; idx<original.length; idx++)
{
var item = original[idx].clone() //make a copy
this.valuelist[i] = item
i++
}
return newcopy
}
MergeSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}
QuickSortModel = function() //construct the model
{
}
QuickSortModel.prototype.init = function(ctl)
{
this.mycontroller = ctl
this.valuelist = new Array()
var howmany = 15
for (var i=0; i<howmany; i++)
{
var min = 5
var max = 300
var y = Math.floor(Math.random() * (max - min + 1)) + min
var item = new DataItem(i,y,"black")
this.valuelist.push(item)
}
this.script = new Array()
this.script.push(this.makescene(this.valuelist))
this.quickSort(this.valuelist)
this.script.push(this.makescene(this.valuelist))
return this.script
}
QuickSortModel.prototype.quickSort=function(alist)
{
this.quickSortHelper(alist,0,alist.length-1)
}
QuickSortModel.prototype.quickSortHelper=function(alist,first,last)
{
if (first<last)
{
var splitpoint = this.partition(alist,first,last)
this.chunkcolor(first,splitpoint-1,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(first,splitpoint-1,"black")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(splitpoint+1,last,"black")
this.script.push(this.makescene(this.valuelist))
this.quickSortHelper(alist,first,splitpoint-1)
this.quickSortHelper(alist,splitpoint+1,last)
}
}
QuickSortModel.prototype.partition = function(alist,first,last)
{
var pivotvalue = alist[first].getValue()
alist[first].setColor("red")
this.script.push(this.makescene(this.valuelist))
var leftmark = first+1
var rightmark = last
alist[leftmark].setColor("blue")
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))
var done = false
while (! done)
{
while (leftmark <= rightmark && alist[leftmark].getValue() <= pivotvalue)
{
alist[leftmark].setColor("black")
leftmark = leftmark + 1
if (leftmark <= rightmark)
{
alist[leftmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
while (alist[rightmark].getValue() >= pivotvalue && rightmark >= leftmark)
{
alist[rightmark].setColor("black")
rightmark = rightmark - 1
if (rightmark >= leftmark)
{
alist[rightmark].setColor("blue")
this.script.push(this.makescene(this.valuelist))}
}
if (rightmark < leftmark)
done = true
else
{
temp = alist[leftmark]
alist[leftmark] = alist[rightmark]
alist[rightmark] = temp
this.script.push(this.makescene(this.valuelist))
alist[leftmark].setColor("black")
alist[rightmark].setColor("black")
}
}
var temp = alist[first]
alist[first] = alist[rightmark]
alist[rightmark] = temp
alist[first].setColor("black")
alist[rightmark].setColor("red")
this.script.push(this.makescene(this.valuelist))
this.chunkcolor(0,this.valuelist.length-1,"black")
this.script.push(this.makescene(this.valuelist))
return rightmark
}
QuickSortModel.prototype.chunkcolor=function(start,end,c)
{
for (var clearidx=start; clearidx<=end; clearidx++)
this.valuelist[clearidx].setColor(c)
}
QuickSortModel.prototype.makescene = function(somearray)
{
var newscene = new Array()
for (var idx=0; idx<somearray.length; idx++)
{
var item = somearray[idx].clone() //make a copy
newscene.push(item)
}
return newscene
}

View file

@ -0,0 +1,61 @@
BarViewer = function() //construct the view
{
}
BarViewer.prototype.init = function(c)
{
this.ctx = c
}
BarViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillRect(p*7 + 2,
this.ctx.canvas.height-ascene[p].getValue(),
3,
ascene[p].getValue())
}
}
ScatterViewer = function() //contruct a list of numbers view
{
}
ScatterViewer.prototype.init = function(c)
{
this.ctx = c
}
ScatterViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].getValue(), p*7 + 2,
this.ctx.canvas.height-ascene[p].getValue())
}
}
BoxViewer = function() //contruct an array of boxes view
{
}
BoxViewer.prototype.init = function(c)
{
this.ctx = c
}
BoxViewer.prototype.render = function(ascene)
{
for (var p=0; p<ascene.length; p++)
{
this.ctx.fillStyle=ascene[p].color
this.ctx.fillText(ascene[p].getValue(), p*25 + 3, 200)
this.ctx.strokeStyle = ascene[p].color
this.ctx.strokeRect(p*25+2,185,25,25)
}
}

View file

@ -0,0 +1,2 @@
from .assess import *

View file

@ -0,0 +1,112 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from assessbase import Assessment
from multiplechoice import *
from textfield import *
from blankfill import *
import json
import random
def setup(app):
app.add_directive('mchoicemf',MChoiceMF)
app.add_directive('mchoicema',MChoiceMA)
app.add_directive('fillintheblank',FillInTheBlank)
app.add_directive('mcmfrandom',MChoiceRandomMF)
app.add_directive('addbutton',AddButton)
app.add_directive('qnum',QuestionNumber)
app.add_role('textfield',textfield_role)
app.add_javascript('assess.js')
app.add_node(MChoiceNode, html=(visit_mc_node, depart_mc_node))
app.add_node(FITBNode, html=(visit_fitb_node, depart_fitb_node))
class AddButton(Directive):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
def run(self):
"""
:param self:
:return:
.. addbutton:: bname
...
"""
TEMPLATE_START = '''
<div id="%(divid)s" class="alert alert-warning">
<form name="%(divid)s_form" method="get" action="" onsubmit="return false;">
'''
TEMPLATE_END = '''
<button class='btn btn-inverse' name="reset" onclick="resetPage('%(divid)s')">Forget My Answers</button>
</form>
</div>
'''
self.options['divid'] = self.arguments[0]
res = ""
res = TEMPLATE_START % self.options
res += TEMPLATE_END % self.options
return [nodes.raw('',res , format='html')]
class QuestionNumber(Directive):
"""Set Parameters for Question Numbering"""
required_arguments = 0
optional_arguments = 3
has_content = False
option_spec = { 'prefix': directives.unchanged,
'suffix': directives.unchanged,
'start': directives.positive_int
}
def run(self):
env = self.state.document.settings.env
if 'start' in self.options:
env.assesscounter = self.options['start'] - 1
if 'prefix' in self.options:
env.assessprefix = self.options['prefix']
if 'suffix' in self.options:
env.assesssuffix = self.options['suffix']
return []
#####################

View file

@ -0,0 +1,95 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
_base_js_escapes = (
('\\', r'\u005C'),
('\'', r'\u0027'),
('"', r'\u0022'),
("'", r'\u0027'),
('>', r'\u003E'),
('<', r'\u003C'),
('&', r'\u0026'),
('=', r'\u003D'),
('-', r'\u002D'),
(';', r'\u003B'),
(u'\u2028', r'\u2028'),
(u'\u2029', r'\u2029')
)
# Escape every ASCII character with a value less than 32.
_js_escapes = (_base_js_escapes +
tuple([('%c' % z, '\\u%04X' % z) for z in range(32)]))
# escapejs from Django: https://www.djangoproject.com/
def escapejs(value):
"""Hex encodes characters for use in JavaScript strings."""
if not isinstance(value, basestring):
value = str(value)
for bad, good in _js_escapes:
value = value.replace(bad, good)
return value
class Assessment(Directive):
"""Base Class for assessments"""
def getNumber(self):
env = self.state.document.settings.env
if not hasattr(env,'assesscounter'):
env.assesscounter = 0
env.assesscounter += 1
res = "Q-%d"
if hasattr(env,'assessprefix'):
res = env.assessprefix + "%d"
res = res % env.assesscounter
if hasattr(env, 'assesssuffix'):
res += env.assesssuffix
return res
def run(self):
self.options['qnumber'] = self.getNumber()
self.options['divid'] = self.arguments[0]
if self.content[0][:2] == '..': # first line is a directive
self.content[0] = self.options['qnumber'] + ': \n\n' + self.content[0]
else:
self.content[0] = self.options['qnumber'] + ': ' + self.content[0]
if self.content:
if 'iscode' in self.options:
self.options['bodytext'] = '<pre>' + "\n".join(self.content) + '</pre>'
else:
self.options['bodytext'] = "\n".join(self.content)
else:
self.options['bodytext'] = '\n'

View file

@ -0,0 +1,123 @@
# Copyright (C) 2013 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from assessbase import *
import json
import random
class FITBNode(nodes.General, nodes.Element):
def __init__(self,content):
"""
Arguments:
- `self`:
- `content`:
"""
super(FITBNode,self).__init__()
self.fitb_options = content
def visit_fitb_node(self,node):
res = node.template_start % node.fitb_options
self.body.append(res)
def depart_fitb_node(self,node):
fbl = []
for k in sorted(node.fitb_options.keys()):
if 'feedback' in k:
pair = eval(node.fitb_options[k])
p1 = escapejs(pair[1])
newpair = (pair[0],p1)
fbl.append(newpair)
if 'casei' in node.fitb_options:
node.fitb_options['casei'] = 'true'
else:
node.fitb_options['casei'] = 'false'
node.fitb_options['fbl'] = json.dumps(fbl).replace('"',"'")
res = ""
res += node.template_end % node.fitb_options
self.body.append(res)
class FillInTheBlank(Assessment):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
option_spec = {'correct':directives.unchanged,
'feedback':directives.unchanged,
'feedback1':directives.unchanged,
'feedback2':directives.unchanged,
'feedback3':directives.unchanged,
'feedback4':directives.unchanged,
'blankid':directives.unchanged,
'iscode':directives.flag,
'casei':directives.flag # case insensitive matching
}
def run(self):
"""
process the fillintheblank directive and generate html for output.
:param self:
:return:
.. fillintheblank:: qname
:iscode: boolean
:correct: somestring
:feedback: -- displayed if wrong
:feedback: ('.*', 'this is the message')
Question text
...
"""
TEMPLATE_START = '''
<div id="%(divid)s" class="alert alert-warning">
'''
TEMPLATE_END = '''
<script>
$(document).ready(function(){checkPreviousFIB('%(divid)s');});
</script>
<button class='btn btn-success' name="do answer" onclick="checkFIBStorage('%(divid)s', '%(blankid)s', '%(correct)s',%(fbl)s, %(casei)s)">Check Me</button>
<button class='btn btn-default' id="%(divid)s_bcomp" disabled name="compare" onclick="compareFITBAnswers('%(divid)s');">Compare Me</button>
<br />
<br />
<div id="%(divid)s_feedback">
</div>
</div>
'''
super(FillInTheBlank,self).run()
fitbNode = FITBNode(self.options)
fitbNode.template_start = TEMPLATE_START
fitbNode.template_end = TEMPLATE_END
self.state.nested_parse(self.content, self.content_offset, fitbNode)
return [fitbNode]

View file

@ -0,0 +1 @@
var checkMe=function(a,b,c){var d,e=document.forms[a+"_form"].elements.group1;for(var f=e.length-1;f>=0;f--)e[f].checked&&(d=e[f].value);feedBack("#"+a+"_feedback",d==b,c)},feedBack=function(a,b,c){b?$(a).html("You are Correct!"):$(a).html("Inorrect. "+c)};

View file

@ -0,0 +1,352 @@
# Copyright (C) 2013 Bradley N. Miller, Barabara Ericson
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from assessbase import *
import json
import random
class MChoiceNode(nodes.General, nodes.Element):
def __init__(self,content):
"""
Arguments:
- `self`:
- `content`:
"""
super(MChoiceNode,self).__init__()
self.mc_options = content
def visit_mc_node(self,node):
res = ""
res = node.template_start % node.mc_options
self.body.append(res)
def depart_mc_node(self,node):
res = node.template_form_start % node.mc_options
feedbackStr = "["
currFeedback = ""
# Add all of the possible answers
okeys = node.mc_options.keys()
okeys.sort()
for k in okeys:
if 'answer_' in k:
x,label = k.split('_')
node.mc_options['alabel'] = label
node.mc_options['atext'] = node.mc_options[k]
res += node.template_option % node.mc_options
currFeedback = "feedback_" + label
feedbackStr = feedbackStr + "'" + escapejs(node.mc_options[currFeedback]) + "', "
# store the feedback array with key feedback minus last comma
node.mc_options['feedback'] = feedbackStr[0:-2] + "]"
res += node.template_end % node.mc_options
self.body.append(res)
#####################
# multiple choice question with multiple feedback
# author - Barb Ericson
# author - Anusha
class MChoiceMF(Assessment):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
option_spec = {'answer_a':directives.unchanged,
'answer_b':directives.unchanged,
'answer_c':directives.unchanged,
'answer_d':directives.unchanged,
'answer_e':directives.unchanged,
'correct':directives.unchanged,
'feedback_a':directives.unchanged,
'feedback_b':directives.unchanged,
'feedback_c':directives.unchanged,
'feedback_d':directives.unchanged,
'feedback_e':directives.unchanged,
'iscode':directives.flag
}
def run(self):
"""
process the multiplechoice directive and generate html for output.
:param self:
:return:
.. mcmfstorage:: qname
:iscode: boolean
:answer_a: possible answer -- what follows _ is label
:answer_b: possible answer
...
:answer_e: possible answer
:correct: leter of correct answer
:feedback_a: displayed if a is picked
:feedback_b: displayed if b is picked
:feedback_c: displayed if c is picked
:feedback_d: displayed if d is picked
:feedback_e: displayed if e is picked
Question text
...
"""
TEMPLATE_START = '''
<div id="%(divid)s" class="alert alert-warning">
'''
OPTION = '''
<input type="radio" name="group1" value="%(alabel)s" id="%(divid)s_opt_%(alabel)s" />
<label for= "%(divid)s_opt_%(alabel)s"> %(alabel)s) %(atext)s</label><br />
'''
TEMPLATE_END = '''
<script>
$(document).ready(function(){checkRadio('%(divid)s');});
</script>
<button class='btn btn-success' name="do answer" onclick="checkMCMFStorage('%(divid)s','%(correct)s',%(feedback)s)">Check Me</button>
<button class='btn btn-default' id="%(divid)s_bcomp" disabled name="compare" onclick="compareAnswers('%(divid)s');">Compare Me</button>
</form><br />
<div id="%(divid)s_feedback">
</div>
</div>
'''
super(MChoiceMF,self).run()
mcNode = MChoiceNode(self.options)
mcNode.template_start = TEMPLATE_START
mcNode.template_form_start = '''<form name="%(divid)s_form" method="get" action="" onsubmit="return false;">'''
mcNode.template_option = OPTION
mcNode.template_end = TEMPLATE_END
self.state.nested_parse(self.content, self.content_offset, mcNode)
return [mcNode]
#####################
# multiple choice question with multiple correct answers
# author - Barb Ericson
class MChoiceMA(Assessment):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
option_spec = {'answer_a':directives.unchanged,
'answer_b':directives.unchanged,
'answer_c':directives.unchanged,
'answer_d':directives.unchanged,
'answer_e':directives.unchanged,
'correct':directives.unchanged,
'feedback_a':directives.unchanged,
'feedback_b':directives.unchanged,
'feedback_c':directives.unchanged,
'feedback_d':directives.unchanged,
'feedback_e':directives.unchanged,
'iscode':directives.flag
}
def run(self):
"""
process the multiplechoice directive and generate html for output.
:param self:
:return:
.. mchoicemf:: qname
:iscode: boolean
:answer_a: possible answer -- what follows _ is label
:answer_b: possible answer
...
:answer_e: possible answer
:correct: comma seperated list of correct values a, b, c
:feedback_a: displayed if a is picked
:feedback_b: displayed if b is picked
:feedback_c: displayed if c is picked
:feedback_d: displayed if d is picked
:feedback_e: displayed if e is picked
Question text
...
"""
TEMPLATE_START = '''
<div id="%(divid)s" class="alert alert-warning">
'''
OPTION = '''
<input type="checkbox" name="group1" value="%(alabel)s" id="%(divid)s_opt_%(alabel)s" />
<label for= "%(divid)s_opt_%(alabel)s"> %(alabel)s) %(atext)s</label><br />
'''
TEMPLATE_END = '''
<script>
$(document).ready(function(){checkMultipleSelect('%(divid)s');});
</script>
<button class='btn btn-success' name="do answer" onclick="checkMCMAStorage('%(divid)s','%(correct)s',%(feedback)s)">Check Me</button>
<button class='btn btn-default' id="%(divid)s_bcomp" disabled name="compare" onclick="compareAnswers('%(divid)s');">Compare Me</button>
</form><br />
<div id="%(divid)s_feedback">
</div>
</div>
'''
super(MChoiceMA,self).run()
mcNode = MChoiceNode(self.options)
mcNode.template_start = TEMPLATE_START
mcNode.template_form_start = '''<form name="%(divid)s_form" method="get" action="" onsubmit="return false;">'''
mcNode.template_option = OPTION
mcNode.template_end = TEMPLATE_END
self.state.nested_parse(self.content, self.content_offset, mcNode)
return [mcNode]
################################
#####################
# display a multiple choice question with feedback that randomizes the answers
class MChoiceRandomMF(Assessment):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
option_spec = {'answer_a':directives.unchanged,
'answer_b':directives.unchanged,
'answer_c':directives.unchanged,
'answer_d':directives.unchanged,
'answer_e':directives.unchanged,
'correct':directives.unchanged,
'feedback_a':directives.unchanged,
'feedback_b':directives.unchanged,
'feedback_c':directives.unchanged,
'feedback_d':directives.unchanged,
'feedback_e':directives.unchanged,
'iscode':directives.flag
}
def run(self):
"""
process the multiplechoice directive and generate html for output.
:param self:
:return:
.. mcmfrandom:: qname
:iscode: boolean
:answer_a: possible answer -- what follows _ is label
:answer_b: possible answer
...
:answer_e: possible answer
:correct: leter of correct answer
:feedback_a: displayed if a is picked
:feedback_b: displayed if b is picked
:feedback_c: displayed if c is picked
:feedback_d: displayed if d is picked
:feedback_e: displayed if e is picked
Question text
...
"""
TEMPLATE_START = '''
<div id="%(divid)s" class="alert alert-warning">
<p>%(qnumber)s: %(bodytext)s</p>
<form name="%(divid)s_form" method="get" action="" onsubmit="return true;">
'''
OPTION = '''
<div id="%(divid)s_op%(opi)s"></div>
'''
TEMPLATE_END = '''
<div id="%(divid)s_bt"></div>
</form>
<div id="%(divid)s_feedback">
</div>
<script>
$(document).ready(function(){createHTML_MCMFRandom("%(divid)s","%(a)s","%(f)s","%(corr)s");});
</script>
</div>
'''
super(MChoiceRandomMF,self).run()
res = ""
res = TEMPLATE_START % self.options
feedbackStr = "["
currFeedback = ""
# Add all of the possible answers
okeys = self.options.keys()
okeys.sort()
answ=""
feed=""
ansArr=[]
feedArray=[]
for k in okeys:
if 'answer_' in k:
ansArr.append(k)
for f in ansArr:
t,flabel=f.split("_")
feedArray.append(flabel)
i=0
for k in okeys:
if 'answer_' in k:
answ=answ+self.options[ansArr[i]]+"*separator*"
feed=feed+self.options["feedback_"+feedArray[i]]+"*separator*"
self.options['opi']=i+1
res += OPTION % self.options
i=i+1
# Store the Answer and Feedback arrays
self.options['a']=answ
self.options['f']=feed
op=self.options['correct']
if(op=='a'):
index=0
elif(op=='b'):
index=1
elif(op=='c'):
index=2
elif(op=='d'):
index=3
elif(op=='e'):
index=4
self.options['corr']=self.options[ansArr[index]]
res += TEMPLATE_END % self.options
return [nodes.raw('',res , format='html')]

View file

@ -0,0 +1,53 @@
<html>
<head>
<title>trial</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript" ></script>
</head>
<body>
<div id="prototype">
<p>Evaluate the following boolean expression: True or False</p>
<form name="prototype_form" method="get" action="" onsubmit="return false;">
<input type="radio" name="group1" value="a" id="prototype_opt_1" />
<label for= "prototype_opt_1"> a) True</label><br />
<input type="radio" name="group1" value="b" id="prototype_opt_2" />
<label for="prototype_opt_2"> b) False</label><br />
<input type="radio" name="group1" value="c" id="prototype_opt_3">
<label for="prototype_opt_3"> c) Unknown</label><br />
<input type="button" name="do answer"
value="Check Me" onclick="checkMe('prototype','a','try again')"/>
</form>
<div id="prototype_feedback">
</div>
</div>
<script type="text/javascript"> // can go in assessfuncs??
var checkMe = function(divid, expected,feedback) {
var given;
var buttonObjs = document.forms[divid+"_form"].elements['group1']
for (var i = buttonObjs.length - 1; i >= 0; i--) {
if (buttonObjs[i].checked) {
given = buttonObjs[i].value;
}
};
// update number of trials??
// log this to the db
feedBack('#'+divid+'_feedback',given == expected, feedback)
}
var feedBack = function(divid,correct,feedbackText) {
if (correct) {
$(divid).html('You are Correct!');
} else {
$(divid).html("Inorrect. Here's something to think about: " + feedbackText );
}
}
// for each form in the div
// get the id of the form
// call checkMe on the form... -- need metadata what kind of question what parms etc
// hidden fields for meta data??? each form defines a checkme function with no parameters
// that calls the actual function that checks the answer properly??
// summarize
</script>
</body>
</html>

View file

@ -0,0 +1,65 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
import json
import random
# setup is called in assess.py
# app.add_node(MChoiceNode, html=(visit_mc_node, depart_mc_node))
# app.add_node(FITBNode, html=(visit_fitb_node, depart_fitb_node))
def textfield_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
'''
Usage:
In your document you can write :textfield:`myid:myvalue:width`
This will translate to:
<input type='text' id='myid' class="form-control input-small" style="display:inline; width:width;" value='myvalue'></input>
where width can be specified in pixels or percentage of page width (standard CSS syntax).
Width can also be specified using relative sizes:
mini, small, medium, large, xlarge, and xxlarge
'''
iid, value, width = text.split(':')
if 'mini' in width:
width = '60px'
elif 'small' in width:
width = '90px'
elif 'medium' in width:
width = '150px'
elif 'large' in width:
width = '210px'
elif 'xlarge' in width:
width = '270px'
elif 'xxlarge' in width:
width = '530px'
res = '''<input type='text' id='%s' class="form-control" style="display:inline; width: %s;" value="%s"></input>''' % (iid,width,value)
return [nodes.raw('',res, format='html')],[]

View file

@ -0,0 +1 @@
from .visualizer import *

View file

@ -0,0 +1,256 @@
# Online Python Tutor
# https://github.com/pgbovine/OnlinePythonTutor/
#
# Copyright (C) 2010-2012 Philip J. Guo (philip@pgbovine.net)
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Thanks to John DeNero for making the encoder work on both Python 2 and 3
# Given an arbitrary piece of Python data, encode it in such a manner
# that it can be later encoded into JSON.
# http://json.org/
#
# We use this function to encode run-time traces of data structures
# to send to the front-end.
#
# Format:
# Primitives:
# * None, int, long, float, str, bool - unchanged
# (json.dumps encodes these fine verbatim)
#
# Compound objects:
# * list - ['LIST', elt1, elt2, elt3, ..., eltN]
# * tuple - ['TUPLE', elt1, elt2, elt3, ..., eltN]
# * set - ['SET', elt1, elt2, elt3, ..., eltN]
# * dict - ['DICT', [key1, value1], [key2, value2], ..., [keyN, valueN]]
# * instance - ['INSTANCE', class name, [attr1, value1], [attr2, value2], ..., [attrN, valueN]]
# * class - ['CLASS', class name, [list of superclass names], [attr1, value1], [attr2, value2], ..., [attrN, valueN]]
# * function - ['FUNCTION', function name, parent frame ID (for nested functions)]
# * module - ['module', module name]
# * other - [<type name>, string representation of object]
# * compound object reference - ['REF', target object's unique_id]
#
# the unique_id is derived from id(), which allows us to capture aliasing
# number of significant digits for floats
FLOAT_PRECISION = 4
import re, types
import sys
typeRE = re.compile("<type '(.*)'>")
classRE = re.compile("<class '(.*)'>")
import inspect
is_python3 = (sys.version_info[0] == 3)
if is_python3:
long = None # Avoid NameError when evaluating "long"
def is_class(dat):
"""Return whether dat is a class."""
if is_python3:
return isinstance(dat, type)
else:
return type(dat) in (types.ClassType, types.TypeType)
def is_instance(dat):
"""Return whether dat is an instance of a class."""
if is_python3:
return isinstance(type(dat), type) and not isinstance(dat, type)
else:
# ugh, classRE match is a bit of a hack :(
return type(dat) == types.InstanceType or classRE.match(str(type(dat)))
def get_name(obj):
"""Return the name of an object."""
return obj.__name__ if hasattr(obj, '__name__') else get_name(type(obj))
# Note that this might BLOAT MEMORY CONSUMPTION since we're holding on
# to every reference ever created by the program without ever releasing
# anything!
class ObjectEncoder:
def __init__(self):
# Key: canonicalized small ID
# Value: encoded (compound) heap object
self.encoded_heap_objects = {}
self.id_to_small_IDs = {}
self.cur_small_ID = 1
def get_heap(self):
return self.encoded_heap_objects
def reset_heap(self):
# VERY IMPORTANT to reassign to an empty dict rather than just
# clearing the existing dict, since get_heap() could have been
# called earlier to return a reference to a previous heap state
self.encoded_heap_objects = {}
def set_function_parent_frame_ID(self, ref_obj, enclosing_frame_id):
assert ref_obj[0] == 'REF'
func_obj = self.encoded_heap_objects[ref_obj[1]]
assert func_obj[0] == 'FUNCTION'
func_obj[-1] = enclosing_frame_id
# return either a primitive object or an object reference;
# and as a side effect, update encoded_heap_objects
def encode(self, dat, get_parent):
"""Encode a data value DAT using the GET_PARENT function for parent ids."""
# primitive type
if type(dat) in (int, long, float, str, bool, type(None)):
if type(dat) is float:
return round(dat, FLOAT_PRECISION)
else:
return dat
# compound type - return an object reference and update encoded_heap_objects
else:
my_id = id(dat)
try:
my_small_id = self.id_to_small_IDs[my_id]
except KeyError:
my_small_id = self.cur_small_ID
self.id_to_small_IDs[my_id] = self.cur_small_ID
self.cur_small_ID += 1
del my_id # to prevent bugs later in this function
ret = ['REF', my_small_id]
# punt early if you've already encoded this object
if my_small_id in self.encoded_heap_objects:
return ret
# major side-effect!
new_obj = []
self.encoded_heap_objects[my_small_id] = new_obj
typ = type(dat)
if typ == list:
new_obj.append('LIST')
for e in dat:
new_obj.append(self.encode(e, get_parent))
elif typ == tuple:
new_obj.append('TUPLE')
for e in dat:
new_obj.append(self.encode(e, get_parent))
elif typ == set:
new_obj.append('SET')
for e in dat:
new_obj.append(self.encode(e, get_parent))
elif typ == dict:
new_obj.append('DICT')
for (k, v) in dat.items():
# don't display some built-in locals ...
if k not in ('__module__', '__return__', '__locals__'):
new_obj.append([self.encode(k, get_parent), self.encode(v, get_parent)])
elif typ in (types.FunctionType, types.MethodType):
if is_python3:
argspec = inspect.getfullargspec(dat)
else:
argspec = inspect.getargspec(dat)
printed_args = [e for e in argspec.args]
if argspec.varargs:
printed_args.append('*' + argspec.varargs)
if is_python3:
if argspec.varkw:
printed_args.append('**' + argspec.varkw)
if argspec.kwonlyargs:
printed_args.extend(argspec.kwonlyargs)
else:
if argspec.keywords:
printed_args.append('**' + argspec.keywords)
func_name = get_name(dat)
pretty_name = func_name + '(' + ', '.join(printed_args) + ')'
encoded_val = ['FUNCTION', pretty_name, None]
if get_parent:
enclosing_frame_id = get_parent(dat)
encoded_val[2] = enclosing_frame_id
new_obj.extend(encoded_val)
elif typ is types.BuiltinFunctionType:
pretty_name = get_name(dat) + '(...)'
new_obj.extend(['FUNCTION', pretty_name, None])
elif is_class(dat) or is_instance(dat):
self.encode_class_or_instance(dat, new_obj)
elif typ is types.ModuleType:
new_obj.extend(['module', dat.__name__])
else:
typeStr = str(typ)
m = typeRE.match(typeStr)
if not m:
m = classRE.match(typeStr)
assert m, typ
new_obj.extend([m.group(1), str(dat)])
return ret
def encode_class_or_instance(self, dat, new_obj):
"""Encode dat as a class or instance."""
if is_instance(dat):
if hasattr(dat, '__class__'):
# common case ...
class_name = get_name(dat.__class__)
else:
# super special case for something like
# "from datetime import datetime_CAPI" in Python 3.2,
# which is some weird 'PyCapsule' type ...
# http://docs.python.org/release/3.1.5/c-api/capsule.html
class_name = get_name(type(dat))
new_obj.extend(['INSTANCE', class_name])
# don't traverse inside modules, or else risk EXPLODING the visualization
if class_name == 'module':
return
else:
superclass_names = [e.__name__ for e in dat.__bases__ if e is not object]
new_obj.extend(['CLASS', get_name(dat), superclass_names])
# traverse inside of its __dict__ to grab attributes
# (filter out useless-seeming ones):
hidden = ('__doc__', '__module__', '__return__', '__dict__',
'__locals__', '__weakref__')
if hasattr(dat, '__dict__'):
user_attrs = sorted([e for e in dat.__dict__ if e not in hidden])
else:
user_attrs = []
for attr in user_attrs:
new_obj.append([self.encode(attr, None), self.encode(dat.__dict__[attr], None)])

View file

@ -0,0 +1,872 @@
# Online Python Tutor
# https://github.com/pgbovine/OnlinePythonTutor/
#
# Copyright (C) 2010-2012 Philip J. Guo (philip@pgbovine.net)
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# This is the meat of the Online Python Tutor back-end. It implements a
# full logger for Python program execution (based on pdb, the standard
# Python debugger imported via the bdb module), printing out the values
# of all in-scope data structures after each executed instruction.
import sys
import bdb # the KEY import here!
import re
import traceback
import types
is_python3 = (sys.version_info[0] == 3)
if is_python3:
import io as cStringIO
else:
import cStringIO
import pg_encoder
# TODO: not threadsafe:
# upper-bound on the number of executed lines, in order to guard against
# infinite loops
MAX_EXECUTED_LINES = 500
#DEBUG = False
DEBUG = False
# simple sandboxing scheme:
#
# - use resource.setrlimit to deprive this process of ANY file descriptors
# (which will cause file read/write and subprocess shell launches to fail)
# - restrict user builtins and module imports
# (beware that this is NOT foolproof at all ... there are known flaws!)
#
# ALWAYS use defense-in-depth and don't just rely on these simple mechanisms
try:
import resource
resource_module_loaded = True
except ImportError:
# Google App Engine doesn't seem to have the 'resource' module
resource_module_loaded = False
# ugh, I can't figure out why in Python 2, __builtins__ seems to
# be a dict, but in Python 3, __builtins__ seems to be a module,
# so just handle both cases ... UGLY!
if type(__builtins__) is dict:
BUILTIN_IMPORT = __builtins__['__import__']
else:
assert type(__builtins__) is types.ModuleType
BUILTIN_IMPORT = __builtins__.__import__
# whitelist of module imports
ALLOWED_MODULE_IMPORTS = ('math', 'random', 'datetime',
'functools', 'operator', 'string',
'collections', 're', 'json',
'heapq', 'bisect')
# PREEMPTIVELY import all of these modules, so that when the user's
# script imports them, it won't try to do a file read (since they've
# already been imported and cached in memory). Remember that when
# the user's code runs, resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0))
# will already be in effect, so no more files can be opened.
for m in ALLOWED_MODULE_IMPORTS:
__import__(m)
# Restrict imports to a whitelist
def __restricted_import__(*args):
if args[0] in ALLOWED_MODULE_IMPORTS:
return BUILTIN_IMPORT(*args)
else:
raise ImportError('{0} not supported'.format(args[0]))
# blacklist of builtins
BANNED_BUILTINS = ('reload', 'input', 'apply', 'open', 'compile',
'file', 'eval', 'exec', 'execfile',
'exit', 'quit', 'raw_input', 'help',
'dir', 'globals', 'locals', 'vars')
IGNORE_VARS = set(('__user_stdout__', '__builtins__', '__name__', '__exception__', '__doc__', '__package__'))
def get_user_stdout(frame):
return frame.f_globals['__user_stdout__'].getvalue()
def get_user_globals(frame):
d = filter_var_dict(frame.f_globals)
# also filter out __return__ for globals only, but NOT for locals
if '__return__' in d:
del d['__return__']
return d
def get_user_locals(frame):
return filter_var_dict(frame.f_locals)
def filter_var_dict(d):
ret = {}
for (k,v) in d.items():
if k not in IGNORE_VARS:
ret[k] = v
return ret
# yield all function objects locally-reachable from frame,
# making sure to traverse inside all compound objects ...
def visit_all_locally_reachable_function_objs(frame):
for (k, v) in get_user_locals(frame).items():
for e in visit_function_obj(v, set()):
if e: # only non-null if it's a function object
assert type(e) in (types.FunctionType, types.MethodType)
yield e
# TODO: this might be slow if we're traversing inside lots of objects:
def visit_function_obj(v, ids_seen_set):
v_id = id(v)
# to prevent infinite loop
if v_id in ids_seen_set:
yield None
else:
ids_seen_set.add(v_id)
typ = type(v)
# simple base case
if typ in (types.FunctionType, types.MethodType):
yield v
# recursive cases
elif typ in (list, tuple, set):
for child in v:
for child_res in visit_function_obj(child, ids_seen_set):
yield child_res
elif typ == dict or pg_encoder.is_class(v) or pg_encoder.is_instance(v):
contents_dict = None
if typ == dict:
contents_dict = v
# warning: some classes or instances don't have __dict__ attributes
elif hasattr(v, '__dict__'):
contents_dict = v.__dict__
if contents_dict:
for (key_child, val_child) in contents_dict.items():
for key_child_res in visit_function_obj(key_child, ids_seen_set):
yield key_child_res
for val_child_res in visit_function_obj(val_child, ids_seen_set):
yield val_child_res
# degenerate base case
yield None
class PGLogger(bdb.Bdb):
def __init__(self, cumulative_mode, finalizer_func, disable_security_checks=False):
bdb.Bdb.__init__(self)
self.mainpyfile = ''
self._wait_for_mainpyfile = 0
self.disable_security_checks = disable_security_checks
# a function that takes the output trace as a parameter and
# processes it
self.finalizer_func = finalizer_func
# if True, then displays ALL stack frames that have ever existed
# rather than only those currently on the stack (and their
# lexical parents)
self.cumulative_mode = cumulative_mode
# each entry contains a dict with the information for a single
# executed line
self.trace = []
#http://stackoverflow.com/questions/2112396/in-python-in-google-app-engine-how-do-you-capture-output-produced-by-the-print
self.GAE_STDOUT = sys.stdout
# Key: function object
# Value: parent frame
self.closures = {}
# set of function objects that were defined in the global scope
self.globally_defined_funcs = set()
# Key: frame object
# Value: monotonically increasing small ID, based on call order
self.frame_ordered_ids = {}
self.cur_frame_id = 1
# List of frames to KEEP AROUND after the function exits.
# If cumulative_mode is True, then keep ALL frames in
# zombie_frames; otherwise keep only frames where
# nested functions were defined within them.
self.zombie_frames = []
# set of elements within zombie_frames that are also
# LEXICAL PARENTS of other frames
self.parent_frames_set = set()
# all globals that ever appeared in the program, in the order in
# which they appeared. note that this might be a superset of all
# the globals that exist at any particular execution point,
# since globals might have been deleted (using, say, 'del')
self.all_globals_in_order = []
# very important for this single object to persist throughout
# execution, or else canonical small IDs won't be consistent.
self.encoder = pg_encoder.ObjectEncoder()
self.executed_script = None # Python script to be executed!
def get_frame_id(self, cur_frame):
return self.frame_ordered_ids[cur_frame]
# Returns the (lexical) parent of a function value.
def get_parent_of_function(self, val):
if val not in self.closures:
return None
return self.get_frame_id(self.closures[val])
# Returns the (lexical) parent frame of the function that was called
# to create the stack frame 'frame'.
#
# OKAY, this is a SUPER hack, but I don't see a way around it
# since it's impossible to tell exactly which function
# ('closure') object was called to create 'frame'.
#
# The Python interpreter doesn't maintain this information,
# so unless we hack the interpreter, we will simply have
# to make an educated guess based on the contents of local
# variables inherited from possible parent frame candidates.
def get_parent_frame(self, frame):
for (func_obj, parent_frame) in self.closures.items():
# ok, there's a possible match, but let's compare the
# local variables in parent_frame to those of frame
# to make sure. this is a hack that happens to work because in
# Python, each stack frame inherits ('inlines') a copy of the
# variables from its (lexical) parent frame.
if func_obj.__code__ == frame.f_code:
all_matched = True
for k in frame.f_locals:
# Do not try to match local names
if k in frame.f_code.co_varnames:
continue
if k != '__return__' and k in parent_frame.f_locals:
if parent_frame.f_locals[k] != frame.f_locals[k]:
all_matched = False
break
if all_matched:
return parent_frame
return None
def lookup_zombie_frame_by_id(self, frame_id):
# TODO: kinda inefficient
for e in self.zombie_frames:
if self.get_frame_id(e) == frame_id:
return e
assert False # should never get here
# unused ...
#def reset(self):
# bdb.Bdb.reset(self)
# self.forget()
def forget(self):
self.lineno = None
self.stack = []
self.curindex = 0
self.curframe = None
def setup(self, f, t):
self.forget()
self.stack, self.curindex = self.get_stack(f, t)
self.curframe = self.stack[self.curindex][0]
# Override Bdb methods
def user_call(self, frame, argument_list):
"""This method is called when there is the remote possibility
that we ever need to stop in this function."""
if self._wait_for_mainpyfile:
return
if self.stop_here(frame):
# delete __return__ so that on subsequent calls to
# a generator function, the OLD yielded (returned)
# value gets deleted from the frame ...
try:
del frame.f_locals['__return__']
except KeyError:
pass
self.interaction(frame, None, 'call')
def user_line(self, frame):
"""This function is called when we stop or break at this line."""
if self._wait_for_mainpyfile:
if (self.canonic(frame.f_code.co_filename) != "<string>" or
frame.f_lineno <= 0):
return
self._wait_for_mainpyfile = 0
self.interaction(frame, None, 'step_line')
def user_return(self, frame, return_value):
"""This function is called when a return trap is set here."""
frame.f_locals['__return__'] = return_value
self.interaction(frame, None, 'return')
def user_exception(self, frame, exc_info):
exc_type, exc_value, exc_traceback = exc_info
"""This function is called if an exception occurs,
but only if we are to stop at or just below this level."""
frame.f_locals['__exception__'] = exc_type, exc_value
if type(exc_type) == type(''):
exc_type_name = exc_type
else: exc_type_name = exc_type.__name__
self.interaction(frame, exc_traceback, 'exception')
# General interaction function
def interaction(self, frame, traceback, event_type):
self.setup(frame, traceback)
tos = self.stack[self.curindex]
top_frame = tos[0]
lineno = tos[1]
# don't trace inside of ANY functions that aren't user-written code
# (e.g., those from imported modules -- e.g., random, re -- or the
# __restricted_import__ function in this file)
#
# empirically, it seems like the FIRST entry in self.stack is
# the 'run' function from bdb.py, but everything else on the
# stack is the user program's "real stack"
for (cur_frame, cur_line) in self.stack[1:]:
# it seems like user-written code has a filename of '<string>',
# but maybe there are false positives too?
if self.canonic(cur_frame.f_code.co_filename) != '<string>':
return
# also don't trace inside of the magic "constructor" code
if cur_frame.f_code.co_name == '__new__':
return
# or __repr__, which is often called when running print statements
if cur_frame.f_code.co_name == '__repr__':
return
# debug ...
#print('===', file=sys.stderr)
#for (e,ln) in self.stack:
# print(e.f_code.co_name + ' ' + e.f_code.co_filename + ' ' + str(ln), file=sys.stderr)
#print('', file=sys.stderr)
# don't trace inside of our __restricted_import__ helper function
# (this check is now subsumed by the above check)
#if top_frame.f_code.co_name == '__restricted_import__':
# return
self.encoder.reset_heap() # VERY VERY VERY IMPORTANT,
# or else we won't properly capture heap object mutations in the trace!
if event_type == 'call':
# Don't be so strict about this assertion because it FAILS
# when you're calling a generator (not for the first time),
# since that frame has already previously been on the stack ...
#assert top_frame not in self.frame_ordered_ids
self.frame_ordered_ids[top_frame] = self.cur_frame_id
self.cur_frame_id += 1
if self.cumulative_mode:
self.zombie_frames.append(top_frame)
# only render zombie frames that are NO LONGER on the stack
cur_stack_frames = [e[0] for e in self.stack]
zombie_frames_to_render = [e for e in self.zombie_frames if e not in cur_stack_frames]
# each element is a pair of (function name, ENCODED locals dict)
encoded_stack_locals = []
# returns a dict with keys: function name, frame id, id of parent frame, encoded_locals dict
def create_encoded_stack_entry(cur_frame):
ret = {}
parent_frame_id_list = []
f = cur_frame
while True:
p = self.get_parent_frame(f)
if p:
pid = self.get_frame_id(p)
assert pid
parent_frame_id_list.append(pid)
f = p
else:
break
cur_name = cur_frame.f_code.co_name
if cur_name == '':
cur_name = 'unnamed function'
# encode in a JSON-friendly format now, in order to prevent ill
# effects of aliasing later down the line ...
encoded_locals = {}
for (k, v) in get_user_locals(cur_frame).items():
is_in_parent_frame = False
# don't display locals that appear in your parents' stack frames,
# since that's redundant
for pid in parent_frame_id_list:
parent_frame = self.lookup_zombie_frame_by_id(pid)
if k in parent_frame.f_locals:
# ignore __return__, which is never copied
if k != '__return__':
# these values SHOULD BE ALIASES
# (don't do an 'is' check since it might not fire for primitives)
if parent_frame.f_locals[k] == v:
is_in_parent_frame = True
if is_in_parent_frame and k not in cur_frame.f_code.co_varnames:
continue
# don't display some built-in locals ...
if k == '__module__':
continue
encoded_val = self.encoder.encode(v, self.get_parent_of_function)
encoded_locals[k] = encoded_val
# order the variable names in a sensible way:
# Let's start with co_varnames, since it (often) contains all
# variables in this frame, some of which might not exist yet.
ordered_varnames = []
for e in cur_frame.f_code.co_varnames:
if e in encoded_locals:
ordered_varnames.append(e)
# sometimes co_varnames doesn't contain all of the true local
# variables: e.g., when executing a 'class' definition. in that
# case, iterate over encoded_locals and push them onto the end
# of ordered_varnames in alphabetical order
for e in sorted(encoded_locals.keys()):
if e != '__return__' and e not in ordered_varnames:
ordered_varnames.append(e)
# finally, put __return__ at the very end
if '__return__' in encoded_locals:
ordered_varnames.append('__return__')
# doctor Python 3 initializer to look like a normal function (denero)
if '__locals__' in encoded_locals:
ordered_varnames.remove('__locals__')
local = encoded_locals.pop('__locals__')
if encoded_locals.get('__return__', True) is None:
encoded_locals['__return__'] = local
# crucial sanity checks!
assert len(ordered_varnames) == len(encoded_locals)
for e in ordered_varnames:
assert e in encoded_locals
return dict(func_name=cur_name,
is_parent=(cur_frame in self.parent_frames_set),
frame_id=self.get_frame_id(cur_frame),
parent_frame_id_list=parent_frame_id_list,
encoded_locals=encoded_locals,
ordered_varnames=ordered_varnames)
i = self.curindex
# look for whether a nested function has been defined during
# this particular call:
if i > 1: # i == 1 implies that there's only a global scope visible
for v in visit_all_locally_reachable_function_objs(top_frame):
if (v not in self.closures and \
v not in self.globally_defined_funcs):
# Look for the presence of the code object (v.func_code
# for Python 2 or v.__code__ for Python 3) in the
# constant pool (f_code.co_consts) of an enclosing
# stack frame, and set that frame as your parent.
#
# This technique properly handles lambdas passed as
# function parameters. e.g., this example:
#
# def foo(x):
# bar(lambda y: x + y)
# def bar(a):
# print a(20)
# foo(10)
chosen_parent_frame = None
for (my_frame, my_lineno) in self.stack:
if chosen_parent_frame:
break
for frame_const in my_frame.f_code.co_consts:
if frame_const is (v.__code__ if is_python3 else v.func_code):
chosen_parent_frame = my_frame
break
assert chosen_parent_frame # I hope this always passes :0
# this condition should be False for functions declared in global scope ...
if chosen_parent_frame in self.frame_ordered_ids:
self.closures[v] = chosen_parent_frame
self.parent_frames_set.add(chosen_parent_frame) # unequivocally add to this set!!!
if not chosen_parent_frame in self.zombie_frames:
self.zombie_frames.append(chosen_parent_frame)
else:
# if there is only a global scope visible ...
for (k, v) in get_user_globals(top_frame).items():
if (type(v) in (types.FunctionType, types.MethodType) and \
v not in self.closures):
self.globally_defined_funcs.add(v)
# climb up until you find '<module>', which is (hopefully) the global scope
while True:
cur_frame = self.stack[i][0]
cur_name = cur_frame.f_code.co_name
if cur_name == '<module>':
break
encoded_stack_locals.append(create_encoded_stack_entry(cur_frame))
i -= 1
zombie_encoded_stack_locals = [create_encoded_stack_entry(e) for e in zombie_frames_to_render]
# encode in a JSON-friendly format now, in order to prevent ill
# effects of aliasing later down the line ...
encoded_globals = {}
for (k, v) in get_user_globals(tos[0]).items():
encoded_val = self.encoder.encode(v, self.get_parent_of_function)
encoded_globals[k] = encoded_val
if k not in self.all_globals_in_order:
self.all_globals_in_order.append(k)
# filter out globals that don't exist at this execution point
# (because they've been, say, deleted with 'del')
ordered_globals = [e for e in self.all_globals_in_order if e in encoded_globals]
assert len(ordered_globals) == len(encoded_globals)
# merge zombie_encoded_stack_locals and encoded_stack_locals
# into one master ordered list using some simple rules for
# making it look aesthetically pretty
stack_to_render = [];
# first push all regular stack entries
if encoded_stack_locals:
for e in encoded_stack_locals:
e['is_zombie'] = False
e['is_highlighted'] = False
stack_to_render.append(e)
# highlight the top-most active stack entry
stack_to_render[0]['is_highlighted'] = True
# now push all zombie stack entries
for e in zombie_encoded_stack_locals:
# don't display return value for zombie frames
# TODO: reconsider ...
'''
try:
e['ordered_varnames'].remove('__return__')
except ValueError:
pass
'''
e['is_zombie'] = True
e['is_highlighted'] = False # never highlight zombie entries
stack_to_render.append(e)
# now sort by frame_id since that sorts frames in "chronological
# order" based on the order they were invoked
stack_to_render.sort(key=lambda e: e['frame_id'])
# create a unique hash for this stack entry, so that the
# frontend can uniquely identify it when doing incremental
# rendering. the strategy is to use a frankenstein-like mix of the
# relevant fields to properly disambiguate closures and recursive
# calls to the same function
for e in stack_to_render:
hash_str = e['func_name']
# frame_id is UNIQUE, so it can disambiguate recursive calls
hash_str += '_f' + str(e['frame_id'])
# needed to refresh GUI display ...
if e['is_parent']:
hash_str += '_p'
# TODO: this is no longer needed, right? (since frame_id is unique)
#if e['parent_frame_id_list']:
# hash_str += '_p' + '_'.join([str(i) for i in e['parent_frame_id_list']])
if e['is_zombie']:
hash_str += '_z'
e['unique_hash'] = hash_str
trace_entry = dict(line=lineno,
event=event_type,
func_name=tos[0].f_code.co_name,
globals=encoded_globals,
ordered_globals=ordered_globals,
stack_to_render=stack_to_render,
heap=self.encoder.get_heap(),
stdout=get_user_stdout(tos[0]))
# if there's an exception, then record its info:
if event_type == 'exception':
# always check in f_locals
exc = frame.f_locals['__exception__']
trace_entry['exception_msg'] = exc[0].__name__ + ': ' + str(exc[1])
self.trace.append(trace_entry)
# sanity check to make sure the state of the world at a 'call' instruction
# is identical to that at the instruction immediately following it ...
'''
if len(self.trace) > 1:
cur = self.trace[-1]
prev = self.trace[-2]
if prev['event'] == 'call':
assert cur['globals'] == prev['globals']
for (s1, s2) in zip(cur['stack_to_render'], prev['stack_to_render']):
assert s1 == s2
assert cur['heap'] == prev['heap']
assert cur['stdout'] == prev['stdout']
'''
if len(self.trace) >= MAX_EXECUTED_LINES:
self.trace.append(dict(event='instruction_limit_reached', exception_msg='(stopped after ' + str(MAX_EXECUTED_LINES) + ' steps to prevent possible infinite loop)'))
self.force_terminate()
self.forget()
def _runscript(self, script_str):
self.executed_script = script_str
# When bdb sets tracing, a number of call and line events happens
# BEFORE debugger even reaches user's code (and the exact sequence of
# events depends on python version). So we take special measures to
# avoid stopping before we reach the main script (see user_line and
# user_call for details).
self._wait_for_mainpyfile = 1
# ok, let's try to sorta 'sandbox' the user script by not
# allowing certain potentially dangerous operations.
user_builtins = {}
# ugh, I can't figure out why in Python 2, __builtins__ seems to
# be a dict, but in Python 3, __builtins__ seems to be a module,
# so just handle both cases ... UGLY!
if type(__builtins__) is dict:
builtin_items = __builtins__.items()
else:
assert type(__builtins__) is types.ModuleType
builtin_items = []
for k in dir(__builtins__):
builtin_items.append((k, getattr(__builtins__, k)))
for (k, v) in builtin_items:
if k in BANNED_BUILTINS:
continue
elif k == '__import__':
user_builtins[k] = __restricted_import__
else:
user_builtins[k] = v
user_stdout = cStringIO.StringIO()
sys.stdout = user_stdout
user_globals = {"__name__" : "__main__",
"__builtins__" : user_builtins,
"__user_stdout__" : user_stdout}
try:
# enforce resource limits RIGHT BEFORE running script_str
# set ~200MB virtual memory limit AND a 5-second CPU time
# limit (tuned for Webfaction shared hosting) to protect against
# memory bombs such as:
# x = 2
# while True: x = x*x
if resource_module_loaded and (not self.disable_security_checks):
resource.setrlimit(resource.RLIMIT_AS, (200000000, 200000000))
resource.setrlimit(resource.RLIMIT_CPU, (5, 5))
# protect against unauthorized filesystem accesses ...
resource.setrlimit(resource.RLIMIT_NOFILE, (0, 0)) # no opened files allowed
# VERY WEIRD. If you activate this resource limitation, it
# ends up generating an EMPTY trace for the following program:
# "x = 0\nfor i in range(10):\n x += 1\n print x\n x += 1\n"
# (at least on my Webfaction hosting with Python 2.7)
#resource.setrlimit(resource.RLIMIT_FSIZE, (0, 0)) # (redundancy for paranoia)
# sys.modules contains an in-memory cache of already-loaded
# modules, so if you delete modules from here, they will
# need to be re-loaded from the filesystem.
#
# Thus, as an extra precaution, remove these modules so that
# they can't be re-imported without opening a new file,
# which is disallowed by resource.RLIMIT_NOFILE
#
# Of course, this isn't a foolproof solution by any means,
# and it might lead to UNEXPECTED FAILURES later in execution.
del sys.modules['os']
del sys.modules['sys']
self.run(script_str, user_globals, user_globals)
# sys.exit ...
except SystemExit:
#sys.exit(0)
raise bdb.BdbQuit
except:
if DEBUG:
traceback.print_exc()
trace_entry = dict(event='uncaught_exception')
(exc_type, exc_val, exc_tb) = sys.exc_info()
if hasattr(exc_val, 'lineno'):
trace_entry['line'] = exc_val.lineno
if hasattr(exc_val, 'offset'):
trace_entry['offset'] = exc_val.offset
trace_entry['exception_msg'] = type(exc_val).__name__ + ": " + str(exc_val)
# SUPER SUBTLE! if this exact same exception has already been
# recorded by the program, then DON'T record it again as an
# uncaught_exception
already_caught = False
for e in self.trace:
if e['event'] == 'exception' and e['exception_msg'] == trace_entry['exception_msg']:
already_caught = True
break
if not already_caught:
self.trace.append(trace_entry)
raise bdb.BdbQuit # need to forceably STOP execution
def force_terminate(self):
#self.finalize()
raise bdb.BdbQuit # need to forceably STOP execution
def finalize(self):
sys.stdout = self.GAE_STDOUT # very important!
assert len(self.trace) <= (MAX_EXECUTED_LINES + 1)
# don't do this anymore ...
'''
# filter all entries after 'return' from '<module>', since they
# seem extraneous:
res = []
for e in self.trace:
res.append(e)
if e['event'] == 'return' and e['func_name'] == '<module>':
break
'''
res = self.trace
# if the SECOND to last entry is an 'exception'
# and the last entry is return from <module>, then axe the last
# entry, for aesthetic reasons :)
if len(res) >= 2 and \
res[-2]['event'] == 'exception' and \
res[-1]['event'] == 'return' and res[-1]['func_name'] == '<module>':
res.pop()
self.trace = res
return self.finalizer_func(self.executed_script, self.trace)
# the MAIN meaty function!!!
def exec_script_str(script_str, cumulative_mode, finalizer_func):
logger = PGLogger(cumulative_mode, finalizer_func)
try:
logger._runscript(script_str)
except bdb.BdbQuit:
pass
finally:
logger.finalize()
# disables security check and returns the result of finalizer_func
# WARNING: ONLY RUN THIS LOCALLY and never over the web, since
# security checks are disabled
def exec_script_str_local(script_str, cumulative_mode, finalizer_func):
logger = PGLogger(cumulative_mode, finalizer_func, disable_security_checks=True)
try:
logger._runscript(script_str)
except bdb.BdbQuit:
pass
finally:
return logger.finalize()

View file

@ -0,0 +1,210 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
from pg_logger import exec_script_str_local
import json
def setup(app):
app.add_directive('codelens',Codelens)
app.add_stylesheet('css/pytutor.css')
app.add_stylesheet('css/basic.css')
app.add_javascript('js/d3.v2.min.js')
app.add_javascript('jquery-migrate-1.2.1.min.js') # needed so that ba-bbq can use the latest jQuery that we've included
app.add_javascript('js/jquery.ba-bbq.min.js')
app.add_javascript('js/jquery.jsPlumb-1.3.10-all-min.js')
app.add_javascript('js/pytutor.js')
VIS = '''
<div id="%(divid)s"></div>
<p class="cl_caption"><span class="cl_caption_text">%(caption)s (%(divid)s)</span> </p>'''
QUESTION = '''
<div id="%(divid)s_modal" class="modal fade codelens-modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Check your understanding</h4>
</div>
<div class="modal-body">
<p>%(question)s</p>
<input id="%(divid)s_textbox" type="textbox" class="form-control" style="width:200px;" />
<br />
<button id="%(divid)s_tracecheck" class='btn btn-default tracecheck' onclick="traceQCheckMe('%(divid)s_textbox','%(divid)s','%(correct)s')">
Check Me
</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Continue</button>
<br />
<p id="%(divid)s_feedbacktext" class="feedbacktext alert alert-warning"></p>
</div>
</div>
</div>
</div>
'''
DATA = '''
<script type="text/javascript">
%(tracedata)s
var %(divid)s_vis;
$(document).ready(function() {
%(divid)s_vis = new ExecutionVisualizer('%(divid)s',%(divid)s_trace,
{embeddedMode: %(embedded)s,
verticalStack: true,
heightChangeCallback: redrawAllVisualizerArrows,
codeDivWidth: 500
});
attachLoggers(%(divid)s_vis,'%(divid)s');
allVisualizers.push(%(divid)s_vis);
});
$(document).ready(function() {
$("#%(divid)s_tracecheck").click(function() {
logBookEvent({'event':'codelens', 'act': 'check', 'div_id':'%(divid)s'});
});
});
if (allVisualizers === undefined) {
var allVisualizers = [];
}
$(window).resize(function() {
%(divid)s_vis.redrawConnectors();
});
</script>
'''
# Some documentation to help the author.
# Here's and example of a single stack frame.
# you might ask a qestion about the value of a global variable
# in which case the correct answer is expressed as:
#
# globals.a
#
# You could ask about a value on the heap
#
# heap.variable
#
# You could ask about a local variable -- not shown here.
#
# locals.variable
#
# You could even ask about what line is going to be executed next
#
# line
# {
# "ordered_globals": [
# "a",
# "b"
# ],
# "stdout": "1\n",
# "func_name": "<module>",
# "stack_to_render": [],
# "globals": {
# "a": 1,
# "b": 1
# },
# "heap": {},
# "line": 5,
# "event": "return"
# }
class Codelens(Directive):
required_arguments = 1
optional_arguments = 1
option_spec = {
'tracedata':directives.unchanged,
'caption':directives.unchanged,
'showoutput':directives.flag,
'question':directives.unchanged,
'correct':directives.unchanged,
'feedback':directives.unchanged,
'breakline':directives.nonnegative_int
}
has_content = True
def run(self):
self.JS_VARNAME = ""
self.JS_VARVAL = ""
def raw_dict(input_code, output_trace):
ret = dict(code=input_code, trace=output_trace)
return ret
def js_var_finalizer(input_code, output_trace):
global JS_VARNAME
ret = dict(code=input_code, trace=output_trace)
json_output = json.dumps(ret, indent=None)
return "var %s = %s;" % (self.JS_VARNAME, json_output)
self.options['divid'] = self.arguments[0]
if self.content:
source = "\n".join(self.content)
else:
source = '\n'
CUMULATIVE_MODE=False
self.JS_VARNAME = self.options['divid']+'_trace'
if 'showoutput' not in self.options:
self.options['embedded'] = 'true' # to set embeddedmode to true
else:
self.options['embedded'] = 'false'
if 'question' in self.options:
curTrace = exec_script_str_local(source, CUMULATIVE_MODE, raw_dict)
self.inject_questions(curTrace)
json_output = json.dumps(curTrace, indent=None)
self.options['tracedata'] = "var %s = %s;" % (self.JS_VARNAME, json_output)
else:
self.options['tracedata'] = exec_script_str_local(source, CUMULATIVE_MODE, js_var_finalizer)
res = VIS
if 'caption' not in self.options:
self.options['caption'] = ''
if 'question' in self.options:
res += QUESTION
if 'tracedata' in self.options:
res += DATA
return [nodes.raw('',res % self.options,format='html')]
def inject_questions(self,curTrace):
if 'breakline' not in self.options:
raise RuntimeError('Must have breakline option')
breakline = self.options['breakline']
for frame in curTrace['trace']:
if frame['line'] == breakline:
frame['question'] = dict(text=self.options['question'],
correct = self.options['correct'],
div = self.options['divid']+'_modal',
feedback = self.options['feedback'] )

View file

@ -0,0 +1,127 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('datafile',DataFile)
app.add_javascript('bookfuncs.js')
app.add_javascript('skulpt/dist/skulpt.js')
app.add_javascript('skulpt/dist/builtin.js')
app.add_node(DataFileNode, html=(visit_df_node, depart_df_node))
app.connect('doctree-resolved',process_datafile_nodes)
app.connect('env-purge-doc', purge_datafiles)
PRE = '''
<pre id="%(divid)s" style="display: %(hide)s;">
%(filecontent)s
</pre>
'''
TEXTA = '''
<textarea id="%(divid)s" rows="%(rows)d" cols="%(cols)d">
%(filecontent)s
</textarea>
'''
class DataFileNode(nodes.General, nodes.Element):
def __init__(self,content):
"""
Arguments:
- `self`:
- `content`:
"""
super(DataFileNode,self).__init__()
self.df_content = content
# self for these functions is an instance of the writer class. For example
# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator
# The node that is passed as a parameter is an instance of our node class.
def visit_df_node(self,node):
if node.df_content['edit'] == True:
res = TEXTA
else:
res = PRE
res = res % node.df_content
res = res.replace("u'","'") # hack: there must be a better way to include the list and avoid unicode strings
self.body.append(res)
def depart_df_node(self,node):
''' This is called at the start of processing an datafile node. If datafile had recursive nodes
etc and did not want to do all of the processing in visit_ac_node any finishing touches could be
added here.
'''
pass
def process_datafile_nodes(app,env,docname):
pass
def purge_datafiles(app,env,docname):
pass
class DataFile(Directive):
required_arguments = 1
optional_arguments = 2
has_content = True
option_spec = {
'hide':directives.flag,
'edit':directives.flag,
'rows':directives.positive_int,
'cols':directives.positive_int
}
def run(self):
env = self.state.document.settings.env
if not hasattr(env,'datafilecounter'):
env.datafilecounter = 0
env.datafilecounter += 1
if 'cols' not in self.options:
self.options['cols'] = min(65,max([len(x) for x in self.content]))
if 'rows'not in self.options:
self.options['rows'] = 20
self.options['divid'] = self.arguments[0]
if self.content:
source = "\n".join(self.content)
else:
source = '\n'
self.options['filecontent'] = source
if 'hide' not in self.options:
self.options['hide'] = 'block'
else:
self.options['hide'] = 'none'
if 'edit' not in self.options:
self.options['edit'] = False
return [DataFileNode(self.options)]

View file

@ -0,0 +1,2 @@
from .disqus import *

View file

@ -0,0 +1,115 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'isaacdontjelindell'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
DISQUS_BOX = """\
<script type="text/javascript">
function %(identifier)s_disqus(source) {
if (window.DISQUS) {
$('#disqus_thread').insertAfter(source); //put the DIV for the Disqus box after the link
//if Disqus exists, call it's reset method with new parameters
DISQUS.reset({
reload: true,
config: function () {
this.page.identifier = '%(identifier)s_' + eBookConfig.course;
this.page.url = 'http://%(identifier)s_'+eBookConfig.course+'.interactivepython.com/#!';
}
});
} else {
//insert a wrapper in HTML after the relevant "show comments" link
$('<div id="disqus_thread"></div>').insertAfter(source);
// set Disqus required vars
disqus_shortname = '%(shortname)s';
disqus_identifier = '%(identifier)s_' + eBookConfig.course;
disqus_url = 'http://%(identifier)s_'+eBookConfig.course+'.interactivepython.com/#!';
//append the Disqus embed script to HTML
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
$('head').append(dsq);
}
}
</script>
"""
DISQUS_LINK = """
<a href="#disqus_thread" class='disqus_thread_link' data-disqus-identifier="%(identifier)s" onclick="%(identifier)s_disqus(this);">Show Comments</a>
<script type='text/javascript'>
$("a[data-disqus-identifier='%(identifier)s']").attr('data-disqus-identifier', '%(identifier)s_' + eBookConfig.course);
</script>
"""
def setup(app):
app.add_directive('disqus', DisqusDirective)
app.add_node(DisqusNode, html=(visit_disqus_node, depart_disqus_node))
app.connect('doctree-resolved' ,process_disqus_nodes)
app.connect('env-purge-doc', purge_disqus_nodes)
class DisqusNode(nodes.General, nodes.Element):
def __init__(self,content):
super(DisqusNode,self).__init__()
self.disqus_components = content
def visit_disqus_node(self, node):
res = DISQUS_BOX
res += DISQUS_LINK
res = res % node.disqus_components
self.body.append(res)
def depart_disqus_node(self,node):
pass
def process_disqus_nodes(app, env, docname):
pass
def purge_disqus_nodes(app, env, docname):
pass
class DisqusDirective(Directive):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
has_content = False
option_spec = {'shortname':directives.unchanged_required,
'identifier':directives.unchanged_required
}
def run(self):
"""
generate html to include Disqus box.
:param self:
:return:
"""
return [DisqusNode(self.options)]

View file

@ -0,0 +1,2 @@
from .meta import *

View file

@ -0,0 +1,62 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('shortname',Meta)
app.add_directive('description',Meta)
class Meta(Directive):
required_arguments = 1
optional_arguments = 50
def run(self):
"""
process the video directive and generate html for output.
:param self:
:return:
"""
return [nodes.raw('','', format='html')]
source = """\
This is some text.
.. shortname:: divid
.. description:: foo bar baz
This is some more text.
"""
if __name__ == '__main__':
from docutils.core import publish_parts
directives.register_directive('shortname',Meta)
directives.register_directive('description',Meta)
doc_parts = publish_parts(source,
settings_overrides={'output_encoding': 'utf8',
'initial_header_level': 2},
writer_name="html")
print doc_parts['html_body']

View file

@ -0,0 +1 @@
from .poll import *

View file

@ -0,0 +1,149 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'isaacdontjelindell'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('poll', PollDirective)
app.add_node(PollNode, html=(visit_poll_node, depart_poll_node))
app.add_javascript('poll.js')
app.add_stylesheet('poll.css')
BEGIN = """ <div id='%(divid)s' class='poll alert alert-warning'> """
BEGIN_FORM = """
<form id='%(divid)s_poll' name='%(divid)s_poll' action="">
<fieldset>
<legend>Poll</legend>
<div class='poll-question'>%(content)s</div>
<div id='%(divid)s_poll_input'>
<div class='poll-options'>
"""
POLL_ELEMENT = """
<label class='radio-inline'>
<input type='radio' name='%(divid)s_opt' id='%(divid)s_%(value)s' value='%(value)s'>
%(value)s
</label>
"""
END_POLL_OPTIONS = """ </div> """
COMMENT = """
<br />
<input type='text' class='form-control' style='width:300px;' name='%(divid)s_comment' placeholder='Any comments?'>
<br />
"""
END_POLL_INPUT = """
<button type='button' id='%(divid)s_submit' class='btn btn-success' onclick="submitPoll('%(divid)s');">Submit</button>
</div>
"""
END_FORM = """
</fieldset>
</form>
"""
RESULTS_DIV = """ <div id='%(divid)s_results'></div> """
END = """
<script type='text/javascript'>
// check if the user has already answered this poll
$(function() {
var len = localStorage.length;
if (len > 0) {
for (var i = 0; i < len; i++) {
var key = localStorage.key(i);
if (key === '%(divid)s') {
var ex = localStorage.getItem(key);
if(ex === "true") {
// hide the poll inputs
$("#%(divid)s_poll_input").hide();
// show the results of the poll
var data = {};
data.div_id = '%(divid)s';
data.course = eBookConfig.course;
jQuery.get(eBookConfig.ajaxURL+'getpollresults', data, showPollResults);
}
}
}
}
});
</script>
</div>
"""
class PollNode(nodes.General, nodes.Element):
def __init__(self, options):
super(PollNode, self).__init__()
self.pollnode_components = options
def visit_poll_node(self, node):
res = BEGIN
res += BEGIN_FORM
for i in range(1, node.pollnode_components['scale']+1):
res += POLL_ELEMENT % {'divid':node.pollnode_components['divid'], 'value':i}
res += END_POLL_OPTIONS
if 'allowcomment' in node.pollnode_components:
res += COMMENT
res += END_POLL_INPUT
res += END_FORM
res += RESULTS_DIV
res += END
res = res % node.pollnode_components
self.body.append(res)
def depart_poll_node(self,node):
pass
class PollDirective(Directive):
required_arguments = 1 # the div id
optional_arguments = 0
final_argument_whitespace = True
has_content = True
option_spec = {'scale':directives.positive_int,
'allowcomment': directives.flag}
node_class = PollNode
def run(self):
# Raise an error if the directive does not have contents.
self.assert_has_content()
self.options['divid'] = self.arguments[0]
self.options['content'] = "<p>".join(self.content)
poll_node = PollNode(self.options)
return [poll_node]

View file

@ -0,0 +1 @@
from .reveal import *

View file

@ -0,0 +1,64 @@
__author__ = 'isaacdontjelindell'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('reveal', RevealDirective)
app.add_node(RevealNode, html=(visit_reveal_node, depart_reveal_node))
BEGIN = """
<button type='button' id='%(divid)s_show' class='btn btn-default' style='margin-bottom:10px;' onclick="$(this).hide();$('#%(divid)s').show();$('#%(divid)s_hide').show();$('#%(divid)s').find('.CodeMirror').each(function(i, el){el.CodeMirror.refresh();});">
%(showtitle)s
</button>
<button type='button' id='%(divid)s_hide' class='btn btn-default' onclick="$(this).hide();$('#%(divid)s').hide();$('#%(divid)s_show').show();" style='display:none'>%(hidetitle)s</button>
<div id='%(divid)s' style='display:none'>
"""
END = """
</div>
"""
class RevealNode(nodes.General, nodes.Element):
def __init__(self,content):
super(RevealNode,self).__init__()
self.reveal_components = content
def visit_reveal_node(self, node):
res = BEGIN % node.reveal_components
self.body.append(res)
def depart_reveal_node(self,node):
res = END % node.reveal_components
self.body.append(res)
class RevealDirective(Directive):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
has_content = True
option_spec = {"showtitle":directives.unchanged,
"hidetitle":directives.unchanged}
def run(self):
self.assert_has_content() # an empty reveal block isn't very useful...
if not 'showtitle' in self.options:
self.options['showtitle'] = "Show"
if not 'hidetitle' in self.options:
self.options['hidetitle'] = "Hide"
self.options['divid'] = self.arguments[0]
reveal_node = RevealNode(self.options)
self.state.nested_parse(self.content, self.content_offset, reveal_node)
return [reveal_node]

View file

@ -0,0 +1 @@
from .tabbedStuff import *

View file

@ -0,0 +1,178 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'isaacdontjelindell'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('tabbed', TabbedStuffDirective)
app.add_directive('tab', TabDirective)
app.add_node(TabNode, html=(visit_tab_node, depart_tab_node))
app.add_node(TabbedStuffNode, html=(visit_tabbedstuff_node, depart_tabbedstuff_node))
app.add_stylesheet('tabbedstuff.css')
BEGIN = """<div id='%(divid)s' class='alert alert-warning'>"""
TABLIST_BEGIN = """<ul class='nav nav-tabs' id='%(divid)s_tab'>"""
TABLIST_ELEMENT = """
<li>
<a data-toggle='tab' href='#%(divid)s-%(tabname)s'><span>%(tabfriendlyname)s</span></a>
</li>
"""
TABLIST_END = """</ul>"""
TABCONTENT_BEGIN = """<div class='tab-content'>"""
TABCONTENT_END = """</div>"""
TABDIV_BEGIN = """<div class='tab-pane' id='%(divid)s-%(tabname)s'>"""
TABDIV_END = """</div>"""
END = """
</div>
<script type='text/javascript'>
$('#%(divid)s .nav-tabs a').click(function (e) {
e.preventDefault();
$(this).tab('show');
})
// activate the first tab
var el = $('#%(divid)s .nav-tabs a')[0];
$(el).tab('show');
$('#%(divid)s .nav-tabs a').on('shown.bs.tab', function (e) {
var content_div = $(e.target.attributes.href.value);
content_div.find('.disqus_thread_link').each(function() {
$(this).click();
});
content_div.find('.CodeMirror').each(function(i, el) {
el.CodeMirror.refresh();
});
})
</script>
"""
class TabNode(nodes.General, nodes.Element):
def __init__(self, content):
super(TabNode, self).__init__()
self.tabnode_components = content
self.tabname = content['tabname']
def visit_tab_node(self, node):
divid = node.parent.divid
tabname = node.tabname
# remove spaces from tabname to allow it to be used as the div id.
res = TABDIV_BEGIN % {'divid':divid,
'tabname':tabname.replace(" ", "")}
self.body.append(res)
def depart_tab_node(self,node):
self.body.append(TABDIV_END)
class TabbedStuffNode(nodes.General, nodes.Element):
'''A TabbedStuffNode contains one or more TabNodes'''
def __init__(self,content):
super(TabbedStuffNode,self).__init__()
self.tabbed_stuff_components = content
self.divid = content['divid']
def visit_tabbedstuff_node(self, node):
divid = node.divid
# this is all the child tab nodes
tabs = node.traverse(include_self=False, descend=True, condition=TabNode)
res = BEGIN % {'divid':divid}
res += TABLIST_BEGIN
# make the tab list (<ul>).
# tabfriendlyname can contain spaces and will be displayed as the name of the tab.
# tabname is the same as tabfriendlyname but with spaces removed, so it can be
# used as the div id.
for tab in tabs:
res += TABLIST_ELEMENT % {'divid':divid,
'tabfriendlyname':tab.tabname,
'tabname':tab.tabname.replace(" ", "")}
res += TABLIST_END # </ul>
res += TABCONTENT_BEGIN
self.body.append(res)
def depart_tabbedstuff_node(self,node):
divid = node.divid
# close the tab plugin div and init the Bootstrap tabs
res = TABCONTENT_END
res += END
res = res % {'divid':divid}
self.body.append(res)
class TabDirective(Directive):
required_arguments = 1 # the name of the tab
optional_arguments = 0
final_argument_whitespace = True
has_content = True
option_spec = {}
node_class = TabNode
def run(self):
# Raise an error if the directive does not have contents.
self.assert_has_content()
# Create the node, to be populated by "nested_parse".
self.options['tabname'] = self.arguments[0]
tab_node = TabNode(self.options)
# Parse the child nodes (content of the tab)
self.state.nested_parse(self.content, self.content_offset, tab_node)
return [tab_node]
class TabbedStuffDirective(Directive):
required_arguments = 1 # the div to put the tabbed exhibit in
optional_arguments = 0
final_argument_whitespace = True
has_content = True
def run(self):
# Raise an error if the directive does not have contents.
self.assert_has_content()
self.options['divid'] = self.arguments[0]
# Create the node, to be populated by "nested_parse".
tabbedstuff_node = TabbedStuffNode(self.options)
# Parse the directive contents (should be 1 or more tab directives)
self.state.nested_parse(self.content, self.content_offset, tabbedstuff_node)
return [tabbedstuff_node]

View file

@ -0,0 +1,2 @@
from .video import *

View file

@ -0,0 +1,134 @@
# Copyright (C) 2011 Bradley N. Miller
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
__author__ = 'bmiller'
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.parsers.rst import Directive
def setup(app):
app.add_directive('video',Video)
app.add_stylesheet('video.css')
CODE = """\
<a id="%(divid)s_thumb" style='position:relative;'>
<img src="%(thumb)s" />
<div class='video-play-overlay'></div>
</a>
<div id="%(divid)s" class="video_popup" >
<video %(controls)s %(preload)s %(loop)s >
%(sources)s
No supported video types
</video>
</div>
"""
POPUP = """\
<script>
jQuery(function ($) {
$('#%(divid)s_thumb').click(function (e) {
$('#%(divid)s').modal();
return false;
});
});
</script>
"""
INLINE = """\
<script>
jQuery(function($) {
$('#%(divid)s_thumb').click(function(e) {
$('#%(divid)s').show();
$('#%(divid)s_thumb').hide();
logBookEvent({'event':'video','act':'play','div_id': '%(divid)s'});
// Log the run event
});
});
</script>
"""
SOURCE = """<source src="%s" type="video/%s"></source>"""
class Video(Directive):
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
has_content = True
option_spec = {'controls':directives.flag,
'loop': directives.flag,
'thumb': directives.uri,
'preload': directives.flag
}
def run(self):
"""
process the video directive and generate html for output.
:param self:
:return:
"""
mimeMap = {'mov':'mp4','webm':'webm', 'm4v':'m4v'}
sources = [SOURCE % (directives.uri(line),mimeMap[line[line.rindex(".")+1:]]) for line in self.content]
self.options['divid'] = self.arguments[0]
if 'controls' in self.options:
self.options['controls'] = 'controls'
if 'loop' in self.options:
self.options['loop'] = 'loop'
else:
self.options['loop'] = ''
if 'preload' in self.options:
self.options['preload'] = 'preload="auto"'
else:
self.options['preload'] = 'preload="none"'
self.options['sources'] = "\n ".join(sources)
res = CODE % self.options
if 'popup' in self.options:
res += POPUP % self.options
else:
res += INLINE % self.options
return [nodes.raw('',res , format='html')]
source = """\
This is some text.
.. video:: divid
:controls:
:thumb: _static/turtlestill.png
:loop:
http://knuth.luther.edu/~bmiller/foo.mov
http://knuth.luther.edu/~bmiller/foo.webm
This is some more text.
"""
if __name__ == '__main__':
from docutils.core import publish_parts
directives.register_directive('video',Video)
doc_parts = publish_parts(source,
settings_overrides={'output_encoding': 'utf8',
'initial_header_level': 2},
writer_name="html")
print doc_parts['html_body']