11"""
2- Vectorized evaluation routines for Plot3D and DensityPlot, which share a good bit of code.
3-
4- TODO: fill out eval_DensityPlot
2+ Vectorized evaluation routines for Plot3D, DensityPlot, ComplexPlot, and ComplexPlot3D,
3+ which share a good bit of code.
54"""
65
76import math
87
98import numpy as np
109
10+ from mathics .builtin .colors .color_internals import convert_color
1111from mathics .core .evaluation import Evaluation
1212from mathics .core .symbols import strip_context
1313from mathics .core .systemsymbols import SymbolNone , SymbolRGBColor
1717from .util import GraphicsGenerator
1818
1919
20- def make_plot (plot_options , evaluation : Evaluation , dim : int , is_complex : bool ):
20+ def make_plot (plot_options , evaluation : Evaluation , dim : int , is_complex : bool , emit ):
2121 graphics = GraphicsGenerator (dim )
2222
2323 # pull out plot options
@@ -36,17 +36,6 @@ def make_plot(plot_options, evaluation: Evaluation, dim: int, is_complex: bool):
3636 if plot_options .mesh is SymbolNone :
3737 nmesh = 0
3838
39- # color-blind friendly palette from https://davidmathlogic.com/colorblind
40- palette = [
41- (255 , 176 , 0 ), # orange
42- (100 , 143 , 255 ), # blue
43- (220 , 38 , 127 ), # red
44- (50 , 150 , 140 ), # green
45- (120 , 94 , 240 ), # purple
46- (254 , 97 , 0 ), # dark orange
47- (0 , 114 , 178 ), # dark blue
48- ]
49-
5039 # compile the functions
5140 with Timer ("compile" ):
5241 compiled_functions = [
@@ -90,7 +79,8 @@ def compute_over_grid(nx, ny):
9079 # TODO: check that imag is all 0?
9180 # TODO: needed this for Hypergeometric - look into that
9281 # assert np.all(np.isreal(zs)), "array contains complex values"
93- zs = np .real (zs )
82+ if not is_complex :
83+ zs = np .real (zs )
9484
9585 # if it's a constant, make it a full array
9686 if isinstance (zs , (float , int , complex )):
@@ -110,34 +100,9 @@ def compute_over_grid(nx, ny):
110100 # transpose and flatten to ((nx-1)*(ny-1), 4) array, suitable for use in GraphicsComplex
111101 quads = quads .T .reshape (- 1 , 4 )
112102
113- # Plot3D
114- if dim == 3 :
115- # choose a color
116- rgb = palette [i % len (palette )]
117- rgb = [c / 255.0 for c in rgb ]
118- # graphics.add_color(SymbolRGBColor, rgb)
119- graphics .add_directives ([SymbolRGBColor , * rgb ])
120-
121- # add a GraphicsComplex displaying a surface for this function
122- graphics .add_complex (xyzs , lines = None , polys = quads )
123-
124- # DensityPlot
125- elif dim == 2 :
126- # Fixed palette for now
127- # TODO: accept color options
128- with Timer ("compute colors" ):
129- zs = xyzs [:, 2 ]
130- z_min , z_max = min (zs ), max (zs )
131- zs = zs [:, np .newaxis ] # allow broadcasting
132- c_min , c_max = [0.5 , 0 , 0.1 ], [1.0 , 0.9 , 0.5 ]
133- c_min , c_max = (
134- np .full ((len (zs ), 3 ), c_min ),
135- np .full ((len (zs ), 3 ), c_max ),
136- )
137- colors = ((zs - z_min ) * c_max + (z_max - zs ) * c_min ) / (z_max - z_min )
138-
139- # flatten the points and add the quads
140- graphics .add_complex (xyzs [:, 0 :2 ], lines = None , polys = quads , colors = colors )
103+ # pass the xyzs and quads back to the caller to add colors and emit quads as appropriate
104+ emit (graphics , i , xyzs , quads )
105+
141106
142107 # If requested by the Mesh attribute create a mesh of lines covering the surfaces
143108 # For now only for Plot3D
@@ -153,9 +118,9 @@ def compute_over_grid(nx, ny):
153118 # Each mesh line has high res (nx or ny) so it follows
154119 # the contours of the surface.
155120 for xyzs , inxs in compute_over_grid (nx , nmesh ):
156- graphics .add_complex (xyzs , lines = inxs , polys = None )
121+ graphics .add_complex (xyzs . astype ( float ) , lines = inxs , polys = None )
157122 for xyzs , inxs in compute_over_grid (nmesh , ny ):
158- graphics .add_complex (xyzs , lines = inxs .T , polys = None )
123+ graphics .add_complex (xyzs . astype ( float ) , lines = inxs .T , polys = None )
159124
160125 return graphics
161126
@@ -165,28 +130,106 @@ def eval_Plot3D(
165130 plot_options ,
166131 evaluation : Evaluation ,
167132):
168- return make_plot (plot_options , evaluation , dim = 3 , is_complex = False )
133+ def emit (graphics , i , xyzs , quads ):
134+
135+ # color-blind friendly palette from https://davidmathlogic.com/colorblind
136+ palette = [
137+ (255 , 176 , 0 ), # orange
138+ (100 , 143 , 255 ), # blue
139+ (220 , 38 , 127 ), # red
140+ (50 , 150 , 140 ), # green
141+ (120 , 94 , 240 ), # purple
142+ (254 , 97 , 0 ), # dark orange
143+ (0 , 114 , 178 ), # dark blue
144+ ]
145+
146+ # choose a color
147+ rgb = palette [i % len (palette )]
148+ rgb = [c / 255.0 for c in rgb ]
149+ # graphics.add_color(SymbolRGBColor, rgb)
150+ graphics .add_directives ([SymbolRGBColor , * rgb ])
151+
152+ # add a GraphicsComplex displaying a surface for this function
153+ graphics .add_complex (xyzs , lines = None , polys = quads )
154+
155+
156+ return make_plot (plot_options , evaluation , dim = 3 , is_complex = False , emit = emit )
169157
170158
171159@Timer ("eval_DensityPlot" )
172160def eval_DensityPlot (
173161 plot_options ,
174162 evaluation : Evaluation ,
175163):
176- return make_plot (plot_options , evaluation , dim = 2 , is_complex = False )
164+ def emit (graphics , i , xyzs , quads ):
165+
166+ # Fixed palette for now
167+ # TODO: accept color options
168+ with Timer ("compute colors" ):
169+ zs = xyzs [:, 2 ]
170+ z_min , z_max = min (zs ), max (zs )
171+ zs = zs [:, np .newaxis ] # allow broadcasting
172+ c_min , c_max = [0.5 , 0 , 0.1 ], [1.0 , 0.9 , 0.5 ]
173+ c_min , c_max = (
174+ np .full ((len (zs ), 3 ), c_min ),
175+ np .full ((len (zs ), 3 ), c_max ),
176+ )
177+ colors = ((zs - z_min ) * c_max + (z_max - zs ) * c_min ) / (z_max - z_min )
178+
179+ # flatten the points and add the quads
180+ graphics .add_complex (xyzs [:, 0 :2 ], lines = None , polys = quads , colors = colors )
181+
182+ return make_plot (plot_options , evaluation , dim = 2 , is_complex = False , emit = emit )
183+
184+
185+ @Timer ("complex colors" )
186+ def complex_colors (zs , s = None ):
187+
188+ # hue depends on phase
189+ h = np .angle (zs , deg = True ) / 360
190+
191+ # saturation depends on magnitude
192+ if s is None :
193+ zabs = abs (zs )
194+ zabs = - np .log (zabs )
195+ zmin , zmax = min (zabs ), max (zabs )
196+ s = (zabs - zmin ) / (zmax - zmin )
197+ else :
198+ s = np .full (zs .shape , s )
199+
200+ # brightness is constant
201+ b = np .full (zs .shape , 1.0 )
202+
203+ # convert to rgb
204+ hsb = np .array ([h , s , b ]).T
205+ rgb = convert_color (hsb , "HSB" , "RGB" , False )
206+
207+ return rgb
177208
178209
179210@Timer ("eval_ComplexPlot3D" )
180211def eval_ComplexPlot3D (
181212 plot_options ,
182213 evaluation : Evaluation ,
183214):
184- return None
215+ def emit (graphics , i , xyzs , quads ):
216+ zs = xyzs [:,2 ]
217+ rgb = complex_colors (zs , s = 0.8 )
218+ xyzs [:,2 ] = abs (zs )
219+ graphics .add_complex (xyzs .astype (float ), lines = None , polys = quads , colors = rgb )
220+
221+ return make_plot (plot_options , evaluation , dim = 3 , is_complex = True , emit = emit )
222+
185223
186224
187225@Timer ("eval_ComplexPlot" )
188226def eval_ComplexPlot (
189227 plot_options ,
190228 evaluation : Evaluation ,
191229):
192- return make_plot (plot_options , evaluation , dim = 2 , is_complex = True )
230+ def emit (graphics , i , xyzs , quads ):
231+ # flatten the points and add the quads
232+ rgb = complex_colors (xyzs [:,2 ])
233+ graphics .add_complex (xyzs [:, 0 :2 ].astype (float ), lines = None , polys = quads , colors = rgb )
234+
235+ return make_plot (plot_options , evaluation , dim = 2 , is_complex = True , emit = emit )
0 commit comments