2727from mathics .core .list import ListExpression
2828from mathics .core .symbols import Symbol , SymbolList
2929from mathics .core .systemsymbols import (
30+ SymbolAll ,
3031 SymbolBlack ,
3132 SymbolEdgeForm ,
33+ SymbolFull ,
3234 SymbolGraphics ,
33- SymbolGraphics3D ,
3435 SymbolLine ,
3536 SymbolLog10 ,
36- SymbolPolygon ,
37+ SymbolNone ,
3738 SymbolRGBColor ,
3839 SymbolStyle ,
3940)
5051 get_plot_range ,
5152 get_plot_range_option ,
5253)
53- from mathics .eval .drawing .plot3d import construct_density_plot , eval_plot3d
54+ from mathics .eval .drawing .plot3d import eval_DensityPlot , eval_Plot3D
5455from mathics .eval .nevaluator import eval_N
5556
5657# This tells documentation how to sort this module
@@ -407,7 +408,117 @@ def process_function_and_options(
407408 return functions , x_name , py_start , py_stop , x_range , y_range , expr_limits , expr
408409
409410
411+ # TODO: add more options
412+ # TODO: generalize, use for other plots
413+ class PlotOptions :
414+ """
415+ Extract Options common to many types of plotting.
416+ This aims to reduce duplication of code,
417+ and to make it easier to pass options to eval_* routines.
418+ """
419+
420+ # TODO: more precise types
421+ ranges : list
422+ mesh : str
423+ plotpoints : list
424+ maxdepth : int
425+
426+ def error (self , what , * args , ** kwargs ):
427+ if not isinstance (what , str ):
428+ what = what .get_name ()
429+ self .evaluation .message (what , * args , ** kwargs )
430+ raise ValueError ()
431+
432+ def __init__ (self , expr , range_exprs , options , evaluation ):
433+ self .evaluation = evaluation
434+
435+ # plot ranges
436+ self .ranges = []
437+ for range_expr in range_exprs :
438+ if not range_expr .has_form ("List" , 3 ):
439+ self .error (expr , "invrange" , range_expr )
440+ if not isinstance (range_expr .elements [0 ], Symbol ):
441+ self .error (expr , "invrange" , range_expr )
442+ range = [range_expr .elements [0 ]]
443+ for limit_expr in range_expr .elements [1 :3 ]:
444+ limit = limit_expr .round_to_float (evaluation )
445+ if limit is None :
446+ self .error (expr , "plln" , limit_expr , range_expr )
447+ range .append (limit )
448+ if range [2 ] <= range [1 ]:
449+ self .error (expr , "invrange" , range_expr )
450+ self .ranges .append (range )
451+
452+ # Mesh option
453+ mesh = expr .get_option (options , "Mesh" , evaluation )
454+ if mesh not in (SymbolNone , SymbolFull , SymbolAll ):
455+ evaluation .message ("Mesh" , "ilevels" , mesh )
456+ mesh = SymbolFull
457+ self .mesh = mesh
458+
459+ # PlotPoints option
460+ plotpoints_option = expr .get_option (options , "PlotPoints" , evaluation )
461+ plotpoints = plotpoints_option .to_python ()
462+
463+ def check_plotpoints (steps ):
464+ if isinstance (steps , int ) and steps > 0 :
465+ return True
466+ return False
467+
468+ if plotpoints == "System`None" :
469+ plotpoints = (7 , 7 )
470+ elif check_plotpoints (plotpoints ):
471+ plotpoints = (plotpoints , plotpoints )
472+ if not (
473+ isinstance (plotpoints , (list , tuple ))
474+ and len (plotpoints ) == 2
475+ and check_plotpoints (plotpoints [0 ])
476+ and check_plotpoints (plotpoints [1 ])
477+ ):
478+ evaluation .message (expr .get_name (), "invpltpts" , plotpoints )
479+ plotpoints = (7 , 7 )
480+ self .plotpoints = plotpoints
481+
482+ # MaxRecursion Option
483+ maxrec_option = expr .get_option (options , "MaxRecursion" , evaluation )
484+ max_depth = maxrec_option .to_python ()
485+ if isinstance (max_depth , int ):
486+ if max_depth < 0 :
487+ max_depth = 0
488+ evaluation .message (expr .get_name (), "invmaxrec" , max_depth , 15 )
489+ elif max_depth > 15 :
490+ max_depth = 15
491+ evaluation .message (expr .get_name (), "invmaxrec" , max_depth , 15 )
492+ else :
493+ pass # valid
494+ elif max_depth == float ("inf" ):
495+ max_depth = 15
496+ evaluation .message (expr .get_name (), "invmaxrec" , max_depth , 15 )
497+ else :
498+ max_depth = 0
499+ evaluation .message (expr .get_name (), "invmaxrec" , max_depth , 15 )
500+ self .max_depth = max_depth
501+
502+ # ColorFunction and ColorFunctionScaling options
503+ # This was pulled from construct_density_plot (now eval_DensityPlot).
504+ # TODO: What does pop=True do? is it right?
505+ # TODO: can we move some of the subsequent processing in eval_DensityPlot to here?
506+ # TODO: what is the type of these? that may change if we do the above...
507+ self .color_function = expr .get_option (
508+ options , "ColorFunction" , evaluation , pop = True
509+ )
510+ self .color_function_scaling = expr .get_option (
511+ options , "ColorFunctionScaling" , evaluation , pop = True
512+ )
513+
514+
410515class _Plot3D (Builtin ):
516+ """Common base class for Plot3D and DensityPlot"""
517+
518+ # Check for correct number of args
519+ eval_error = Builtin .generic_argument_error
520+ expected_args = 3
521+
411522 messages = {
412523 "invmaxrec" : (
413524 "MaxRecursion must be a non-negative integer; the recursion value "
@@ -422,25 +533,35 @@ class _Plot3D(Builtin):
422533 "Value of PlotPoints -> `1` is not a positive integer "
423534 "or appropriate list of positive integers."
424535 ),
536+ "invrange" : (
537+ "Plot range `1` must be of the form {variable, min, max}, "
538+ "where max > min."
539+ ),
425540 }
426541
427542 def eval (
428543 self ,
429544 functions ,
430- x ,
431- xstart ,
432- xstop ,
433- y ,
434- ystart ,
435- ystop ,
545+ xrange ,
546+ yrange ,
436547 evaluation : Evaluation ,
437548 options : dict ,
438549 ):
439- """%(name)s[functions_, {x_Symbol, xstart_, xstop_},
440- {y_Symbol, ystart_, ystop_}, OptionsPattern[%(name)s]]"""
441- return eval_plot3d (
442- self , functions , x , xstart , xstop , y , ystart , ystop , evaluation , options
443- )
550+ """%(name)s[functions_, xrange_, yrange_, OptionsPattern[%(name)s]]"""
551+
552+ # TODO: test error for too many, too few, no args
553+
554+ # parse options, bailing out if anything is wrong
555+ try :
556+ plot_options = PlotOptions (self , [xrange , yrange ], options , evaluation )
557+ except ValueError :
558+ return None
559+
560+ # ask the subclass to get one or more functions as appropriate
561+ plot_options .functions = self .get_functions_param (functions )
562+
563+ # delegate to subclass, which will call the appropriate eval_* function
564+ return self .do_eval (plot_options , evaluation , options )
444565
445566
446567class BarChart (_Chart ):
@@ -563,7 +684,6 @@ class ColorDataFunction(Builtin):
563684 """
564685
565686 summary_text = "color scheme object"
566- pass
567687
568688
569689class DensityPlot (_Plot3D ):
@@ -608,22 +728,17 @@ class DensityPlot(_Plot3D):
608728 )
609729 summary_text = "density plot for a function"
610730
731+ # TODO: error if more than one function here
611732 def get_functions_param (self , functions ):
733+ """can only have one function"""
612734 return [functions ]
613735
614- def construct_graphics (
615- self , triangles , mesh_points , v_min , v_max , options , evaluation
616- ):
617- return construct_density_plot (
618- self , triangles , mesh_points , v_min , v_max , options , evaluation
619- )
620-
621- def final_graphics (self , graphics , options ):
622- return Expression (
623- SymbolGraphics ,
624- ListExpression (* graphics ),
625- * options_to_rules (options , Graphics .options ),
626- )
736+ # called by superclass
737+ def do_eval (self , plot_options , evaluation , options ):
738+ """called by superclass to call appropriate eval_* function"""
739+ graphics = eval_DensityPlot (plot_options , evaluation )
740+ graphics_expr = graphics .generate (options_to_rules (options , Graphics .options ))
741+ return graphics_expr
627742
628743
629744class DiscretePlot (_Plot ):
@@ -1753,43 +1868,14 @@ class Plot3D(_Plot3D):
17531868 summary_text = "plots 3D surfaces of one or more functions"
17541869
17551870 def get_functions_param (self , functions ):
1871+ """May have a function or a list of functions"""
17561872 if functions .has_form ("List" , None ):
17571873 return functions .elements
17581874 else :
17591875 return [functions ]
17601876
1761- def construct_graphics (
1762- self , triangles , mesh_points , v_min , v_max , options , evaluation : Evaluation
1763- ):
1764- graphics = []
1765- for p1 , p2 , p3 in triangles :
1766- graphics .append (
1767- Expression (
1768- SymbolPolygon ,
1769- ListExpression (
1770- to_mathics_list (* p1 ),
1771- to_mathics_list (* p2 ),
1772- to_mathics_list (* p3 ),
1773- ),
1774- )
1775- )
1776- # Add the Grid
1777- for xi in range (len (mesh_points )):
1778- line = []
1779- for yi in range (len (mesh_points [xi ])):
1780- line .append (
1781- to_mathics_list (
1782- mesh_points [xi ][yi ][0 ],
1783- mesh_points [xi ][yi ][1 ],
1784- mesh_points [xi ][yi ][2 ],
1785- )
1786- )
1787- graphics .append (Expression (SymbolLine , ListExpression (* line )))
1788- return graphics
1789-
1790- def final_graphics (self , graphics , options : dict ):
1791- return Expression (
1792- SymbolGraphics3D ,
1793- ListExpression (* graphics ),
1794- * options_to_rules (options , Graphics3D .options ),
1795- )
1877+ def do_eval (self , plot_options , evaluation , options ):
1878+ """called by superclass to call appropriate eval_* function"""
1879+ graphics = eval_Plot3D (plot_options , evaluation )
1880+ graphics_expr = graphics .generate (options_to_rules (options , Graphics3D .options ))
1881+ return graphics_expr
0 commit comments