├── examples ├── examples.pdf ├── examples1.png ├── examples10.png ├── examples2.png ├── examples3.png ├── examples4.png ├── examples5.png ├── examples6.png ├── examples7.png ├── examples8.png ├── examples9.png └── examples.typ ├── typst.toml ├── LICENSE ├── README.md └── plotsy-3d.typ /examples/examples.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples.pdf -------------------------------------------------------------------------------- /examples/examples1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples1.png -------------------------------------------------------------------------------- /examples/examples10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples10.png -------------------------------------------------------------------------------- /examples/examples2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples2.png -------------------------------------------------------------------------------- /examples/examples3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples3.png -------------------------------------------------------------------------------- /examples/examples4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples4.png -------------------------------------------------------------------------------- /examples/examples5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples5.png -------------------------------------------------------------------------------- /examples/examples6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples6.png -------------------------------------------------------------------------------- /examples/examples7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples7.png -------------------------------------------------------------------------------- /examples/examples8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples8.png -------------------------------------------------------------------------------- /examples/examples9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/HEAD/examples/examples9.png -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotsy-3d" 3 | version = "0.2.1" 4 | entrypoint = "plotsy-3d.typ" 5 | repository = "https://github.com/misskacie/plotsy-3d" 6 | authors = ["misskacie"] 7 | license = "LGPL-3.0-or-later" 8 | description = "3D plotting for surfaces and parametric equations using CeTZ similar to pgfplots for LaTeX" 9 | keywords = ["3d","plotting","graphing","cetz"] 10 | categories = ["visualization"] 11 | disciplines = ["mathematics"] 12 | exclude = ["./examples"] 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /examples/examples.typ: -------------------------------------------------------------------------------- 1 | #set page("a5", flipped:true, margin: (x:5pt, y:5pt)) 2 | #set text(size:14pt) 3 | #import "../plotsy-3d.typ": * 4 | 5 | 6 | #let xfunc(u,v) = u*calc.sin(v) 7 | #let yfunc(u,v) = u*calc.cos(v) 8 | #let zfunc(u,v) = u 9 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 10 | return purple.transparentize(20%).lighten((z/(z-hi - z-lo)) * 80%) 11 | 12 | } 13 | #let scale-factor = 0.25 14 | #let (xscale,yscale,zscale) = (0.3,0.2,0.3) 15 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 16 | 17 | == Parametric Surface 18 | $ x(u,v) = u sin(v), space y(u,v)= u cos(v), space z(u,v)= u $ 19 | #figure( 20 | plot-3d-parametric-surface( 21 | xfunc, 22 | yfunc, 23 | zfunc, 24 | xaxis: (-5,5), 25 | yaxis: (-5,5), 26 | zaxis: (0,5), 27 | color-func: color-func, 28 | subdivisions:5, 29 | scale-dim: scale-dim, 30 | udomain:(0, calc.pi+1), 31 | vdomain:(0, 2*calc.pi+1), 32 | axis-step: (5,5,5), 33 | dot-thickness: 0.05em, 34 | front-axis-thickness: 0.1em, 35 | front-axis-dot-scale: (0.04, 0.04), 36 | rear-axis-dot-scale: (0.08,0.08), 37 | rear-axis-text-size: 0.5em, 38 | axis-label-size: 1.5em, 39 | xyz-colors: (red,green,blue), 40 | ) 41 | ) 42 | 43 | #pagebreak() 44 | 45 | #let xfunc(u,v) = 6*calc.cos(u) * calc.sin(v) 46 | #let yfunc(u,v) = 6*calc.sin(u) * calc.sin(v) 47 | #let zfunc(u,v) = 6*calc.cos(v) 48 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 49 | return green.transparentize(20%).darken((z/(z-hi - z-lo)) * 175%) 50 | } 51 | 52 | #let scale-factor = 0.12 53 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 54 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 55 | 56 | == Parametric Surface 57 | $ x(u,v) = 6 cos(u) sin(v), space y(u,v)= 6 sin(u) sin(v), space z(u,v)= 6 cos(v) $ 58 | #figure( 59 | plot-3d-parametric-surface( 60 | xfunc, 61 | yfunc, 62 | zfunc, 63 | color-func: color-func, 64 | render-order: 7, 65 | subdivisions:10, 66 | scale-dim: scale-dim, 67 | udomain:(1*calc.pi, 3*calc.pi+1), 68 | vdomain:(0, calc.pi+1), 69 | axis-step: (5,5,5), 70 | dot-thickness: 0.05em, 71 | front-axis-thickness: 0.1em, 72 | front-axis-dot-scale: (0.04, 0.04), 73 | rear-axis-dot-scale: (0.08,0.08), 74 | rear-axis-text-size: 0.5em, 75 | // rotation-matrix: ((1,0,0), (-1,1,1)), 76 | axis-label-size: 1.5em, 77 | xyz-colors: (black, black, black), 78 | ) 79 | ) 80 | 81 | #pagebreak() 82 | 83 | == 3D Surface 84 | $ z=y sin(x) - x cos(y) $ 85 | 86 | #let size = 5 87 | #let scale-factor = 0.3 88 | #let (xscale,yscale,zscale) = (0.3,0.3,0.05) 89 | 90 | #let func(x,y) = y*calc.sin(x) -x*calc.cos(y) 91 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 92 | return purple.transparentize(20%).darken((z/(z-hi - z-lo)) * 300%) 93 | } 94 | 95 | #figure( 96 | plot-3d-surface( 97 | func, 98 | color-func: color-func, 99 | subdivisions: 5, 100 | subdivision-mode: "increase", 101 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 102 | xdomain: (-size,size), 103 | ydomain: (-size,size), 104 | pad-high: (0,0,2), 105 | pad-low: (0,0,0), 106 | axis-label-offset: (0.2,0.1,0.1), 107 | axis-text-offset: 0.045, 108 | xyz-colors: (red,green,blue), 109 | ) 110 | ) 111 | 112 | #pagebreak() 113 | 114 | 115 | == 3D Surface 116 | $ z= x^2 + y^2 $ 117 | #let size = 10 118 | #let scale-factor = 0.11 119 | #let (xscale,yscale,zscale) = (0.3,0.3,0.02) 120 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 121 | #let func(x,y) = x*x + y*y 122 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 123 | return blue.transparentize(20%).darken((y/(y-hi - y-lo))*100%).lighten((x/(x-hi - x-lo)) * 50%) 124 | } 125 | 126 | #figure( 127 | plot-3d-surface( 128 | func, 129 | color-func: color-func, 130 | subdivisions: 2, 131 | subdivision-mode: "decrease", 132 | scale-dim: scale-dim, 133 | xdomain: (-size,size), 134 | ydomain: (-size,size), 135 | pad-high: (0,0,0), 136 | pad-low: (0,0,5), 137 | axis-step: (3,3,75), 138 | dot-thickness: 0.05em, 139 | front-axis-thickness: 0.1em, 140 | front-axis-dot-scale: (0.05,0.05), 141 | rear-axis-dot-scale: (0.08,0.08), 142 | rear-axis-text-size: 0.5em, 143 | axis-label-size: 1.5em, 144 | axis-labels: ($x$, $y$, $x^2 + y^2$), 145 | xyz-colors: (red,green,blue), 146 | ) 147 | ) 148 | #let size = 10 149 | #let scale-factor = 0.09 150 | #let (xscale,yscale,zscale) = (0.3,0.3,0.05) 151 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 152 | #let func(x,y) = x*x 153 | 154 | #pagebreak() 155 | 156 | == 3D Vector Field 157 | $ arrow(p)(x,y,z) = (x+0.5) hat(i) + (y+0.5) hat(j) + (z+1) hat(k) $ 158 | 159 | #let size = 10 160 | #let scale-factor = 0.12 161 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 162 | 163 | #let i-func(x,y,z) = x + 0.5 164 | #let j-func(x,y,z) = y + 0.5 165 | #let k-func(x,y,z) = z + 1 166 | 167 | 168 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 169 | return purple.darken(z/(z-hi - z-lo) * 100%).transparentize(0%) 170 | } 171 | 172 | #figure( 173 | plot-3d-vector-field( 174 | i-func, 175 | j-func, 176 | k-func, 177 | color-func: color-func, 178 | subdivisions: 3, 179 | subdivision-mode: "decrease", 180 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 181 | xdomain: (-size,size), 182 | ydomain: (-size,size), 183 | zdomain: (0,size), 184 | // pad-high: (0,0,2), 185 | rotation-matrix: ((-1.5, 1.2, 4), (0, -1, 0)), 186 | axis-label-offset: (0.4,0.2,0.2), 187 | axis-text-offset: 0.08, 188 | vector-size: 0.1em, 189 | xyz-colors: (red,green,blue), 190 | ) 191 | ) 192 | 193 | #pagebreak() 194 | == Parametric Curve 195 | $ x(t) = 15 cos(t), space y(t)= sin(t), space z(t)= t $ 196 | 197 | #let xfunc(t) = 15*calc.cos(t) 198 | #let yfunc(t) = calc.sin(t) 199 | #let zfunc(t) = t 200 | 201 | #let size = 5 202 | 203 | #figure( 204 | plot-3d-parametric-curve( 205 | xfunc, 206 | yfunc, 207 | zfunc, 208 | subdivisions:30, 209 | scale-dim: (0.03,0.045,0.045), 210 | tdomain:(0,10), 211 | axis-step: (5,5,5), 212 | dot-thickness: 0.05em, 213 | front-axis-thickness: 0.1em, 214 | front-axis-dot-scale: (0.04, 0.04), 215 | rear-axis-dot-scale: (0.08,0.08), 216 | rear-axis-text-size: 0.5em, 217 | axis-label-size: 1.5em, 218 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 219 | xyz-colors: (red,green,blue), 220 | ) 221 | ) 222 | #pagebreak() 223 | 224 | 225 | == 3D Surface 226 | $ z= x^2 $ 227 | #let size = 10 228 | #let scale-factor = 0.10 229 | #let (xscale,yscale,zscale) = (0.3,0.3,0.03) 230 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 231 | 232 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 233 | return olive.transparentize(20%).darken(10%).lighten((z/(z-lo - z-hi)) * 100%) 234 | } 235 | 236 | #figure( 237 | plot-3d-surface( 238 | func, 239 | color-func: color-func, 240 | subdivisions: 1, 241 | subdivision-mode: "increase", 242 | scale-dim: scale-dim, 243 | xdomain: (-size,size), 244 | ydomain: (-size,size), 245 | pad-high: (0,0,0), 246 | pad-low: (0,0,5), 247 | axis-step: (3,3,75), 248 | dot-thickness: 0.05em, 249 | front-axis-thickness: 0.1em, 250 | front-axis-dot-scale: (0.05,0.05), 251 | rear-axis-dot-scale: (0.08,0.2), 252 | rear-axis-text-size: 0.5em, 253 | axis-label-size: 1.5em, 254 | xyz-colors: (red,green,blue), 255 | ) 256 | ) 257 | #pagebreak() 258 | == 3D Surface 259 | $ z= x^2 $ 260 | 261 | #figure( 262 | plot-3d-surface( 263 | func, 264 | color-func: color-func, 265 | scale-dim: scale-dim, 266 | xdomain: (-size,size), 267 | ydomain: (-size,size), 268 | pad-high: (0,0,10), 269 | pad-low: (0,0,0), 270 | axis-step: (3,3,10), 271 | dot-thickness: 0.05em, 272 | front-axis-thickness: 0.1em, 273 | front-axis-dot-scale: (0.05,0.05), 274 | rear-axis-dot-scale: (0.08,0.08), 275 | rear-axis-text-size: 0.5em, 276 | axis-label-size: 1.5em, 277 | rotation-matrix: ((1,0,0), (-1,1,1)), 278 | axis-label-offset: (0.3,0.2,0.4), 279 | axis-text-offset: 0.075, 280 | xyz-colors: (red,green,blue), 281 | ) 282 | ) 283 | #pagebreak() 284 | == 3D Surface 285 | $ z = - e^x + 20 cos(y) $ 286 | #let size = 5 287 | #let scale-factor = 0.32 288 | #let (xscale,yscale,zscale) = (0.3,0.3,0.005) 289 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 290 | #let func(x,y) = -calc.exp(x) + 20*calc.cos(y) 291 | 292 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 293 | return teal.transparentize(20%).darken(50%).lighten((z/(z-lo - z-hi)) * 90%) 294 | } 295 | 296 | #figure( 297 | plot-3d-surface( 298 | func, 299 | color-func: color-func, 300 | subdivisions: 2, 301 | subdivision-mode: "increase", 302 | scale-dim: scale-dim, 303 | xdomain: (1,size), 304 | ydomain: (-size,size), 305 | pad-high: (0,0,24), 306 | pad-low: (0,0,5), 307 | axis-step: (3,3,20), 308 | dot-thickness: 0.05em, 309 | front-axis-thickness: 0.1em, 310 | front-axis-dot-scale: (0.05,0.05), 311 | rear-axis-dot-scale: (0.08,0.08), 312 | rear-axis-text-size: 0.5em, 313 | axis-label-size: 1.5em, 314 | axis-text-offset: 0.04, 315 | axis-label-offset: (0.1,0.1,0.1), 316 | xyz-colors: (red,green,blue), 317 | ) 318 | ) 319 | 320 | #pagebreak() 321 | == 3D Surface 322 | $ z = 10x $ 323 | 324 | #let size = 10 325 | #let scale-factor = 0.15 326 | #let (xscale,yscale,zscale) = (0.3,0.3,0.005) 327 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 328 | #let func(x,y) = 10*x 329 | 330 | #figure( 331 | plot-3d-surface( 332 | func, 333 | subdivisions: 1, 334 | subdivision-mode: "increase", 335 | scale-dim: scale-dim, 336 | xdomain: (-size,size), 337 | ydomain: (-size,size), 338 | pad-high: (0,0,0), 339 | pad-low: (0,0,5), 340 | axis-step: (3,3,75), 341 | dot-thickness: 0.05em, 342 | front-axis-thickness: 0.1em, 343 | front-axis-dot-scale: (0.05,0.05), 344 | rear-axis-dot-scale: (0.08,0.08), 345 | rear-axis-text-size: 0.5em, 346 | axis-label-size: 1.5em, 347 | xyz-colors: (red,green,blue), 348 | ) 349 | ) 350 | 351 | 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The `plotsy-3d` Package 2 |
Version 0.2.1
3 | 4 | 5 | **plotsy-3d** is a [Typst](https://github.com/typst/typst) package for rendering 3D objects built on top of [CeTZ](https://github.com/cetz-package/cetz). Similar functionality to pgfplots for LaTeX but currently less developed. 6 | 7 | 8 |

9 | 10 | 11 |

12 | 13 |

14 | 15 | 16 |

17 | 18 |

19 | 20 | 21 |

22 | 23 | ## Features: 24 | 25 | * 3D Function plotting of the form `z = f(x,y)` 26 | * Parametric curve plotting of the form `x(t), y(t), z(t)` 27 | * Parametric surface plotting of the form `x(u,v), y(u,v), z(u,v)` 28 | * Plots autoscale with font size for consistent style 29 | 30 | See **Usage** or `examples/examples.typ` for the code 31 | 32 | 33 | ## Future Plans (contributors welcome): 34 | - [ ] Nicer way to draw vectors 35 | - [ ] Better way to handle render order 36 | - [ ] Function to generate 2D plots for XZ XY YZ projections of any 3d render 37 | - [ ] User Manual 38 | - [ ] Make the code and api nicer 39 | 40 | ## Usage 41 | 42 | ### Parametric Function Plotting 43 | ```typ 44 | #import "@preview/plotsy-3d:0.2.0": plot-3d-parametric-curve 45 | 46 | #let xfunc(t) = 15*calc.cos(t) 47 | #let yfunc(t) = calc.sin(t) 48 | #let zfunc(t) = t 49 | 50 | == Parametric Curve 51 | $ x(t) = 15 cos(t), space y(t)= sin(t), space z(t)= t $ 52 | #plot-3d-parametric-curve( 53 | xfunc, 54 | yfunc, 55 | zfunc, 56 | subdivisions:30, //number of line segments per unit 57 | scale-dim: (0.03,0.05,0.05), // relative and global scaling 58 | tdomain:(0,10), 59 | axis-step: (5,5,5), // adjust distance between x, y, z number labels 60 | dot-thickness: 0.05em, 61 | front-axis-thickness: 0.1em, 62 | front-axis-dot-scale: (0.04, 0.04), 63 | rear-axis-dot-scale: (0.08,0.08), 64 | rear-axis-text-size: 0.5em, 65 | axis-label-size: 1.5em, 66 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)) // matrix.transform-rotate-dir() from cetz 67 | xyz-colors: (red,green,blue), 68 | ) 69 | ``` 70 | 71 | ### 3D Surface Plotting 72 | ```typ 73 | #import "@preview/plotsy-3d:0.2.0": plot-3d-surface 74 | 75 | #let size = 10 76 | #let scale-factor = 0.11 77 | #let (xscale,yscale,zscale) = (0.3,0.3,0.02) 78 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 79 | #let func(x,y) = x*x + y*y 80 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 81 | return blue.transparentize(20%).darken((y/(y-hi - y-lo))*100%).lighten((x/(x-hi - x-lo)) * 50%) 82 | } 83 | 84 | == 3D Surface 85 | $ z= x^2 + y^2 $ 86 | #plot-3d-surface( 87 | func, 88 | color-func: color-func, 89 | subdivisions: 2, 90 | subdivision-mode: "decrease", 91 | scale-dim: scale-dim, 92 | xdomain: (-size,size), 93 | ydomain: (-size,size), 94 | pad-high: (0,0,0), // padding around the domain with no function displayed 95 | pad-low: (0,0,5), 96 | axis-step: (3,3,75), 97 | dot-thickness: 0.05em, 98 | front-axis-thickness: 0.1em, 99 | front-axis-dot-scale: (0.05,0.05), 100 | rear-axis-dot-scale: (0.08,0.08), 101 | rear-axis-text-size: 0.5em, 102 | axis-label-size: 1.5em, 103 | xyz-colors: (red,green,blue), 104 | ) 105 | ``` 106 | 107 | ### Parametric Surface Plotting 108 | ```typ 109 | #import "@preview/plotsy-3d:0.2.0": plot-3d-parametric-surface 110 | 111 | #let xfunc(u,v) = u*calc.sin(v) 112 | #let yfunc(u,v) = u*calc.cos(v) 113 | #let zfunc(u,v) = u 114 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 115 | return purple.transparentize(20%).lighten((z/(z-hi - z-lo)) * 80%) 116 | 117 | } 118 | #let scale-factor = 0.25 119 | #let (xscale,yscale,zscale) = (0.3,0.2,0.3) 120 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 121 | 122 | == Parametric Surface 123 | $ x(u,v) = u sin(v), space y(u,v)= u cos(v), space z(u,v)= u $ 124 | #plot-3d-parametric-surface( 125 | xfunc, 126 | yfunc, 127 | zfunc, 128 | xaxis: (-5,5), // set the minimum axis size, scales with function if needed 129 | yaxis: (-5,5), 130 | zaxis: (0,5), 131 | color-func: color-func, 132 | subdivisions:5, 133 | scale-dim: scale-dim, 134 | udomain:(0, calc.pi+1), // note this gets truncated to an integer 135 | vdomain:(0, 2*calc.pi+1), // note this gets truncated to an integer 136 | axis-step: (5,5,5), 137 | dot-thickness: 0.05em, 138 | front-axis-thickness: 0.1em, 139 | front-axis-dot-scale: (0.04, 0.04), 140 | rear-axis-dot-scale: (0.08,0.08), 141 | rear-axis-text-size: 0.5em, 142 | axis-label-size: 1.5em, 143 | xyz-colors: (red,green,blue), 144 | ) 145 | ``` 146 | 147 | ### Vector Field Plotting 148 | ```typ 149 | #import "@preview/plotsy-3d:0.2.0": plot-3d-vector-field 150 | 151 | #let size = 10 152 | #let scale-factor = 0.12 153 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 154 | #let i-func(x,y,z) = x + 0.5 155 | #let j-func(x,y,z) = y + 0.5 156 | #let k-func(x,y,z) = z + 1 157 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 158 | return purple.darken(z/(z-hi - z-lo) * 100%) 159 | } 160 | 161 | == 3D Vector Field 162 | $ arrow(p)(x,y,z) = (x+0.5) hat(i) + (y+0.5) hat(j) + (z+1) hat(k) $ 163 | #plot-3d-vector-field( 164 | i-func, 165 | j-func, 166 | k-func, 167 | color-func: color-func, 168 | subdivisions: 3, 169 | subdivision-mode: "decrease", 170 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 171 | xdomain: (-size,size), 172 | ydomain: (-size,size), 173 | zdomain: (0,size), 174 | // pad-high: (0,0,2), 175 | rotation-matrix: ((-1.5, 1.2, 4), (0, -1, 0)), 176 | axis-label-offset: (0.4,0.2,0.2), 177 | axis-text-offset: 0.08, 178 | vector-size: 0.1em, 179 | vector-length-scale: 1, 180 | xyz-colors: (red,green,blue), 181 | ) 182 | ``` 183 | 184 | ## Plotting Function Default Parameters 185 | ```typ 186 | plot-3d-vector-field( 187 | i-func, 188 | j-func, 189 | k-func, 190 | color-func: default-color-func, 191 | subdivisions:1, 192 | subdivision-mode: "increase", 193 | scale-dim: (1,1,0.5), 194 | xdomain:(0,10), 195 | ydomain:(0,10), 196 | zdomain:(0,10), 197 | axis-step: (5,5,5), 198 | dot-thickness: 0.05em, 199 | front-axis-thickness: 0.1em, 200 | front-axis-dot-scale: (0.05, 0.05), 201 | rear-axis-dot-scale: (0.08,0.08), 202 | rear-axis-text-size: 0.5em, 203 | axis-labels: ($x$, $y$, $z$), 204 | axis-label-size: 1.5em, 205 | axis-label-offset: (0.3,0.2,0.15), 206 | axis-text-offset: 0.075, 207 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 208 | vector-size: 0.02em, 209 | vector-length-scale: 1, 210 | xyz-colors: (red, green, blue), 211 | ) 212 | ``` 213 | 214 | ```typ 215 | plot-3d-parametric-surface( 216 | x-func, 217 | y-func, 218 | z-func, 219 | color-func: default-color-func, 220 | subdivisions:1, 221 | render-order: 0, 222 | scale-dim: (1,1,0.5), 223 | udomain:(0,1), 224 | vdomain:(0,1), 225 | xaxis:(0,10), 226 | yaxis:(0,10), 227 | zaxis:(0,10), 228 | axis-step: (5,5,5), 229 | dot-thickness: 0.05em, 230 | front-axis-thickness: 0.1em, 231 | front-axis-dot-scale: (0.05, 0.05), 232 | rear-axis-dot-scale: (0.08,0.08), 233 | rear-axis-text-size: 0.5em, 234 | axis-labels: ($x$, $y$, $z$), 235 | axis-label-size: 1.5em, 236 | axis-label-offset: (0.3,0.2,0.15), 237 | axis-text-offset: 0.075, 238 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 239 | xyz-colors: (red,green,blue), 240 | ) 241 | ``` 242 | 243 | ```typ 244 | plot-3d-parametric-curve( 245 | x-func, 246 | y-func, 247 | z-func, 248 | color-func: default-line-color-func, 249 | subdivisions:1, 250 | scale-dim: (1,1,0.5), 251 | tdomain:(0,1), 252 | xaxis:(0,10), 253 | yaxis:(0,10), 254 | zaxis:(0,10), 255 | axis-step: (5,5,5), 256 | dot-thickness: 0.05em, 257 | front-axis-thickness: 0.1em, 258 | front-axis-dot-scale: (0.05, 0.05), 259 | rear-axis-dot-scale: (0.08,0.08), 260 | rear-axis-text-size: 0.5em, 261 | axis-labels: ($x$, $y$, $z$), 262 | axis-label-size: 1.5em, 263 | axis-label-offset: (0.3,0.2,0.15), 264 | axis-text-offset: 0.075, 265 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 266 | xyz-colors: (red,green,blue), 267 | ) 268 | ``` 269 | 270 | ```typ 271 | plot-3d-surface( 272 | func, 273 | func2: none, 274 | color-func: default-color-func, 275 | subdivision-mode: "increase", 276 | subdivisions: 1, 277 | scale-dim: (1,1,0.5), 278 | xdomain:(0,10), 279 | ydomain: (0,10), 280 | pad-high: (0,0,0), 281 | pad-low: (0,0,0), 282 | axis-step: (5,5,5), 283 | dot-thickness: 0.05em, 284 | front-axis-thickness: 0.1em, 285 | front-axis-dot-scale: (0.5, 1), 286 | rear-axis-dot-scale: (0.08,0.08), 287 | rear-axis-text-size: 0.5em, 288 | axis-labels: ($x$, $y$, $z$), 289 | axis-label-size: 1.5em, 290 | axis-label-offset: (0.3,0.2,0.15), 291 | axis-text-offset: 0.075, 292 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 293 | xyz-colors: (red,green,blue) 294 | ) 295 | ``` 296 | 297 | 298 | ### Custom Plotting 299 | For custom combinations of plots and lines, you can make a copy of the relevant plot function from `plotsy-3d.typ` and add multiple plots onto the same axis in the same cetz canvas using the backend render functions. 300 | 301 | ## More Examples 302 | 303 | 307 | 308 |

309 | 310 | 311 |

312 | 313 |

314 | 315 | 316 |

317 | 318 | ## Build Examples 319 | From project root: 320 | `typst compile examples/examples.typ --root .` 321 | `typst compile examples/examples.typ --root . examples/examples{p}.png` 322 | 323 | 324 | ## Star History 325 | 326 | 327 | 328 | 329 | 330 | Star History Chart 331 | 332 | 333 | 334 | ## Changelog 335 | 336 | ### V0.2.1 337 | * Added vector-length-scale option to plot-3d-vector-field 338 | * Defensively fix bug in Cetz Line causing zero division error 339 | ### V0.2.0 340 | * Changed all code to use kebab-case 341 | * Fixed missing parameters from functions 342 | ### V0.1.0 343 | * 3D Function plotting of the form z = f(x,y) 344 | * 3D Parametric curve plotting of the form x(t), y(t), z(t) 345 | * 3D Parametric function plotting of the form x(u,v), y(u,v), z(u,v) 346 | * 3D Vector Field Plotting of the form r(x,y,z) = (x) i + (y) j + (z) k 347 | 348 | -------------------------------------------------------------------------------- /plotsy-3d.typ: -------------------------------------------------------------------------------- 1 | // plotsy-3d 2 | // Author: misskacie 3 | // License: LGPL-3.0-or-later 4 | #import "@preview/cetz:0.4.1": canvas, draw, matrix 5 | 6 | #let render-rear-axis( 7 | axis-low: (0,0,0), 8 | axis-high: (10,10,10), 9 | axis-step: (5,5,5), 10 | axis-dot-scale: (0.08,0.08), 11 | dot-thickness: 0.05em, 12 | axis-text-size: 1em, 13 | axis-text-offset: 0.07, 14 | ) = { 15 | import draw: * 16 | let (xaxis-low,yaxis-low,zaxis-low) = axis-low 17 | let (xaxis-high,yaxis-high,zaxis-high) = axis-high 18 | let (xaxis-step,yaxis-step,zaxis-step) = axis-step 19 | 20 | let xaxis-stroke = (paint:black, dash: (axis-dot-scale.at(0) *1em, axis-dot-scale.at(1) *1em)) 21 | let yaxis-stroke = (paint:black, dash: (axis-dot-scale.at(0) *1em, axis-dot-scale.at(1) *1em)) 22 | let zaxis-stroke = (paint:black, dash: (axis-dot-scale.at(0) *1em, axis-dot-scale.at(1) *1em)) 23 | 24 | set-style( 25 | stroke:(thickness: dot-thickness) 26 | ) 27 | let text-offset = axis-text-offset * text.size.pt() 28 | 29 | for xoffset in range(xaxis-high - xaxis-low, step:xaxis-step) { 30 | let xcoord = xaxis-low + xoffset 31 | line( 32 | (xcoord,yaxis-low,zaxis-low), 33 | (xcoord,yaxis-high,zaxis-low), 34 | stroke: yaxis-stroke 35 | ) 36 | content((xcoord,yaxis-high + text-offset, zaxis-low), text(size:axis-text-size)[#xcoord]) 37 | line( 38 | (xcoord,yaxis-low,zaxis-low), 39 | (xcoord,yaxis-low,zaxis-high), 40 | stroke:zaxis-stroke 41 | ) 42 | } 43 | content((xaxis-high,yaxis-high + text-offset,zaxis-low), text(size:axis-text-size)[#xaxis-high]) 44 | 45 | for yoffset in range(yaxis-high - yaxis-low, step:yaxis-step) { 46 | let ycoord = yaxis-low + yoffset 47 | line( 48 | (xaxis-low,ycoord,zaxis-low), 49 | (xaxis-high,ycoord,zaxis-low), 50 | stroke: xaxis-stroke 51 | ) 52 | content((xaxis-high + text-offset,ycoord,zaxis-low), text(size:axis-text-size)[#ycoord]) 53 | line( 54 | (xaxis-low,ycoord,zaxis-low), 55 | (xaxis-low,ycoord,zaxis-high), 56 | stroke:zaxis-stroke 57 | ) 58 | } 59 | content((xaxis-high + text-offset,yaxis-high,zaxis-low), text(size:axis-text-size)[#yaxis-high]) 60 | 61 | for zoffset in range(zaxis-high - zaxis-low, step:zaxis-step) { 62 | let zcoord = zaxis-low + zoffset 63 | line( 64 | (xaxis-low,yaxis-low,zcoord), 65 | (xaxis-low,yaxis-high,zcoord), 66 | stroke:yaxis-stroke 67 | ) 68 | content((xaxis-low - text-offset,yaxis-high,zcoord), text(size:axis-text-size)[#zcoord]) 69 | line( 70 | (xaxis-low,yaxis-low,zcoord), 71 | (xaxis-high,yaxis-low,zcoord), 72 | stroke:xaxis-stroke 73 | ) 74 | } 75 | content((xaxis-low - text-offset,yaxis-high,zaxis-high), text(size:axis-text-size)[#zaxis-high]) 76 | 77 | 78 | line( 79 | (xaxis-high,yaxis-low,zaxis-low), 80 | (xaxis-high,yaxis-low,zaxis-high), 81 | stroke: zaxis-stroke 82 | ) 83 | // content((), [], anchor: "east", padding: 2em) 84 | 85 | 86 | line( 87 | (xaxis-low,yaxis-low,zaxis-high), 88 | (xaxis-high,yaxis-low,zaxis-high), 89 | stroke: xaxis-stroke 90 | ) 91 | content((), [], anchor: "west", padding: 2em) 92 | 93 | line( 94 | (xaxis-low,yaxis-low,zaxis-high), 95 | (xaxis-low,yaxis-high,zaxis-high), 96 | stroke: yaxis-stroke 97 | ) 98 | } 99 | 100 | #let render-front-axis( 101 | axis-low:(0,0,0), 102 | axis-high: (10,10,10), 103 | front-axis-dot-scale: (0.05,0.05), 104 | front-axis-thickness: 0.04em, 105 | xyz-colors: (red,green,blue), 106 | axis-label-size:1.5em, 107 | axis-label-offset: (0.3,0.2,0.15), 108 | axis-labels: ($x$, $y$, $z$), 109 | ) = { 110 | import draw: * 111 | 112 | let (xaxis-low, yaxis-low, zaxis-low) = axis-low 113 | let (xaxis-high, yaxis-high, zaxis-high) = axis-high 114 | let axis-stroke = (paint:black, dash: (front-axis-dot-scale.at(0) *1em, front-axis-dot-scale.at(1) *1em)) 115 | set-style( 116 | stroke:(thickness: front-axis-thickness), 117 | ) 118 | line( 119 | (xaxis-low, yaxis-high, zaxis-low), 120 | (xaxis-low, yaxis-high, zaxis-high), 121 | stroke: (paint:xyz-colors.at(0), cap: "square"), name: "zaxis" 122 | ) //z 123 | 124 | line( 125 | (xaxis-low, yaxis-high, zaxis-low), 126 | (xaxis-high, yaxis-high, zaxis-low), 127 | stroke:(paint:xyz-colors.at(1), cap: "square"), name: "xaxis" 128 | ) //x 129 | 130 | line( 131 | (xaxis-high, yaxis-low, zaxis-low), 132 | (xaxis-high, yaxis-high, zaxis-low), 133 | stroke:(paint:xyz-colors.at(2), cap: "square"), name: "yaxis" 134 | ) //y 135 | 136 | line( 137 | (xaxis-high, yaxis-high, zaxis-low), 138 | (xaxis-high, yaxis-high, zaxis-high), 139 | stroke: axis-stroke 140 | ) //z 141 | 142 | line( 143 | (xaxis-low, yaxis-high, zaxis-high), 144 | (xaxis-high, yaxis-high, zaxis-high), 145 | stroke: axis-stroke 146 | ) //x 147 | 148 | line( 149 | (xaxis-high,yaxis-low,zaxis-high), 150 | (xaxis-high,yaxis-high,zaxis-high), 151 | stroke:axis-stroke 152 | ) //y 153 | 154 | content(( 155 | xaxis-low + (xaxis-high - xaxis-low)/2, 156 | yaxis-high + axis-label-offset.at(0) * text.size.pt(), 157 | zaxis-low 158 | ), 159 | anchor: "north", 160 | text(size:axis-label-size)[#axis-labels.at(0)] 161 | ) 162 | 163 | content(( 164 | xaxis-high + axis-label-offset.at(1) * text.size.pt(), 165 | yaxis-low + (yaxis-high - yaxis-low)/2, 166 | zaxis-low 167 | ), 168 | anchor: "north-west", 169 | text(size:axis-label-size)[#axis-labels.at(1)] 170 | ) 171 | 172 | content(( 173 | xaxis-low - axis-label-offset.at(2) * text.size.pt(), 174 | yaxis-high, 175 | zaxis-low + (zaxis-high - zaxis-low)/2 176 | ), 177 | anchor: "east", 178 | text(size:axis-label-size)[#axis-labels.at(2)] 179 | ) 180 | 181 | } 182 | 183 | #let get-surface-zpoints( 184 | func, 185 | render-step: 1, 186 | samples: 1, 187 | xdomain:(0,10), 188 | ydomain: (0,10), 189 | axis-step: (5,5,5) 190 | ) = { 191 | import draw: * 192 | let (xaxis-low,xaxis-high) = xdomain 193 | let (yaxis-low,yaxis-high) = ydomain 194 | let (zaxis-low, zaxis-high) = (0,0) 195 | let zpoints = () 196 | let step = 1/samples 197 | 198 | for xregion in range(xaxis-low * samples, xaxis-high * samples + render-step, step: render-step) { 199 | let zpoints-temp = () 200 | for yregion in range(yaxis-low * samples, yaxis-high * samples + render-step, step: render-step) { 201 | let x = xregion * step 202 | let y = yregion * step 203 | zpoints-temp.push(func(x,y)) 204 | } 205 | zpoints.push(zpoints-temp) 206 | let possible-min = calc.min(..zpoints-temp) 207 | let possible-max = calc.max(..zpoints-temp) 208 | if (possible-min < zaxis-low) { 209 | zaxis-low = calc.floor(possible-min) 210 | } 211 | if (possible-max > zaxis-high) { 212 | zaxis-high = calc.ceil(possible-max) 213 | } 214 | } 215 | return (zdomain: (zaxis-low,zaxis-high), zpoints: zpoints) 216 | } 217 | 218 | #let render-surface( 219 | func, 220 | color-func, 221 | samples: 1, 222 | render-step:1, 223 | xdomain:(0,10), 224 | ydomain: (0,10), 225 | zdomain: (0,10), 226 | zpoints: (), 227 | axis-step: (5,5,5), 228 | dot-thickness:0.05em, 229 | ) = { 230 | import draw: * 231 | 232 | 233 | let (xaxis-low,xaxis-high) = xdomain 234 | let (yaxis-low,yaxis-high) = ydomain 235 | let (zaxis-low,zaxis-high) = zdomain 236 | 237 | let step = 1/samples 238 | 239 | let i = 0 240 | let j = 0 241 | for xregion in range(xaxis-low * samples, xaxis-high * samples, step:render-step) { 242 | for yregion in range(yaxis-low * samples, yaxis-high * samples, step: render-step) { 243 | let x = xregion * step 244 | let y = yregion * step 245 | let offset = step * render-step 246 | line( 247 | (x, y, zpoints.at(i).at(j)), 248 | (x, y + offset, zpoints.at(i).at(j+1)), 249 | (x + offset, y + offset, zpoints.at(i+1).at(j+1)), 250 | (x + offset, y, zpoints.at(i+1).at(j)), 251 | stroke:0.02em, 252 | fill: color-func(x, y, zpoints.at(i).at(j), xaxis-low, xaxis-high, yaxis-low, yaxis-high, zaxis-low,zaxis-high) 253 | ) 254 | j += 1 255 | } 256 | j = 0 257 | i += 1 258 | } 259 | } 260 | 261 | 262 | #let get-parametric-surface-points( 263 | x-func, 264 | y-func, 265 | z-func, 266 | subdivisions: 10, 267 | udomain: (0,1), 268 | vdomain: (0,1), 269 | ) = { 270 | import draw: * 271 | 272 | let (u-low, u-high) = udomain 273 | let (v-low, v-high) = vdomain 274 | let xsurf-lo = 0 275 | let ysurf-lo = 0 276 | let zsurf-lo = 0 277 | let xsurf-hi = 0 278 | let ysurf-hi = 0 279 | let zsurf-hi = 0 280 | let scale-factor = 1/subdivisions 281 | 282 | let surface-points = () 283 | for uregion in range(int(u-low * subdivisions), int(u-high * subdivisions)) { 284 | let surface-points-temp = () 285 | for vregion in range(int(v-low * subdivisions), int(v-high * subdivisions)) { 286 | let u = uregion * scale-factor 287 | let v= vregion * scale-factor 288 | 289 | let point = (x-func(u,v), y-func(u,v), z-func(u,v)) 290 | surface-points-temp.push(point) 291 | 292 | if (point.at(0) > xsurf-hi) { 293 | xsurf-hi = calc.ceil(point.at(0)) 294 | } 295 | if (point.at(0) < xsurf-lo) { 296 | xsurf-lo = calc.floor(point.at(0)) 297 | } 298 | if (point.at(1) > ysurf-hi) { 299 | ysurf-hi = calc.ceil(point.at(1)) 300 | } 301 | if (point.at(1) < zsurf-lo) { 302 | ysurf-lo = calc.floor(point.at(1)) 303 | } 304 | if (point.at(2) > zsurf-hi) { 305 | zsurf-hi = calc.ceil(point.at(2)) 306 | } 307 | if (point.at(2) < zsurf-lo) { 308 | zsurf-lo = calc.floor(point.at(2)) 309 | } 310 | } 311 | surface-points.push(surface-points-temp) 312 | } 313 | 314 | return (surface-points, (xsurf-lo, xsurf-hi), (ysurf-lo, ysurf-hi), (zsurf-lo, zsurf-hi)) 315 | } 316 | 317 | 318 | 319 | #let get-parametric-curve-points( 320 | x-func, 321 | y-func, 322 | z-func, 323 | subdivisions: 10, 324 | tdomain: (0,1), 325 | ) = { 326 | import draw: * 327 | 328 | let (t-low, t-high) = tdomain 329 | let xcurve-lo = 0 330 | let ycurve-lo = 0 331 | let zcurve-lo = 0 332 | let xcurve-hi = 0 333 | let ycurve-hi = 0 334 | let zcurve-hi = 0 335 | 336 | let scale-factor = 1/subdivisions 337 | let curve-points = () 338 | for tregion in range(t-low * subdivisions, t-high * subdivisions) { 339 | let t = tregion * scale-factor 340 | let point = (x-func(t), y-func(t), z-func(t)) 341 | curve-points.push(point) 342 | 343 | if (point.at(0) > xcurve-hi) { 344 | xcurve-hi = calc.ceil(point.at(0)) 345 | } 346 | if (point.at(0) < xcurve-lo) { 347 | xcurve-lo = calc.floor(point.at(0)) 348 | } 349 | if (point.at(1) > ycurve-hi) { 350 | ycurve-hi = calc.ceil(point.at(1)) 351 | } 352 | if (point.at(1) < ycurve-lo) { 353 | ycurve-lo = calc.floor(point.at(1)) 354 | } 355 | if (point.at(2) > zcurve-hi) { 356 | zcurve-hi = calc.ceil(point.at(2)) 357 | } 358 | if (point.at(2) < zcurve-lo) { 359 | zcurve-lo = calc.floor(point.at(2)) 360 | } 361 | } 362 | 363 | return (curve-points, (xcurve-lo, xcurve-hi), (ycurve-lo, ycurve-hi), (zcurve-lo, zcurve-hi)) 364 | } 365 | 366 | #let default-color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 367 | return purple.transparentize(20%).darken(50%).lighten((z/(z-lo - z-hi)) * 90%) 368 | } 369 | 370 | #let plot-3d-surface( 371 | func, 372 | func2: none, 373 | color-func: default-color-func, 374 | subdivision-mode: "increase", 375 | subdivisions: 1, 376 | scale-dim: (1,1,0.5), 377 | xdomain:(0,10), 378 | ydomain: (0,10), 379 | pad-high: (0,0,0), 380 | pad-low: (0,0,0), 381 | axis-step: (5,5,5), 382 | dot-thickness: 0.05em, 383 | front-axis-thickness: 0.1em, 384 | front-axis-dot-scale: (0.5, 1), 385 | rear-axis-dot-scale: (0.08,0.08), 386 | rear-axis-text-size: 0.5em, 387 | axis-labels: ($x$, $y$, $z$), 388 | axis-label-size: 1.5em, 389 | axis-label-offset: (0.3,0.2,0.15), 390 | axis-text-offset: 0.075, 391 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 392 | xyz-colors: (red,green,blue) 393 | ) = { 394 | let samples = 1 395 | let render-step = 1 396 | 397 | if (subdivision-mode == "increase"){ 398 | samples = subdivisions 399 | } else if (subdivision-mode == "decrease") { 400 | render-step = subdivisions 401 | } 402 | 403 | context[#canvas({ 404 | import draw: * 405 | let (xscale, yscale, zscale) = scale-dim 406 | 407 | set-transform(matrix.transform-rotate-dir(rotation-matrix.at(0),rotation-matrix.at(1))) 408 | scale(x: xscale*text.size.pt(), y: yscale*text.size.pt(), z: zscale*text.size.pt()) 409 | 410 | 411 | let (xaxis-low,xaxis-high) = xdomain 412 | let (yaxis-low,yaxis-high) = ydomain 413 | let (xpad-low,ypad-low,zpad-low) = pad-low 414 | let (xpad-high,ypad-high,zpad-high) = pad-high 415 | let step = 1/samples 416 | 417 | let (zdomain, zpoints) = get-surface-zpoints(func, samples: samples,render-step:render-step, xdomain: xdomain, ydomain: ydomain, axis-step:axis-step) 418 | 419 | let (zaxis-low,zaxis-high) = zdomain 420 | 421 | render-rear-axis( 422 | axis-low: (xaxis-low - xpad-low,yaxis-low - ypad-low, zaxis-low - zpad-low), 423 | axis-high: (xaxis-high + xpad-high,yaxis-high + ypad-high, zaxis-high +zpad-high), 424 | axis-step: axis-step, 425 | dot-thickness: dot-thickness, 426 | axis-dot-scale: rear-axis-dot-scale, 427 | axis-text-size: rear-axis-text-size, 428 | axis-text-offset: axis-text-offset 429 | ) 430 | 431 | render-surface( 432 | func, 433 | color-func, 434 | samples: samples, 435 | render-step:render-step, 436 | xdomain: xdomain, 437 | ydomain: ydomain, 438 | zdomain:zdomain, 439 | axis-step:axis-step, 440 | zpoints: zpoints, 441 | ) 442 | 443 | if (func2 != none) { 444 | let (zdomain, zpoints) = get-surface-zpoints(func, samples: samples,render-step:render-step, xdomain: xdomain, ydomain: ydomain, axis-step:axis-step) 445 | 446 | render-surface( 447 | func2, 448 | color-func, 449 | samples: samples, 450 | render-step:render-step, 451 | xdomain: xdomain, 452 | ydomain: ydomain, 453 | zdomain:zdomain, 454 | axis-step:axis-step, 455 | zpoints: zpoints, 456 | ) 457 | 458 | } 459 | 460 | render-front-axis( 461 | axis-low: (xaxis-low - xpad-low,yaxis-low - ypad-low,zaxis-low - zpad-low), 462 | axis-high: (xaxis-high + xpad-high,yaxis-high + ypad-high,zaxis-high + zpad-high), 463 | axis-label-size: axis-label-size, 464 | front-axis-dot-scale: front-axis-dot-scale, 465 | front-axis-thickness: front-axis-thickness, 466 | axis-label-offset: axis-label-offset, 467 | xyz-colors: xyz-colors, 468 | axis-labels: axis-labels, 469 | ) 470 | 471 | }) 472 | ] 473 | } 474 | 475 | #let default-line-color-func(i,imax) = { 476 | return purple.transparentize(20%).darken((i/imax) * 100%) 477 | } 478 | 479 | #let render-parametric-curve( 480 | curve-points:(), 481 | color-func: default-line-color-func 482 | ) = { 483 | import draw: * 484 | let npoints = curve-points.len() 485 | for i in range(curve-points.len() - 1) { 486 | line(curve-points.at(i), curve-points.at(i+1), stroke: color-func(i,npoints) + 0.2em) 487 | } 488 | } 489 | 490 | #let plot-3d-parametric-curve( 491 | x-func, 492 | y-func, 493 | z-func, 494 | color-func: default-line-color-func, 495 | subdivisions:1, 496 | scale-dim: (1,1,0.5), 497 | tdomain:(0,1), 498 | xaxis:(0,10), 499 | yaxis:(0,10), 500 | zaxis:(0,10), 501 | axis-step: (5,5,5), 502 | dot-thickness: 0.05em, 503 | front-axis-thickness: 0.1em, 504 | front-axis-dot-scale: (0.05, 0.05), 505 | rear-axis-dot-scale: (0.08,0.08), 506 | rear-axis-text-size: 0.5em, 507 | axis-labels: ($x$, $y$, $z$), 508 | axis-label-size: 1.5em, 509 | axis-label-offset: (0.3,0.2,0.15), 510 | axis-text-offset: 0.075, 511 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 512 | xyz-colors: (red,green,blue), 513 | ) = { 514 | context[#canvas({ 515 | import draw: * 516 | let (xscale, yscale, zscale) = scale-dim 517 | set-transform(matrix.transform-rotate-dir(rotation-matrix.at(0),rotation-matrix.at(1))) 518 | scale(x: xscale*text.size.pt(), y: yscale*text.size.pt(), z: zscale*text.size.pt()) 519 | 520 | 521 | let (xaxis-low,xaxis-high) = xaxis 522 | let (yaxis-low,yaxis-high) = yaxis 523 | let (zaxis-low,zaxis-high) = zaxis 524 | 525 | 526 | let (curve-points, (xcurve-lo, xcurve-hi), (ycurve-lo, ycurve-hi), (zcurve-lo, zcurve-hi)) = get-parametric-curve-points( 527 | x-func, 528 | y-func, 529 | z-func, 530 | subdivisions: subdivisions, 531 | tdomain: tdomain, 532 | ) 533 | 534 | render-rear-axis( 535 | axis-low: (calc.min(xcurve-lo, xaxis-low),calc.min(ycurve-lo, yaxis-low), calc.min(zcurve-lo, zaxis-low)), 536 | axis-high: (calc.max(xcurve-hi, xaxis-high),calc.max(ycurve-hi, yaxis-high), calc.max(zcurve-hi, zaxis-high)), 537 | axis-step: axis-step, 538 | dot-thickness: dot-thickness, 539 | axis-dot-scale: rear-axis-dot-scale, 540 | axis-text-size: rear-axis-text-size, 541 | axis-text-offset: axis-text-offset, 542 | ) 543 | 544 | render-parametric-curve(curve-points: curve-points, color-func: color-func) 545 | 546 | 547 | render-front-axis( 548 | axis-low: (calc.min(xcurve-lo, xaxis-low),calc.min(ycurve-lo, yaxis-low), calc.min(zcurve-lo, zaxis-low)), 549 | axis-high: (calc.max(xcurve-hi, xaxis-high),calc.max(ycurve-hi, yaxis-high), calc.max(zcurve-hi, zaxis-high)), 550 | axis-label-size: axis-label-size, 551 | front-axis-dot-scale: front-axis-dot-scale, 552 | front-axis-thickness: front-axis-thickness, 553 | axis-label-offset: axis-label-offset, 554 | xyz-colors: xyz-colors, 555 | axis-labels: axis-labels, 556 | ) 557 | 558 | }) 559 | ] 560 | } 561 | 562 | 563 | #let render-parametric-surface( 564 | surface-points:(), 565 | color-func: default-color-func, 566 | xdomain: (0,10), 567 | ydomain: (0,10), 568 | zdomain: (0,10), 569 | order: 0, 570 | ) = { 571 | import draw: * 572 | let npoints = surface-points.len() * surface-points.at(0).len() 573 | 574 | if (0 == order) { 575 | for i in range(surface-points.len() - 1) { 576 | for j in range(surface-points.at(0).len() - 1) { 577 | line(surface-points.at(i).at(j), surface-points.at(i).at(j+1), surface-points.at(i+1).at(j+1), surface-points.at(i+1).at(j), stroke: 0.02em, 578 | fill: color-func( 579 | surface-points.at(i).at(j).at(0), 580 | surface-points.at(i).at(j).at(1), 581 | surface-points.at(i).at(j).at(2), 582 | xdomain.at(0), xdomain.at(1), 583 | ydomain.at(0), ydomain.at(1), 584 | zdomain.at(0), zdomain.at(1)) 585 | ) 586 | } 587 | } 588 | } else if (1 == order) { 589 | for j in range(surface-points.at(0).len() - 1) { 590 | for i in range(surface-points.len() - 1) { 591 | line(surface-points.at(i).at(j), surface-points.at(i).at(j+1), surface-points.at(i+1).at(j+1), surface-points.at(i+1).at(j), stroke: 0.02em, 592 | fill: color-func( 593 | surface-points.at(i).at(j).at(0), 594 | surface-points.at(i).at(j).at(1), 595 | surface-points.at(i).at(j).at(2), 596 | xdomain.at(0), xdomain.at(1), 597 | ydomain.at(0), ydomain.at(1), 598 | zdomain.at(0), zdomain.at(1)) 599 | ) 600 | } 601 | } 602 | } 603 | } 604 | 605 | 606 | #let plot-3d-parametric-surface( 607 | x-func, 608 | y-func, 609 | z-func, 610 | color-func: default-color-func, 611 | subdivisions:1, 612 | render-order: 0, 613 | scale-dim: (1,1,0.5), 614 | udomain:(0,1), 615 | vdomain:(0,1), 616 | xaxis:(0,10), 617 | yaxis:(0,10), 618 | zaxis:(0,10), 619 | axis-step: (5,5,5), 620 | dot-thickness: 0.05em, 621 | front-axis-thickness: 0.1em, 622 | front-axis-dot-scale: (0.05, 0.05), 623 | rear-axis-dot-scale: (0.08,0.08), 624 | rear-axis-text-size: 0.5em, 625 | axis-labels: ($x$, $y$, $z$), 626 | axis-label-size: 1.5em, 627 | axis-label-offset: (0.3,0.2,0.15), 628 | axis-text-offset: 0.075, 629 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 630 | xyz-colors: (red,green,blue), 631 | ) = { 632 | context[#canvas({ 633 | import draw: * 634 | let (xscale, yscale, zscale) = scale-dim 635 | set-transform(matrix.transform-rotate-dir(rotation-matrix.at(0),rotation-matrix.at(1))) 636 | scale(x: xscale*text.size.pt(), y: yscale*text.size.pt(), z: zscale*text.size.pt()) 637 | 638 | 639 | let (xaxis-low,xaxis-high) = xaxis 640 | let (yaxis-low,yaxis-high) = yaxis 641 | let (zaxis-low,zaxis-high) = zaxis 642 | 643 | 644 | let (surface-points, (xsurf-lo, xsurf-hi), (ysurf-lo, ysurf-hi), (zsurf-lo, zsurf-hi)) = get-parametric-surface-points( 645 | x-func, 646 | y-func, 647 | z-func, 648 | subdivisions: subdivisions, 649 | udomain: udomain, 650 | vdomain: vdomain 651 | ) 652 | 653 | let xdomain = (xsurf-lo, xsurf-hi) 654 | let ydomain = (ysurf-lo, ysurf-hi) 655 | let zdomain = (zsurf-lo, zsurf-hi) 656 | 657 | render-rear-axis( 658 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 659 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 660 | axis-step: axis-step, 661 | dot-thickness: dot-thickness, 662 | axis-dot-scale: rear-axis-dot-scale, 663 | axis-text-size: rear-axis-text-size, 664 | axis-text-offset: axis-text-offset, 665 | ) 666 | 667 | let order = 0 668 | if (1 == render-order) { 669 | surface-points = surface-points.rev() 670 | } else if (2 == render-order) { 671 | for i in range(surface-points.len()) { 672 | surface-points.at(i) = surface-points.at(i).rev() 673 | } 674 | } else if (3 == render-order) { 675 | surface-points = surface-points.rev() 676 | for i in range(surface-points.len()) { 677 | surface-points.at(i) = surface-points.at(i).rev() 678 | } 679 | } else if (4 == render-order) { 680 | order = 1 681 | } else if (5 == render-order) { 682 | surface-points = surface-points.rev() 683 | order = 1 684 | } else if (6 == render-order) { 685 | for i in range(surface-points.len()) { 686 | surface-points.at(i) = surface-points.at(i).rev() 687 | } 688 | order = 1 689 | } else if (7 == render-order) { 690 | order = 1 691 | surface-points = surface-points.rev() 692 | for i in range(surface-points.len()) { 693 | surface-points.at(i) = surface-points.at(i).rev() 694 | } 695 | } 696 | render-parametric-surface(surface-points:surface-points, color-func: color-func, xdomain: xdomain, ydomain: ydomain, zdomain: zdomain, order: order) 697 | 698 | render-front-axis( 699 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 700 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 701 | axis-label-size: axis-label-size, 702 | front-axis-dot-scale: front-axis-dot-scale, 703 | front-axis-thickness: front-axis-thickness, 704 | axis-label-offset: axis-label-offset, 705 | xyz-colors: xyz-colors, 706 | axis-labels: axis-labels, 707 | ) 708 | 709 | }) 710 | ] 711 | } 712 | 713 | #let get-vector-field-vectors( 714 | i-func, 715 | j-func, 716 | k-func, 717 | render-step: 1, 718 | samples: 1, 719 | xdomain:(0,10), 720 | ydomain: (0,10), 721 | zdomain: (0,10), 722 | vector-length-scale: 1, 723 | ) = { 724 | 725 | let (xaxis-low,xaxis-high) = xdomain 726 | let (yaxis-low,yaxis-high) = ydomain 727 | let (zaxis-low,zaxis-high) = zdomain 728 | 729 | let xcurve-lo = 0 730 | let ycurve-lo = 0 731 | let zcurve-lo = 0 732 | let xcurve-hi = 0 733 | let ycurve-hi = 0 734 | let zcurve-hi = 0 735 | let step = 1/samples 736 | let vectors = () 737 | for xregion in range(xaxis-low * samples, xaxis-high * samples, step:render-step) { 738 | for yregion in range(yaxis-low * samples, yaxis-high * samples, step: render-step) { 739 | for zregion in range(zaxis-low * samples, zaxis-high * samples, step: render-step) { 740 | let x = xregion * step 741 | let y = yregion * step 742 | let z = zregion * step 743 | let end-x = i-func(x,y,z) 744 | let end-y = j-func(x,y,z) 745 | let end-z = k-func(x,y,z) 746 | vectors.push( ( 747 | (x, y, z), 748 | ( (end-x - x)*vector-length-scale + x, (end-y - y)*vector-length-scale + y, (end-z - z)*vector-length-scale +z) 749 | ) 750 | ) 751 | if (end-x > xcurve-hi) { 752 | xcurve-hi = calc.ceil(end-x) 753 | } 754 | if (end-x < xcurve-lo) { 755 | xcurve-lo = calc.floor(end-x) 756 | } 757 | if (end-y > ycurve-hi) { 758 | ycurve-hi = calc.ceil(end-y) 759 | } 760 | if (end-y < ycurve-lo) { 761 | ycurve-lo = calc.floor(end-y) 762 | } 763 | if (end-z > zcurve-hi) { 764 | zcurve-hi = calc.ceil(end-z) 765 | } 766 | if (end-z< zcurve-lo) { 767 | zcurve-lo = calc.floor(end-z) 768 | } 769 | } 770 | } 771 | } 772 | return (vectors, (xcurve-lo, xcurve-hi), (ycurve-lo, ycurve-hi), (zcurve-lo, zcurve-hi)) 773 | } 774 | 775 | 776 | #let render-3d-vector-field( 777 | vectors, 778 | color-func, 779 | xdomain:(0,10), 780 | ydomain:(0,10), 781 | zdomain:(0,10), 782 | vector-size: 0.02em, 783 | ) = { 784 | import draw: * 785 | 786 | for i in range(vectors.len()) { 787 | if (vectors.at(i).at(0) != vectors.at(i).at(1)) { 788 | line(vectors.at(i).at(0),vectors.at(i).at(1),stroke: (vector-size + color-func( 789 | vectors.at(i).at(0).at(0), 790 | vectors.at(i).at(0).at(1), 791 | vectors.at(i).at(0).at(2), 792 | xdomain.at(0), xdomain.at(1), 793 | ydomain.at(0), ydomain.at(1), 794 | zdomain.at(0), zdomain.at(1) 795 | )), mark: (end: "straight")) 796 | } 797 | } 798 | } 799 | 800 | #let plot-3d-vector-field( 801 | i-func, 802 | j-func, 803 | k-func, 804 | color-func: default-color-func, 805 | subdivisions:1, 806 | subdivision-mode: "increase", 807 | scale-dim: (1,1,0.5), 808 | xdomain:(0,10), 809 | ydomain:(0,10), 810 | zdomain:(0,10), 811 | axis-step: (5,5,5), 812 | dot-thickness: 0.05em, 813 | front-axis-thickness: 0.1em, 814 | front-axis-dot-scale: (0.05, 0.05), 815 | rear-axis-dot-scale: (0.08,0.08), 816 | rear-axis-text-size: 0.5em, 817 | axis-labels: ($x$, $y$, $z$), 818 | axis-label-size: 1.5em, 819 | axis-label-offset: (0.3,0.2,0.15), 820 | axis-text-offset: 0.075, 821 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 822 | vector-size: 0.02em, 823 | vector-length-scale: 1, 824 | xyz-colors: (red, green, blue), 825 | ) = { 826 | context[#canvas({ 827 | import draw: * 828 | let (xscale, yscale, zscale) = scale-dim 829 | set-transform(matrix.transform-rotate-dir(rotation-matrix.at(0),rotation-matrix.at(1))) 830 | scale(x: xscale*text.size.pt(), y: yscale*text.size.pt(), z: zscale*text.size.pt()) 831 | 832 | 833 | let (xaxis-low,xaxis-high) = xdomain 834 | let (yaxis-low,yaxis-high) = ydomain 835 | let (zaxis-low,zaxis-high) = zdomain 836 | 837 | let samples = 1 838 | let render-step = 1 839 | if(subdivision-mode == "increase"){ 840 | samples = subdivisions 841 | } else if (subdivision-mode == "decrease") { 842 | render-step = subdivisions 843 | } 844 | 845 | 846 | let (vectors, (xsurf-lo, xsurf-hi), (ysurf-lo, ysurf-hi), (zsurf-lo, zsurf-hi)) = get-vector-field-vectors( 847 | i-func, 848 | j-func, 849 | k-func, 850 | render-step: render-step, 851 | samples: samples, 852 | xdomain:xdomain, 853 | ydomain: ydomain, 854 | zdomain: zdomain, 855 | vector-length-scale: vector-length-scale, 856 | ) 857 | 858 | let xdomain = (xsurf-lo, xsurf-hi) 859 | let ydomain = (ysurf-lo, ysurf-hi) 860 | let zdomain = (zsurf-lo, zsurf-hi) 861 | 862 | render-rear-axis( 863 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 864 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 865 | axis-step: axis-step, 866 | dot-thickness: dot-thickness, 867 | axis-dot-scale: rear-axis-dot-scale, 868 | axis-text-size: rear-axis-text-size, 869 | axis-text-offset: axis-text-offset, 870 | ) 871 | 872 | render-3d-vector-field( 873 | vectors, 874 | color-func, 875 | xdomain: xdomain, 876 | ydomain: ydomain, 877 | zdomain: zdomain, 878 | vector-size: vector-size, 879 | ) 880 | 881 | render-front-axis( 882 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 883 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 884 | axis-label-size: axis-label-size, 885 | front-axis-dot-scale: front-axis-dot-scale, 886 | front-axis-thickness: front-axis-thickness, 887 | axis-label-offset: axis-label-offset, 888 | xyz-colors: xyz-colors, 889 | axis-labels: axis-labels, 890 | ) 891 | 892 | }) 893 | ] 894 | } --------------------------------------------------------------------------------