3838 SymbolLog10 ,
3939 SymbolNone ,
4040 SymbolRGBColor ,
41+ SymbolSequence ,
4142 SymbolStyle ,
4243)
4344from mathics .eval .drawing .charts import draw_bar_chart , eval_chart
5354 get_plot_range ,
5455 get_plot_range_option ,
5556)
57+ from mathics .eval .nevaluator import eval_N
5658
5759# The vectorized plot function generates GraphicsComplex using NumericArray,
5860# which no consumer will currently understand. So lets make it opt-in for now.
6264# TODO: work out exactly how to deploy.
6365use_vectorized_plot = os .getenv ("MATHICS3_USE_VECTORIZED_PLOT" , False )
6466if use_vectorized_plot :
65- from mathics .eval .drawing .plot3d_vectorized import eval_DensityPlot , eval_Plot3D
67+ from mathics .eval .drawing .plot3d_vectorized import (
68+ eval_ComplexPlot ,
69+ eval_ComplexPlot3D ,
70+ eval_DensityPlot ,
71+ eval_Plot3D ,
72+ )
6673else :
67- from mathics .eval .drawing .plot3d import eval_DensityPlot , eval_Plot3D
68-
69- from mathics .eval .nevaluator import eval_N
74+ from mathics .eval .drawing .plot3d import (
75+ eval_ComplexPlot ,
76+ eval_ComplexPlot3D ,
77+ eval_DensityPlot ,
78+ eval_Plot3D ,
79+ )
7080
7181# This tells documentation how to sort this module
7282# Here we are also hiding "drawing" since this erroneously appears at the top level.
@@ -466,11 +476,15 @@ def __init__(self, expr, range_exprs, options, evaluation):
466476 self .error (expr , "invrange" , range_expr )
467477 range = [range_expr .elements [0 ]]
468478 for limit_expr in range_expr .elements [1 :3 ]:
469- limit = limit_expr . round_to_float ( evaluation )
470- if limit is None :
479+ limit = eval_N ( limit_expr , evaluation ). to_python ( )
480+ if not isinstance ( limit , ( int , float , complex )) :
471481 self .error (expr , "plln" , limit_expr , range_expr )
472482 range .append (limit )
473- if range [2 ] <= range [1 ]:
483+ if isinstance (limit , (int , float )) and range [2 ] <= range [1 ]:
484+ self .error (expr , "invrange" , range_expr )
485+ if isinstance (limit , complex ) and (
486+ range [2 ].real <= range [1 ].real or range [2 ].imag <= range [1 ].imag
487+ ):
474488 self .error (expr , "invrange" , range_expr )
475489 self .ranges .append (range )
476490
@@ -539,7 +553,9 @@ def check_plotpoints(steps):
539553
540554
541555class _Plot3D (Builtin ):
542- """Common base class for Plot3D and DensityPlot"""
556+ """Common base class for Plot3D, DensityPlot, ComplexPlot, ComplexPlot3D"""
557+
558+ attributes = A_HOLD_ALL | A_PROTECTED
543559
544560 # Check for correct number of args
545561 eval_error = Builtin .generic_argument_error
@@ -565,29 +581,62 @@ class _Plot3D(Builtin):
565581 ),
566582 }
567583
584+ # Plot3D, ComplexPlot3D
585+ options3d = Graphics3D .options | {
586+ "Axes" : "True" ,
587+ "AspectRatio" : "1" ,
588+ "Mesh" : "Full" ,
589+ "PlotPoints" : "None" ,
590+ "BoxRatios" : "{1, 1, 0.4}" ,
591+ "MaxRecursion" : "2" ,
592+ }
593+
594+ # DensityPlot, ComplexPlot
595+ options2d = Graphics .options | {
596+ "Axes" : "False" ,
597+ "AspectRatio" : "1" ,
598+ "Mesh" : "None" ,
599+ "Frame" : "True" ,
600+ "ColorFunction" : "Automatic" ,
601+ "ColorFunctionScaling" : "True" ,
602+ "PlotPoints" : "None" ,
603+ "MaxRecursion" : "0" ,
604+ # 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
605+ }
606+
568607 def eval (
569608 self ,
570609 functions ,
571- xrange ,
572- yrange ,
610+ ranges ,
573611 evaluation : Evaluation ,
574612 options : dict ,
575613 ):
576- """%(name)s[functions_, xrange_, yrange_ , OptionsPattern[%(name)s]]"""
614+ """%(name)s[functions_, ranges__ , OptionsPattern[%(name)s]]"""
577615
578616 # TODO: test error for too many, too few, no args
579617
580618 # parse options, bailing out if anything is wrong
581619 try :
582- plot_options = PlotOptions (self , [xrange , yrange ], options , evaluation )
620+ ranges = ranges .elements if ranges .head is SymbolSequence else [ranges ]
621+ plot_options = PlotOptions (self , ranges , options , evaluation )
583622 except ValueError :
584623 return None
585624
586- # ask the subclass to get one or more functions as appropriate
587- plot_options .functions = self .get_functions_param (functions )
625+ # TODO: consult many_functions variable set by subclass and error
626+ # if many_functions is False but multiple are supplied
627+ if functions .has_form ("List" , None ):
628+ plot_options .functions = functions .elements
629+ else :
630+ plot_options .functions = [functions ]
588631
589- # delegate to subclass, which will call the appropriate eval_* function
590- return self .do_eval (plot_options , evaluation , options )
632+ # subclass must set eval_function and graphics_class
633+ graphics = self .eval_function (plot_options , evaluation )
634+ if not graphics :
635+ return
636+ graphics_expr = graphics .generate (
637+ options_to_rules (options , self .graphics_class .options )
638+ )
639+ return graphics_expr
591640
592641
593642class BarChart (_Chart ):
@@ -712,6 +761,54 @@ class ColorDataFunction(Builtin):
712761 summary_text = "color scheme object"
713762
714763
764+ class ComplexPlot3D (_Plot3D ):
765+ """
766+ <url>:WMA link: https://reference.wolfram.com/language/ref/ComplexPlot3D.html</url>
767+ <dl>
768+ <dt>'Plot3D'[$f$, {$z$, $z_{min}$, $z_{max}$}]
769+ <dd>creates a three-dimensional plot of the magnitude of $f$ with $z$ ranging from $z_{min}$ to \
770+ $z_{max}$ with surface colored according to phase
771+
772+ See <url>:Drawing Option and Option Values:
773+ /doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
774+ </url> for a list of Plot options.
775+ </dl>
776+
777+ """
778+
779+ summary_text = "plots one or more complex functions as a surface"
780+ expected_args = 2
781+ options = _Plot3D .options3d | {"Mesh" : "None" }
782+
783+ many_functions = True
784+ eval_function = staticmethod (eval_ComplexPlot3D )
785+ graphics_class = Graphics3D
786+
787+
788+ class ComplexPlot (_Plot3D ):
789+ """
790+ <url>:WMA link: https://reference.wolfram.com/language/ref/ComplexPlot.html</url>
791+ <dl>
792+ <dt>'Plot3D'[$f$, {$z$, $z_{min}$, $z_{max}$}]
793+ <dd>creates two-dimensional plot of $f$ with $z$ ranging from $z_{min}$ to \
794+ $z_{max}$ colored according to phase
795+
796+ See <url>:Drawing Option and Option Values:
797+ /doc/reference-of-built-in-symbols/graphics-and-drawing/drawing-options-and-option-values
798+ </url> for a list of Plot options.
799+ </dl>
800+
801+ """
802+
803+ summary_text = "plots a complex function showing phase using colors"
804+ expected_args = 2
805+ options = _Plot3D .options2d
806+
807+ many_functions = False
808+ eval_function = staticmethod (eval_ComplexPlot )
809+ graphics_class = Graphics
810+
811+
715812class DensityPlot (_Plot3D ):
716813 """
717814 <url>:WMA link: https://reference.wolfram.com/language/ref/DensityPlot.html</url>
@@ -736,35 +833,13 @@ class DensityPlot(_Plot3D):
736833 = -Graphics-
737834 """
738835
739- attributes = A_HOLD_ALL | A_PROTECTED
740-
741- options = Graphics .options .copy ()
742- options .update (
743- {
744- "Axes" : "False" ,
745- "AspectRatio" : "1" ,
746- "Mesh" : "None" ,
747- "Frame" : "True" ,
748- "ColorFunction" : "Automatic" ,
749- "ColorFunctionScaling" : "True" ,
750- "PlotPoints" : "None" ,
751- "MaxRecursion" : "0" ,
752- # 'MaxRecursion': '2', # FIXME causes bugs in svg output see #303
753- }
754- )
755836 summary_text = "density plot for a function"
837+ expected_args = 3
838+ options = _Plot3D .options2d
756839
757- # TODO: error if more than one function here
758- def get_functions_param (self , functions ):
759- """can only have one function"""
760- return [functions ]
761-
762- # called by superclass
763- def do_eval (self , plot_options , evaluation , options ):
764- """called by superclass to call appropriate eval_* function"""
765- graphics = eval_DensityPlot (plot_options , evaluation )
766- graphics_expr = graphics .generate (options_to_rules (options , Graphics .options ))
767- return graphics_expr
840+ many_functions = False
841+ eval_function = staticmethod (eval_DensityPlot )
842+ graphics_class = Graphics
768843
769844
770845class DiscretePlot (_Plot ):
@@ -1878,30 +1953,10 @@ class Plot3D(_Plot3D):
18781953 = -Graphics3D-
18791954 """
18801955
1881- attributes = A_HOLD_ALL | A_PROTECTED
1882-
1883- options = Graphics .options .copy ()
1884- options .update (
1885- {
1886- "Axes" : "True" ,
1887- "AspectRatio" : "1" ,
1888- "Mesh" : "Full" ,
1889- "PlotPoints" : "None" ,
1890- "BoxRatios" : "{1, 1, 0.4}" ,
1891- "MaxRecursion" : "2" ,
1892- }
1893- )
18941956 summary_text = "plots 3D surfaces of one or more functions"
1957+ expected_args = 3
1958+ options = _Plot3D .options3d
18951959
1896- def get_functions_param (self , functions ):
1897- """May have a function or a list of functions"""
1898- if functions .has_form ("List" , None ):
1899- return functions .elements
1900- else :
1901- return [functions ]
1902-
1903- def do_eval (self , plot_options , evaluation , options ):
1904- """called by superclass to call appropriate eval_* function"""
1905- graphics = eval_Plot3D (plot_options , evaluation )
1906- graphics_expr = graphics .generate (options_to_rules (options , Graphics3D .options ))
1907- return graphics_expr
1960+ many_functions = True
1961+ eval_function = staticmethod (eval_Plot3D )
1962+ graphics_class = Graphics3D
0 commit comments