Skip to content

Releases: Soma-yu/nuc2d

Changes in 1.0.1

Choose a tag to compare

@Soma-yu Soma-yu released this 25 Sep 12:25

Version 1.0.1 is a packaging fix. The dependency floors 1.0.0 declared were too high: they were the versions this package happened to be developed against, not the oldest ones it works with. That kept it out of environments that pin numpy for reasons of their own, Google Colab among them.

pip install --upgrade nuc2d

Nothing was added, removed or renamed, and the drawing is unchanged: the same figures hash identically under 1.0.0 and 1.0.1.

Dependency floors

  1.0.0 1.0.1
numpy >=2.2 >=1.23.2
matplotlib >=3.10 >=3.6.0
fonttools >=4.65 >=4.40.0
svgwrite >=1.4,<2 unchanged

On Google Colab, pip install nuc2d resolved numpy to 2.5.3 in order to satisfy numpy>=2.2, which broke the numba already installed there:

numba 0.61.2 requires numpy<2.3,>=1.24, but you have numpy 2.5.3 which is incompatible.

Each new floor is the oldest version the test suite has actually been run against, rather than the oldest that might work. All 108 tests pass at numpy 1.23.2, matplotlib 3.6.0 and fonttools 4.40.0, and the SVG written under that combination is byte-identical to the SVG written under the newest releases of all three.

Lowering a floor only widens what can be installed, so nothing that resolved under 1.0.0 stops resolving.

Two return types in nuc2d.font

find_font_path is declared to return str, and _vertical_center_ratio to return float, but each returned whatever its library handed back, which a type checker reads as Any. An Any does not stay where it is produced: it spreads into every value derived from it, so vertical_center_offset — part of the public API, and what anyone placing a label against a drawing goes through — was unchecked at the call site.

Both now convert explicitly. The values are the same; what changes is that a project running a type checker over its own use of nuc2d is given the declared types instead of a hole.

mypy

mypy is now a development dependency, configured in pyproject.toml to run in strict mode against src. It is what found the two returns above.

disallow_any_generics is off. numpy is generic only in recent releases, so requiring type arguments would check the installed numpy rather than this code, and every generic written here already carries them.

A new front page

The README is rewritten around a single running example — a yeast tRNA-Phe cloverleaf, split into two strands — so each section builds on the one before it and shows the figure its own code produces. docs/make_images.py regenerates those figures, so a change to the drawing cannot quietly leave them behind.

It also links an introductory notebook, written in Japanese, that runs in Google Colab.

Changes in 1.0.0

Choose a tag to compare

@Soma-yu Soma-yu released this 19 Sep 21:10

Version 1.0.0 fixes the public API. From here on, the names this package exports, the arguments they take and the exceptions they raise change only in a major release. The drawing is not part of that promise: a minor release may place a nucleotide differently or write the same shape as different SVG, so a figure regenerated under a newer version can come out different.

That is the whole reason this release exists. Everything below was gathered into one version so that later ones can add rather than rename.

pip install nuc2d==1.0.0

Optional arguments are keyword-only

The arguments a call is about stay positional — the structure for draw_svg, the drawing and the structure for draw_component. Everything else is passed by name.

draw_svg("(((..+...)))", ["AUGCA", "UGCCAU"])            # 0.6.0
draw_svg("(((..+...)))", sequences=["AUGCA", "UGCCAU"])  # 1.0.0

This is the change that lets the ones after it be additions: a new argument can go where it belongs, instead of being appended to leave the existing order intact.

Every type the package defines is built by name too, with two exceptions: Vec2 and BBox, whose numbers are written out in order as in any other geometry library.

ArcEdge(start, end, EdgeType.BACKBONE, r, r, 0, 0, 1)  # 0.6.0
ArcEdge(                                               # 1.0.0
    start=start, end=end, edge_type=EdgeType.BACKBONE,
    rx=r, ry=r, x_axis_rotation=0.0, large_arc=False, sweep=True,
)

Names

Keyword-only construction makes a field name the only way to reach it, which is a reason to settle every name now.

0.6.0 1.0.0  
draw_svg(dpp_string=...) draw_svg(dot_bracket=...) Also draw_component. The argument is a structure written with (, ), . and +, and that is what the docstrings now say instead of naming a notation.
Edge.type Edge.edge_type A field name is the whole interface now, which is a reason not to leave one sharing a name with a builtin.
Marker, ArrowMarker, LayoutResult.markers Decoration, ArrowDecoration, LayoutResult.decorations SVG has a element, and this package emits one. Two different things should not share a word inside one renderer.
Nucleotide.basepair_probability Nucleotide.equilibrium_probability The field holds the probability of pairing for a paired nucleotide and of being unpaired for an unpaired one, so the old name was wrong for half of them.
DrawingStyle.node_fill DrawingStyle.node_color Beside backbone_color and basepair_color.
DrawingStyle.font_size DrawingStyle.node_font_size It sizes the base letter inside a node, not the colorbar text, which has had its own two settings all along.
DrawingStyle.colorbar_width_ratio DrawingStyle.colorbar_aspect_ratio The same number, 1/30, under a name that says what it is the ratio of: the bar's width over its height, as CSS defines an aspect ratio.
compose(container, ...) compose(group, ...) The argument is the group the composed component is returned with, and the package calls one of those a group everywhere else.

The colorbar's default label is now Equilibrium probability rather than Base-pair probability, for the same reason the field was renamed. Pass colorbar_label for anything else.

The backbone and the base pairs take their own colour

DrawingStyle.edge_color is now backbone_color and basepair_color. Of the three stroke properties an edge carries, two were already chosen per edge type — backbone_width against basepair_width, and the two dash patterns — while the colour was shared. Now all three are.

DrawingStyle(edge_color="dimgray")     # 0.6.0
DrawingStyle(                          # 1.0.0
    backbone_color="dimgray",
    basepair_color="dimgray",
)

Both default to black, so nothing drawn before changes. The arrow at a 3' terminus continues the backbone, so it takes the backbone colour.

Settings that did nothing, or belonged elsewhere

RadialLayoutEngine(pair_width=...) is gone. It set no distance between paired nucleotides; loop_spacing does, because a base pair closing a loop spans one chord of that loop's circle. Passing 5, 20 or 60 gave the same drawing.

ArrowDecoration no longer carries length or node_at_start. How long an arrow to draw is appearance, and Node already settles that kind of question by keeping its radius in DrawingStyle. The length is now DrawingStyle.three_prime_arrow_length, still 7.0.

ArrowMarker(node=node, direction=d, length=12.0)  # 0.6.0
DrawingStyle(three_prime_arrow_length=12.0)       # 1.0.0

Sizes are read from a bounding box

SVGComponent and Placement no longer carry width and height. Both still carry bbox, which reports where the component sits and, through its own width and height, how large it is.

component.width        # 0.6.0
component.bbox.width   # 1.0.0

placement.height # 0.6.0
placement.bbox.height # 1.0.0

The numbers are the same; only the spelling changes.

A box is built and moved with plain numbers

BBox no longer refers to Vec2, and BBox.from_points is gone — a caller enclosing several things unions the boxes they occupy, which is also what it will want once those things have a size of their own.

box.translated(Vec2(dx, dy))  # 0.6.0
box.translated(dx, dy)        # 1.0.0

BBox.from_points(points) # 0.6.0
reduce( # 1.0.0
BBox.union,
(BBox(p.x, p.y, p.x, p.y) for p in points),
BBox.empty(),
)

BBox.__or__ is gone with it. a | b had no call site, and | on a set holds exactly the members of both where union holds a box larger than either. The method says that in its name and its docstring; the operator had nowhere to say it.

a | b          # 0.6.0
a.union(b)     # 1.0.0

A stem carries the loop it encloses

StemRegion.child_loop is required and no longer typed LoopRegion | None. No parsed structure ever produced a stem without one — 16,195 of them across every valid structure up to length 11 — so the optional type asked callers to handle a case they could not meet, and cost the package six type errors against its own py.typed claim.

This matters only to code building a StemRegion by hand or writing a layout engine of its own.

Fixes

  • backbone_dasharray now reaches the whole backbone. A backbone runs as straight segments inside stems and as arcs around loops, and only the segments were dashed. Arcs are 87.8% of all backbone edges, so setting the field dashed an eighth of what it names and left the rest solid.
  • A drawing with no arrow no longer declares an arrowhead nothing points at.
  • A decoration the renderer does not recognise raises TypeError instead of being left out of the drawing without a word, which is what an unrecognised edge already did.
  • A colorbar-bearing drawing is about 2.5× faster. Its 101 gradient stops were looked up one at a time, and a colormap pays its setup per call rather than per value.

Known issue

Some structures do not get the arrow that marks a 3' terminus. This is a drawing defect, not an API one, and it is the first thing 1.0.1 will fix.

Compatibility

Python 3.10 through 3.14, tested on each. The package ships py.typed, so type checkers read its annotations directly.

Changes in 0.6.0

Choose a tag to compare

@Soma-yu Soma-yu released this 18 Sep 16:31

draw_svg and draw_component accept add_colorbar=False, which colors the
nucleotides from probs but leaves the colorbar out. It is for placing a
colorbar of your own with the newly exported render_colorbar: the one these
functions place is as tall as the structure, which leaves it small beside a
structure much wider than it is tall.

Nothing else changes, and neither does the output unless add_colorbar is used.

Changes in 0.5.0

Choose a tag to compare

@Soma-yu Soma-yu released this 18 Sep 16:30

Version 0.5.0 rejects two kinds of string that earlier versions drew.

  • Strands that no base pair connects, such as ...+... or ((...))+((...)),
    raise ParseError. A secondary structure describes one complex, and
    strands nothing holds together are separate molecules that happen to share
    a string.
  • Hairpin loops of fewer than three nucleotides, such as (..), raise
    ParseError. A backbone cannot turn back on itself in fewer, which is the
    same minimum structure prediction tools impose.

Structures that came from a prediction tool are unaffected: neither shape can
occur in one.

Changes in 0.4.0

Choose a tag to compare

@Soma-yu Soma-yu released this 18 Sep 16:29

Version 0.4.0 changes the public API. Existing code written against 0.3.0 needs
the following adjustments.

  • draw_group is now draw_component. It returns a single SVGComponent
    instead of a (Group, BoundingBox) tuple; use component.group and
    component.bbox.
  • BoundingBox(xmin, ymin, width, height) is now BBox(xmin, ymin, xmax, ymax), with width and height as derived properties. A component's
    bounding box now reports where the component actually sits, instead of
    always starting at the origin.
  • draw_svg and draw_component accept layout_engine and colorbar_label.
    On draw_svg these come before width_px and height_px, so any call that
    passes those two positionally needs updating.
  • Malformed structures raise ParseError, and sequences or probability
    matrices that do not match the structure raise ValueError. Both previously
    surfaced as IndexError, or as a silently wrong drawing.
  • DrawingStyle.colorbar_spacing is gone; it never affected the output.

v0.2.0

Choose a tag to compare

@Soma-yu Soma-yu released this 17 Aug 12:16

What's changed

  • Added sequence annotation
  • Added base-pair probability visualization
  • Improved SVG rendering
  • Updated drawing styles
  • Updated documentation