Skip to content

Figures

m4bwav edited this page Oct 26, 2026 · 1 revision

Since 0.2.0 a media item can be a drawn figure instead of a file: {"figure": {...}, "alt": "..."}. ask.py draws it as SVG and sends it to the page as an image (a data: URL, which the page's Content Security Policy already allows), so no file is written and nothing is fetched. There are three types: graph, forces and shapes. Figures work in any set, but they were made for quizzes (Study mode): a distance-time graph, a free body diagram, a fraction bar.

Each figure sits on a white card with rounded corners, so it reads the same in a dark theme. graph and forces are 480 by 300; shapes takes its own width and height (480 by 300 when left out). Give every figure an alt saying what it shows; without one lint warns.

Looking at one first

ask.py figure <spec.json> <out.svg> draws a spec to a file. The file may hold the figure object itself or a whole media item with a figure key. Each picture below is the SVG this command wrote from the 0.2.0 release zip.

graph

x_label, y_label and lines (each with a label and at least two [x, y] points), plus optional x_max, y_max (by default the largest value), grid (default on), dashed per line and marks (labelled points). Lines differ by colour and dash; give a curve as many points.

{
  "type": "graph", "x_label": "Time (s)", "y_label": "Distance (m)", "x_max": 10, "y_max": 50, "grid": true,
  "lines": [
    {"label": "Walker", "points": [[0, 0], [10, 20]]},
    {"label": "Cyclist", "points": [[0, 0], [4, 40], [10, 40]], "dashed": true,
     "marks": [{"x": 4, "y": 40, "label": "stops"}]}
  ]
}
$ python ask.py figure graph.json graph.svg
{
  "ok": true,
  "path": "/work/proj/graph.svg"
}
$ echo $?
0

A distance-time graph: the walker's solid line rises steadily to 20 m at 10 s; the cyclist's dashed line rises to 40 m at 4 s, marked "stops", then stays flat.

forces

A box (object names it) with an arrow per force. dir is right, left, up or down, and newtons must be above 0. The arrow is labelled with label, or with the newtons ("50 N") when there is no label, and is blue unless the force has a color. Several forces in one direction sit side by side.

{
  "type": "forces", "object": "crate",
  "forces": [
    {"dir": "right", "newtons": 30, "label": "push 30 N"},
    {"dir": "left", "newtons": 30, "label": "friction 30 N"},
    {"dir": "up", "newtons": 50, "label": "normal 50 N"},
    {"dir": "down", "newtons": 50}
  ]
}
$ python ask.py figure forces.json forces.svg
{
  "ok": true,
  "path": "/work/proj/forces.svg"
}
$ echo $?
0

A crate with four arrows: push 30 N to the right, friction 30 N to the left, normal 50 N up, and 50 N down.

Arrow lengths compare only along one axis. Each arrow is scaled to the largest force in the figure, but a sideways arrow has more room than an upright one. Here the 30 N arrows are drawn about as long as the 50 N ones. When the sizes matter to the question, put the newtons in the label, as this example does.

shapes

items, each with a shape and its numbers: rect (x, y, w, h), circle (x, y, r), line and arrow (x1, y1, x2, y2) and text (x, y, text). Any item can take a label, a color (the stroke, near black by default) and a fill (none by default). Text is centred on x and sits a little above y; a rectangle's label goes in its middle.

{
  "type": "shapes", "width": 240, "height": 110,
  "items": [
    {"shape": "rect", "x": 20, "y": 20, "w": 50, "h": 50, "fill": "#9cc3e6"},
    {"shape": "rect", "x": 70, "y": 20, "w": 50, "h": 50, "fill": "#9cc3e6"},
    {"shape": "rect", "x": 120, "y": 20, "w": 50, "h": 50, "fill": "#9cc3e6"},
    {"shape": "rect", "x": 170, "y": 20, "w": 50, "h": 50},
    {"shape": "text", "x": 120, "y": 95, "text": "3/4 shaded"}
  ]
}
$ python ask.py figure shapes.json shapes.svg
{
  "ok": true,
  "path": "/work/proj/shapes.svg"
}
$ echo $?
0

A bar of four squares, three shaded blue, labelled "3/4 shaded".

When a spec is wrong

figure prints what is wrong and writes nothing. In a set, the same messages are lint errors under the rule figure (Lint rules and errors).

$ python ask.py figure f.json f.svg  # f.json is {"type": "pie"}
{
  "ok": false,
  "errors": [
    "figure type must be graph, forces or shapes"
  ]
}
$ echo $?
1
$ python ask.py figure f.json f.svg  # f.json is {"type": "forces"}
{
  "ok": false,
  "errors": [
    "a forces figure needs forces: [{dir: right|left|up|down, newtons: n}]"
  ]
}
$ echo $?
1
$ python ask.py figure f.json f.svg  # f.json is {"type": "shapes", "width": 100, "height": 50}
{
  "ok": false,
  "errors": [
    "a shapes figure needs items: [{shape: rect|circle|line|arrow|text, ...}]"
  ]
}
$ echo $?
1

The other messages, from the source: "a graph needs lines: [{label, points: [[x, y], ...]}]", "every graph line needs points: at least two [x, y] number pairs", "x_max must be a number above 0" (or y_max), "every force needs dir (right, left, up or down) and newtons above 0", and, for a shape item missing a number, "shape items need shape (...) and numbers for x, y, w, h (rect), r (circle) or x1, y1, x2, y2 (line, arrow)".

Clone this wiki locally