prefig 0.2.15.dev20250514053750__py3-none-any.whl → 0.5.6.dev20260130060411__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. prefig/cli.py +46 -26
  2. prefig/core/CTM.py +18 -3
  3. prefig/core/__init__.py +2 -0
  4. prefig/core/annotations.py +115 -2
  5. prefig/core/area.py +20 -4
  6. prefig/core/arrow.py +9 -3
  7. prefig/core/axes.py +909 -0
  8. prefig/core/circle.py +13 -4
  9. prefig/core/clip.py +1 -1
  10. prefig/core/coordinates.py +31 -4
  11. prefig/core/diagram.py +129 -6
  12. prefig/core/graph.py +236 -23
  13. prefig/core/grid_axes.py +181 -495
  14. prefig/core/image.py +127 -0
  15. prefig/core/label.py +14 -4
  16. prefig/core/legend.py +4 -4
  17. prefig/core/line.py +92 -1
  18. prefig/core/math_utilities.py +155 -0
  19. prefig/core/network.py +2 -2
  20. prefig/core/parametric_curve.py +15 -3
  21. prefig/core/path.py +25 -0
  22. prefig/core/point.py +18 -2
  23. prefig/core/polygon.py +7 -1
  24. prefig/core/read.py +6 -3
  25. prefig/core/rectangle.py +10 -4
  26. prefig/core/repeat.py +37 -3
  27. prefig/core/shape.py +12 -1
  28. prefig/core/slope_field.py +142 -0
  29. prefig/core/tags.py +8 -2
  30. prefig/core/tangent_line.py +36 -12
  31. prefig/core/user_namespace.py +8 -0
  32. prefig/core/utilities.py +9 -1
  33. prefig/engine.py +73 -28
  34. prefig/resources/diagcess/diagcess.js +7 -1
  35. prefig/resources/schema/pf_schema.rnc +89 -6
  36. prefig/resources/schema/pf_schema.rng +321 -5
  37. prefig/scripts/install_mj.py +10 -11
  38. {prefig-0.2.15.dev20250514053750.dist-info → prefig-0.5.6.dev20260130060411.dist-info}/METADATA +12 -16
  39. prefig-0.5.6.dev20260130060411.dist-info/RECORD +68 -0
  40. prefig-0.2.15.dev20250514053750.dist-info/RECORD +0 -66
  41. {prefig-0.2.15.dev20250514053750.dist-info → prefig-0.5.6.dev20260130060411.dist-info}/LICENSE +0 -0
  42. {prefig-0.2.15.dev20250514053750.dist-info → prefig-0.5.6.dev20260130060411.dist-info}/WHEEL +0 -0
  43. {prefig-0.2.15.dev20250514053750.dist-info → prefig-0.5.6.dev20260130060411.dist-info}/entry_points.txt +0 -0
prefig/core/grid_axes.py CHANGED
@@ -3,12 +3,15 @@ import math
3
3
  import re
4
4
  import logging
5
5
  import numpy as np
6
+ import copy
7
+ from . import math_utilities as math_util
6
8
  from . import utilities as util
7
9
  from . import user_namespace as un
8
10
  from . import label
9
11
  from . import line
10
12
  from . import arrow
11
13
  from . import CTM
14
+ from . import axes
12
15
 
13
16
  log = logging.getLogger('prefigure')
14
17
 
@@ -47,7 +50,66 @@ def find_gridspacing(coordinate_range, pi_format=False):
47
50
  x1 = dx * math.floor(coordinate_range[1]/dx+1e-10)
48
51
  if pi_format:
49
52
  return (x0*math.pi, dx*math.pi, x1*math.pi)
50
- return (x0, dx, x1)
53
+ return [x0, dx, x1]
54
+
55
+ def find_log_positions(r):
56
+ # argument r could have
57
+ # three arguments if user supplied
58
+ # two arguments if not
59
+ # each range 10^j -> 10^j+1 could have 1, 2, 5, 10, or 1/n lines
60
+ x0 = np.log10(r[0])
61
+ x1 = np.log10(r[-1])
62
+ if len(r) == 3:
63
+ if r[1] < 1:
64
+ spacing = r[1]
65
+ elif r[1] < 2:
66
+ spacing = 1
67
+ elif r[1] < 4:
68
+ spacing = 2
69
+ elif r[1] < 7:
70
+ spacing = 5
71
+ else:
72
+ spacing = 10
73
+ else:
74
+ width = abs(x1 - x0)
75
+ if width < 1.5:
76
+ spacing = 10
77
+ elif width < 3:
78
+ spacing = 5
79
+ elif width < 5:
80
+ spacing = 2
81
+ elif width <= 10:
82
+ spacing = 1
83
+ else:
84
+ spacing = 10/width
85
+
86
+ x0 = math.floor(x0)
87
+ x1 = math.ceil(x1)
88
+ positions = []
89
+ if spacing <= 1:
90
+ gap = round(1/spacing)
91
+ x = x0
92
+ while x <= x1:
93
+ positions.append(10**x)
94
+ x += gap
95
+ else:
96
+ if spacing == 2:
97
+ intermediate = [1,5]
98
+ elif spacing == 5:
99
+ intermediate = [1,2,4,6,8]
100
+ elif spacing == 10:
101
+ intermediate = [1,2,3,4,5,6,7,8,9]
102
+ else:
103
+ intermediate = [1]
104
+ x = x0
105
+ while x <= x1:
106
+ positions += [10**x*c for c in intermediate]
107
+ x += 1
108
+ return positions
109
+
110
+ def find_linear_positions(r):
111
+ N = round((r[2] - r[0]) / r[1])
112
+ return np.linspace(r[0], r[2], N+1)
51
113
 
52
114
  # Add a graphical element for a grid. All the grid lines sit inside a group
53
115
  def grid(element, diagram, parent, outline_status):
@@ -58,9 +120,14 @@ def grid(element, diagram, parent, outline_status):
58
120
 
59
121
  thickness = element.get('thickness', '1')
60
122
  stroke = element.get('stroke', r'#ccc')
123
+ id = element.get('id')
124
+ if id is None:
125
+ id = 'pf__grid'
126
+ if not id.startswith('pf__'):
127
+ id = 'pf__' + id
61
128
  grid = ET.SubElement(parent, 'g',
62
129
  attrib={
63
- 'id': element.get('id', 'grid'),
130
+ 'id': id,
64
131
  'stroke': stroke,
65
132
  'stroke-width': thickness
66
133
  }
@@ -73,490 +140,131 @@ def grid(element, diagram, parent, outline_status):
73
140
  h_pi_format = element.get('h-pi-format', 'no') == 'yes'
74
141
  v_pi_format = element.get('v-pi-format', 'no') == 'yes'
75
142
 
143
+ coordinates = element.get('coordinates', 'cartesian')
144
+ scales = diagram.get_scales()
145
+ hspacings_set = False
76
146
  if spacings is not None:
77
147
  try:
78
148
  rx, ry = un.valid_eval(spacings)
149
+ if scales[0] == 'log':
150
+ x_positions = find_log_positions(rx)
151
+ else:
152
+ x_positions = find_linear_positions(rx)
153
+ if scales[1] == 'log':
154
+ y_positions = find_log_positions(ry)
155
+ else:
156
+ y_positions = find_linear_positions(ry)
157
+
158
+ hspacings_set = True
79
159
  except:
80
160
  log.error(f"Error in <grid> parsing spacings={element.get('spacings')}")
81
161
  return
82
162
  else:
83
163
  rx = element.get('hspacing')
84
164
  if rx is None:
85
- rx = find_gridspacing((bbox[0], bbox[2]), h_pi_format)
165
+ if scales[0] == 'log':
166
+ x_positions = find_log_positions((bbox[0], bbox[2]))
167
+ else:
168
+ rx = find_gridspacing((bbox[0], bbox[2]), h_pi_format)
169
+ x_positions = find_linear_positions(rx)
86
170
  else:
87
171
  rx = un.valid_eval(rx)
172
+ if scales[0] == 'log':
173
+ x_positions = find_log_positions(rx)
174
+ else:
175
+ x_positions = find_linear_positions(rx)
176
+ hspacings_set = True
88
177
 
89
- ry = element.get('vspacing')
90
- if ry is None:
91
- ry = find_gridspacing((bbox[1], bbox[3]), v_pi_format)
178
+ if coordinates == 'polar':
179
+ ry = [0, math.pi/6, 2*math.pi]
92
180
  else:
93
- ry = un.valid_eval(ry)
181
+ ry = element.get('vspacing')
182
+ if ry is None:
183
+ if scales[1] == 'log':
184
+ y_positions = find_log_positions((bbox[1], bbox[3]))
185
+ else:
186
+ ry = find_gridspacing((bbox[1], bbox[3]), v_pi_format)
187
+ y_positions = find_linear_positions(ry)
188
+ else:
189
+ ry = un.valid_eval(ry)
190
+ if scales[1] == 'log':
191
+ y_positions = find_log_positions(ry)
192
+ else:
193
+ y_positions = find_linear_positions(ry)
194
+
195
+ if coordinates == 'polar':
196
+ id = diagram.get_clippath()
197
+ grid.set('clip-path', r'url(#{})'.format(id))
198
+
199
+ bbox = list(diagram.bbox())
200
+ endpoints = []
201
+ for _ in range(4):
202
+ endpoints.append(bbox[:2])
203
+ bbox = bbox[1:] + [bbox[0]]
204
+ R = max([math_util.length(p) for p in endpoints])
205
+ if hspacings_set:
206
+ R = rx[2]
207
+ r = rx[1]
208
+ N = 100
209
+ dt = 2*math.pi/N
210
+ while r <= R:
211
+ circle = ET.SubElement(grid, 'path')
212
+ t = 0
213
+ cmds = ['M']
214
+ point = diagram.transform([r*math.cos(t), r*math.sin(t)])
215
+ cmds.append(util.pt2str(point))
216
+ for _ in range(N):
217
+ t += dt
218
+ cmds.append('L')
219
+ point = diagram.transform([r*math.cos(t), r*math.sin(t)])
220
+ cmds.append(util.pt2str(point))
221
+ cmds.append('Z')
222
+ circle.set('d', ' '.join(cmds))
223
+ circle.set('fill', 'none')
224
+ r += rx[1]
225
+
226
+ if element.get('spacing-degrees', 'no') == 'yes':
227
+ ry = [math.radians(t) for t in ry]
228
+ t = ry[0]
229
+ while t <= ry[2]:
230
+ direction = np.array([math.cos(t), math.sin(t)])
231
+ intersection_times = []
232
+ vert, horiz = np.isclose(direction, np.array([0,0]))
233
+ if not vert:
234
+ intersection_times.append(bbox[0]/direction[0])
235
+ intersection_times.append(bbox[2]/direction[0])
236
+ if not horiz:
237
+ intersection_times.append(bbox[1]/direction[1])
238
+ intersection_times.append(bbox[3]/direction[1])
239
+ intersection_time = max(intersection_times)
240
+ if hspacings_set:
241
+ intersection_time = R
242
+ if intersection_time > 0:
243
+ line_el = ET.SubElement(grid, 'line')
244
+ start = diagram.transform((0,0))
245
+ end = diagram.transform(intersection_time*direction)
246
+ line_el.set('x1', util.float2str(start[0]))
247
+ line_el.set('y1', util.float2str(start[1]))
248
+ line_el.set('x2', util.float2str(end[0]))
249
+ line_el.set('y2', util.float2str(end[1]))
250
+
251
+ t += ry[1]
252
+ return
94
253
 
95
- x = rx[0]
96
- while x <= rx[2]:
254
+ # now we'll just build a plain rectangular grid
255
+ for x in x_positions:
256
+ if x < bbox[0] or x > bbox[2]:
257
+ continue
97
258
  line_el = line.mk_line((x,bbox[1]), (x,bbox[3]), diagram)
98
- # line_el.set('type', 'vertical grid')
259
+ line_el.attrib.pop('id')
99
260
  grid.append(line_el)
100
- x += rx[1]
101
261
 
102
- y = ry[0]
103
- while y <= ry[2]:
262
+ for y in y_positions:
263
+ if y < bbox[1] or y > bbox[3]:
264
+ continue
104
265
  line_el = line.mk_line((bbox[0], y), (bbox[2], y), diagram)
105
- # line_el.set('type', 'horizontal grid')
266
+ line_el.attrib.pop('id')
106
267
  grid.append(line_el)
107
- y += ry[1]
108
-
109
- # Automate finding the positions where ticks and labels go
110
- label_delta = {2: 0.2, 3: 0.5, 4: 0.5, 5: 1,
111
- 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1,
112
- 12: 2, 13: 2, 14: 2, 15: 2, 16: 2, 17: 2,
113
- 18: 2, 19: 2, 20: 2}
114
-
115
- def find_label_positions(coordinate_range, pi_format = False):
116
- if pi_format:
117
- coordinate_range = [c/math.pi for c in coordinate_range]
118
- dx = 1
119
- distance = abs(coordinate_range[1]-coordinate_range[0])
120
- while distance > 10:
121
- distance /= 10
122
- dx *= 10
123
- while distance <= 1:
124
- distance *= 10
125
- dx /= 10
126
- if dx > 1:
127
- dx *= label_delta[round(2*distance)]
128
- dx = int(dx)
129
- else:
130
- dx *= label_delta[round(2*distance)]
131
- if coordinate_range[1] < coordinate_range[0]:
132
- dx *= -1
133
- x0 = dx * math.floor(coordinate_range[0]/dx+1e-10)
134
- x1 = dx * math.ceil(coordinate_range[1]/dx-1e-10)
135
- else:
136
- x0 = dx * math.ceil(coordinate_range[0]/dx-1e-10)
137
- x1 = dx * math.floor(coordinate_range[1]/dx+1e-10)
138
- return (x0, dx, x1)
139
-
140
- # find a string representation of x*pi
141
- def get_pi_text(x):
142
- if abs(abs(x) - 1) < 1e-10:
143
- if x < 0:
144
- return r'-\pi'
145
- return r'\pi'
146
-
147
- if abs(x - round(x)) < 1e-10:
148
- return str(round(x))+r'\pi'
149
- if abs(4*x - round(4*x)) < 1e-10:
150
- num = round(4*x)
151
- if num == -1:
152
- return r'-\pi/4'
153
- if num == 1:
154
- return r'\pi/4'
155
- if num % 2 == 1:
156
- return str(num)+r'\pi/4'
157
- if abs(2*x - round(2*x)) < 1e-10:
158
- num = round(2*x)
159
- if num == -1:
160
- return r'-\pi/2'
161
- if num == 1:
162
- return r'\pi/2'
163
- return str(num)+r'\pi/2'
164
- if abs(3*x - round(3*x)) < 1e-10:
165
- num = round(3*x)
166
- if num == -1:
167
- return r'-\pi/3'
168
- if num == 1:
169
- return r'\pi/3'
170
- return str(num)+r'\pi/3'
171
- return r'{0:g}\pi'.format(x)
172
-
173
-
174
- # Add a graphical element for axes. All the axes sit inside a group
175
- # There are a number of options to add: labels, tick marks, etc
176
- position_tolerance = 1e-10
177
- def axes(element, diagram, parent, outline_status):
178
- stroke = element.get('stroke', 'black')
179
- thickness = element.get('thickness', '2')
180
- clear_background = element.get('clear-background', 'no') == 'yes'
181
-
182
- axes = ET.SubElement(parent, 'g',
183
- attrib={
184
- 'id': element.get('id', 'axes'),
185
- 'stroke': stroke,
186
- 'stroke-width': thickness
187
- }
188
- )
189
-
190
- util.cliptobbox(axes, element, diagram)
191
- ctm, bbox = diagram.ctm_bbox()
192
-
193
- top_labels = False
194
- y_axis_location = 0
195
- y_axis_offsets = (0,0)
196
- h_zero_include = False
197
- if bbox[1] * bbox[3] >= 0:
198
- if bbox[3] <= 0:
199
- top_labels = True
200
- y_axis_location = bbox[3]
201
- if bbox[3] < 0:
202
- y_axis_offsets = (0,-5)
203
- else:
204
- if abs(bbox[1]) > 1e-10:
205
- y_axis_location = bbox[1]
206
- y_axis_offsets = (5,0)
207
-
208
- h_frame = element.get('h-frame', None)
209
- if h_frame == 'bottom':
210
- y_axis_location = bbox[1]
211
- y_axis_offsets = (0,0)
212
- h_zero_include = True
213
- if h_frame == 'top':
214
- y_axis_location = bbox[3]
215
- y_axis_offsets = (0,0)
216
- h_zero_include = True
217
- top_labels = True
218
-
219
- y_axis_offsets = np.array(y_axis_offsets)
220
-
221
- right_labels = False
222
- x_axis_location = 0
223
- x_axis_offsets = (0,0)
224
- v_zero_include = False
225
- if bbox[0] * bbox[2] >= 0:
226
- if bbox[2] <= 0:
227
- right_labels = True
228
- x_axis_location = bbox[2]
229
- if bbox[2] < 0:
230
- x_axis_offsets = (0,-10)
231
- else:
232
- if abs(bbox[0]) > 1e-10:
233
- x_axis_location = bbox[0]
234
- x_axis_offsets = (10,0)
235
-
236
- v_frame = element.get('v-frame', None)
237
- if v_frame == 'left':
238
- x_axis_location = bbox[0]
239
- x_axis_offsets = (0,0)
240
- v_zero_include = True
241
- if v_frame == 'right':
242
- x_axis_location = bbox[2]
243
- x_axis_offsets = (0,0)
244
- v_zero_include = True
245
- right_labels = True
246
-
247
- x_axis_offsets = np.array(x_axis_offsets)
248
-
249
- try:
250
- arrows = int(element.get('arrows', '0'))
251
- except:
252
- log.error(f"Error in <axes> parsing arrows={element.get('arrows')}")
253
- arrows = 0
254
-
255
- # process xlabel and ylabel
256
- for child in element:
257
- if child.tag == "xlabel":
258
- child.tag = "label"
259
- child.set("user-coords", "no")
260
- anchor = diagram.transform((bbox[2], y_axis_location))
261
- child.set("anchor", util.pt2str(anchor, spacer=","))
262
- if child.get("alignment", None) is None:
263
- child.set("alignment", "east")
264
- if child.get("offset", None) is None:
265
- if arrows > 0:
266
- child.set("offset", "(2,0)")
267
- else:
268
- child.set("offset", "(1,0)")
269
- if clear_background:
270
- child.set('clear-background', 'yes')
271
- label.label(child, diagram, parent)
272
- continue
273
- if child.tag == "ylabel":
274
- child.tag = "label"
275
- child.set("user-coords", "no")
276
- anchor = diagram.transform((x_axis_location, bbox[3]))
277
- child.set("anchor", util.pt2str(anchor, spacer=","))
278
- if child.get("alignment", None) is None:
279
- child.set("alignment", "north")
280
- if child.get("offset", None) is None:
281
- if arrows > 0:
282
- child.set("offset", "(0,2)")
283
- else:
284
- child.set("offset", "(0,1)")
285
- if clear_background:
286
- child.set('clear-background', 'yes')
287
- label.label(child, diagram, parent)
288
- continue
289
- log.info(f"{child.tag} element is not allowed inside a <label>")
290
- continue
291
-
292
- decorations = element.get('decorations', 'yes')
293
-
294
- left_axis = diagram.transform((bbox[0], y_axis_location))
295
- right_axis = diagram.transform((bbox[2], y_axis_location))
296
-
297
- h_line_el = line.mk_line(left_axis,
298
- right_axis,
299
- diagram,
300
- endpoint_offsets = x_axis_offsets,
301
- user_coords = False)
302
- h_line_el.set('stroke', stroke)
303
- # h_line_el.set('type', 'horizontal axis')
304
- h_line_el.set('stroke-width', thickness)
305
- if element.get('axes', 'yes') == 'yes':
306
- axes.append(h_line_el)
307
-
308
- bottom_axis = diagram.transform((x_axis_location, bbox[1]))
309
- top_axis = diagram.transform((x_axis_location, bbox[3]))
310
-
311
- v_line_el = line.mk_line(bottom_axis,
312
- top_axis,
313
- diagram,
314
- endpoint_offsets = y_axis_offsets,
315
- user_coords = False)
316
- v_line_el.set('stroke', stroke)
317
- # v_line_el.set('type', 'vertical axis')
318
- v_line_el.set('stroke-width', thickness)
319
- if element.get('axes', 'yes') == 'yes':
320
- axes.append(v_line_el)
321
-
322
- if arrows > 0:
323
- arrow.add_arrowhead_to_path(diagram, 'marker-end', h_line_el)
324
- arrow.add_arrowhead_to_path(diagram, 'marker-end', v_line_el)
325
- if arrows > 1:
326
- arrow.add_arrowhead_to_path(diagram, 'marker-start', h_line_el)
327
- arrow.add_arrowhead_to_path(diagram, 'marker-start', v_line_el)
328
-
329
- if element.get('labels', 'yes') == 'no':
330
- return
331
-
332
- hticks = element.get('hticks', None)
333
- vticks = element.get('vticks', None)
334
-
335
- h_pi_format = element.get('h-pi-format', 'no') == 'yes'
336
- v_pi_format = element.get('v-pi-format', 'no') == 'yes'
337
-
338
- hlabels = element.get('hlabels')
339
- if hlabels is None:
340
- hlabels = find_label_positions((bbox[0], bbox[2]),
341
- pi_format = h_pi_format)
342
- else:
343
- try:
344
- hlabels = un.valid_eval(hlabels)
345
- except:
346
- log.error(f"Error in <axes> parsing hlabels={hlabels}")
347
- return
348
- if h_pi_format:
349
- hlabels = 1/math.pi * hlabels
350
-
351
- g_hticks = ET.SubElement(axes, 'g',
352
- attrib={
353
- # 'type': 'horizontal ticks'
354
- }
355
- )
356
- diagram.add_id(g_hticks)
357
-
358
- h_exclude = [bbox[0], bbox[2]]
359
- if not h_zero_include:
360
- h_exclude.append(0)
361
-
362
- if diagram.output_format() == 'tactile':
363
- ticksize = (18, 0)
364
- else:
365
- ticksize = (3, 3)
366
- if hticks is not None:
367
- try:
368
- hticks = un.valid_eval(hticks)
369
- except:
370
- log.error(f"Error in <axes> parsing hticks={hticks}")
371
- return
372
- x = hticks[0]
373
- tick_direction = 1
374
- if top_labels:
375
- tick_direction = -1
376
- while x <= hticks[2]:
377
- if any([abs(x-p) < position_tolerance for p in [bbox[0], bbox[2]]]):
378
- x += hticks[1]
379
- continue
380
- p = diagram.transform((x,y_axis_location))
381
- line_el = line.mk_line((p[0], p[1]+tick_direction*ticksize[0]),
382
- (p[0], p[1]-tick_direction*ticksize[1]),
383
- diagram,
384
- user_coords=False)
385
- # line_el.set('type', 'tick on horizontal axis')
386
- g_hticks.append(line_el)
387
- x += hticks[1]
388
-
389
- h_scale = 1
390
- if h_pi_format:
391
- h_scale = math.pi
392
- if decorations == 'yes' or element.get('hlabels', None) is not None:
393
- x = hlabels[0]
394
- tick_direction = 1
395
- if top_labels:
396
- tick_direction = -1
397
- while x <= hlabels[2]:
398
- if any([abs(x*h_scale-p) < position_tolerance for p in h_exclude]):
399
- x += hlabels[1]
400
- continue
401
-
402
- xlabel = ET.Element('label')
403
- math_element = ET.SubElement(xlabel, 'm')
404
- math_element.text = r'\text{'+'{0:g}'.format(x)+'}'
405
- if h_pi_format:
406
- math_element.text = get_pi_text(x)
407
-
408
- xlabel.set('p', '({},{})'.format(x*h_scale, y_axis_location))
409
- if diagram.output_format() == 'tactile':
410
- if top_labels:
411
- xlabel.set('alignment', 'hat')
412
- xlabel.set('offset', '(0,0)')
413
- else:
414
- xlabel.set('alignment', 'ha')
415
- xlabel.set('offset', '(0,0)')
416
- else:
417
- if top_labels:
418
- xlabel.set('alignment', 'north')
419
- xlabel.set('offset', '(0,7)')
420
- else:
421
- xlabel.set('alignment', 'south')
422
- xlabel.set('offset', '(0,-7)')
423
- # xlabel.set('clear-background', 'no')
424
- if clear_background:
425
- xlabel.set('clear-background', 'yes')
426
- label.label(xlabel, diagram, parent, outline_status)
427
-
428
- p = diagram.transform((x*h_scale,y_axis_location))
429
- line_el = line.mk_line((p[0], p[1]+tick_direction*ticksize[0]),
430
- (p[0], p[1]-tick_direction*ticksize[1]),
431
- diagram,
432
- user_coords=False)
433
- # line_el.set('type', 'tick on horizontal axis')
434
- g_hticks.append(line_el)
435
-
436
- x += hlabels[1]
437
-
438
- vlabels = element.get('vlabels')
439
- if vlabels is None:
440
- vlabels = find_label_positions((bbox[1], bbox[3]),
441
- pi_format = v_pi_format)
442
- else:
443
- try:
444
- vlabels = un.valid_eval(vlabels)
445
- except:
446
- log.error(f"Error in <axes> parsing vlabels={vlabels}")
447
- return
448
- if v_pi_format:
449
- vlabels = 1/math.pi * vlabels
450
-
451
- # g_vticks = ET.SubElement(axes, 'g', attrib={
452
- # 'type': 'vertical ticks'
453
- # }
454
- # )
455
- g_vticks = ET.SubElement(axes, 'g')
456
- diagram.add_id(g_vticks)
457
-
458
- v_exclude = [bbox[1], bbox[3]]
459
- if not v_zero_include:
460
- v_exclude.append(0)
461
-
462
- if vticks is not None:
463
- try:
464
- vticks = un.valid_eval(vticks)
465
- except:
466
- log.error(f"Error in <axes> parsing vticks={vticks}")
467
- return
468
- y = vticks[0]
469
- tick_direction = 1
470
- if right_labels:
471
- tick_direction = -1
472
- while y <= vticks[2]:
473
- if any([abs(y-p) < position_tolerance for p in [bbox[1], bbox[3]]]):
474
- y += vticks[1]
475
- continue
476
- p = diagram.transform((x_axis_location, y))
477
- line_el = line.mk_line((p[0]-tick_direction*ticksize[0], p[1]),
478
- (p[0]+tick_direction*ticksize[1], p[1]),
479
- diagram,
480
- user_coords=False)
481
- # line_el.set('type', 'tick on vertical axis')
482
- g_vticks.append(line_el)
483
- y += vticks[1]
484
-
485
- v_scale = 1
486
- if v_pi_format:
487
- v_scale = math.pi
488
- if decorations == 'yes' or element.get('vlabels', None) is not None:
489
- y = vlabels[0]
490
- tick_direction = 1
491
- if right_labels:
492
- tick_direction = -1
493
- while y <= vlabels[2]:
494
- if any([abs(y*v_scale-p) < position_tolerance for p in v_exclude]):
495
- y += vlabels[1]
496
- continue
497
-
498
- ylabel = ET.Element('label')
499
- math_element = ET.SubElement(ylabel, 'm')
500
- math_element.text = r'\text{'+'{0:g}'.format(y)+'}'
501
- if v_pi_format:
502
- math_element.text = get_pi_text(y)
503
- # process as a math number
504
- ylabel.set('p', '({},{})'.format(x_axis_location, y*v_scale))
505
-
506
- if diagram.output_format() == 'tactile':
507
- if right_labels:
508
- ylabel.set('alignment', 'east')
509
- ylabel.set('offset', '(25, 0)')
510
- else:
511
- ylabel.set('alignment', 'va')
512
- ylabel.set('offset', '(-25, 0)')
513
- else:
514
- if right_labels:
515
- ylabel.set('alignment', 'east')
516
- ylabel.set('offset', '(7,0)')
517
- else:
518
- ylabel.set('alignment', 'west')
519
- ylabel.set('offset', '(-7,0)')
520
-
521
- # ylabel.set('clear-background', 'no')
522
- if clear_background:
523
- ylabel.set('clear-background', 'yes')
524
- label.label(ylabel, diagram, parent, outline_status)
525
- p = diagram.transform((x_axis_location, y*v_scale))
526
- line_el = line.mk_line((p[0]-tick_direction*ticksize[0], p[1]),
527
- (p[0]+tick_direction*ticksize[1], p[1]),
528
- diagram,
529
- user_coords=False)
530
- # line_el.set('type', 'tick on vertical axis')
531
- g_vticks.append(line_el)
532
- y += vlabels[1]
533
-
534
- xlabel = element.get('xlabel')
535
- if xlabel is not None:
536
- el = ET.Element('label')
537
- math_element = ET.SubElement(el, 'm')
538
- math_element.text = xlabel
539
- el.set('clear-background', 'no')
540
- el.set('p', '({},{})'.format(bbox[2], y_axis_location))
541
- el.set('alignment', 'xl')
542
- if arrows > 0:
543
- if diagram.output_format() == 'tactile':
544
- el.set('offset', '(-6,6)')
545
- else:
546
- el.set('offset', '(-2,2)')
547
- label.label(el, diagram, parent, outline_status)
548
-
549
- ylabel = element.get('ylabel')
550
- if ylabel is not None:
551
- el = ET.Element('label')
552
- math_element = ET.SubElement(el, 'm')
553
- math_element.text = ylabel
554
- el.set('clear-background', 'no')
555
- el.set('p', '({},{})'.format(x_axis_location, bbox[3]))
556
- el.set('alignment', 'se')
557
- if arrows > 0:
558
- el.set('offset', '(2,-2)')
559
- label.label(el, diagram, parent, outline_status)
560
268
 
561
269
 
562
270
  # Adds both a grid and axes with spacings found automatically
@@ -565,49 +273,27 @@ def grid_axes(element, diagram, parent, outline_status):
565
273
  group = ET.SubElement(parent, 'g',
566
274
  attrib=
567
275
  {
568
- 'id': 'grid-axes'
276
+ 'id': 'pf__grid-axes'
569
277
  }
570
278
  )
571
279
 
572
280
  group_annotation = ET.Element('annotation')
573
- group_annotation.set('ref', 'grid-axes')
281
+ group_annotation.set('ref', 'pf__grid-axes')
574
282
  group_annotation.set('text', 'The coordinate grid and axes')
575
283
  diagram. add_default_annotation(group_annotation)
576
284
 
577
285
  grid(element, diagram, group, outline_status)
578
286
 
579
287
  annotation = ET.Element('annotation')
580
- annotation.set('ref', 'grid')
288
+ annotation.set('ref', 'pf__grid')
581
289
  annotation.set('text', 'The coordinate grid')
582
290
  group_annotation.append(annotation)
583
291
 
584
- el = ET.Element('axes')
585
- el.set('id', 'axes')
586
- if element.get('xlabel') is not None:
587
- el.set('xlabel', element.get('xlabel'))
588
- if element.get('ylabel') is not None:
589
- el.set('ylabel', element.get('ylabel'))
590
- if element.get('decorations') is not None:
591
- el.set('decorations', element.get('decorations'))
592
- if element.get('hlabels') is not None:
593
- el.set('hlabels', element.get('hlabels'))
594
- if element.get('vlabels') is not None:
595
- el.set('vlabels', element.get('vlabels'))
596
- if element.get('h-pi-format') is not None:
597
- el.set('h-pi-format', element.get('h-pi-format'))
598
- if element.get('v-pi-format') is not None:
599
- el.set('v-pi-format', element.get('v-pi-format'))
600
- if element.get('h-frame') is not None:
601
- el.set('h-frame', element.get('h-frame'))
602
- if element.get('v-frame') is not None:
603
- el.set('v-frame', element.get('v-frame'))
604
- for child in element:
605
- el.append(child)
606
-
607
- axes(el, diagram, group, outline_status)
292
+ element.set('id', 'pf__axes')
293
+ axes.axes(element, diagram, group, outline_status)
608
294
 
609
295
  annotation = ET.Element('annotation')
610
- annotation.set('ref', 'axes')
296
+ annotation.set('ref', 'pf__axes')
611
297
  annotation.set('text', 'The coordinate axes')
612
298
  group_annotation.append(annotation)
613
299