├── LICENSE ├── README.md ├── examples ├── examples.pdf ├── examples.typ ├── examples1.png ├── examples10.png ├── examples2.png ├── examples3.png ├── examples4.png ├── examples5.png ├── examples6.png ├── examples7.png ├── examples8.png └── examples9.png ├── plotsy-3d.typ └── typst.toml /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The `plotsy-3d` Package 2 |
Version 0.2.0 DEV
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.1.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 | ) 68 | ``` 69 | 70 | ### 3D Surface Plotting 71 | ```typ 72 | #import "@preview/plotsy-3d:0.1.0": plot-3d-surface 73 | 74 | #let size = 10 75 | #let scale-factor = 0.11 76 | #let (xscale,yscale,zscale) = (0.3,0.3,0.02) 77 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 78 | #let func(x,y) = x*x + y*y 79 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 80 | return blue.transparentize(20%).darken((y/(y-hi - y-lo))*100%).lighten((x/(x-hi - x-lo)) * 50%) 81 | } 82 | 83 | == 3D Surface 84 | $ z= x^2 + y^2 $ 85 | #plot-3d-surface( 86 | func, 87 | color-func: color-func, 88 | subdivisions: 2, 89 | subdivision-mode: "decrease", 90 | scale-dim: scale-dim, 91 | xdomain: (-size,size), 92 | ydomain: (-size,size), 93 | pad-high: (0,0,0), // padding around the domain with no function displayed 94 | pad-low: (0,0,5), 95 | axis-step: (3,3,75), 96 | dot-thickness: 0.05em, 97 | front-axis-thickness: 0.1em, 98 | front-axis-dot-scale: (0.05,0.05), 99 | rear-axis-dot-scale: (0.08,0.08), 100 | rear-axis-text-size: 0.5em, 101 | axis-label-size: 1.5em, 102 | ) 103 | ``` 104 | 105 | ### Parametric Surface Plotting 106 | ```typ 107 | #import "@preview/plotsy-3d:0.1.0": plot-3d-parametric-surface 108 | 109 | #let xfunc(u,v) = u*calc.sin(v) 110 | #let yfunc(u,v) = u*calc.cos(v) 111 | #let zfunc(u,v) = u 112 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 113 | return purple.transparentize(20%).lighten((z/(z-hi - z-lo)) * 80%) 114 | 115 | } 116 | #let scale-factor = 0.25 117 | #let (xscale,yscale,zscale) = (0.3,0.2,0.3) 118 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 119 | 120 | == Parametric Surface 121 | $ x(u,v) = u sin(v), space y(u,v)= u cos(v), space z(u,v)= u $ 122 | #plot-3d-parametric-surface( 123 | xfunc, 124 | yfunc, 125 | zfunc, 126 | xaxis: (-5,5), // set the minimum axis size, scales with function if needed 127 | yaxis: (-5,5), 128 | zaxis: (0,5), 129 | color-func: color-func, 130 | subdivisions:5, 131 | scale-dim: scale-dim, 132 | udomain:(0, calc.pi+1), // note this gets truncated to an integer 133 | vdomain:(0, 2*calc.pi+1), // note this gets truncated to an integer 134 | axis-step: (5,5,5), 135 | dot-thickness: 0.05em, 136 | front-axis-thickness: 0.1em, 137 | front-axis-dot-scale: (0.04, 0.04), 138 | rear-axis-dot-scale: (0.08,0.08), 139 | rear-axis-text-size: 0.5em, 140 | axis-label-size: 1.5em, 141 | ) 142 | ``` 143 | 144 | ### Vector Field Plotting 145 | ```typ 146 | #import "@preview/plotsy-3d:0.1.0": plot-3d-vector-field 147 | 148 | #let size = 10 149 | #let scale-factor = 0.12 150 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 151 | #let i-func(x,y,z) = x + 0.5 152 | #let j-func(x,y,z) = y + 0.5 153 | #let k-func(x,y,z) = z + 1 154 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 155 | return purple.darken(z/(z-hi - z-lo) * 100%) 156 | } 157 | 158 | == 3D Vector Field 159 | $ arrow(p)(x,y,z) = (x+0.5) hat(i) + (y+0.5) hat(j) + (z+1) hat(k) $ 160 | #plot-3d-vector-field( 161 | i-func, 162 | j-func, 163 | k-func, 164 | color-func: color-func, 165 | subdivisions: 3, 166 | subdivision-mode: "decrease", 167 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 168 | xdomain: (-size,size), 169 | ydomain: (-size,size), 170 | zdomain: (0,size), 171 | // pad-high: (0,0,2), 172 | rotation-matrix: ((-1.5, 1.2, 4), (0, -1, 0)), 173 | axis-label-offset: (0.4,0.2,0.2), 174 | axis-text-offset: 0.08, 175 | vector-size: 0.1em, 176 | ) 177 | ``` 178 | 179 | ### Custom Plotting 180 | 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. 181 | 182 | ## More Examples 183 | 184 | 188 | 189 |

190 | 191 | 192 |

193 | 194 |

195 | 196 | 197 |

198 | 199 | ## Build Examples 200 | From project root: 201 | `typst compile examples/examples.typ --root .` 202 | `typst compile examples/examples.typ --root . examples/examples{p}.png` 203 | 204 | 205 | ## Star History 206 | 207 | 208 | 209 | 210 | 211 | Star History Chart 212 | 213 | 214 | 215 | ## Changelog 216 | 217 | ### V0.2.0 218 | * Changed all code to use kebab-case 219 | * Fixed missing parameters from functions 220 | 221 | -------------------------------------------------------------------------------- /examples/examples.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples.pdf -------------------------------------------------------------------------------- /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 | ) 40 | ) 41 | 42 | #pagebreak() 43 | 44 | #let xfunc(u,v) = 6*calc.cos(u) * calc.sin(v) 45 | #let yfunc(u,v) = 6*calc.sin(u) * calc.sin(v) 46 | #let zfunc(u,v) = 6*calc.cos(v) 47 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 48 | return green.transparentize(20%).darken((z/(z-hi - z-lo)) * 175%) 49 | } 50 | 51 | #let scale-factor = 0.12 52 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 53 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 54 | 55 | == Parametric Surface 56 | $ x(u,v) = 6 cos(u) sin(v), space y(u,v)= 6 sin(u) sin(v), space z(u,v)= 6 cos(v) $ 57 | #figure( 58 | plot-3d-parametric-surface( 59 | xfunc, 60 | yfunc, 61 | zfunc, 62 | color-func: color-func, 63 | render-order: 7, 64 | subdivisions:10, 65 | scale-dim: scale-dim, 66 | udomain:(1*calc.pi, 3*calc.pi+1), 67 | vdomain:(0, calc.pi+1), 68 | axis-step: (5,5,5), 69 | dot-thickness: 0.05em, 70 | front-axis-thickness: 0.1em, 71 | front-axis-dot-scale: (0.04, 0.04), 72 | rear-axis-dot-scale: (0.08,0.08), 73 | rear-axis-text-size: 0.5em, 74 | // rotation-matrix: ((1,0,0), (-1,1,1)), 75 | axis-label-size: 1.5em, 76 | ) 77 | ) 78 | 79 | #pagebreak() 80 | 81 | == 3D Surface 82 | $ z=y sin(x) - x cos(y) $ 83 | 84 | #let size = 5 85 | #let scale-factor = 0.3 86 | #let (xscale,yscale,zscale) = (0.3,0.3,0.05) 87 | 88 | #let func(x,y) = y*calc.sin(x) -x*calc.cos(y) 89 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 90 | return purple.transparentize(20%).darken((z/(z-hi - z-lo)) * 300%) 91 | } 92 | 93 | #figure( 94 | plot-3d-surface( 95 | func, 96 | color-func: color-func, 97 | subdivisions: 5, 98 | subdivision-mode: "increase", 99 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 100 | xdomain: (-size,size), 101 | ydomain: (-size,size), 102 | pad-high: (0,0,2), 103 | pad-low: (0,0,0), 104 | axis-label-offset: (0.2,0.1,0.1), 105 | axis-text-offset: 0.045, 106 | ) 107 | ) 108 | 109 | #pagebreak() 110 | 111 | 112 | == 3D Surface 113 | $ z= x^2 + y^2 $ 114 | #let size = 10 115 | #let scale-factor = 0.11 116 | #let (xscale,yscale,zscale) = (0.3,0.3,0.02) 117 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 118 | #let func(x,y) = x*x + y*y 119 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 120 | return blue.transparentize(20%).darken((y/(y-hi - y-lo))*100%).lighten((x/(x-hi - x-lo)) * 50%) 121 | } 122 | 123 | #figure( 124 | plot-3d-surface( 125 | func, 126 | color-func: color-func, 127 | subdivisions: 2, 128 | subdivision-mode: "decrease", 129 | scale-dim: scale-dim, 130 | xdomain: (-size,size), 131 | ydomain: (-size,size), 132 | pad-high: (0,0,0), 133 | pad-low: (0,0,5), 134 | axis-step: (3,3,75), 135 | dot-thickness: 0.05em, 136 | front-axis-thickness: 0.1em, 137 | front-axis-dot-scale: (0.05,0.05), 138 | rear-axis-dot-scale: (0.08,0.08), 139 | rear-axis-text-size: 0.5em, 140 | axis-label-size: 1.5em, 141 | axis-labels: ($x$, $y$, $x^2 + y^2$), 142 | ) 143 | ) 144 | #let size = 10 145 | #let scale-factor = 0.09 146 | #let (xscale,yscale,zscale) = (0.3,0.3,0.05) 147 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 148 | #let func(x,y) = x*x 149 | 150 | #pagebreak() 151 | 152 | == 3D Vector Field 153 | $ arrow(p)(x,y,z) = (x+0.5) hat(i) + (y+0.5) hat(j) + (z+1) hat(k) $ 154 | 155 | #let size = 10 156 | #let scale-factor = 0.12 157 | #let (xscale,yscale,zscale) = (0.3,0.3,0.3) 158 | 159 | #let i-func(x,y,z) = x + 0.5 160 | #let j-func(x,y,z) = y + 0.5 161 | #let k-func(x,y,z) = z + 1 162 | 163 | 164 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 165 | return purple.darken(z/(z-hi - z-lo) * 100%).transparentize(0%) 166 | } 167 | 168 | #figure( 169 | plot-3d-vector-field( 170 | i-func, 171 | j-func, 172 | k-func, 173 | color-func: color-func, 174 | subdivisions: 3, 175 | subdivision-mode: "decrease", 176 | scale-dim: (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor), 177 | xdomain: (-size,size), 178 | ydomain: (-size,size), 179 | zdomain: (0,size), 180 | // pad-high: (0,0,2), 181 | rotation-matrix: ((-1.5, 1.2, 4), (0, -1, 0)), 182 | axis-label-offset: (0.4,0.2,0.2), 183 | axis-text-offset: 0.08, 184 | vector-size: 0.1em, 185 | ) 186 | ) 187 | 188 | #pagebreak() 189 | == Parametric Curve 190 | $ x(t) = 15 cos(t), space y(t)= sin(t), space z(t)= t $ 191 | 192 | #let xfunc(t) = 15*calc.cos(t) 193 | #let yfunc(t) = calc.sin(t) 194 | #let zfunc(t) = t 195 | 196 | #let size = 5 197 | 198 | #figure( 199 | plot-3d-parametric-curve( 200 | xfunc, 201 | yfunc, 202 | zfunc, 203 | subdivisions:30, 204 | scale-dim: (0.03,0.045,0.045), 205 | tdomain:(0,10), 206 | axis-step: (5,5,5), 207 | dot-thickness: 0.05em, 208 | front-axis-thickness: 0.1em, 209 | front-axis-dot-scale: (0.04, 0.04), 210 | rear-axis-dot-scale: (0.08,0.08), 211 | rear-axis-text-size: 0.5em, 212 | axis-label-size: 1.5em, 213 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)) 214 | ) 215 | ) 216 | #pagebreak() 217 | 218 | 219 | == 3D Surface 220 | $ z= x^2 $ 221 | #let size = 10 222 | #let scale-factor = 0.10 223 | #let (xscale,yscale,zscale) = (0.3,0.3,0.03) 224 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 225 | 226 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 227 | return olive.transparentize(20%).darken(10%).lighten((z/(z-lo - z-hi)) * 100%) 228 | } 229 | 230 | #figure( 231 | plot-3d-surface( 232 | func, 233 | color-func: color-func, 234 | subdivisions: 1, 235 | subdivision-mode: "increase", 236 | scale-dim: scale-dim, 237 | xdomain: (-size,size), 238 | ydomain: (-size,size), 239 | pad-high: (0,0,0), 240 | pad-low: (0,0,5), 241 | axis-step: (3,3,75), 242 | dot-thickness: 0.05em, 243 | front-axis-thickness: 0.1em, 244 | front-axis-dot-scale: (0.05,0.05), 245 | rear-axis-dot-scale: (0.08,0.2), 246 | rear-axis-text-size: 0.5em, 247 | axis-label-size: 1.5em, 248 | ) 249 | ) 250 | #pagebreak() 251 | == 3D Surface 252 | $ z= x^2 $ 253 | 254 | #figure( 255 | plot-3d-surface( 256 | func, 257 | color-func: color-func, 258 | scale-dim: scale-dim, 259 | xdomain: (-size,size), 260 | ydomain: (-size,size), 261 | pad-high: (0,0,10), 262 | pad-low: (0,0,0), 263 | axis-step: (3,3,10), 264 | dot-thickness: 0.05em, 265 | front-axis-thickness: 0.1em, 266 | front-axis-dot-scale: (0.05,0.05), 267 | rear-axis-dot-scale: (0.08,0.08), 268 | rear-axis-text-size: 0.5em, 269 | axis-label-size: 1.5em, 270 | rotation-matrix: ((1,0,0), (-1,1,1)), 271 | axis-label-offset: (0.3,0.2,0.4), 272 | axis-text-offset: 0.075, 273 | ) 274 | ) 275 | #pagebreak() 276 | == 3D Surface 277 | $ z = - e^x + 20 cos(y) $ 278 | #let size = 5 279 | #let scale-factor = 0.32 280 | #let (xscale,yscale,zscale) = (0.3,0.3,0.005) 281 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 282 | #let func(x,y) = -calc.exp(x) + 20*calc.cos(y) 283 | 284 | #let color-func(x, y, z, x-lo,x-hi,y-lo,y-hi,z-lo,z-hi) = { 285 | return teal.transparentize(20%).darken(50%).lighten((z/(z-lo - z-hi)) * 90%) 286 | } 287 | 288 | #figure( 289 | plot-3d-surface( 290 | func, 291 | color-func: color-func, 292 | subdivisions: 2, 293 | subdivision-mode: "increase", 294 | scale-dim: scale-dim, 295 | xdomain: (1,size), 296 | ydomain: (-size,size), 297 | pad-high: (0,0,24), 298 | pad-low: (0,0,5), 299 | axis-step: (3,3,20), 300 | dot-thickness: 0.05em, 301 | front-axis-thickness: 0.1em, 302 | front-axis-dot-scale: (0.05,0.05), 303 | rear-axis-dot-scale: (0.08,0.08), 304 | rear-axis-text-size: 0.5em, 305 | axis-label-size: 1.5em, 306 | axis-text-offset: 0.04, 307 | axis-label-offset: (0.1,0.1,0.1), 308 | ) 309 | ) 310 | 311 | #pagebreak() 312 | == 3D Surface 313 | $ z = 10x $ 314 | 315 | #let size = 10 316 | #let scale-factor = 0.15 317 | #let (xscale,yscale,zscale) = (0.3,0.3,0.005) 318 | #let scale-dim = (xscale*scale-factor,yscale*scale-factor, zscale*scale-factor) 319 | #let func(x,y) = 10*x 320 | 321 | #figure( 322 | plot-3d-surface( 323 | func, 324 | subdivisions: 1, 325 | subdivision-mode: "increase", 326 | scale-dim: scale-dim, 327 | xdomain: (-size,size), 328 | ydomain: (-size,size), 329 | pad-high: (0,0,0), 330 | pad-low: (0,0,5), 331 | axis-step: (3,3,75), 332 | dot-thickness: 0.05em, 333 | front-axis-thickness: 0.1em, 334 | front-axis-dot-scale: (0.05,0.05), 335 | rear-axis-dot-scale: (0.08,0.08), 336 | rear-axis-text-size: 0.5em, 337 | axis-label-size: 1.5em, 338 | ) 339 | ) 340 | 341 | 342 | -------------------------------------------------------------------------------- /examples/examples1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples1.png -------------------------------------------------------------------------------- /examples/examples10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples10.png -------------------------------------------------------------------------------- /examples/examples2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples2.png -------------------------------------------------------------------------------- /examples/examples3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples3.png -------------------------------------------------------------------------------- /examples/examples4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples4.png -------------------------------------------------------------------------------- /examples/examples5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples5.png -------------------------------------------------------------------------------- /examples/examples6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples6.png -------------------------------------------------------------------------------- /examples/examples7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples7.png -------------------------------------------------------------------------------- /examples/examples8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples8.png -------------------------------------------------------------------------------- /examples/examples9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misskacie/plotsy-3d/fb2cd905396cea96dab2d16be8185530c55b5916/examples/examples9.png -------------------------------------------------------------------------------- /plotsy-3d.typ: -------------------------------------------------------------------------------- 1 | // plotsy-3d 2 | // Author: misskacie 3 | // License: LGPL-3.0-or-later 4 | #import "@preview/cetz:0.3.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 | ) = { 723 | 724 | let (xaxis-low,xaxis-high) = xdomain 725 | let (yaxis-low,yaxis-high) = ydomain 726 | let (zaxis-low,zaxis-high) = zdomain 727 | 728 | let xcurve-lo = 0 729 | let ycurve-lo = 0 730 | let zcurve-lo = 0 731 | let xcurve-hi = 0 732 | let ycurve-hi = 0 733 | let zcurve-hi = 0 734 | let step = 1/samples 735 | let vectors = () 736 | for xregion in range(xaxis-low * samples, xaxis-high * samples, step:render-step) { 737 | for yregion in range(yaxis-low * samples, yaxis-high * samples, step: render-step) { 738 | for zregion in range(zaxis-low * samples, zaxis-high * samples, step: render-step) { 739 | let x = xregion * step 740 | let y = yregion * step 741 | let z = zregion * step 742 | let end-x = i-func(x,y,z) 743 | let end-y = j-func(x,y,z) 744 | let end-z = k-func(x,y,z) 745 | vectors.push( ( 746 | (x, y, z), 747 | (end-x, end-y, end-z) 748 | ) 749 | ) 750 | if (end-x > xcurve-hi) { 751 | xcurve-hi = calc.ceil(end-x) 752 | } 753 | if (end-x < xcurve-lo) { 754 | xcurve-lo = calc.floor(end-x) 755 | } 756 | if (end-y > ycurve-hi) { 757 | ycurve-hi = calc.ceil(end-y) 758 | } 759 | if (end-y < ycurve-lo) { 760 | ycurve-lo = calc.floor(end-y) 761 | } 762 | if (end-z > zcurve-hi) { 763 | zcurve-hi = calc.ceil(end-z) 764 | } 765 | if (end-z< zcurve-lo) { 766 | zcurve-lo = calc.floor(end-z) 767 | } 768 | } 769 | } 770 | } 771 | return (vectors, (xcurve-lo, xcurve-hi), (ycurve-lo, ycurve-hi), (zcurve-lo, zcurve-hi)) 772 | } 773 | 774 | 775 | #let render-3d-vector-field( 776 | vectors, 777 | color-func, 778 | xdomain:(0,10), 779 | ydomain:(0,10), 780 | zdomain:(0,10), 781 | vector-size: 0.02em, 782 | ) = { 783 | import draw: * 784 | 785 | for i in range(vectors.len()) { 786 | line(vectors.at(i).at(0),vectors.at(i).at(1),stroke: (vector-size + color-func( 787 | vectors.at(i).at(0).at(0), 788 | vectors.at(i).at(0).at(1), 789 | vectors.at(i).at(0).at(2), 790 | xdomain.at(0), xdomain.at(1), 791 | ydomain.at(0), ydomain.at(1), 792 | zdomain.at(0), zdomain.at(1) 793 | )), mark: (end: "straight")) 794 | } 795 | } 796 | 797 | #let plot-3d-vector-field( 798 | i-func, 799 | j-func, 800 | k-func, 801 | color-func: default-color-func, 802 | subdivisions:1, 803 | subdivision-mode: "increase", 804 | scale-dim: (1,1,0.5), 805 | xdomain:(0,10), 806 | ydomain:(0,10), 807 | zdomain:(0,10), 808 | axis-step: (5,5,5), 809 | dot-thickness: 0.05em, 810 | front-axis-thickness: 0.1em, 811 | front-axis-dot-scale: (0.05, 0.05), 812 | rear-axis-dot-scale: (0.08,0.08), 813 | rear-axis-text-size: 0.5em, 814 | axis-labels: ($x$, $y$, $z$), 815 | axis-label-size: 1.5em, 816 | axis-label-offset: (0.3,0.2,0.15), 817 | axis-text-offset: 0.075, 818 | rotation-matrix: ((-2, 2, 4), (0, -1, 0)), 819 | vector-size: 0.02em, 820 | xyz-colors: (red, green, blue), 821 | ) = { 822 | context[#canvas({ 823 | import draw: * 824 | let (xscale, yscale, zscale) = scale-dim 825 | set-transform(matrix.transform-rotate-dir(rotation-matrix.at(0),rotation-matrix.at(1))) 826 | scale(x: xscale*text.size.pt(), y: yscale*text.size.pt(), z: zscale*text.size.pt()) 827 | 828 | 829 | let (xaxis-low,xaxis-high) = xdomain 830 | let (yaxis-low,yaxis-high) = ydomain 831 | let (zaxis-low,zaxis-high) = zdomain 832 | 833 | let samples = 1 834 | let render-step = 1 835 | if(subdivision-mode == "increase"){ 836 | samples = subdivisions 837 | } else if (subdivision-mode == "decrease") { 838 | render-step = subdivisions 839 | } 840 | 841 | 842 | let (vectors, (xsurf-lo, xsurf-hi), (ysurf-lo, ysurf-hi), (zsurf-lo, zsurf-hi)) = get-vector-field-vectors( 843 | i-func, 844 | j-func, 845 | k-func, 846 | render-step: render-step, 847 | samples: samples, 848 | xdomain:xdomain, 849 | ydomain: ydomain, 850 | zdomain: zdomain, 851 | ) 852 | 853 | let xdomain = (xsurf-lo, xsurf-hi) 854 | let ydomain = (ysurf-lo, ysurf-hi) 855 | let zdomain = (zsurf-lo, zsurf-hi) 856 | 857 | render-rear-axis( 858 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 859 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 860 | axis-step: axis-step, 861 | dot-thickness: dot-thickness, 862 | axis-dot-scale: rear-axis-dot-scale, 863 | axis-text-size: rear-axis-text-size, 864 | axis-text-offset: axis-text-offset, 865 | ) 866 | 867 | render-3d-vector-field( 868 | vectors, 869 | color-func, 870 | xdomain: xdomain, 871 | ydomain: ydomain, 872 | zdomain: zdomain, 873 | vector-size: vector-size, 874 | ) 875 | 876 | render-front-axis( 877 | axis-low: (calc.min(xsurf-lo, xaxis-low),calc.min(ysurf-lo, yaxis-low), calc.min(zsurf-lo, zaxis-low)), 878 | axis-high: (calc.max(xsurf-hi, xaxis-high),calc.max(ysurf-hi, yaxis-high), calc.max(zsurf-hi, zaxis-high)), 879 | axis-label-size: axis-label-size, 880 | front-axis-dot-scale: front-axis-dot-scale, 881 | front-axis-thickness: front-axis-thickness, 882 | axis-label-offset: axis-label-offset, 883 | xyz-colors: xyz-colors, 884 | axis-labels: axis-labels, 885 | ) 886 | 887 | }) 888 | ] 889 | } -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plotsy-3d" 3 | version = "0.1.0" 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/*"] --------------------------------------------------------------------------------