Skip to content

Commit

Permalink
Add a ListStepPlot refactor _ListPlot a little
Browse files Browse the repository at this point in the history
  • Loading branch information
rocky committed Sep 26, 2024
1 parent 7a8d418 commit 40ca3fa
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 33 deletions.
69 changes: 65 additions & 4 deletions mathics/builtin/drawing/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import itertools
import numbers
from abc import ABC
from functools import lru_cache
from math import cos, pi, sin, sqrt
from typing import Callable, Optional
Expand Down Expand Up @@ -44,13 +45,14 @@
SymbolSlot,
SymbolStyle,
)
from mathics.eval.nevaluator import eval_N
from mathics.eval.plot import (
from mathics.eval.drawing.plot import (
ListPlotType,
compile_quiet_function,
eval_ListPlot,
eval_Plot,
get_plot_range,
)
from mathics.eval.nevaluator import eval_N

# This tells documentation how to sort this module
# Here we are also hiding "drawing" since this erroneously appears at the top level.
Expand Down Expand Up @@ -274,7 +276,7 @@ def color_data_function(self, name):
return Expression(SymbolColorDataFunction, *arguments)


class _ListPlot(Builtin):
class _ListPlot(Builtin, ABC):
"""
Base class for ListPlot, and ListLinePlot
2-Dimensional plot a list of points in some fashion.
Expand Down Expand Up @@ -310,6 +312,16 @@ def eval(self, points, evaluation: Evaluation, options: dict):
# FIXME: arrange for self to have a .symbolname property or attribute
expr = Expression(Symbol(self.get_name()), points, *options_to_rules(options))

class_name = self.__class__.__name__
if class_name == "ListPlot":
plot_type = ListPlotType.ListPlot
elif class_name == "ListLinePlot":
plot_type = ListPlotType.ListLinePlot
elif class_name == "ListStepPlot":
plot_type = ListPlotType.ListStepPlot
else:
plot_type = None

plotrange_option = self.get_option(options, "PlotRange", evaluation)
plotrange = eval_N(plotrange_option, evaluation).to_python()
if plotrange == "System`All":
Expand Down Expand Up @@ -366,6 +378,7 @@ def eval(self, points, evaluation: Evaluation, options: dict):
is_joined_plot=is_joined_plot,
filling=filling,
use_log_scale=self.use_log_scale,
list_plot_type=plot_type,
options=options,
)

Expand All @@ -382,7 +395,7 @@ def colors(self):
return colors


class _Plot(Builtin):
class _Plot(Builtin, ABC):
attributes = A_HOLD_ALL | A_PROTECTED | A_READ_PROTECTED

expect_list = False
Expand Down Expand Up @@ -1680,6 +1693,7 @@ def apply_fn(fn: Callable, x_value: int) -> Optional[float]:
is_joined_plot=False,
filling=False,
use_log_scale=False,
plot_type=ListPlot.DiscretePlot,
options=options,
)

Expand Down Expand Up @@ -2035,6 +2049,53 @@ class ListLinePlot(_ListPlot):
summary_text = "plot lines through lists of points"


class ListStepPlot(_ListPlot):
"""
<url>
:WMA link:
https://reference.wolfram.com/language/ref/ListStepPlot.html</url>
<dl>
<dt>'ListStepPlot[{$y_1$, $y_2$, ...}]'
<dd>plots a line through a list of $y$-values, assuming integer $x$-values 1, 2, 3, ...
<dt>'ListStepPlot[{{$x_1$, $y_1$}, {$x_2$, $y_2$}, ...}]'
<dd>plots a line through a list of $x$, $y$ pairs.
<dt>'ListStepPlot[{$list_1$, $list_2$, ...}]'
<dd>plots several lines.
</dl>
>> ListStepPlot[{1, 1, 2, 3, 5, 8, 13, 21}]
= -Graphics-
By default ListStepPlots are joined, but that can be disabled.
>> ListStepPlot[{1, 1, 2, 3, 5, 8, 13, 21}, Joined->False]
= -Graphics-
ListPlot accepts a superset of the Graphics options.
>> ListStepPlot[{{-2, -1}, {-1, -1}, {1, 3}}, Filling->Axis]
= -Graphics-
"""

attributes = A_HOLD_ALL | A_PROTECTED

options = Graphics.options.copy()
options.update(
{
"Axes": "True",
"AspectRatio": "1 / GoldenRatio",
"Mesh": "None",
"PlotRange": "Automatic",
"PlotPoints": "None",
"Filling": "None",
"Joined": "True",
}
)
summary_text = "plot values in steps"


class ListLogPlot(_ListPlot):
"""
<url>:WMA link: https://reference.wolfram.com/language/ref/ListLogPlot.html</url>
Expand Down
Empty file.
102 changes: 73 additions & 29 deletions mathics/eval/plot.py → mathics/eval/drawing/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
That is done as another pass after M-expression evaluation finishes.
"""

from enum import Enum
from math import cos, isinf, isnan, pi, sqrt
from typing import Callable, Iterable, List, Optional, Type, Union

Expand All @@ -30,6 +31,14 @@
SymbolPolygon,
)

ListPlotNames = (
"DiscretePlot",
"ListPlot",
"ListLinePlot",
"ListStepPlot",
)
ListPlotType = Enum("ListPlotType", ListPlotNames)

RealPoint6 = Real(0.6)
RealPoint2 = Real(0.2)

Expand Down Expand Up @@ -129,6 +138,7 @@ def eval_ListPlot(
is_joined_plot: bool,
filling,
use_log_scale: bool,
list_plot_type: ListPlotType,
options: dict,
):
"""
Expand Down Expand Up @@ -219,6 +229,9 @@ def eval_ListPlot(
i = 0
while i < len(plot_groups[lidx]):
seg = plot_group[i]
# skip empty segments How do they get in though?
if not seg:
continue
for j, point in enumerate(seg):
x_min = min(x_min, point[0])
x_max = max(x_min, point[0])
Expand All @@ -232,6 +245,23 @@ def eval_ListPlot(
plot_groups[lidx][i + 1] = seg[j + 1 :]
i -= 1
break
pass

# For step plots we have 2n - 1 points, which
# we create from the n points here
# We insert a new point from the y coodinate
# of the previous point in between each new point
# other than the first point
if list_plot_type == ListPlotType.ListStepPlot:
step_plot_group = []
last_point = seg[0]
for j, point in enumerate(seg):
if j != 0:
step_plot_group.append([point[0], last_point[1]])
step_plot_group.append(point)
step_plot_group.append(point)
last_point = point
plot_groups[lidx][i] = step_plot_group

i += 1

Expand Down Expand Up @@ -269,43 +299,57 @@ def eval_ListPlot(
for index, plot_group in enumerate(plot_groups):
graphics.append(Expression(SymbolHue, Real(hue), RealPoint6, RealPoint6))
for segment in plot_group:
mathics_segment = from_python(segment)
if is_joined_plot:
graphics.append(Expression(SymbolLine, mathics_segment))
if filling is not None:
graphics.append(
Expression(
SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2
if not is_joined_plot and list_plot_type == ListPlotType.ListStepPlot:
line_segments = [
(segment[i], segment[i + 1])
for i in range(0, len(segment) - 1)
if segment[i][0] != segment[i + 1][0]
]
for line_segment in line_segments:
graphics.append(Expression(SymbolLine, from_python(line_segment)))
pass
else:
mathics_segment = from_python(segment)
if is_joined_plot:
graphics.append(Expression(SymbolLine, mathics_segment))
if filling is not None:
graphics.append(
Expression(
SymbolHue, Real(hue), RealPoint6, RealPoint6, RealPoint2
)
)
)
fill_area = list(segment)
fill_area.append([segment[-1][0], filling])
fill_area.append([segment[0][0], filling])
graphics.append(Expression(SymbolPolygon, from_python(fill_area)))
elif is_axis_filling:
graphics.append(Expression(SymbolPoint, mathics_segment))
for mathics_point in mathics_segment:
graphics.append(
Expression(
SymbolLine,
ListExpression(
ListExpression(mathics_point[0], Integer0),
mathics_point,
),
fill_area = list(segment)
fill_area.append([segment[-1][0], filling])
fill_area.append([segment[0][0], filling])
graphics.append(
Expression(SymbolPolygon, from_python(fill_area))
)
)
else:
graphics.append(Expression(SymbolPoint, mathics_segment))
if filling is not None:
for point in segment:
elif is_axis_filling:
graphics.append(Expression(SymbolPoint, mathics_segment))
for mathics_point in mathics_segment:
graphics.append(
Expression(
SymbolLine,
from_python(
[[point[0], filling], [point[0], point[1]]]
ListExpression(
ListExpression(mathics_point[0], Integer0),
mathics_point,
),
)
)
else:
graphics.append(Expression(SymbolPoint, mathics_segment))
if filling is not None:
for point in segment:
graphics.append(
Expression(
SymbolLine,
from_python(
[[point[0], filling], [point[0], point[1]]]
),
)
)
pass
pass

if index % 4 == 0:
hue += hue_pos
Expand Down

0 comments on commit 40ca3fa

Please sign in to comment.