1010
1111from mathics .core .evaluation import Evaluation
1212from mathics .core .symbols import strip_context
13- from mathics .core .systemsymbols import SymbolRGBColor , SymbolNone
13+ from mathics .core .systemsymbols import SymbolRGBColor , SymbolNone , SymbolEdgeForm
1414from mathics .timing import Timer
1515
1616from .plot_compile import plot_compile
@@ -27,13 +27,14 @@ def eval_Plot3D(
2727 # pull out plot options
2828 _ , xmin , xmax = plot_options .ranges [0 ]
2929 _ , ymin , ymax = plot_options .ranges [1 ]
30- nx , ny = plot_options .plotpoints
3130 names = [strip_context (str (range [0 ])) for range in plot_options .ranges ]
3231
33- # compute (nx, ny) grids of xs and ys for corresponding vertexes
34- xs = np .linspace (xmin , xmax , nx )
35- ys = np .linspace (ymin , ymax , ny )
36- xs , ys = np .meshgrid (xs , ys )
32+ # Mesh option
33+ nmesh = None
34+ if isinstance (plot_options .mesh , int ):
35+ nmesh = plot_options .mesh
36+ elif plot_options .mesh is not SymbolNone :
37+ nmesh = 20
3738
3839 # https://davidmathlogic.com/colorblind
3940 palette = [
@@ -48,53 +49,81 @@ def eval_Plot3D(
4849 #(0, 0, 0), # black
4950 ]
5051
51- for i , function in enumerate ( plot_options . functions ):
52- with Timer ("compile" ):
53- function = plot_compile (evaluation , function , names )
52+ # compile the functions
53+ with Timer ("compile" ):
54+ compiled_functions = [ plot_compile (evaluation , function , names ) for function in plot_options . functions ]
5455
55- # compute zs from xs and ys using compiled function
56- with Timer ("compute zs" ):
57- zs = function (** {str (names [0 ]): xs , str (names [1 ]): ys })
56+ def compute_over_grid (nx , ny ):
5857
59- # sometimes expr gets compiled into something that returns a complex
60- # even though the imaginary part is 0
61- # TODO: check that imag is all 0?
62- # TODO: needed this for Hypergeometric - look into that
63- # assert np.all(np.isreal(zs)), "array contains complex values"
64- zs = np .real (zs )
58+ # compute (nx, ny) grids of xs and ys for corresponding vertexes
59+ xs = np .linspace (xmin , xmax , nx )
60+ ys = np .linspace (ymin , ymax , ny )
61+ xs , ys = np .meshgrid (xs , ys )
6562
66- # if it's a constant, make it a full array
67- if isinstance (zs , (float , int , complex )):
68- zs = np .full (xs .shape , zs )
63+ # (nx,ny) array of numbers from 0 to n-1 that are
64+ # indexes into xyzs array for corresponding vertex
65+ # +1 because these will be used as WL indexes, which are 1-based
66+ inxs = np .arange (math .prod (xs .shape )).reshape (xs .shape ) + 1
6967
70- with Timer ("stack" ):
71- # (nx*ny, 3) array of points, to be indexed by quads
72- xyzs = np .stack ([xs , ys , zs ]).transpose (1 , 2 , 0 ).reshape (- 1 , 3 )
73-
74- # (nx,ny) array of numbers from 0 to n-1 that are
75- # indexes into xyzs array for corresponding vertex
76- inxs = np .arange (math .prod (xs .shape )).reshape (xs .shape )
68+ for function in compiled_functions :
7769
78- # shift inxs array four different ways and stack to form
79- # (4, nx-1, ny-1) array of quads represented as indexes into xyzs array
80- quads = np .stack (
81- [inxs [:- 1 , :- 1 ], inxs [:- 1 , 1 :], inxs [1 :, 1 :], inxs [1 :, :- 1 ]]
82- )
70+ # compute zs from xs and ys using compiled function
71+ with Timer ("compute zs" ):
72+ zs = function (** {str (names [0 ]): xs , str (names [1 ]): ys })
8373
84- # transpose and flatten to ((nx-1)*(ny-1), 4) array, suitable for use in GraphicsComplex
85- quads = quads .T .reshape (- 1 , 4 )
74+ # sometimes expr gets compiled into something that returns a complex
75+ # even though the imaginary part is 0
76+ # TODO: check that imag is all 0?
77+ # TODO: needed this for Hypergeometric - look into that
78+ # assert np.all(np.isreal(zs)), "array contains complex values"
79+ zs = np .real (zs )
8680
87- # ugh - indexes in Polygon are 1-based
88- quads += 1
81+ # if it's a constant, make it a full array
82+ if isinstance (zs , (float , int , complex )):
83+ zs = np .full (xs .shape , zs )
8984
90- # choose a color
91- rgb = palette [i % len (palette )]
92- rgb = [c / 255.0 for c in rgb ]
93- graphics .add_color (SymbolRGBColor , rgb )
94-
95- # add a GraphicsComplex for this function
96- graphics .add_complex (xyzs , lines = None , polys = quads )
85+ # (nx*ny, 3) array of points, to be indexed by quads
86+ xyzs = np .stack ([xs , ys , zs ]).transpose (1 , 2 , 0 ).reshape (- 1 , 3 )
9787
88+ yield xyzs , inxs
89+
90+ # generate the quads and emit a GraphicsComplex containing them
91+ for i , (xyzs , inxs ) in enumerate (compute_over_grid (* plot_options .plotpoints )):
92+
93+ # shift inxs array four different ways and stack to form
94+ # (4, nx-1, ny-1) array of quads represented as indexes into xyzs array
95+ quads = np .stack (
96+ [inxs [:- 1 , :- 1 ], inxs [:- 1 , 1 :], inxs [1 :, 1 :], inxs [1 :, :- 1 ]]
97+ )
98+
99+ # transpose and flatten to ((nx-1)*(ny-1), 4) array, suitable for use in GraphicsComplex
100+ quads = quads .T .reshape (- 1 , 4 )
101+
102+ # choose a color
103+ rgb = palette [i % len (palette )]
104+ rgb = [c / 255.0 for c in rgb ]
105+ #graphics.add_color(SymbolRGBColor, rgb)
106+ graphics .add_directives ([SymbolRGBColor , * rgb ])
107+
108+ # add a GraphicsComplex for this function
109+ graphics .add_complex (xyzs , lines = None , polys = quads )
110+
111+ # if requested by the Mesh attribute create a mesh of lines covering the surfaces
112+ if nmesh :
113+
114+ # meshes are black for now
115+ graphics .add_directives ([SymbolRGBColor , 0 ,0 ,0 ])
116+
117+ with Timer ("Mesh" ):
118+ nmesh = 20 # TODO: use supplied option
119+ nx , ny = plot_options .plotpoints
120+ # Do nmesh lines in each direction.
121+ # Each mesh line has high res (nx or ny) so it follows
122+ # the contours of the surface.
123+ for xyzs , inxs in compute_over_grid (nx , nmesh ):
124+ graphics .add_complex (xyzs , lines = inxs , polys = None )
125+ for xyzs , inxs in compute_over_grid (nmesh , ny ):
126+ graphics .add_complex (xyzs , lines = inxs .T , polys = None )
98127
99128 return graphics
100129
0 commit comments