From ee6124c0e738d2cd6245d6ef965a2196b884cd04 Mon Sep 17 00:00:00 2001 From: Chris Sangwin Date: Wed, 8 Nov 2023 05:00:55 +0000 Subject: [PATCH 1/3] Add full plot URLs inside iframe block code. I.e. replace !ploturl! directly. --- doc/en/Authoring/Parsons.md | 54 +++++++++++++++++++++- stack/cas/castext2/blocks/iframe.block.php | 7 +-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/doc/en/Authoring/Parsons.md b/doc/en/Authoring/Parsons.md index f1e41718194..6be42af695a 100644 --- a/doc/en/Authoring/Parsons.md +++ b/doc/en/Authoring/Parsons.md @@ -134,7 +134,57 @@ Additional display options including `height` and `width` may also be passed to Since HTML can be embedded into strings dragged within a Parson's block, images can be included with the HTML `` tags as normal. -STACK-generated [plots](../Plots/index.md) cannot be included just using `{@plot(x^2,[x,-1,1])@}` as might be expected. This is because of the _order_ of evaluation. The full URL of the image is only created in the (complex) chain of events after the value has been substituted into the Javascript code. Instead, to embed STACK-generated images evaluate a static string using the Maxima `castext` function, and then use the value of `s1` in the Parson's block. For example. +STACK-generated [plots](../Plots/index.md) can also be included just using `{@plot(x^2,[x,-1,1])@}` as might be expected. This is because of the _order_ of evaluation. The full URL of the image is only created in the (complex) chain of events after the value has been substituted into the Javascript code. - s1:castext("{@plot(x^2,[x,-1,1],[size,250,250])@}"); +```` +[[ parsons input="ans1"]] +{ + "A":"The inverse function of \\(f(x)=x^2\\)) has graph", + "B":{#plot(x^2,[x,-1,1],[size,250,250])#}, +}; +[[/parsons]] +```` + +Notice that since the value of `plot(...)` is a Maxima string of `` tag, there is no need to add in string quotes when defining the JSON above. The `{#...#}` will print `"` as part of the output. However, for convenience string quotes are removed from the display form `{@...@}` (as typically you just want the plot without quotes). Hence this is an alternative. + +```` +[[ parsons input="ans1"]] +{ + "A":"The inverse function of \\(f(x)=x^2\\)) has graph", + "B":"{@plot(sqrt(x),[x,-1,1],[size,250,250])@}", +}; +[[/parsons]] +```` + +An alternatove is to use the Maxima `castext` function, e.g. + + s1:castext("Consider this graph {@plot(x^2,[x,-1,1],[size,250,250])@}"); +and then use the value of `s1`, either in the Parson's block or when defining a `proof_steps` array in the question variables. E.g. define the question variables + +```` +s1:castext("{@plot(sqrt(x),[x,-1,1],[size,250,250])@}"); +proof_steps:[ + [ "A", "The inverse function of \\(f(x)=x^2\\) has graph"], + [ "B", s1] +]; +```` + +and the question text + +```` +[[ parsons input="ans1"]] +{# stackjson_stringify(proof_steps) #} +[[/parsons]] +```` + +A last direct example of question variables + +```` +proof_steps:[ + [ "A", plot(sqrt(x),[x,-1,1],[size,250,250])], + [ "b", plot(x,[x,-1,1],[size,250,250])], + [ "C", plot(x^2,[x,-1,1],[size,250,250])], + [ "D", plot(x^3,[x,-1,1],[size,250,250])] +]; +```` diff --git a/stack/cas/castext2/blocks/iframe.block.php b/stack/cas/castext2/blocks/iframe.block.php index ca3ce331212..c3b10013b2f 100644 --- a/stack/cas/castext2/blocks/iframe.block.php +++ b/stack/cas/castext2/blocks/iframe.block.php @@ -48,9 +48,6 @@ public function compile($format, $options): ?MP_Node { new MP_String(json_encode($this->params)) ]); - // All formatting assumed to be raw HTML here. - $frmt = castext2_parser_utils::RAWFORMAT; - $opt2 = []; if ($options !== null) { $opt2 = array_merge([], $options); @@ -59,6 +56,7 @@ public function compile($format, $options): ?MP_Node { // Note that [[style]], [[body]], [[script]] blocks will be separated during post-processing. foreach ($this->children as $child) { + // All formatting assumed to be raw HTML here. $c = $child->compile(castext2_parser_utils::RAWFORMAT, $opt2); if ($c !== null) { $r->items[] = $c; @@ -169,6 +167,9 @@ public function postprocess(array $params, castext2_processor $processor): strin $code .= $scripts; $code .= '' . $content . ''; + // Ensure plots get their full URL at this point. + $code = str_replace('!ploturl!', + moodle_url::make_file_url('/question/type/stack/plot.php', '/'), $code); // Escape some JavaScript strings. $args = [ json_encode($frameid), From bec029ae0b0c70786230ecd298e16fb0d7ed8bb3 Mon Sep 17 00:00:00 2001 From: Chris Sangwin Date: Wed, 8 Nov 2023 07:49:44 +0000 Subject: [PATCH 2/3] Add in `margin` option to plots. --- doc/en/Authoring/Parsons.md | 8 ++++---- doc/en/Developer/Development_track.md | 3 ++- doc/en/Plots/Plots.md | 20 ++++++++++++++++++-- stack/cas/security-map.json | 3 +++ stack/maxima/stackmaxima.mac | 19 ++++++++++++++----- tests/castext_test.php | 5 +++-- 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/doc/en/Authoring/Parsons.md b/doc/en/Authoring/Parsons.md index 6be42af695a..b7d5b65f813 100644 --- a/doc/en/Authoring/Parsons.md +++ b/doc/en/Authoring/Parsons.md @@ -182,9 +182,9 @@ A last direct example of question variables ```` proof_steps:[ - [ "A", plot(sqrt(x),[x,-1,1],[size,250,250])], - [ "b", plot(x,[x,-1,1],[size,250,250])], - [ "C", plot(x^2,[x,-1,1],[size,250,250])], - [ "D", plot(x^3,[x,-1,1],[size,250,250])] + [ "A", plot(sqrt(x),[x,-1,1],[size,180,180],[margin,1.7],[yx_ratio, 1],[plottags,false])], + [ "B", plot(x,[x,-1,1],[size,180,180],[margin,1.7],[yx_ratio, 1],[plottags,false])], + [ "C", plot(x^2,[x,-1,1],[size,180,180],[margin,1.7],[yx_ratio, 1],[plottags,false])], + [ "D", plot(x^3,[x,-1,1],[size,180,180],[margin,1.7],[yx_ratio, 1],[plottags,false])] ]; ```` diff --git a/doc/en/Developer/Development_track.md b/doc/en/Developer/Development_track.md index 2dc29efe565..f97a06bf575 100644 --- a/doc/en/Developer/Development_track.md +++ b/doc/en/Developer/Development_track.md @@ -23,7 +23,8 @@ Please note, this is the _last_ version of STACK which will support Moodle 3.x. 5. Add an option to support the use of a [comma as the decimal separator](Syntax_numbers.md). 6. Confirm support for PHP 8.2, (fixes issue #986). 7. Add in a [GeoGebra block](../Authoring/GeoGebra.md), and see [GeoGebra authoring](../Topics/GeoGebra.md). Thanks to Tim Lutz for contributing this code as part of the AuthOMath project. -8. Add in better support for proof as [Parson's problems](../Authoring/Parsons.md). +8. Add in an option `margin` to control margins around STACK-generated plots. +9. Add in better support for proof as [Parson's problems](../Authoring/Parsons.md). Create a working Parson's block diff --git a/doc/en/Plots/Plots.md b/doc/en/Plots/Plots.md index df117a94a17..572444eba89 100644 --- a/doc/en/Plots/Plots.md +++ b/doc/en/Plots/Plots.md @@ -27,6 +27,19 @@ The following `plot` options are currently supported by STACK. If you would li To change the size of the image use the Maxima variable `size`, e.g. `plot(x^2,[x,-1,1],[size,250,250])`. +### Image margin + +To change the size of the margin around the image use the variable `margin`, e.g. `plot(x^2,[x,-1,1],[margin,5])`. + +The value of this parameter is used to set gnuplot's margin parameters to the same value `X`. There is no way to set these individually. + + set lmargin X + set rmargin X + set tmargin X + set bmargin X + +The margin also contains any axes numbers, labels etc. outside the plot area. A value of `[margin, 0]` will therefore crop some of the labels. + ### Alternate text for an image (alt tag) {#alttext} The default alternate text for an image (img alt tag) generated by a plot command such as @@ -86,8 +99,6 @@ The grid is controlled by the maxima command `grid2d`. Compare the following. {@plot([x^2/(1+x^2),2*x/(1+x^2)^2], [x, -2, 2], [y,-2.1,2.1], grid2d)@} {@plot([x^2/(1+x^2),2*x/(1+x^2)^2], [x, -2, 2], [y,-2.1,2.1])@} - - ## Piecewise functions A piecewise function can be defined with `if` statements. @@ -146,6 +157,11 @@ It is possible to create multiple plots using the question blocks features. E.g {@plot(x^n,[x,-1,1],[size,250,250],[plottags,false])@} [[/ foreach]] +To illustrate how the `margin` option can be used compare the above with + + [[foreach n="[1,2,3,4,5,6,7,8]"]] + {@plot(x^n,[x,-1,1],[size,250,250],[plottags,false],[margin,1.8])@} + [[/ foreach]] ## Bode plots diff --git a/stack/cas/security-map.json b/stack/cas/security-map.json index f9cd7bc785d..5fca2a12b72 100644 --- a/stack/cas/security-map.json +++ b/stack/cas/security-map.json @@ -4410,6 +4410,9 @@ "mapprint": { "variable": "?" }, + "margin": { + "variable": "?" + }, "mat_cond": { "function": "s" }, diff --git a/stack/maxima/stackmaxima.mac b/stack/maxima/stackmaxima.mac index 7153916f388..757a9bd885b 100644 --- a/stack/maxima/stackmaxima.mac +++ b/stack/maxima/stackmaxima.mac @@ -980,7 +980,7 @@ set_plot_option([gnuplot_default_term_command, ""]); plot(ex, [ra]) := /*stack_web_plot*/ block([simp:true, tfn, tfnp1, tfnp2, tfnp3, afn, ufn, lvs, preamble, sysp, sysr, filename, tn, alt, altc, alttext, ral, ralforbid, pltargs, plotfunmake, plotdebug, - plotgrid2d, size, psize, plot_size, plot_tags, stack_mtell_quiet, plotpid], + plotgrid2d, size, psize, plot_size, plot_tags, stack_mtell_quiet, plotpid, margin], stack_mtell_quiet:true, plotdebug: false, /* Check for grid2d in the plotoptions. */ @@ -1025,7 +1025,7 @@ plot(ex, [ra]) := /*stack_web_plot*/ /**********************************************************/ /* Remove from option list ral any non-permitted options. */ kill(y), - permitted_options: [y, xlabel, ylabel, label, legend, color, style, point_type, nticks, logx, logy, axes, box, plot_realpart, yx_ratio, xtics, ytics, ztics, adapt_depth], + permitted_options: [y, xlabel, ylabel, label, legend, color, style, point_type, nticks, logx, logy, axes, box, plot_realpart, yx_ratio, xtics, ytics, ztics, adapt_depth, margin], /* In the case the list of variables is empty we need to add in "x" so the constant functions can be plotted. */ if not(emptyp(lvs)) then permitted_options:append([first(lvs)], permitted_options) else permitted_options:append([x], permitted_options), @@ -1057,13 +1057,22 @@ plot(ex, [ra]) := /*stack_web_plot*/ ) else block( ral: append(ral, [[legend, false]]) ), - /* Add in the command for the grid. */ - if plotgrid2d and MAXIMA_VERSION_NUM>34 then - ral: append(ral, [grid2d]), if not(PLOT_TERMINAL="svg") then preamble:concat(preamble, "set terminal ", PLOT_TERMINAL, " ", PLOT_TERM_OPT, psize, " set output ", afn), /* Gnuplot only allows alpha-numeric characters in the plot name, so not even spaces! This is a problem with the string function. */ if PLOT_TERMINAL="svg" then preamble:concat("set terminal ", PLOT_TERMINAL, psize, " ", PLOT_TERM_OPT), + /* Reduce margins around plots: this can be added in again with CSS if required, but can't be clipped later. */ + if member(margin, maplist(first, ral)) then block([lv], + /* If we have [legend, true] then we should use the default legend */ + lv: first(sublist(ral, lambda([ex], listp(ex) and (first(ex)=margin)))), + ral:delete(lv, ral), + if not(numberp(second(lv))) then error("Plot error: margin must be a number, found: ", second(lv)), + lv:second(lv), + if PLOT_TERMINAL="svg" then preamble:concat(preamble, ascii(10), "set lmargin ", string(lv), ascii(10), "set rmargin ", string(lv), ascii(10), "set tmargin ", string(lv), ascii(10), "set bmargin ", string(lv)) + ), + /* Add in the command for the grid. */ + if plotgrid2d and MAXIMA_VERSION_NUM>34 then + ral: append(ral, [grid2d]), if plotdebug then print(preamble), if PLOT_TERMINAL="svg" then set_plot_option([svg_file, afn]), if PLOT_TERMINAL="svg" then diff --git a/tests/castext_test.php b/tests/castext_test.php index 05685815d94..b73de80b27b 100644 --- a/tests/castext_test.php +++ b/tests/castext_test.php @@ -672,7 +672,7 @@ public function test_plot_alttext_error() { /** * @covers \qtype_stack\stack_cas_castext2_latex */ - public function test_plot_small() { + public function test_plot_small_margin() { $a2 = array('p:sin(x)'); $s2 = array(); @@ -681,7 +681,8 @@ public function test_plot_small() { } $cs2 = new stack_cas_session2($s2, null, 0); - $at1 = castext2_evaluatable::make_from_source('A small plot: {@plot(p, [x,-2,3], [size,200,100])@}', 'test-case'); + $at1 = castext2_evaluatable::make_from_source('A small plot: {@plot(p, [x,-2,3], [size,200,100], [margin, 1])@}', + 'test-case'); $this->assertTrue($at1->get_valid()); $cs2->add_statement($at1); $cs2->instantiate(); From 85e055119615735468423828b315315496257c12 Mon Sep 17 00:00:00 2001 From: Chris Sangwin Date: Thu, 9 Nov 2023 09:10:10 +0000 Subject: [PATCH 3/3] Update Parsons.md Update the docs. --- doc/en/Topics/Parsons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/Topics/Parsons.md b/doc/en/Topics/Parsons.md index 80fbc05c92e..b36a7dc58bf 100644 --- a/doc/en/Topics/Parsons.md +++ b/doc/en/Topics/Parsons.md @@ -191,7 +191,7 @@ To display a correct proof as a "teacher's answer" 1. Create a new input `ans2`. 2. The _Input type_ field should be **String**. 3. The _Model answer_ field should display the correct proof constructed from a proof construction functions `ta` and a list of proof steps `proof_steps`. Set the model answer to `proof_display(ta, proof_steps)`. You can choose any of the other display functions in the [CAS libraries for representing text-based proofs](../Proof/Proof_CAS_library.md). -4. Set the option "Student must verify" to "no". +4. Set the option "Student must verify" to "no" and "Show the validation" to "no". 5. Hide this input with CSS `

...

`. This input is not used in any PRT.