From 7d5c6b7c7844d2f4869aceafcfaab8b830e756e4 Mon Sep 17 00:00:00 2001 From: Chris Sangwin <C.J.Sangwin@ed.ac.uk> Date: Fri, 28 Apr 2023 20:27:21 +0100 Subject: [PATCH] Add a text-based summary of the PRT to the edit form and basic use report. --- edit_stack_form.php | 11 +++- lang/en/qtype_stack.php | 5 +- questiontestreport.php | 24 +-------- questiontype.php | 13 +++-- stack/graphlayout/graph.php | 27 +++++++++- stack/graphlayout/prtnode.php | 62 ++++++++++++++++++++++ stack/graphlayout/textrenderer.php | 83 ++++++++++++++++++++++++++++++ stack/prt.class.php | 7 ++- 8 files changed, 200 insertions(+), 32 deletions(-) create mode 100644 stack/graphlayout/prtnode.php create mode 100644 stack/graphlayout/textrenderer.php diff --git a/edit_stack_form.php b/edit_stack_form.php index 1bea6c665..9f2f313e7 100644 --- a/edit_stack_form.php +++ b/edit_stack_form.php @@ -500,8 +500,15 @@ class qtype_stack_edit_form extends question_edit_form { $mform->addElement('static', $prtname . 'inputsnote', '', stack_string('prtwillbecomeactivewhen', html_writer::tag('b', $inputnames))); - $mform->addElement('static', $prtname . 'graph', '', - stack_abstract_graph_svg_renderer::render($graph, $prtname . 'graphsvg')); + $tablerow = array(stack_abstract_graph_svg_renderer::render($graph, $prtname . 'graphsvg'), + stack_prt_graph_text_renderer::render($graph)); + $html = ''; + foreach ($tablerow as $td) { + $html .= html_writer::tag('td', $td); + } + $html = html_writer::tag('tr', $html); + $html = html_writer::tag('table', $html); + $mform->addElement('static', $prtname . 'graph', '', $html); $nextnodechoices = array('-1' => stack_string('stop')); foreach ($graph->get_nodes() as $node) { diff --git a/lang/en/qtype_stack.php b/lang/en/qtype_stack.php index ced03985b..f65ceaabb 100644 --- a/lang/en/qtype_stack.php +++ b/lang/en/qtype_stack.php @@ -310,6 +310,9 @@ $string['questionvariables_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php $string['questionvariablevalues'] = 'Question variable values'; $string['quiet'] = 'Quiet'; $string['quiet_help'] = 'When set to yes any feedback automatically generated by the answer tests is suppressed, and not displayed to the student. The feedback fields in the branches are unaffected by this option.'; +// The icon fa-volume-off isn't very good really. +$string['quiet_icon_true'] = '<span style="font-size: 1.25em; color:red;"><i class="fa fa-microphone-slash" aria-hidden="true"></i></span>'; +$string['quiet_icon_false'] = '<span style="font-size: 1.25em; color:blue;"><i class="fa fa-commenting-o"></i></span>'; $string['renamequestionparts'] = 'Rename parts of the question'; $string['requiredfield'] = 'This field is required!'; $string['requirelowestterms'] = 'Require lowest terms'; @@ -354,7 +357,7 @@ $string['testoptions_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Autho $string['testoptionsinvalid'] = 'The test options are invalid: {$a}'; $string['testoptionsrequired'] = 'Test options are required for this test.'; $string['description'] = 'Description'; -$string['description_err'] = 'The node description is longer than 32 characters.'; +$string['description_err'] = 'The node description is longer than 255 characters.'; $string['testoptions_help'] = 'This field the teacher to record the purpose of the test'; $string['testoptions_link'] = '%%WWWROOT%%/question/type/stack/doc/doc.php/Authoring/Potential_response_trees.md'; $string['truebranch'] = 'True branch'; diff --git a/questiontestreport.php b/questiontestreport.php index 63e0c1ac5..660052ba7 100644 --- a/questiontestreport.php +++ b/questiontestreport.php @@ -354,17 +354,7 @@ foreach ($question->prts as $prtname => $prt) { $graph = $prt->get_prt_graph(); $tablerow[] = stack_abstract_graph_svg_renderer::render($graph, $prtname . 'graphsvg'); - - $nodes = $prt->get_nodes_summary(); - $fields = array('displayname', 'answertest', 'trueanswernote', 'falseanswernote'); - foreach ($fields as $field) { - $textsummary = array(); - foreach ($nodes as $key => $node) { - $textsummary[] = $node->$field; - } - $textsummary = html_writer::tag('pre', s(implode("\n", $textsummary))); - $tablerow[] = html_writer::tag('div', $textsummary, array('class' => 'questionvariables')); - } + $tablerow[] = stack_prt_graph_text_renderer::render($graph); $maxima = html_writer::tag('summary', $prtname) . html_writer::tag('pre', s($prt->get_maxima_representation())); $maxima = html_writer::tag('details', $maxima); @@ -412,17 +402,7 @@ foreach ($question->prts as $prtname => $prt) { $graph = $prt->get_prt_graph(); $tablerow[] = stack_abstract_graph_svg_renderer::render($graph, $prtname . 'graphsvg'); - - $nodes = $prt->get_nodes_summary(); - $fields = array('displayname', 'answertest', 'trueanswernote', 'falseanswernote'); - foreach ($fields as $field) { - $textsummary = array(); - foreach ($nodes as $key => $node) { - $textsummary[] = $node->$field; - } - $textsummary = html_writer::tag('pre', s(implode("\n", $textsummary))); - $tablerow[] = html_writer::tag('div', $textsummary, array('class' => 'questionvariables')); - } + $tablerow[] = stack_prt_graph_text_renderer::render($graph); $maxima = html_writer::tag('pre', s($sumout[$prtname])); $tablerow[] = html_writer::tag('div', $maxima, array('class' => 'questionvariables')); diff --git a/questiontype.php b/questiontype.php index bceb9298c..f70960dc3 100644 --- a/questiontype.php +++ b/questiontype.php @@ -2095,7 +2095,7 @@ class qtype_stack extends question_type { } $description = $fromform[$prtname . 'description'][$nodekey]; - if (mb_strlen($description) > 32) { + if (mb_strlen($description) > 255) { $errors[$nodegroup][] = stack_string('description_err'); } @@ -2278,7 +2278,7 @@ class qtype_stack extends question_type { if (is_numeric($fs)) { $fs = round($fs, 2); } - $graph->add_node($key + 1, $description, $left, $right, + $graph->add_prt_node($key + 1, $description[$key], $left, $right, $truescoremode[$key] . $ts, $falsescoremode[$key] . $fs, '#fgroup_id_' . $prtname . 'node_' . $key); @@ -2315,10 +2315,14 @@ class qtype_stack extends question_type { } else { $right = $node->falsenextnode + 1; } - $graph->add_node($node->nodename + 1, $node->description, $left, $right, + $graph->add_prt_node($node->nodename + 1, $node->description, $left, $right, $node->truescoremode . $node->truescore, $node->falsescoremode . $node->falsescore, '#fgroup_id_' . $prtname . 'node_' . $node->nodename); + // Generate a text-based representation of the cas command. + $at = stack_potentialresponse_tree_lite::compile_node_answertest($node); + $graph->add_prt_text($node->nodename + 1, $at, $node->quiet, + $node->trueanswernote, $node->falseanswernote); } $graph->layout(); $this->prtgraph[$prtname] = $graph; @@ -2326,8 +2330,9 @@ class qtype_stack extends question_type { } // Otherwise, it is a new PRT. Just one node. + // And we don't add any text-based information for new PRTs. $graph = new stack_abstract_graph(); - $graph->add_node('1', '', null, null, '=1', '=0', '#fgroup_id_' . $prtname . 'node_0'); + $graph->add_prt_node('1', '', null, null, '=1', '=0', '#fgroup_id_' . $prtname . 'node_0'); $graph->layout(); $this->prtgraph[$prtname] = $graph; return $graph; diff --git a/stack/graphlayout/graph.php b/stack/graphlayout/graph.php index db926fa76..7e85b6f6d 100644 --- a/stack/graphlayout/graph.php +++ b/stack/graphlayout/graph.php @@ -25,9 +25,10 @@ defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__ . '/graphnode.php'); +require_once(__DIR__ . '/prtnode.php'); require_once(__DIR__ . '/graphclump.php'); require_once(__DIR__ . '/svgrenderer.php'); +require_once(__DIR__ . '/textrenderer.php'); /** * Abstract representation of a graph (e.g. a PRT). @@ -82,6 +83,30 @@ class stack_abstract_graph { $leftlabel, $rightlabel, $url); } + /** + * Add a prt node to the graph. These nodes have more text-based fields for better representation. + * + * @param string $name name of the node to add. + * @param string $description text-based description of the node to add. + * @param string $leftchild name of the left child node. + * @param string $rightchild name of the right child node. + * @param string $leftlabel lable to display on the edge to the left child. + * @param string $rightlabel lable to display on the edge to the right child. + * @param string $url if set, this node should be a link to that URL. + */ + public function add_prt_node($name, $description, $leftchild, $rightchild, $leftlabel = '', $rightlabel = '', $url = '') { + $this->nodes[$name] = new stack_prt_graph_node($name, $description, $leftchild, $rightchild, + $leftlabel, $rightlabel, $url); + } + + public function add_prt_text($name, $casstatement, $quiet, $truenote, $falsenote) { + if ($this->nodes[$name] instanceof stack_prt_graph_node) { + $this->nodes[$name]->add_prt_text($casstatement, $quiet, $truenote, $falsenote); + } else { + throw new stack_exception('Trying to add text-based fields to the wrong kind of node.'); + } + } + public function remove_node($nametodelete) { foreach ($this->nodes as $name => $node) { if ($name == $nametodelete) { diff --git a/stack/graphlayout/prtnode.php b/stack/graphlayout/prtnode.php new file mode 100644 index 000000000..874a6f5ac --- /dev/null +++ b/stack/graphlayout/prtnode.php @@ -0,0 +1,62 @@ +<?php +// This file is part of Stack - http://stack.maths.ed.ac.uk/ +// +// Stack 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. +// +// Stack 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 Stack. If not, see <http://www.gnu.org/licenses/>. + +/** + * Class to represent a vertex in an abstract graph of a PRT. + * + * @package qtype_stack + * @copyright 2013 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once(__DIR__ . '/graphnode.php'); + +/** + * Represents a node in a STACK PRT extending {@link stack_abstract_graph}. + * + * @copyright 2023 The University of Edinburgh + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class stack_prt_graph_node extends stack_abstract_graph_node { + /** + * @var string statement sent to Maxima by this node. + */ + public $casstatement; + + /** + * @var boolean Is the feedback from this test igored? + */ + public $quiet; + + /** + * @var string Note create by the true branch. + */ + public $truenote; + + /** + * @var string Note create by the false branch. + */ + public $falsenote; + + public function add_prt_text($casstatement, $quiet, $truenote, $falsenote) { + $this->casstatement = $casstatement; + $this->quiet = $quiet; + $this->truenote = $truenote; + $this->falsenote = $falsenote; + } +} diff --git a/stack/graphlayout/textrenderer.php b/stack/graphlayout/textrenderer.php new file mode 100644 index 000000000..324350adf --- /dev/null +++ b/stack/graphlayout/textrenderer.php @@ -0,0 +1,83 @@ +<?php +// This file is part of Stack - http://stack.maths.ed.ac.uk/ +// +// Stack 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. +// +// Stack 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 Stack. If not, see <http://www.gnu.org/licenses/>. + + +/** + * Displays a prt graph using an html table with text. + * + * @package qtype_stack + * @copyright 2023 The University of Edinburgh + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Displays a {@link stack_abstract_graph} as text. + * + * @copyright 2023 The University of Edinburgh + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class stack_prt_graph_text_renderer { + /** + * Output a graph as SVG. + * @param stack_abstract_graph $g the graph to display. + * @param string $id an id to add to the SVG node in the HTML. + */ + public static function render(stack_abstract_graph $g) { + $renderer = new self($g); + return $renderer->to_html(); + } + + /** + * Constructor. + * @param stack_abstract_graph $g the graph to display. + */ + protected function __construct(stack_abstract_graph $g) { + $this->g = $g; + } + + /** + * Actually generate the HTML for the nodes in the graph. + * @return string HTML code. Can be embedded straight into a HTML page. + */ + protected function to_html() { + + $table = array(); + foreach ($this->g->get_nodes() as $node) { + $quiet = stack_string('quiet_icon_false'); + if ($node->quiet) { + $quiet = stack_string('quiet_icon_true'); + } + // Put the name and description in one cell. It looks better. + $table[] = array($node->name . '. ' . $node->description, + html_writer::tag('code', s($node->casstatement)), $quiet, + $node->truenote, $node->falsenote); + } + + $html = ''; + foreach ($table as $tablerow) { + $row = ''; + foreach ($tablerow as $td) { + $row .= html_writer::tag('td', $td); + } + $html .= html_writer::tag('tr', $row) . "\n"; + } + // TODO: style the table with more padding. + $html = html_writer::start_tag('table', array('class' => 'prttexttable')) . $html . + html_writer::end_tag('table'); + + return $html; + } +} diff --git a/stack/prt.class.php b/stack/prt.class.php index 66fe0d111..3490d8867 100644 --- a/stack/prt.class.php +++ b/stack/prt.class.php @@ -286,6 +286,7 @@ class stack_potentialresponse_tree_lite { $n->falseanswernote = $node->falseanswernote; $n->falsescore = $node->falsescore; $n->falsescoremode = $node->falsescoremode; + $n->quiet = $node->quiet; $n->answertest = $this->compile_node_answertest($node); $name = (((int) $node->nodename) + 1); if (trim($node->description) !== '') { @@ -557,7 +558,7 @@ class stack_potentialresponse_tree_lite { /* * Generate the complete maxima command for a single answertest in a specific node. */ - private function compile_node_answertest($node) { + public static function compile_node_answertest($node) { // TODO: make this saner, the way Stateful lets the tests do their own // call construction might duplicate things but it does not require this // much knowledge about the shape of things. @@ -868,8 +869,10 @@ class stack_potentialresponse_tree_lite { if ($labels && array_key_exists($node->falseanswernote, $labels)) { $rlabel = $labels[$node->falseanswernote]; } - $graph->add_node($key + 1, $node, $left, $right, $llabel, $rlabel, + $graph->add_prt_node($key + 1, $node->description, $left, $right, $llabel, $rlabel, '#fgroup_id_' . $this->name . 'node_' . $key); + $graph->add_prt_text($node->nodename + 1, $node->answertest, $node->quiet, + $node->trueanswernote, $node->falseanswernote); } $graph->layout(); -- GitLab