Best in classloading…

Remix figure gallery

Marking as you go: tap the ★ on any panel you are happy with to mark it best in class. The bar at the top is how much of the house style is signed off. A ⚑ flag says "this one is wrong"; a star says "this one is finished" ; they are separate on purpose, because one is a worklist and the other is a readiness number.

Every figure helper in the shared kit, rendered live from remix_lib/kit.js using the examples in remix_lib/figure_registry.json. Nothing is hand-drawn, so a panel cannot drift from the helper it documents. Grid panels are generated from remix_lib/grid_standards.json, the same file the grid gates read.
Owner: print at 100% on A4 and check every figure prints clearly, especially that gridlines are dark enough to see and draw on. Tell me the number of any that prints faint or wrong. We render ONE house style for every board (your standing decision that the Edexcel grid weight and colours are the standard throughout), so grid_standards.json's per-board tier counts are reference data about the real papers, not what we draw. Panels marked house style differs from some real papers are the deliberate divergences and are worth your eye.
Checking labels behave: open a card's Stress test strip. Rather than a random remix, each helper carries a fixed, deliberately awkward spread — the longest labels, the narrowest angles, empty input — because collisions happen at extremes, not at random values, and a fixed case can go into the visual baseline so a fix can never silently come undone.
Author agent: find your figure here BEFORE drawing one. A figure with no entry is where a new helper gets proposed, not where a one-off gets invented. Amber-outlined cards are measured by the student and must declare a physical mm width.

Grid standard in force

tiercolourrgbweight drawnEdexcel measured
minor (every square)#bdbcbcrgb(189, 188, 188)0.5u0.5pt = 0.176mm
major (5th)#bdbcbcrgb(189, 188, 188)1u1pt = 0.352mm
10th / axis#bdbcbcrgb(189, 188, 188)1.5u1.5pt = 0.528mm
Colours and the 1:2:3 weight ratio are vector-measured from real Edexcel question papers (reference/edexcel_grid_calibration.json; 12 lattice figures across 8 papers, 757 strokes). The two weight columns are not the same thing: the drawn weight is in SVG user units, so what it prints at depends on each figure's own resolution; the Edexcel column is the physical target on paper. Each grid panel below reports what it actually drew, measured from its own SVG. No gallery panel declares a physical width, so none of them can be ruler-checked against the mm column — a real remix paper can, because its construction grids are mm-pinned.

Open flags on this page

    Question figures — drawn in the question the student reads

    1 bearingFig
    A bearing from one point to another: dashed north line with an arrowhead, the clockwise arc, and the three-figure angle.
    Use when: Any three-figure bearing question.
    bearingFig({bearing, from, to, label, fs, w})
    072
    NAB072°
    bearingFig({"bearing":72,"from":"A","to":"B","fs":15,"w":1.9})
    215
    NAB215°
    bearingFig({"bearing":215,"fs":15,"w":1.9})
    310
    NAB310°
    bearingFig({"bearing":310,"fs":15,"w":1.9})
    Stress test — 1 deliberately awkward case (longest labels, narrowest angles, empty input)
    due north degenerate
    NAB000°
    Replaces:
    2 compositeSolid
    Two solids joined at a shared circular face, with the join drawn as dashed back arc + solid front arc.
    Use when: Volume/surface-area of a composite (hemisphere-on-cylinder, cone-on-cylinder, cone-on-hemisphere).
    compositeSolid({kind, r, h, rLabel, hLabel, fs, w})
    hemisphere on cylinder
    6 cm15 cm
    compositeSolid({"kind":"hemisphere-on-cylinder","rLabel":"6 cm","hLabel":"15 cm","fs":15,"w":1.9})
    cone on cylinder
    6 cm15 cm
    compositeSolid({"kind":"cone-on-cylinder","rLabel":"6 cm","hLabel":"15 cm","fs":15,"w":1.9})
    cone on hemisphere
    6 cm
    compositeSolid({"kind":"cone-on-hemisphere","rLabel":"6 cm","fs":15,"w":1.9})
    Stress test — 1 deliberately awkward case (longest labels, narrowest angles, empty input)
    cone on hemisphere wide label
    137.25 mm
    Replaces:
    3 coneSolid
    Schematic cone outline: apex + base ellipse, front arc solid, back arc dashed.
    Use when: A reference/naming cone figure (faces/edges/vertices, volume, surface area).
    coneSolid({cx, yBase, rx, ry, H})
    default
    coneSolid({"cx":80,"yBase":100,"rx":46,"ry":15,"H":88})
    Replaces: hand-computed elliptical-arc cone outlines
    4 coordGrid
    Four-quadrant coordinate grid: axes THROUGH the origin with arrowheads, an O at the origin, italic x and y at the arrow tips, and one gridline per unit. This is the transformations/coordinates grid.
    Use when: ANY question that plots points or moves a shape: transformations, coordinates, vectors on a grid, describing a translation. NOT for a graph with axes at the edge ; that is plotGrid.
    coordGrid({xmin,xmax,ymin,ymax,cell,gl})
    four-quadrant -5..5
    minor 0.5u #bdbcbc
    xy-5-4-3-2-112345-5-4-3-2-112345O
    coordGrid({"xmin":-5,"xmax":5,"ymin":-5,"ymax":5,"cell":26})
    first quadrant 0..8
    minor 0.5u #bdbcbc
    xy1234567812345678O
    coordGrid({"xmin":0,"xmax":8,"ymin":0,"ymax":8,"cell":24})
    stress-wide -8..8 by -4..4
    minor 0.5u #bdbcbc
    xy-8-7-6-5-4-3-212345678-4-3-21234O
    coordGrid({"xmin":-8,"xmax":8,"ymin":-4,"ymax":4,"cell":20})
    Replaces:
    5 cornerPatternSvg
    The 'm x m block of unit squares plus one extra square touching each of its 4 corners' sequence archetype (AQA-style pattern-count questions), as unit-square rects. Returns SVG INNARDS, not a finished figure, so it composes with other ink; the registry records that with `fragment`.
    Use when: A pattern-sequence question built from this specific corner-touching-squares shape (small reference icons AND/OR a student-drawn pattern on a grid).
    cornerPatternSvg(cornerPatternCells(m,ox,oy), cellPx, off, fill, strokeC, sw)
    m=2
    cornerPatternSvg([[1,1],[1,2],[2,1],[2,2],[0,0],[3,0],[0,3],[3,3]], 16, 0, "#ccc", "#111", 1)
    Replaces: hand-rolled block-plus-corner-squares rect loops
    6 isoCuboid
    Cuboid in the house isometric projection, hidden edges dashed, edges labelled.
    Use when: Any cuboid: volume, surface area, nets, space diagonals.
    isoCuboid({x, y, w, h, d, iso, hidden, dash, labels})
    labelled
    8 cm5 cm3 cm
    isoCuboid({"x":40,"y":130,"w":150,"h":70,"d":50,"labels":{"w":"8 cm","h":"5 cm","d":"3 cm"}})
    Replaces: raw <polygon> faces with hand-computed isometric offsets
    7 cylinderSolid
    Cylinder in line art, upright or on its side, SCALED from its real diameter and height. Dimensions via dimLine, so a label can never sit on the line it measures.
    Use when: Any cylinder: volume, surface area, capacity, a plan/elevation source solid.
    cylinderSolid({cx, yBase, d, h, dLabel, rLabel, hLabel, horizontal, notToScale, board})
    upright, diameter
    70 m65 mNot drawn accurately
    cylinderSolid({"cx":150,"yBase":215,"d":70,"h":65,"dLabel":"70 m","hLabel":"65 m","notToScale":true,"board":"AQA"})
    upright, radius
    7 cm40 cmNot drawn accurately
    cylinderSolid({"cx":150,"yBase":215,"d":14,"h":40,"rLabel":"7 cm","hLabel":"40 cm","notToScale":true,"board":"AQA"})
    horizontal
    180 cm60 cmNot drawn accurately
    cylinderSolid({"cx":150,"yBase":190,"d":60,"h":180,"horizontal":true,"dLabel":"60 cm","hLabel":"180 cm","notToScale":true,"board":"AQA"})
    stress: extreme tall/thin
    8 cm200 cmNot drawn accurately
    cylinderSolid({"cx":150,"yBase":215,"d":8,"h":200,"dLabel":"8 cm","hLabel":"200 cm","notToScale":true,"board":"AQA"})
    stress: extreme flat/wide
    200 cm8 cmNot drawn accurately
    cylinderSolid({"cx":150,"yBase":205,"d":200,"h":8,"dLabel":"200 cm","hLabel":"8 cm","notToScale":true,"board":"AQA"})
    stress: long label on the height
    30 cm137.5 cmNot drawn accurately
    cylinderSolid({"cx":150,"yBase":215,"d":30,"h":90,"dLabel":"30 cm","hLabel":"137.5 cm","notToScale":true,"board":"AQA"})
    Replaces: hand-rolled cylinder outlines across 7 question files and 6 solution modules
    8 formulaBox
    The bordered 'you are given' formula panel. Every part positioned from MEASURED text width, and its mini diagrams drawn by the real solid helpers so the icon and the question figure cannot drift apart.
    Use when: A question that supplies a volume/area formula beside its solid.
    formulaBox({x, y, rows:[{text, frac:[n,d], tail, icon}], fs, w, labelIcons})
    cone + sphere
    Volume of a cone =13πr²hrhVolume of a sphere =43πr³r
    formulaBox({"x":10,"y":14,"rows":[{"text":"Volume of a cone =","frac":[1,3],"tail":"πr²h","icon":"cone"},{"text":"Volume of a sphere =","frac":[4,3],"tail":"πr³","icon":"sphere"}]})
    cylinder + hemisphere
    Volume of a cylinder =πr²hrhVolume of a hemisphere =23πr³r
    formulaBox({"x":10,"y":14,"rows":[{"text":"Volume of a cylinder =","tail":"πr²h","icon":"cylinder"},{"text":"Volume of a hemisphere =","frac":[2,3],"tail":"πr³","icon":"hemisphere"}]})
    no icons
    Volume of a cylinder =πr²hCurved surface area =2πrh
    formulaBox({"x":10,"y":14,"rows":[{"text":"Volume of a cylinder =","tail":"πr²h"},{"text":"Curved surface area =","tail":"2πrh"}]})
    stress: very long formula text
    Volume of a frustum of a cone =13πh(R²+Rr+r²)rh
    formulaBox({"x":10,"y":14,"rows":[{"text":"Volume of a frustum of a cone =","frac":[1,3],"tail":"πh(R²+Rr+r²)","icon":"cone"}]})
    Replaces: hand-rolled formula panels at 8 sites, all positioned with tuned per-word x offsets
    9 freqTree2
    Two-level frequency tree: root count splits into two branches, each into two leaves, every node a labelled circle holding a COUNT (never a branch fraction — that's probTree2).
    Use when: "Complete the frequency tree" questions with counts (not probabilities) at every node.
    freqTree2({total, mid:[{label,value,show,hi}x2], leaves:[{label,value,show,hi}x4], topLabel, leafTopLabel, mmW})
    partly complete
    10060HomeAwayYesNoYesNoFansScarf
    freqTree2({"total":100,"topLabel":"Fans","leafTopLabel":"Scarf","mid":[{"label":"Home","value":60,"show":true},{"label":"Away","value":40,"show":false}],"leaves":[{"label":"Yes","value":18,"show":false,"hi":true},{"label":"No","value":42,"show":false,"hi":true},{"label":"Yes","value":12,"show":false,"hi":true},{"label":"No","value":28,"show":false,"hi":true}]})
    Replaces: hand-rolled circle-node frequency trees with hard-coded edge/label offsets
    10 frustumSolid
    Cone frustum: two ellipse ends, slanted sides, dashed back arcs, optional removed-apex construction.
    Use when: Frustum volume, or a 'cone with the top removed' question.
    frustumSolid({rTop, rBase, H, labels, showApex, fs, w})
    plain frustum
    frustumSolid({"rTop":30,"rBase":64,"H":90,"fs":15,"w":1.9})
    with removed apex
    frustumSolid({"rTop":30,"rBase":64,"H":90,"showApex":true,"fs":15,"w":1.9})
    Replaces:
    11 halfPipeSolid
    Half cylinder open along its axis: a trough/gutter/half-pipe. Semicircular cross-section extruded back-right, opaque front end face.
    Use when: Volume or surface area of a trough, gutter or half-pipe. NOT cylinderSolid({horizontal:true}) ; that projection cannot show a cut along the axis.
    halfPipeSolid({r, len, depthAngle, dLabel, rLabel, lLabel, fs, w, notToScale, board})
    default
    halfPipeSolid({"fs":15,"w":1.9})
    diameter and length
    40 cm150 cm
    halfPipeSolid({"dLabel":"40 cm","lLabel":"150 cm","fs":15,"w":1.9})
    radius only
    20 cm
    halfPipeSolid({"rLabel":"20 cm","fs":15,"w":1.9})
    Stress test — 2 deliberately awkward cases (longest labels, narrowest angles, empty input)
    long shallow trough
    0.9 m3.2 m
    short steep wide label
    137.25 mm
    Replaces: hand-rolled trough/gutter outlines
    12 prismSolid
    Prism drawn from a cross-section swept along a depth, hidden edges dashed.
    Use when: Volume/surface-area of a prism where the cross-section matters.
    prismSolid({section, depth, labels, fs, w})
    triangular prism
    prismSolid({"section":"triangle","labels":{"h":"6 cm","b":"8 cm","d":"15 cm"},"fs":15,"w":1.9})
    trapezoidal prism
    prismSolid({"section":"trapezium","labels":{"d":"20 cm"},"fs":15,"w":1.9})
    Replaces:
    13 schematicAxes
    Arrowed x and y axes with NO grid: the coordinate figure a paper DRAWS FOR you rather than one the student plots on. Returns {s,X,Y,W,H} so the caller adds its own ink in data coordinates and wraps it, which is what lets it compose with a circle, a curve or a tangent in one picture.
    Use when: Any 'the diagram shows' coordinate figure that is NOT drawn on graph paper: a sketched curve, a circle centred at the origin, a tangent meeting the axes, a region between lines. NO GRID DOES NOT MEAN NO PENCIL: ocr-jun2024-p5h Q22 is @drawnOn yes and graphspec-registered, because the student sketches a curve freehand onto blank axes. Judge @drawnOn by whether the student marks the figure, not by whether it has squares; only reach for svgWrapMM when something on it is MEASURED.
    schematicAxes({xmin,xmax,ymin,ymax,cell,xName,yName,origin,originName,fs,pad})
    first quadrant
    xyO
    schematicAxes({"xmin":-1,"xmax":8,"ymin":-1,"ymax":6,"cell":24})
    four quadrant
    xyO
    schematicAxes({"xmin":-5,"xmax":5,"ymin":-4,"ymax":4,"cell":24})
    named axes, no origin letter
    tv
    schematicAxes({"xmin":0,"xmax":7,"ymin":0,"ymax":5,"cell":26,"xName":"t","yName":"v","origin":false})
    Stress test — 3 deliberately awkward cases (longest labels, narrowest angles, empty input)
    tall and narrow
    xyO
    wide and flat
    xyO
    origin hard against the left edge
    xyO
    Replaces: hand-typed <polygon> arrowheads on a pair of axis <line>s, with the x/y/O labels placed by eye
    14 sphereSolid
    Sphere, bowl or dome in LINE ART (owner ruling M119), scaled from its real diameter. Equator drawn front-solid/back-dashed so a sphere does not read as a flat circle.
    Use when: Any sphere/hemisphere: volume, surface area, a composite solid's cap.
    sphereSolid({cx, cy, d, r, dLabel, rLabel, dThrough, hemisphere, flip, equator, notToScale, board})
    sphere, diameter above
    18 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":150,"d":18,"dLabel":"18 cm","notToScale":true,"board":"AQA"})
    sphere, radius
    9 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":150,"r":9,"rLabel":"9 cm","notToScale":true,"board":"AQA"})
    sphere, diameter through
    18 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":150,"d":18,"dLabel":"18 cm","dThrough":true,"notToScale":true,"board":"AQA"})
    bowl, diameter across rim
    11 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":130,"d":11,"dLabel":"11 cm","dThrough":true,"hemisphere":true,"notToScale":true,"board":"AQA"})
    dome
    11 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":170,"d":11,"dLabel":"11 cm","dThrough":true,"hemisphere":true,"flip":true,"notToScale":true,"board":"AQA"})
    stress: long radius label
    12.75 cmNot drawn accurately
    sphereSolid({"cx":150,"cy":150,"r":9,"rLabel":"12.75 cm","notToScale":true,"board":"AQA"})
    Replaces: hand-rolled spheres/hemispheres across 13 question files and 13 solution modules; the grey-shaded 3D spheres of edexcel-jun2019-p1h Q12
    15 pyramidSolid
    Square-based (or triangular) pyramid in 45-degree oblique projection, matching the boards own convention: parallelogram base, apex over the centre, hidden back edges dashed.
    Use when: A reference/naming pyramid figure (faces/edges/vertices, nets, volume).
    pyramidSolid({cx, yBase, bw, bh, H, depth, frontOn})
    default
    pyramidSolid({"cx":80,"yBase":100,"bw":52,"bh":27,"H":82})
    square base, 45-degree oblique (the board's own projection)
    pyramidSolid({"cx":90,"yBase":120,"bw":52,"H":100})
    legacy front-on diamond (frontOn)
    pyramidSolid({"cx":90,"yBase":120,"bw":52,"bh":27,"H":82,"frontOn":true})
    triangular base
    pyramidSolid({"cx":90,"yBase":120,"bw":52,"bh":27,"H":82,"base":"triangle"})
    Replaces: hand-computed apex/base-corner pyramid outlines
    16 tickBoxes
    A vertical list of empty tick boxes with their option text, in the board's tick-a-box style.
    Use when: A question asking the student to TICK a box. A circle/tick/choose MCQ is print-broken on a worksheet unless its options actually render, which is what this helper does.
    tickBoxes([optionText, ...], {box, gap})
    three options
    Always trueSometimes trueNever true
    tickBoxes(["Always true","Sometimes true","Never true"])
    two options, wider boxes
    Town ATown B
    tickBoxes(["Town A","Town B"], {"box":18})
    Replaces:
    17 vecParallelogram
    Parallelogram with a point dividing a side in ratio m:n, for vector geometry.
    Use when: Vector geometry on a parallelogram with a division point.
    vecParallelogram({m, n, board})
    1:3
    OABCNacNot to scale
    vecParallelogram({"m":1,"n":3})
    Replaces: raw <polygon> parallelogram plus hand-placed vertex letters

    Both — used in questions and inside solutions

    18 titledCard
    A rounded-rect advert/poster card: bold title then a stack of text lines (a shop-deal / offer-board figure).
    Use when: Comparing two or more shops'/offers' prices printed on card-style figures.
    titledCard(x, y, w, h, title, lines, opts)
    single card
    Shop A1 tin 64pBuy 4, pay for 3
    titledCard(10, 10, 250, 120, "Shop A", [{"text":"1 tin 64p","fs":14},{"text":"Buy 4, pay for 3","fs":12}])
    Replaces: hand-rolled rounded-rect + title + text-line loops
    19 angleFactsFig
    The angle-fact configurations: angles on a straight line, angles at a point, and parallel lines cut by a transversal.
    Use when: Angle reasoning on a line/at a point, or corresponding/alternate/co-interior angles.
    angleFactsFig({kind, labels, slant, fs, w})
    parallel lines
    3x - 15q
    angleFactsFig({"kind":"parallel-lines","labels":["3x - 15",null,null,"q"],"fs":15,"w":1.9})
    parallel lines shallow slant
    abcd
    angleFactsFig({"kind":"parallel-lines","slant":35,"labels":["a","b","c","d"],"fs":15,"w":1.9})
    Stress test — 1 deliberately awkward case (longest labels, narrowest angles, empty input)
    parallel lines very shallow
    124°xyz
    Replaces:
    20 boxPlotGridphysical size
    Box plot on a measured grid, one or two lanes, with a physical cell size.
    Use when: Any box plot the student reads off or draws on. Declare mmPerCell and register a graphspec entry.
    boxPlotGrid({xmin, xmax, minor, major, cell, box, whisker, stats, twoLane, box2Up, label, unit, mmPerCell, fit, ...})
    single
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    0102030405060minutes
    boxPlotGrid({"xmin":0,"xmax":60,"minor":2,"major":10,"box":[20,30,42],"whisker":[10,55],"unit":"minutes"})
    Replaces: hand-rolled lattice + <rect> box + whisker <line>s
    21 givenBox
    A bordered box of GIVEN information (a recipe, a price list, a set of facts). Hugs its content; the box position and the text alignment inside it are set separately.
    Use when: Any block of given data the student reads but does not write in. `align` positions the BOX, `textAlign` aligns the LINES inside it (owner M139: an ingredients list reads left-justified inside a centred box).
    givenBox(content, {title, align, textAlign, inline, maxWidth})
    ingredients, left-justified lines in a centred box
    Ingredients for 25 gingerbread biscuits
    120 g of flour
    180 g of margarine
    160 g of ground ginger
    givenBox("120 g of flour<br>180 g of margarine<br>160 g of ground ginger", {"title":"Ingredients for 25 gingerbread biscuits","textAlign":"left"})
    centred (default)
    Counters in a bag
    12 red
    8 blue
    givenBox("12 red<br>8 blue", {"title":"Counters in a bag"})
    Replaces:
    22 circleFig
    Circle with any of centre, radius, diameter, chord, tangent and arc, each labelled.
    Use when: Circle vocabulary, circumference/area, or a circle-parts naming figure.
    circleFig({r, centre, centreName, radiusAt, rLabel, diameterAt, dLabel, chordFrom, chordTo, arcFrom, arcTo, arcLong, tangentAt, tangentRadius, fs, w})
    radius labelled
    O7 cm
    circleFig({"radiusAt":35,"rLabel":"7 cm","fs":15,"w":1.9})
    diameter labelled
    O14 cm
    circleFig({"diameterAt":0,"dLabel":"14 cm","fs":15,"w":1.9})
    chord labelled
    O8 cm
    circleFig({"chord":"8 cm","chordFrom":210,"chordTo":-30,"fs":15,"w":1.9})
    tangent and radius
    O
    circleFig({"tangent":true,"tangentAt":-90,"tangentRadius":true,"fs":15,"w":1.9})
    arc highlighted between two marked points
    circleFig({"r":90,"centre":true,"centreName":" ","arcFrom":20,"arcTo":140})
    Stress test — 1 deliberately awkward case (longest labels, narrowest angles, empty input)
    chord tangent and radius together
    O6.5 cm12.5 cm
    Replaces:
    23 circleTheoremFig
    All eight circle theorems in one helper, correct by construction: angle at the centre, semicircle, same segment, cyclic quadrilateral, tangent-radius, two tangents, alternate segment, perpendicular from the centre bisects a chord.
    Use when: Any circle-theorem figure. Do NOT hand-roll one ; the tangent, two-tangent and alternate-segment constructions are the ones that go subtly wrong by hand.
    circleTheoremFig({theorem, r, labels:{}, angles:{}, apex, showOP, fs, w, notToScale, board})
    angle at the centre
    ABCOx2x
    circleTheoremFig({"theorem":"angle-at-centre","labels":{"centre":"2x","circumference":"x"},"fs":15,"w":1.9})
    angle in a semicircle
    ABCO34°
    circleTheoremFig({"theorem":"semicircle","labels":{"a2":"34"},"fs":15,"w":1.9})
    angles in the same segment
    ABCD52°x
    circleTheoremFig({"theorem":"same-segment","labels":{"c2":"52","d2":"x"},"fs":15,"w":1.9})
    cyclic quadrilateral
    ABCD96°x
    circleTheoremFig({"theorem":"cyclic-quad","labels":{"a2":"96","c2":"x"},"fs":15,"w":1.9})
    tangent and radius
    TPO38°
    circleTheoremFig({"theorem":"tangent-radius","labels":{"p2":"38"},"fs":15,"w":1.9})
    two tangents
    ABPO52°
    circleTheoremFig({"theorem":"two-tangents","apex":52,"labels":{"apex":"52"},"fs":15,"w":1.9})
    alternate segment
    TAB64°x
    circleTheoremFig({"theorem":"alternate-segment","labels":{"tangent":"64","inscribed":"x"},"fs":15,"w":1.9})
    perpendicular bisects a chord
    ABOM8 cm6 cm
    circleTheoremFig({"theorem":"chord-bisector","labels":{"chord":"8 cm","dist":"6 cm"},"fs":15,"w":1.9})
    Stress test — 5 deliberately awkward cases (longest labels, narrowest angles, empty input)
    alternate segment mirrored
    TAByy
    two tangents right angle
    ABPO90°
    two tangents very narrow
    ABPO20°
    chord near the arc
    ABOM9.6 cm7 cm
    chord near vertical wide label
    ABOM137.25 mm5 cm
    Replaces: the seven per-theorem builders in output/gcse/circle-theorems/gen.js
    24 plotGridphysical size
    THE plotting grid: tiered gridlines to the board's standard, axes, and any of curves, points, bars, lines of best fit, shaded regions, read-off guides.
    Use when: ANY grid the student plots on or reads off. The grid KIND (gt) must match the task and is gated by grid_kind_check.js: never a generic lattice ('squared'/'transformations') for a plotting task, and never a forced tiers:1.
    plotGrid({xmin, xmax, ymin, ymax, xmajor, xminor, ymajor, yminor, cell, gt, tiers, points, curves, bars, lobf, lobfColour, readoff, shade, xlabel, ylabel, xTickText, points2, segments2, curves2, mmPerCell, fit, ...})
    barcharthouse style differs from some real papers
    we render 3-tier (tiered) · real papers: AQA 1-tier, OCR 1-tier, Edexcel 3-tier
    minor 0.5u #bdbcbc5th 1u #bdbcbc
    01234567805101520ColourFrequency
    plotGrid({"gt":"barchart","xmin":0,"xmax":8,"ymin":0,"ymax":20,"xmajor":1,"ymajor":5,"xlabel":"Colour","ylabel":["Frequency"]})
    boxplot
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    01020304050600246810Speed (mph)Frequency
    plotGrid({"gt":"boxplot","xmin":0,"xmax":60,"ymin":0,"ymax":10,"xmajor":10,"ymajor":2,"xlabel":"Speed (mph)","ylabel":["Frequency"]})
    conversion
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    0123456789100510152025LitresPints
    plotGrid({"gt":"conversion","xmin":0,"xmax":10,"ymin":0,"ymax":25,"xmajor":1,"ymajor":5,"xlabel":"Litres","ylabel":["Pints"]})
    cumfreq
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    02468100102030405060Weight (w kg)Cumulativefrequency
    plotGrid({"gt":"cumfreq","xmin":0,"xmax":10,"ymin":0,"ymax":60,"xmajor":2,"ymajor":10,"xlabel":"Weight (w kg)","ylabel":["Cumulative","frequency"]})
    curveplot
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    -3-2-1123-4-224681012Oxy
    plotGrid({"gt":"curveplot","xmin":-3,"xmax":3,"ymin":-4,"ymax":12,"xmajor":1,"ymajor":2,"xlabel":"x","ylabel":"y"})
    histogramhouse style differs from some real papers
    we render 3-tier (tiered) · real papers: AQA 1-tier, OCR 3-tier, Edexcel 3-tier
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    140150160170180190200012345Height (cm)Frequencydensity
    plotGrid({"gt":"histogram","xmin":140,"xmax":200,"ymin":0,"ymax":5,"xmajor":10,"xminor":2,"ymajor":1,"yminor":0.2,"xlabel":"Height (cm)","ylabel":["Frequency","density"]})
    readoff
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    024681001020304050Time (s)Distance (m)
    plotGrid({"gt":"readoff","xmin":0,"xmax":10,"ymin":0,"ymax":50,"xmajor":2,"ymajor":10,"xlabel":"Time (s)","ylabel":"Distance (m)"})
    scatter
    we render 3-tier (tiered)
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    02468100246810Arm span (m)Height (m)
    plotGrid({"gt":"scatter","xmin":0,"xmax":10,"ymin":0,"ymax":10,"xmajor":2,"ymajor":2,"xlabel":"Arm span (m)","ylabel":["Height (m)"]})
    sketchaxes
    we render no grid
    -4-3-2-11234-4-22468Oxy
    plotGrid({"gt":"sketchaxes","xmin":-4,"xmax":4,"ymin":-4,"ymax":8,"xmajor":1,"ymajor":2,"xlabel":"x","ylabel":"y","originAxes":true})
    speedtimehouse style differs from some real papers
    we render 3-tier (tiered) · real papers: AQA 1-tier, OCR 1-tier, Edexcel 3-tier
    minor 0.5u #bdbcbc5th 1u #bdbcbc10th / axis 1.5u #bdbcbc
    01020304050600510152025Time (s)Speed (m/s)
    plotGrid({"gt":"speedtime","xmin":0,"xmax":60,"ymin":0,"ymax":25,"xmajor":10,"xminor":2,"ymajor":5,"yminor":1,"xlabel":"Time (s)","ylabel":["Speed (m/s)"]})
    squared
    we render 1-tier (uniform)
    minor 1u #bdbcbc
    -6-5-4-3-2-1123456-6-5-4-3-2-1123456Oxy
    plotGrid({"gt":"squared","xmin":-6,"xmax":6,"ymin":-6,"ymax":6,"xmajor":1,"ymajor":1,"xlabel":"x","ylabel":"y","originAxes":true})
    transformations
    we render 1-tier (uniform)
    minor 1u #bdbcbc
    -6-5-4-3-2-1123456-6-5-4-3-2-1123456Oxy
    plotGrid({"gt":"transformations","xmin":-6,"xmax":6,"ymin":-6,"ymax":6,"xmajor":1,"ymajor":1,"xlabel":"x","ylabel":"y","originAxes":true})
    Replaces: hand-rolled gridline loops, which also lose the gl/glM print-visibility classes and so vanish from the print oracle
    25 dimLine
    A double-headed dimension arrow with its label centred ON the line, the standard way to mark a measured length beside a shape. The label sits centred by design: that is the legitimate on-line-label idiom.
    Use when: Marking a length, height or width alongside a drawn shape. Hand-rolling an arrow with its own polygon head instead is the hand-roll class the adoption gate exists to stop.
    dimLine(P, Q, label, {awayFrom, fs, gap})
    horizontal 60 m
    60 m
    dimLine([20,120], [220,120], "60 m")
    vertical 45 m
    45 m
    dimLine([240,20], [240,180], "45 m")
    Replaces:
    26 ineqLine
    Inequality on a number line: open or filled boundary circle, plus either a direction arrow (one-sided) or a second circle joined to the first (a bounded double inequality).
    Use when: Showing or reading an inequality on a number line, one-sided (x > a, boundary + dir) or BOUNDED (a < x <= b, boundary/dir plus boundary2/dir2). Handles the open/closed circle convention per END so it cannot be drawn the wrong way round.
    ineqLine({lo, hi, unit, boundary, dir, boundary2, dir2, show, showColour, varLabel, bothArrows, ocrNeg, raisedMinus})
    x > 2
    -4-3-2-10123456
    ineqLine({"lo":-4,"hi":6,"boundary":2,"dir":"gt"})
    Replaces: hand-drawn line + <circle> boundary + arrow <polygon>
    27 realLineGraph
    Plain line graph with gradient/line overlays, axis labels and padding control.
    Use when: A graph that is READ rather than plotted on. If the student plots or measures, use plotGrid instead so the grid kind and physical size are right.
    realLineGraph({w, h, lines, grad, xlabel, ylabel, ylabelLeft, padL, padR, padT, padB})
    two lines
    OCostTime
    realLineGraph({"w":360,"h":240,"lines":[[[0,0],[4,6],[8,4]]],"xlabel":"Time","ylabel":"Cost"})
    Replaces: hand-rolled axes + <polyline> data
    28 pieChart
    Pie chart from sector degrees, with labels placed clear of the radii.
    Use when: Pie chart questions. Sector degrees must total 360 or it returns '' rather than a wrong picture.
    pieChart({sectors:[{deg, label, ...}], r, shown, fs, afs, arcR})
    4 sectors
    BusWalkCarCycle
    pieChart({"sectors":[{"deg":120,"label":"Bus"},{"deg":90,"label":"Walk"},{"deg":90,"label":"Car"},{"deg":60,"label":"Cycle"}]})
    Replaces: raw <path> arc sectors with hand-computed sweep flags
    29 polyFigure
    General labelled polygon from explicit vertices: edge labels, vertex letters, tick marks, angle arcs.
    Use when: Any polygon whose vertices you already know. Prefer regularPolygon for regular n-gons and quadFig for the named quadrilaterals.
    polyFigure({pts, sides, angles, names, ticks, fs, w})
    pentagon with edge labels
    8 cm
    polyFigure({"pts":[[0,0],[80,0],[104,62],[40,100],[-24,62]],"sides":["8 cm",null,null,null,null],"fs":15,"w":1.9})
    quad with two angles
    ABCDx70°
    polyFigure({"pts":[[0,0],[110,0],[92,74],[14,74]],"angles":{"0":"x","1":"70"},"names":["A","B","C","D"],"fs":15,"w":1.9})
    Replaces:
    30 probScale
    0-to-1 probability scale with lettered arrows to mark events on.
    Use when: Marking or reading the likelihood of events on a 0-1 scale.
    probScale({n, labels, w})
    4 events
    01ABCD
    probScale({"n":4})
    Replaces: hand-drawn scale line with manually positioned arrow marks
    31 probTree2
    Two-stage probability tree, 2 or 3 branches per node, widths from measured labels.
    Use when: Any two-stage probability question, with or without replacement.
    probTree2({out1, out2, p1, p2, headers, fs, gap})
    2 x 2
    28red45red15blue68blue45red15blue
    probTree2({"root":"start","out1":["red","blue"],"l1":[[2,8],[6,8]],"out2":[["red","blue"],["red","blue"]],"l2":[[[4,5],[1,5]],[[4,5],[1,5]]]})
    short branch runs (run1/run2) - fits a wide label set
    Game 1Game 2910wins910wins110does not win110does not win910wins110does not win
    probTree2({"root":"start","out1":["wins","does not win"],"l1":[[9,10],[1,10]],"out2":[["wins","does not win"],["wins","does not win"]],"l2":[[[9,10],[1,10]],[[9,10],[1,10]]],"headers":["Game 1","Game 2"],"run1":120,"run2":105,"dy2":145})
    Replaces: hand-drawn branch <line>s with hard-coded label offsets
    32 regularPolygon
    Regular n-gon with equal-side ticks, optional interior/exterior angle arcs and centre spokes.
    Use when: Interior/exterior angle work on a regular polygon.
    regularPolygon({n, r, ticks, interior, exterior, fs, w})
    hexagon
    regularPolygon({"n":6,"ticks":true,"fs":15,"w":1.9})
    octagon interior angle
    x
    regularPolygon({"n":8,"interior":"x","fs":15,"w":1.9})
    pentagon exterior angle
    72
    regularPolygon({"n":5,"exterior":"72","fs":15,"w":1.9})
    Replaces:
    33 quadFig
    The named quadrilaterals drawn to type, with the right tick/arrow markings for each.
    Use when: A named quadrilateral where the TYPE carries the marking convention (parallel arrows, equal ticks).
    quadFig({kind, sides, angles, ticks, arrows, fs, w})
    parallelogram
    quadFig({"kind":"parallelogram","fs":15,"w":1.9})
    trapezium
    quadFig({"kind":"trapezium","fs":15,"w":1.9})
    kite with ticks
    quadFig({"kind":"kite","ticks":true,"fs":15,"w":1.9})
    rhombus
    quadFig({"kind":"rhombus","fs":15,"w":1.9})
    Replaces:
    34 stemLeaf
    Stem-and-leaf diagram with aligned leaves and a key.
    Use when: Any stem-and-leaf question. Alignment is computed, so leaves cannot drift out of column.
    stemLeaf({rows, key, rowH, colW, stemW, fs, padL, padT, leafC, rowRules, keyBox})
    with key
    125720338314Key:undefinedundefined represents undefined
    stemLeaf({"rows":[{"stem":1,"leaves":[2,5,7]},{"stem":2,"leaves":[0,3,3,8]},{"stem":3,"leaves":[1,4]}],"key":"1 | 2 means 12"})
    board convention (ruled rows, key boxed to the right)
    3025414475368Key:30 represents 30
    stemLeaf({"rows":[{"stem":3,"leaves":[0,2,5]},{"stem":4,"leaves":[1,4,4,7]},{"stem":5,"leaves":[3,6,8]}],"key":{"stem":3,"leaf":0,"value":30},"rowRules":true,"keyRight":true,"rowH":38,"colW":26})
    Replaces: hand-positioned <text> columns and a rule <line>
    35 sampleTable
    A bordered data table in the board's plain style: uniform 1px cell edges, bold header TEXT, no thick rules, and a borderless empty top-left corner. Cells can be pre-filled or left blank for the student.
    Use when: Any table a student reads from or completes: two-way tables, frequency tables, systematic listing, a results grid. Reaching for raw table markup instead is the class-6 table-fidelity flag (p47 Q9). THE TWO-ROW COMPLETION STANDARD (owner, M124): a table a student WRITES IN takes equalCols + compact, so every non-header column is one width wide enough to write in and the rows are about 20% shorter. Content-sizing is for tables that are only READ; on a completion table it makes the write-in cells the narrowest on the row.
    sampleTable({headers, rows:[{label, cells, shown}], noLabelCol, equalCols, compact, totalRule})
    two-way with headers
    WalkBusTotal
    Year 10121830
    Year 11925
    Total55
    sampleTable({"headers":["Walk","Bus","Total"],"rows":[{"label":"Year 10","cells":["12","18","30"]},{"label":"Year 11","cells":["9","","25"]},{"label":"Total","cells":["","","55"]}]})
    completion column, one row given
    AB
     
     
    sampleTable({"noLabelCol":true,"headers":[],"rows":[{"cells":["AB"],"shown":[true]},{"cells":["AC"],"shown":[false]},{"cells":["BC"],"shown":[false]}]})
    two-row completion (equal columns, compact)
    $b$542 
    $D$5 90
    sampleTable({"equalCols":true,"compact":true,"rows":[{"label":"$b$","cells":["54","2",""],"shown":[true,true,false]},{"label":"$D$","cells":["5","","90"],"shown":[true,false,true]}]})
    two-way table with the Total rule (thicker before Total row/column)
    FootballRugbyTotal
    Year 106429
    Year 1163125
    Total75248
    sampleTable({"headers":["Football","Rugby","Total"],"totalRule":true,"equalCols":true,"rows":[{"label":"Year 10","cells":[64,"",29]},{"label":"Year 11","cells":["",63,125]},{"label":"Total","cells":["",75,248]}]})
    Replaces:
    36 labelledTriangle
    Labelled triangle: vertex letters, edge labels, angle arcs and right-angle squares. Schematic by default, true-to-scale on request.
    Use when: Any labelled triangle: Pythagoras, trigonometry, angle reasoning, area. ONE helper for both question and teaching figures.
    labelledTriangle({A, B, C, names, sides:{ab,bc,ca}, angles:{A,B,C}, right, toScale, notToScale, fs, rotate, flipV, legOrder, iso})
    right-angled at B
    ABC8 cm6 cmxNot drawn accurately
    labelledTriangle({"right":"B","sides":{"ab":"8 cm","bc":"6 cm"},"angles":{"A":"x"}})
    angles, schematic
    ABC27 cm42°100°Not drawn accurately
    labelledTriangle({"angles":{"A":"42°","B":"100°"},"sides":{"ab":"27 cm"}})
    same data, toScale
    ABC27 cm42°100°
    labelledTriangle({"angles":{"A":"42°","B":"100°"},"sides":{"ab":"27 cm"},"toScale":true})
    no vertex letters (names:false)
    x29 cm72°Not drawn accurately
    labelledTriangle({"names":false,"right":"A","sides":{"ab":"x","bc":"29 cm"},"angles":{"C":"72"}})
    mirrored vertically (flipV) - apex below the base
    ABC7.6 cm62°
    labelledTriangle({"right":"B","angles":{"C":"62°"},"sides":{"bc":"7.6 cm"},"flipV":true,"notToScale":false})
    Stress test — 5 deliberately awkward cases (longest labels, narrowest angles, empty input)
    very long edge labels
    ABC14.75 cm9.125 cm17.4 cmNot drawn accurately
    obtuse at A
    ABC11 m6 m128°Not drawn accurately
    equilateral
    ABC8 cm60°60°Not drawn accurately
    no labels at all
    ABCNot drawn accurately
    toScale, very acute
    ABC30 cm20°25°
    Replaces: raw <polygon> triangles with hand-placed vertex letters and angle arcs
    37 venn2
    Two-set Venn with region counts, set labels and a universal-set box.
    Use when: Two-set Venn questions, filled in or blank for the student to complete.
    venn2({setA, setB, aOnly, both, bOnly, outside, uniInside, blankLabels, noLeaders})
    counts
    ξFootballNetball7594
    venn2({"setA":"Football","setB":"Netball","aOnly":7,"both":5,"bOnly":9,"outside":4})
    Replaces: two raw <circle> outlines plus manually placed <text> counts

    Teaching figures — drawn inside solutions to explain a method

    38 areaModel
    The grid (box) method for expanding brackets, headers outside the grid and partial products inside.
    Use when: Expanding a bracket or a pair of brackets by partial products, and the reverse. NOT sampleTable: the headers are the brackets being multiplied and sit OUTSIDE the border, and the cells hold products rather than data. Pass cells:null for the EMPTY grid a student fills in. Column widths follow their content, so a long term does not squash its neighbour.
    areaModel({cols, rows, cells, fs})
    single bracket
    x+119x9x²99x
    areaModel({"cols":["x","+11"],"rows":["9x"],"cells":[["9x²","99x"]]})
    two brackets
    x+5x+33x5x15
    areaModel({"cols":["x","+5"],"rows":["x","+3"],"cells":[["x²","5x"],["3x","15"]]})
    empty, for the student
    x-72x
    areaModel({"cols":["x","-7"],"rows":["2x"],"cells":null})
    Stress test — 2 deliberately awkward cases (longest labels, narrowest angles, empty input)
    long terms of unequal width
    12x²-1447xy84x³y-1008xy
    numbers only, nothing italic
    30+420+76002108028
    Replaces: hand-rolled <rect> grids with pinned header coordinates for the box method
    39 arrowChain
    A chain of steps joined by labelled arrows, every width measured from its own text.
    Use when: Showing a method as a sequence: a value transformed step by step, a chain of conversions, a reverse-percentage route. The arrow is never shorter than its own caption, so a long caption lengthens the arrow instead of overlapping a neighbour. Pass `sep` to replace the arrows with an OPERATOR and the chain becomes a TERM STRIP (5m + 2n + 8p, each term boxed): an arrow between boxes says "then", a plus says "and", so a strip drawn with arrows is wrong. `sep` takes a string for every gap or an array for one per gap.
    arrowChain({items, labels, boxed, sep, fs})
    reverse percentage
    £40+ 15%£46
    arrowChain({"items":["£40","£46"],"labels":["+ 15%"]})
    three steps
    12 cm× 10120 mm÷ 10000.12 m
    arrowChain({"items":["12 cm","120 mm","0.12 m"],"labels":["× 10","÷ 1000"]})
    term strip (sep)
    5m+2n+8p
    arrowChain({"items":["5m","2n","8p"],"sep":"+","italic":true})
    term strip, mixed operators
    3a-2b+c
    arrowChain({"items":["3a","2b","c"],"sep":["-","+"],"italic":true})
    Stress test — 3 deliberately awkward cases (longest labels, narrowest angles, empty input)
    long captions
    £1 200increase by 15% (one year)£1 380
    five items
    2×36×318×354×3162
    unboxed
    0.6× 10060%
    Replaces: hand-drawn arrow <line>s with hard-coded label offsets
    40 barModel
    Ratio and part-whole bar model, segments proportional but never narrower than their own label.
    Use when: Ratio, proportion, fractions of an amount, part-whole reasoning. 51 of 51 modules hand-rolled this, the highest hand-roll rate of any figure, and it is the most context-portable figure there is.
    barModel({parts, labels, total, unit, shades, fs})
    3 : 2 with total
    AnnBen£45
    barModel({"parts":[3,2],"labels":["Ann","Ben"],"total":"£45","shades":true})
    fifths
    240 g
    barModel({"parts":[1,1,1,1,1],"labels":["","","","",""],"total":"240 g"})
    Stress test — 3 deliberately awkward cases (longest labels, narrowest angles, empty input)
    long labels, uneven parts
    Amira's shareBen£1 240
    eight equal parts
    1 whole
    single bar
    the whole amount?
    Replaces: raw <rect> rows with hand-computed widths
    41 factorTree
    Prime factor tree, splitting at the smallest prime each time. Primes ringed in red.
    Use when: OWNER STANDARD: any prime-factorisation solution must show the tree. Lays out from measured label widths, so branches cannot collide.
    factorTree(n, {fs, gapY, mmW})
    360
    360218029024531535
    factorTree(360)
    84
    8424222137
    factorTree(84)
    Replaces: hand-pinned tree with hard-coded node coordinates · a division ladder (a different method)
    42 functionMachine
    Function machine: input, one or more boxed operations, output, box width from the measured label.
    Use when: Function machines, inverse operations, forming or solving an equation step by step. Generalises the Q20 fix: a longer operation gets a wider box, gaps stay uniform, and the output sits clear of the arrowhead.
    functionMachine({input, ops, output, fs})
    two ops
    x× 3+ 419
    functionMachine({"input":"x","ops":["× 3","+ 4"],"output":"19"})
    long label
    nsquare itsubtract 7m
    functionMachine({"input":"n","ops":["square it","subtract 7"],"output":"m"})
    Stress test — 3 deliberately awkward cases (longest labels, narrowest angles, empty input)
    one very long operation
    xmultiply by 3 then squarey
    four boxes
    n× 2+ 5÷ 3− 1m
    no input or output
    × 4
    Replaces: hand-pinned machine diagrams with fixed box widths
    43 numberLine
    Horizontal number line with ticks pointing down and labelled values.
    Use when: Any solution that places values on a line: negatives, inequalities, rounding bounds, ordering. THE most-ignored helper in the kit — check here before drawing one.
    numberLine({lo, hi, step, unit, arrows, raisedMinus})
    -5 to 5
    -5-4-3-2-1012345
    numberLine({"lo":-5,"hi":5,"step":1,"arrows":true})
    decimal steps
    00.250.50.7511.251.51.752
    numberLine({"lo":0,"hi":2,"step":0.25,"unit":46})
    Replaces: hand-drawn axis line + tick <line> loop + <text> labels
    44 sectorFig
    Circle sector, arc or segment, with optional shading and angle, radius and arc labels.
    Use when: Any sector, arc length, sector area or segment. THE biggest hand-roll class in the corpus (175 modules). Reflex angles draw correctly; returns null rather than a wrong picture for an impossible angle.
    sectorFig({r, angle, start, rLabel, angleLabel, arcLabel, shade, chord, fs})
    90 deg shaded
    90°6 cm
    sectorFig({"r":80,"angle":90,"angleLabel":"90°","rLabel":"6 cm","shade":true})
    reflex 260
    260°
    sectorFig({"r":80,"angle":260,"angleLabel":"260°","shade":true})
    segment (chord)
    sectorFig({"r":80,"angle":120,"chord":true,"shade":true})
    Stress test — 6 deliberately awkward cases (longest labels, narrowest angles, empty input)
    8° sliver, long radius label
    20 cm
    15° with long labels
    15°12.5 cm
    25° (owner-flagged overlap)
    25°7 cm
    all three labels at once
    45°9 cmarc 7.1 cm
    180° semicircle
    180°5 cm
    350° near-full
    350°
    Replaces: raw <path> arcs with hand-computed sweep flags
    45 vennPF
    Prime-factor Venn for HCF/LCM: shared factors in the overlap, the rest outside.
    Use when: HCF and LCM by prime factors. Pairs with factorTree: tree first, then this.
    vennPF({aOnly, shared, bOnly, nameA, nameB})
    36 and 48
    3648322322
    vennPF({"nameA":"36","nameB":"48","aOnly":[3],"shared":[2,2,3],"bOnly":[2,2]})
    Replaces: hand-drawn overlapping circles with hard-coded factor positions
    checking…

    Flag a figure

    Goes against the HELPER, so it covers every question that draws it.

    ← Dashboards