Releases: Soma-yu/nuc2d
Release list
Changes in 1.0.1
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
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_dasharraynow 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
TypeErrorinstead 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
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
Version 0.5.0 rejects two kinds of string that earlier versions drew.
- Strands that no base pair connects, such as
...+...or((...))+((...)),
raiseParseError. 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
Version 0.4.0 changes the public API. Existing code written against 0.3.0 needs
the following adjustments.
draw_groupis nowdraw_component. It returns a singleSVGComponent
instead of a(Group, BoundingBox)tuple; usecomponent.groupand
component.bbox.BoundingBox(xmin, ymin, width, height)is nowBBox(xmin, ymin, xmax, ymax), withwidthandheightas derived properties. A component's
bounding box now reports where the component actually sits, instead of
always starting at the origin.draw_svganddraw_componentacceptlayout_engineandcolorbar_label.
Ondraw_svgthese come beforewidth_pxandheight_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 raiseValueError. Both previously
surfaced asIndexError, or as a silently wrong drawing. DrawingStyle.colorbar_spacingis gone; it never affected the output.