Coverage for /usr/lib/python3/dist-packages/sympy/plotting/textplot.py: 10%
87 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core.numbers import Float
2from sympy.core.symbol import Dummy
3from sympy.utilities.lambdify import lambdify
5import math
8def is_valid(x):
9 """Check if a floating point number is valid"""
10 if x is None:
11 return False
12 if isinstance(x, complex):
13 return False
14 return not math.isinf(x) and not math.isnan(x)
17def rescale(y, W, H, mi, ma):
18 """Rescale the given array `y` to fit into the integer values
19 between `0` and `H-1` for the values between ``mi`` and ``ma``.
20 """
21 y_new = []
23 norm = ma - mi
24 offset = (ma + mi) / 2
26 for x in range(W):
27 if is_valid(y[x]):
28 normalized = (y[x] - offset) / norm
29 if not is_valid(normalized):
30 y_new.append(None)
31 else:
32 rescaled = Float((normalized*H + H/2) * (H-1)/H).round()
33 rescaled = int(rescaled)
34 y_new.append(rescaled)
35 else:
36 y_new.append(None)
37 return y_new
40def linspace(start, stop, num):
41 return [start + (stop - start) * x / (num-1) for x in range(num)]
44def textplot_str(expr, a, b, W=55, H=21):
45 """Generator for the lines of the plot"""
46 free = expr.free_symbols
47 if len(free) > 1:
48 raise ValueError(
49 "The expression must have a single variable. (Got {})"
50 .format(free))
51 x = free.pop() if free else Dummy()
52 f = lambdify([x], expr)
53 a = float(a)
54 b = float(b)
56 # Calculate function values
57 x = linspace(a, b, W)
58 y = []
59 for val in x:
60 try:
61 y.append(f(val))
62 # Not sure what exceptions to catch here or why...
63 except (ValueError, TypeError, ZeroDivisionError):
64 y.append(None)
66 # Normalize height to screen space
67 y_valid = list(filter(is_valid, y))
68 if y_valid:
69 ma = max(y_valid)
70 mi = min(y_valid)
71 if ma == mi:
72 if ma:
73 mi, ma = sorted([0, 2*ma])
74 else:
75 mi, ma = -1, 1
76 else:
77 mi, ma = -1, 1
78 y_range = ma - mi
79 precision = math.floor(math.log(y_range, 10)) - 1
80 precision *= -1
81 mi = round(mi, precision)
82 ma = round(ma, precision)
83 y = rescale(y, W, H, mi, ma)
85 y_bins = linspace(mi, ma, H)
87 # Draw plot
88 margin = 7
89 for h in range(H - 1, -1, -1):
90 s = [' '] * W
91 for i in range(W):
92 if y[i] == h:
93 if (i == 0 or y[i - 1] == h - 1) and (i == W - 1 or y[i + 1] == h + 1):
94 s[i] = '/'
95 elif (i == 0 or y[i - 1] == h + 1) and (i == W - 1 or y[i + 1] == h - 1):
96 s[i] = '\\'
97 else:
98 s[i] = '.'
100 if h == 0:
101 for i in range(W):
102 s[i] = '_'
104 # Print y values
105 if h in (0, H//2, H - 1):
106 prefix = ("%g" % y_bins[h]).rjust(margin)[:margin]
107 else:
108 prefix = " "*margin
109 s = "".join(s)
110 if h == H//2:
111 s = s.replace(" ", "-")
112 yield prefix + " |" + s
114 # Print x values
115 bottom = " " * (margin + 2)
116 bottom += ("%g" % x[0]).ljust(W//2)
117 if W % 2 == 1:
118 bottom += ("%g" % x[W//2]).ljust(W//2)
119 else:
120 bottom += ("%g" % x[W//2]).ljust(W//2-1)
121 bottom += "%g" % x[-1]
122 yield bottom
125def textplot(expr, a, b, W=55, H=21):
126 r"""
127 Print a crude ASCII art plot of the SymPy expression 'expr' (which
128 should contain a single symbol, e.g. x or something else) over the
129 interval [a, b].
131 Examples
132 ========
134 >>> from sympy import Symbol, sin
135 >>> from sympy.plotting import textplot
136 >>> t = Symbol('t')
137 >>> textplot(sin(t)*t, 0, 15)
138 14 | ...
139 | .
140 | .
141 | .
142 | .
143 | ...
144 | / . .
145 | /
146 | / .
147 | . . .
148 1.5 |----.......--------------------------------------------
149 |.... \ . .
150 | \ / .
151 | .. / .
152 | \ / .
153 | ....
154 | .
155 | . .
156 |
157 | . .
158 -11 |_______________________________________________________
159 0 7.5 15
160 """
161 for line in textplot_str(expr, a, b, W, H):
162 print(line)