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 }