Skip to content

Commit 9db162f

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents bbaccf5 + cc06072 commit 9db162f

File tree

8 files changed

+557
-261
lines changed

8 files changed

+557
-261
lines changed

mathics/builtin/colors/color_directives.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,12 @@ def to_js(self):
204204
return self.to_rgba()
205205

206206
def to_expr(self):
207-
return to_expression(self.get_name(), *self.components)
207+
"""Convert components to MachineReal consistently so that colors with
208+
numerically-equal components but different numeric atom types compare equal.
209+
"""
210+
return to_expression(
211+
self.get_name(), *self.components, elements_conversion_fn=MachineReal
212+
)
208213

209214
def to_rgba(self):
210215
return self.to_color_space("RGB")

mathics/builtin/drawing/plot.py

Lines changed: 149 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@
2727
from mathics.core.list import ListExpression
2828
from mathics.core.symbols import Symbol, SymbolList
2929
from 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
)
@@ -50,7 +51,7 @@
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
5455
from 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+
410515
class _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

446567
class BarChart(_Chart):
@@ -563,7 +684,6 @@ class ColorDataFunction(Builtin):
563684
"""
564685

565686
summary_text = "color scheme object"
566-
pass
567687

568688

569689
class 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

629744
class 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

Comments
 (0)