website

source code for the ttmaths.uk website
Log | Files | Refs | README

Plot.jsx (4967B)


      1 import { useContext } from 'preact/hooks';
      2 import { PlotContext } from '../../scripts/contexts.js';
      3 import Label from './Label.jsx';
      4 
      5 function Grid() {
      6   const p = useContext(PlotContext);
      7   const d = `M ${p.minX},${p.minY} h ${p.width}${` m -${p.width},1 h ${p.width}`.repeat(p.height)} v -${p.height}${` m -1,${p.height} v -${p.height}`.repeat(p.width)}`;
      8   return <path id="grid" d={d} />;
      9 }
     10 
     11 function Axes() {
     12   const p = useContext(PlotContext);
     13   const arrowheadHeight = 4 * p.strokeWidth;
     14   return (
     15     <g>
     16       <path class="axis" d={`M ${p.minX},0 h ${p.width  - arrowheadHeight}`} marker-end="url(#arrowhead)"/>
     17       <path class="axis" d={`M 0,${p.minY} v ${p.height - arrowheadHeight}`} marker-end="url(#arrowhead)"/>
     18     </g>
     19   );
     20 }
     21 
     22 function AxisTicks() {
     23   const p = useContext(PlotContext);
     24   const every = Math.floor(20 * p.strokeWidth);
     25   return (
     26     <g>
     27       <g>
     28         <g>
     29           {Array.from({ length: p.width - 1 }).map((_, i) => {
     30             const x = i + p.minX + 1;
     31             if (x % every || x === 0) return;
     32             return <Label point={[x, 0]} position="bottom">{x}</Label>;
     33           })}
     34         </g>
     35         <g>
     36           {Array.from({ length: p.height - 1 }).map((_, i) => {
     37             const y = i + p.minY + 1;
     38             if (y % every || y === 0) return;
     39             return <Label point={[0, y]} position="left">{y}</Label>;
     40           })}
     41         </g>
     42         <Label point={[0, 0]} position="bottom-left">0</Label>
     43       </g>
     44       <g>
     45         <Label weight="bold" point={[p.minX + p.width,  0]} position="bottom-left" spacing={3}>x</Label>
     46         <Label weight="bold" point={[0, p.minY + p.height]} position="bottom-left" spacing={3}>y</Label>
     47       </g>
     48     </g>
     49   )
     50 }
     51 
     52 export default function Plot({ children, mode="graph", viewBox, width="300", height="300" }) {
     53   const id = `plot-${Math.floor(10000 * Math.random())}`;
     54   const scaleFactor = Math.max(viewBox[2] / width, viewBox[3] / height);
     55   const p = {
     56            minX: viewBox[0],
     57            minY: viewBox[1],
     58           width: viewBox[2],
     59          height: viewBox[3],
     60            maxX: viewBox[0] + viewBox[2],
     61            maxY: viewBox[1] + viewBox[3],
     62     strokeWidth: 2.5 * scaleFactor,
     63   };
     64   const style = `
     65     g#${id} text {
     66       font-size: ${scaleFactor}em;
     67       stroke-width: ${mode === "graph" ? 1.6 * p.strokeWidth : 0};
     68     }
     69     g#${id} :is(path,polygon,line,polyline,rect,circle,ellipse) {
     70       stroke-width: ${p.strokeWidth};
     71     }
     72     g#${id} path#grid {
     73       stroke-width: ${0.2 * p.strokeWidth};
     74     }
     75     g#${id} polygon {
     76       fill-opacity: ${mode === "graph" ? 0.5 : 0};
     77     }
     78   `;
     79   return (
     80     <svg
     81       width={width} height={height} viewBox={viewBox.join(' ')}
     82       xmlns="http://www.w3.org/2000/svg" role="img"
     83     >
     84       <title>Plot</title>
     85       <defs>
     86         <g>
     87           <marker
     88             id='arrowhead'
     89             viewBox="0 0 4 4"
     90             refX="0" refY="2"
     91             markerWidth="4" markerHeight="4"
     92             orient="auto"
     93           >
     94             <polygon class='marker' points="0,0 4,2 0,4" />
     95           </marker>
     96           <marker
     97             id="cross"
     98             viewBox="0 0 6 6"
     99             refX="3" refY="3"
    100             markerWidth="6" markerHeight="6"
    101           >
    102             <line class='marker' x1="1" y1="1" x2="5" y2="5" />
    103             <line class='marker' x1="1" y1="5" x2="5" y2="1" />
    104           </marker>
    105           <marker
    106             id="hash"
    107             viewBox="0 0 2 6"
    108             refX="1" refY="3"
    109             markerWidth="2" markerHeight="6"
    110             orient="auto"
    111           >
    112             <line class="marker" x1="1" y1="1" x2="1" y2="5" />
    113           </marker>
    114           <marker
    115             id="double-hash"
    116             viewBox="0 0 4 6"
    117             refX="2" refY="3"
    118             markerWidth="4" markerHeight="6"
    119             orient="auto"
    120           >
    121             <line class="marker" x1="1" y1="1" x2="1" y2="5" />
    122             <line class="marker" x1="3" y1="1" x2="3" y2="5" />
    123           </marker>
    124           <marker
    125             id="feather"
    126             viewBox="0 0 6 6"
    127             refX="5" refY="3"
    128             markerWidth="6" markerHeight="6"
    129             orient="auto"
    130           >
    131             <polyline class="marker" points="2,1 5,3 2,5" />
    132           </marker>
    133           <marker
    134             id="double-feather"
    135             viewBox="0 0 9 6"
    136             refX="5" refY="3"
    137             markerWidth="9" markerHeight="6"
    138             orient="auto"
    139           >
    140             <polyline class="marker" points="2,1 5,3 2,5" />
    141             <polyline class="marker" points="5,1 8,3 5,5" />
    142           </marker>
    143         </g>
    144         <style>
    145           {style}
    146         </style>
    147       </defs>
    148       <PlotContext value={p}>
    149         <g id={id} transform="scale(1 -1)">
    150           {mode === "graph" && (
    151             <g>
    152               <Grid />
    153               <Axes />
    154               <AxisTicks />
    155             </g>
    156           )}
    157           {children}
    158         </g>
    159       </PlotContext>
    160     </svg>
    161   );
    162 }