scistackplot 0.1.39__tar.gz → 0.1.40__tar.gz

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 (48) hide show
  1. {scistackplot-0.1.39 → scistackplot-0.1.40}/PKG-INFO +2 -2
  2. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/codegen.py +5 -4
  3. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/diffbars.py +93 -16
  4. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/render/mpl.py +6 -5
  5. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/render/plotly_.py +2 -2
  6. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/weights.py +17 -0
  7. {scistackplot-0.1.39 → scistackplot-0.1.40}/.gitignore +0 -0
  8. {scistackplot-0.1.39 → scistackplot-0.1.40}/README.md +0 -0
  9. {scistackplot-0.1.39 → scistackplot-0.1.40}/pyproject.toml +0 -0
  10. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/__init__.py +0 -0
  11. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/aliases.py +0 -0
  12. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/autosize.py +0 -0
  13. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/capability.py +0 -0
  14. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/cell.py +0 -0
  15. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/colors.py +0 -0
  16. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/compare.py +0 -0
  17. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/custom.py +0 -0
  18. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/dedup.py +0 -0
  19. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/export.py +0 -0
  20. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/figsize.py +0 -0
  21. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/figure_file.py +0 -0
  22. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/framesize.py +0 -0
  23. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/groups.py +0 -0
  24. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/numeric.py +0 -0
  25. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/panels.py +0 -0
  26. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/paper.py +0 -0
  27. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/presets.py +0 -0
  28. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/reduce.py +0 -0
  29. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/reducer.py +0 -0
  30. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/render/__init__.py +0 -0
  31. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/render/base.py +0 -0
  32. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/resolved.py +0 -0
  33. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/restore.py +0 -0
  34. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/roles.py +0 -0
  35. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/series_stats.py +0 -0
  36. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/shape.py +0 -0
  37. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/sources/__init__.py +0 -0
  38. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/sources/base.py +0 -0
  39. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/sources/csv.py +0 -0
  40. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/sources/frame.py +0 -0
  41. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/spaghetti.py +0 -0
  42. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/spec.py +0 -0
  43. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/table.py +0 -0
  44. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/textsize.py +0 -0
  45. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/ticklabels.py +0 -0
  46. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/variants.py +0 -0
  47. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/xaxis.py +0 -0
  48. {scistackplot-0.1.39 → scistackplot-0.1.40}/src/scistackplot/ylimits.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scistackplot
3
- Version: 0.1.39
3
+ Version: 0.1.40
4
4
  Summary: Spec-driven plotting for long-format scientific data — standalone, GUI-friendly
5
5
  Author: SciStack Contributors
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: numpy>=1.21
21
21
  Requires-Dist: pandas>=1.5
22
- Requires-Dist: scistacklog==0.1.39
22
+ Requires-Dist: scistacklog==0.1.40
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: matplotlib>=3.6; extra == 'dev'
25
25
  Requires-Dist: plotly>=5.0; extra == 'dev'
@@ -64,7 +64,7 @@ from .spec import (
64
64
  from .table import MISSING_LEVEL, LongTable
65
65
  from .paper import PAPER, figure_rc_params, paper_axes_code, seaborn_err_kws
66
66
  from .textsize import ResolvedSizes, resolve_sizes
67
- from .weights import sample_weight, spaghetti_weight
67
+ from .weights import SAMPLE_EDGE_WIDTH, sample_weight, spaghetti_weight
68
68
  from .ylimits import GLOBAL_KEY, pinned_ends
69
69
  from .variants import (
70
70
  LATEST,
@@ -1309,7 +1309,7 @@ def _sample_draw_lines(
1309
1309
  ),
1310
1310
  f" _ax.scatter(_part[{_X_POSITION!r}], _part[{y!r}], "
1311
1311
  f"s={weight.marker_area:.3f}, color=_point_colors, alpha={SAMPLE_ALPHA}, "
1312
- f"edgecolors={SAMPLE_EDGE_COLOR!r}, linewidths=0.5, "
1312
+ f"edgecolors={SAMPLE_EDGE_COLOR!r}, linewidths={SAMPLE_EDGE_WIDTH:g}, "
1313
1313
  + (
1314
1314
  # Over the line, and marked as part of its run
1315
1315
  # (render.base.SAMPLE_LINE_POINTS_GID), as the preview.
@@ -1325,7 +1325,8 @@ def _sample_draw_lines(
1325
1325
  " _ax.plot(",
1326
1326
  f" _part[{_X_POSITION!r}], _part[{y!r}],",
1327
1327
  f" linestyle={linestyle!r}, marker='o', markersize={weight.marker_pt:.3f},",
1328
- f" markeredgecolor={SAMPLE_EDGE_COLOR!r}, markeredgewidth=0.5,",
1328
+ f" markeredgecolor={SAMPLE_EDGE_COLOR!r}, "
1329
+ f"markeredgewidth={SAMPLE_EDGE_WIDTH:g},",
1329
1330
  f" color={paint}, alpha={SAMPLE_ALPHA}, "
1330
1331
  f"linewidth={weight.line_pt:.3f}, zorder=3,",
1331
1332
  " )",
@@ -1363,7 +1364,7 @@ def _sample_legend_lines(
1363
1364
  " for _lvl in _sample_levels:",
1364
1365
  " _entries[_lvl] = Line2D([], [], color=_sample_palette[_lvl], "
1365
1366
  f"linestyle={linestyle!r}, marker='o', markeredgecolor={SAMPLE_EDGE_COLOR!r}, "
1366
- "markeredgewidth=0.5)",
1367
+ f"markeredgewidth={SAMPLE_EDGE_WIDTH:g})",
1367
1368
  f" g.add_legend(legend_data=_entries, title={title!r})",
1368
1369
  ]
1369
1370
 
@@ -428,8 +428,15 @@ def log_figure(resolved: "ResolvedPlot") -> None:
428
428
  # bars need depends on ``u``, which depends on the top, so the top is found
429
429
  # by iterating to a fixed point (:func:`place_group`).
430
430
 
431
- #: Clearance between whatever is below and a bar's line (or a leg's foot).
431
+ #: Clearance between whatever is below and a bar's line.
432
432
  GAP_PT = 3.0
433
+ #: Clearance between the data under an end and that leg's foot (2026-10-16:
434
+ #: at GAP_PT the leg read as touching the data). Larger than GAP_PT: the line
435
+ #: only spans, a leg points AT a mark.
436
+ FOOT_GAP_PT = 6.0
437
+ #: The shortest a leg is drawn, from its foot to the line (2026-10-16:
438
+ #: before, the leg over the taller end was half a line width — no leg).
439
+ MIN_LEG_PT = 5.0
433
440
  #: A bar's line width.
434
441
  LINE_PT = 1.0
435
442
  #: Between the line and the bottom of its label's box.
@@ -560,6 +567,8 @@ class BarSizes:
560
567
  #: The label's font size (``TextSizes.differences``).
561
568
  label_font_pt: float
562
569
  gap_pt: float = GAP_PT
570
+ foot_gap_pt: float = FOOT_GAP_PT
571
+ min_leg_pt: float = MIN_LEG_PT
563
572
  line_pt: float = LINE_PT
564
573
  label_gap_pt: float = LABEL_GAP_PT
565
574
  pad_pt: float = PAD_PT
@@ -703,19 +712,31 @@ def _place_once(
703
712
  floors.append(under_label - above_line)
704
713
  floor = max(floors) if floors else bottom
705
714
  y = floor + (sizes.gap_pt + half_line) / per_y
715
+
716
+ # Each leg stops ``foot_gap_pt`` above what is under its end, so it
717
+ # never reads as touching the data. The line rises until every leg is
718
+ # at least ``min_leg_pt`` long (the end under the span's highest mark,
719
+ # or a lower bar's level, would otherwise get no visible leg). Both in
720
+ # points, so neither changes with the y range.
721
+ min_leg = sizes.min_leg_pt / per_y
722
+ foot_gap = sizes.foot_gap_pt / per_y
723
+ # A leg is a line: what it must clear is under its whole width, not
724
+ # its centre (a marker 3.01 pt off a leg of half-width 0.5 pt with a
725
+ # 3.5 pt reach was missed at the centre and touched, 2026-10-16).
726
+ half_leg = half_line / per_x
727
+ unders = [height(x - half_leg, x + half_leg) for x in (left, right)]
728
+ raised = [under + foot_gap + min_leg for under in unders if under is not None]
729
+ if raised:
730
+ y = max(y, max(raised))
731
+ feet = [
732
+ under + foot_gap
733
+ if under is not None
734
+ else max(bottom, y - max(2 * sizes.gap_pt / per_y, min_leg))
735
+ for under in unders
736
+ ]
706
737
  label_bottom = y + above_line
707
738
  label_top = label_bottom + height_pt / per_y
708
739
 
709
- feet = []
710
- for x in (left, right):
711
- under = height(x, x)
712
- foot = (
713
- min(y, under + sizes.gap_pt / per_y)
714
- if under is not None
715
- else max(bottom, y - 2 * sizes.gap_pt / per_y)
716
- )
717
- feet.append(foot)
718
-
719
740
  if pinned and label_top > top:
720
741
  unfit.append(item)
721
742
  continue
@@ -842,6 +863,59 @@ def place_group(
842
863
  )
843
864
 
844
865
 
866
+ def leg_lengths_pt(
867
+ result: GroupPlacement, frames: list[PanelFrame], log: bool = False
868
+ ) -> list[float]:
869
+ """Every placed leg's length in points on the final axis — what
870
+ ``MIN_LEG_PT`` guarantees (unless a leg reaches the axis bottom)."""
871
+ axis = (lambda v: math.log10(v)) if log else (lambda v: v)
872
+ bottom, top = axis(result.limits[0]), axis(result.limits[1])
873
+ lengths = []
874
+ for placed, frame in zip(result.placed, frames):
875
+ per_y = frame.height_pt / (top - bottom) if top > bottom else 0.0
876
+ for bar in placed:
877
+ for foot in (bar.left_foot, bar.right_foot):
878
+ lengths.append((axis(bar.y) - axis(foot)) * per_y)
879
+ return lengths
880
+
881
+
882
+ def foot_clearances_pt(
883
+ result: GroupPlacement, problems: list[PanelProblem], log: bool = False
884
+ ) -> list[float]:
885
+ """Every placed foot's height above the data under it, in points on the
886
+ final axis — what ``FOOT_GAP_PT`` guarantees. *problems* hold the data's
887
+ obstacles only (no bars), so a foot resting over a lower bar reports
888
+ its height above the data, which is more than the gap. A foot with no
889
+ data under it is left out."""
890
+ axis = (lambda v: math.log10(v)) if log else (lambda v: v)
891
+ bottom, top = axis(result.limits[0]), axis(result.limits[1])
892
+ clearances = []
893
+ for placed, problem in zip(result.placed, problems):
894
+ if top <= bottom:
895
+ continue
896
+ per_y = problem.frame.height_pt / (top - bottom)
897
+ per_x = problem.frame.per_unit_x
898
+ obstacles = problem.obstacles.to_axis(log)
899
+ half_leg = (LINE_PT / 2.0) / per_x
900
+ for bar in placed:
901
+ for x, foot in ((bar.left, bar.left_foot), (bar.right, bar.right_foot)):
902
+ under = obstacles.top_over(
903
+ x - half_leg, x + half_leg, per_unit_y=per_y, per_unit_x=per_x
904
+ )
905
+ if under is not None:
906
+ clearances.append((axis(foot) - under) * per_y)
907
+ return clearances
908
+
909
+
910
+ def _legs_note(result: GroupPlacement, problems: list[PanelProblem], log: bool) -> str:
911
+ lengths = leg_lengths_pt(result, [p.frame for p in problems], log)
912
+ clearances = foot_clearances_pt(result, problems, log)
913
+ note = f", shortest leg {min(lengths):.1f} pt" if lengths else ""
914
+ if clearances:
915
+ note += f", lowest foot {min(clearances):.1f} pt above the data"
916
+ return note
917
+
918
+
845
919
  # ---------------------------------------------------------------------------
846
920
  # A resolved figure -> problems
847
921
 
@@ -859,9 +933,10 @@ def panel_obstacles(resolved: "ResolvedPlot", panel: "Panel") -> Obstacles:
859
933
  ``spanmode: "hard"`` stop at the data); a box's flier adds its radius;
860
934
  - strip: every point, jittered ``±STRIP_JITTER``, plus the marker radius;
861
935
  - scatter: every point plus the marker radius;
862
- - spaghetti: every point at its line's offset plus the marker radius,
936
+ - spaghetti: every point at its line's offset plus the marker's reach
937
+ (radius + half its edge stroke, ``weights.ResolvedWeight.marker_reach_pt``),
863
938
  and every segment of every run plus half the line width;
864
- - overlay: every point at ``sample_positions`` plus its radius, and the
939
+ - overlay: every point at ``sample_positions`` plus its reach, and the
865
940
  joining segments when joined.
866
941
  """
867
942
  from .render.base import (
@@ -918,7 +993,7 @@ def panel_obstacles(resolved: "ResolvedPlot", panel: "Panel") -> Obstacles:
918
993
  xs, _ = x_positions(run[encoding.x], resolved)
919
994
  xs = xs + offsets.get(str(series_id), 0.0)
920
995
  ys = pd.to_numeric(run[encoding.y], errors="coerce").to_numpy(dtype=float)
921
- rows.extend(_polyline(xs, ys, weight.marker_pt / 2.0, weight.line_pt / 2.0, True))
996
+ rows.extend(_polyline(xs, ys, weight.marker_reach_pt, weight.line_pt / 2.0, True))
922
997
  else: # scatter, and anything else drawn as points
923
998
  rows.extend((p, p, v, v, marker_radius) for p, v in zip(positions, values))
924
999
 
@@ -929,7 +1004,7 @@ def panel_obstacles(resolved: "ResolvedPlot", panel: "Panel") -> Obstacles:
929
1004
  xs = sample_positions(run.rows, resolved, run.identity)
930
1005
  ys = pd.to_numeric(run.rows[encoding.y], errors="coerce").to_numpy(dtype=float)
931
1006
  joined = resolved.sample_join and len(run.rows) > 1
932
- rows.extend(_polyline(xs, ys, weight.marker_pt / 2.0, weight.line_pt / 2.0, joined))
1007
+ rows.extend(_polyline(xs, ys, weight.marker_reach_pt, weight.line_pt / 2.0, joined))
933
1008
  return Obstacles.build(rows)
934
1009
 
935
1010
 
@@ -1106,7 +1181,9 @@ def place_figure(
1106
1181
  notes.append(
1107
1182
  f"{names}: {sum(len(p) for p in result.placed)} placed, {n_unfit} unfit, "
1108
1183
  f"top {shared[1]:.6g} -> {result.limits[1]:.6g} "
1109
- f"({result.iterations} iteration(s), bars {result.bar_fraction:.0%} of the height)"
1184
+ f"({result.iterations} iteration(s), bars {result.bar_fraction:.0%} of the height"
1185
+ + _legs_note(result, problems, log)
1186
+ + ")"
1110
1187
  + (f", raised to at least {top_floor:.6g} for other figures" if top_floor is not None else "")
1111
1188
  )
1112
1189
  Log.debug(
@@ -25,7 +25,7 @@ from ..spec import PlotKind
25
25
  from ..table import natural_sort_key
26
26
  from ..paper import PAPER, apply_paper_axes, describe_paper, figure_rc_params
27
27
  from ..textsize import ResolvedSizes, sizes_for
28
- from ..weights import describe_weights, sample_weight, spaghetti_weight
28
+ from ..weights import SAMPLE_EDGE_WIDTH, describe_weights, sample_weight, spaghetti_weight
29
29
  from ..ticklabels import (
30
30
  BRACKET_POLICY,
31
31
  TICK_POLICY,
@@ -396,6 +396,7 @@ def _draw_spaghetti(ax, frame, resolved) -> None:
396
396
  # A diameter in pt (weights.spaghetti_weight): at 1x the same
397
397
  # visual size as the scatter kinds' `s` area.
398
398
  markersize=weight.marker_pt,
399
+ markeredgewidth=weight.marker_edge_pt,
399
400
  label=(
400
401
  resolved.text.color_level(level)
401
402
  if (level is not None and position == 0)
@@ -629,7 +630,7 @@ def _draw_sample(ax, panel, resolved: ResolvedPlot) -> tuple[int, int]:
629
630
  marker="o",
630
631
  markersize=weight.marker_pt,
631
632
  markeredgecolor=SAMPLE_EDGE_COLOR,
632
- markeredgewidth=0.5,
633
+ markeredgewidth=weight.marker_edge_pt,
633
634
  zorder=3,
634
635
  )
635
636
  continue
@@ -651,7 +652,7 @@ def _draw_sample(ax, panel, resolved: ResolvedPlot) -> tuple[int, int]:
651
652
  color=[run.point_colors[i] for i in order],
652
653
  alpha=SAMPLE_ALPHA,
653
654
  edgecolors=SAMPLE_EDGE_COLOR,
654
- linewidths=0.5,
655
+ linewidths=weight.marker_edge_pt,
655
656
  zorder=3.1,
656
657
  gid=SAMPLE_LINE_POINTS_GID,
657
658
  )
@@ -663,7 +664,7 @@ def _draw_sample(ax, panel, resolved: ResolvedPlot) -> tuple[int, int]:
663
664
  color=color,
664
665
  alpha=SAMPLE_ALPHA,
665
666
  edgecolors=SAMPLE_EDGE_COLOR,
666
- linewidths=0.5,
667
+ linewidths=weight.marker_edge_pt,
667
668
  zorder=3,
668
669
  )
669
670
  artists += len(lone)
@@ -687,7 +688,7 @@ def _sample_legend_handles(ax, resolved: ResolvedPlot) -> None:
687
688
  linestyle="-" if resolved.sample_join else "none",
688
689
  marker="o",
689
690
  markeredgecolor=SAMPLE_EDGE_COLOR,
690
- markeredgewidth=0.5,
691
+ markeredgewidth=SAMPLE_EDGE_WIDTH,
691
692
  label=resolved.text.sample_level(level),
692
693
  )
693
694
 
@@ -43,7 +43,7 @@ from ..diffbars import (
43
43
  sibling_floors,
44
44
  )
45
45
  from ..textsize import sizes_for
46
- from ..weights import mark_weights_meta, sample_weight, spaghetti_weight
46
+ from ..weights import SAMPLE_EDGE_WIDTH, mark_weights_meta, sample_weight, spaghetti_weight
47
47
  from ..xaxis import LEAF_SEPARATOR
48
48
  from .base import (
49
49
  MARK_OFFSET_GROUP,
@@ -758,7 +758,7 @@ def _sample_traces(
758
758
  if run.uniform
759
759
  else [run.point_colors[i] for i in order],
760
760
  "size": weight.marker_px,
761
- "line": {"color": SAMPLE_EDGE_COLOR, "width": 0.5},
761
+ "line": {"color": SAMPLE_EDGE_COLOR, "width": SAMPLE_EDGE_WIDTH},
762
762
  },
763
763
  "line": {"color": run.line_color, "width": weight.line_pt},
764
764
  "opacity": SAMPLE_ALPHA,
@@ -29,6 +29,14 @@ SAMPLE_MARKER_FRACTION = 0.45
29
29
  SAMPLE_LINE_WIDTH = 1.0
30
30
  #: A spaghetti's own polylines at 1x.
31
31
  SPAGHETTI_LINE_WIDTH = 1.2
32
+ #: The edge stroke round a marker, in points, at every weight (it outlines;
33
+ #: it does not scale). It widens the marker by half its width on each side,
34
+ #: which ``diffbars.panel_obstacles`` must count (2026-10-16: a leg touched a
35
+ #: spaghetti marker the obstacle model thought it cleared).
36
+ SAMPLE_EDGE_WIDTH = 0.5
37
+ #: matplotlib's ``lines.markeredgewidth`` default, which the spaghetti drew
38
+ #: before it was stated here.
39
+ SPAGHETTI_EDGE_WIDTH = 1.0
32
40
  #: The plotly preview draws a marker of ``d`` pt at ``d x 4/3`` px — the
33
41
  #: 96/72 pt-to-px ratio. Pinned so 1x reproduces the preview as it was (8 px
34
42
  #: markers where matplotlib draws 6 pt). Lines are 1 pt = 1 px, as before.
@@ -54,6 +62,13 @@ class ResolvedWeight:
54
62
  marker_pt: float
55
63
  #: Line width in points (and px in the preview).
56
64
  line_pt: float
65
+ #: The marker's edge stroke in points (``markeredgewidth``).
66
+ marker_edge_pt: float
67
+
68
+ @property
69
+ def marker_reach_pt(self) -> float:
70
+ """How far a marker reaches from its centre, edge included."""
71
+ return self.marker_pt / 2.0 + self.marker_edge_pt / 2.0
57
72
 
58
73
  @property
59
74
  def marker_area(self) -> float:
@@ -83,6 +98,7 @@ def sample_weight(style: StyleOptions) -> ResolvedWeight:
83
98
  weight=w,
84
99
  marker_pt=math.sqrt(style.marker_size * SAMPLE_MARKER_FRACTION) * w,
85
100
  line_pt=SAMPLE_LINE_WIDTH * w,
101
+ marker_edge_pt=SAMPLE_EDGE_WIDTH,
86
102
  )
87
103
 
88
104
 
@@ -94,6 +110,7 @@ def spaghetti_weight(style: StyleOptions) -> ResolvedWeight:
94
110
  weight=w,
95
111
  marker_pt=math.sqrt(style.marker_size) * w,
96
112
  line_pt=SPAGHETTI_LINE_WIDTH * w,
113
+ marker_edge_pt=SPAGHETTI_EDGE_WIDTH,
97
114
  )
98
115
 
99
116
 
File without changes
File without changes