1515
1616import palettable
1717
18+ import mathics .eval .drawing .plot3d
19+ import mathics .eval .drawing .plot3d_vectorized
1820from mathics .builtin .drawing .graphics3d import Graphics3D
1921from mathics .builtin .graphics import Graphics
2022from mathics .builtin .options import options_to_rules
3739 SymbolLine ,
3840 SymbolLog10 ,
3941 SymbolNone ,
42+ SymbolPlotRange ,
4043 SymbolRGBColor ,
4144 SymbolSequence ,
4245 SymbolStyle ,
6265# Set option such as $UseVectorizedPlot, and maybe a non-standard Plot3D option.
6366# For now an env variable is simplest.
6467# TODO: work out exactly how to deploy.
68+
69+
70+ # can be set via environment variable at startup time,
71+ # or changed dynamically by setting the use_vectorized_plot flag
6572use_vectorized_plot = os .getenv ("MATHICS3_USE_VECTORIZED_PLOT" , False )
66- if use_vectorized_plot :
67- from mathics .eval .drawing .plot3d_vectorized import (
68- eval_ComplexPlot ,
69- eval_ComplexPlot3D ,
70- eval_DensityPlot ,
71- eval_Plot3D ,
72- )
73- else :
74- from mathics .eval .drawing .plot3d import (
75- eval_ComplexPlot ,
76- eval_ComplexPlot3D ,
77- eval_DensityPlot ,
78- eval_Plot3D ,
73+
74+
75+ # get the plot eval function for the given class,
76+ # depending on whether vectorized plot functions are enabled
77+ def get_plot_eval_function (cls ):
78+ function_name = "eval_" + cls .__name__
79+ plot_module = (
80+ mathics .eval .drawing .plot3d_vectorized
81+ if use_vectorized_plot
82+ else mathics .eval .drawing .plot3d
7983 )
84+ fun = getattr (plot_module , function_name )
85+ return fun
86+
8087
8188# This tells documentation how to sort this module
8289# Here we are also hiding "drawing" since this erroneously appears at the top level.
@@ -467,7 +474,7 @@ def error(self, what, *args, **kwargs):
467474 def __init__ (self , expr , range_exprs , options , evaluation ):
468475 self .evaluation = evaluation
469476
470- # plot ranges
477+ # plot ranges of the form {x,xmin,xmax} etc.
471478 self .ranges = []
472479 for range_expr in range_exprs :
473480 if not range_expr .has_form ("List" , 3 ):
@@ -488,6 +495,19 @@ def __init__(self, expr, range_exprs, options, evaluation):
488495 self .error (expr , "invrange" , range_expr )
489496 self .ranges .append (range )
490497
498+ # Contours option
499+ contours = expr .get_option (options , "Contours" , evaluation )
500+ if contours is not None :
501+ c = contours .to_python ()
502+ if not (
503+ c == "System`Automatic"
504+ or isinstance (c , int )
505+ or isinstance (c , tuple )
506+ and all (isinstance (cc , (int , float )) for cc in c )
507+ ):
508+ self .error (expr , "invcontour" , contours )
509+ self .contours = c
510+
491511 # Mesh option
492512 mesh = expr .get_option (options , "Mesh" , evaluation )
493513 if mesh not in (SymbolNone , SymbolFull , SymbolAll ):
@@ -579,6 +599,9 @@ class _Plot3D(Builtin):
579599 "Plot range `1` must be of the form {variable, min, max}, "
580600 "where max > min."
581601 ),
602+ "invcontour" : (
603+ "Contours option must be Automatic, an integer, or a list of numbers."
604+ ),
582605 }
583606
584607 # Plot3D, ComplexPlot3D
@@ -630,9 +653,43 @@ def eval(
630653 plot_options .functions = [functions ]
631654
632655 # subclass must set eval_function and graphics_class
633- graphics = self .eval_function (plot_options , evaluation )
656+ eval_function = get_plot_eval_function (self .__class__ )
657+ graphics = eval_function (plot_options , evaluation )
634658 if not graphics :
635659 return
660+
661+ # Expand PlotRange option using the {x,xmin,xmax} etc. range specifications
662+ # Pythonize it, so Symbol becomes str, numeric becomes int or float
663+ plot_range = self .get_option (options , str (SymbolPlotRange ), evaluation )
664+ plot_range = plot_range .to_python ()
665+ dim = 3 if self .graphics_class is Graphics3D else 2
666+ if isinstance (plot_range , str ):
667+ # PlotRange -> Automatic becomes PlotRange -> {Automatic, ...}
668+ plot_range = [str (SymbolAutomatic )] * dim
669+ if isinstance (plot_range , (int , float )):
670+ # PlotRange -> s becomes PlotRange -> {Automatic,...,{-s,s}}
671+ pr = plot_range
672+ plot_range = [str (SymbolAutomatic )] * dim
673+ plot_range [- 1 ] = [- pr , pr ]
674+ elif isinstance (plot_range , (list , tuple )) and isinstance (
675+ plot_range [0 ], (int , float )
676+ ):
677+ # PlotRange -> {s0,s1} becomes PlotRange -> {Automatic,...,{s0,s1}}
678+ pr = plot_range
679+ plot_range = [str (SymbolAutomatic )] * dim
680+ plot_range [- 1 ] = pr
681+
682+ # now we have a list of length dim
683+ # handle Automatic ~ {xmin,xmax} etc.
684+ for i , (pr , r ) in enumerate (zip (plot_range , plot_options .ranges )):
685+ # TODO: this treats Automatic and Full as the same, which isn't quite right
686+ if isinstance (pr , str ) and not isinstance (r [1 ], complex ):
687+ plot_range [i ] = r [1 :] # extract {xmin,xmax} from {x,xmin,xmax}
688+
689+ # unpythonize and update PlotRange option
690+ options [str (SymbolPlotRange )] = to_mathics_list (* plot_range )
691+
692+ # generate the Graphics[3D] result
636693 graphics_expr = graphics .generate (
637694 options_to_rules (options , self .graphics_class .options )
638695 )
@@ -781,7 +838,6 @@ class ComplexPlot3D(_Plot3D):
781838 options = _Plot3D .options3d | {"Mesh" : "None" }
782839
783840 many_functions = True
784- eval_function = staticmethod (eval_ComplexPlot3D )
785841 graphics_class = Graphics3D
786842
787843
@@ -805,7 +861,31 @@ class ComplexPlot(_Plot3D):
805861 options = _Plot3D .options2d
806862
807863 many_functions = False
808- eval_function = staticmethod (eval_ComplexPlot )
864+ graphics_class = Graphics
865+
866+
867+ class ContourPlot (_Plot3D ):
868+ """
869+ <url>:WMA link: https://reference.wolfram.com/language/ref/ContourPlot.html</url>
870+ <dl>
871+ <dt>'Contour'[$f$, {$x$, $x_{min}$, $x_{max}$}, {$y$, $y_{min}$, $y_{max}$}]
872+ <dd>creates a two-dimensional contour plot ofh $f$ over the region
873+ $x$ ranging from $x_{min}$ to $x_{max}$ and $y$ ranging from $y_{min}$ to $y_{max}$.
874+
875+ See <url>:Drawing Option and Option Values:
876+ /doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
877+ </url> for a list of Plot options.
878+ </dl>
879+
880+ """
881+
882+ requires = ["skimage" ]
883+ summary_text = "creates a contour plot"
884+ expected_args = 3
885+ options = _Plot3D .options2d | {"Contours" : "Automatic" }
886+ # TODO: other options?
887+
888+ many_functions = True
809889 graphics_class = Graphics
810890
811891
@@ -838,7 +918,6 @@ class DensityPlot(_Plot3D):
838918 options = _Plot3D .options2d
839919
840920 many_functions = False
841- eval_function = staticmethod (eval_DensityPlot )
842921 graphics_class = Graphics
843922
844923
@@ -1958,5 +2037,4 @@ class Plot3D(_Plot3D):
19582037 options = _Plot3D .options3d
19592038
19602039 many_functions = True
1961- eval_function = staticmethod (eval_Plot3D )
19622040 graphics_class = Graphics3D
0 commit comments