coord

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package coord maps scaled positions into device space.

A scale maps a data value into an interval; a coord decides what that interval means. Cartesian says it is a distance along an edge of the panel and is the identity, so every geom draws what it always drew. Polar says one of the two intervals is an angle and the other a radius, and the same geoms then draw a pie, a donut, a radar, a rose or a gauge.

That is the whole of it: the coord is a stage between the scales and the IR, and neither end changes. github.com/timzifer/figure/scale.Scale is untouched — what used to be Cartesian was only that render passed the panel rectangle's edges as the interval — and the IR is untouched too, because an arc is cubics and github.com/timzifer/figure/ir.Path has always had those. See docs/adr/0018-coordinate-systems.md.

What a coord does not do

It does not paint. Coord.Furniture reports where a panel's grid lines, axis lines and tick labels go and render strokes them, because render is the only package that knows the drawing order of a chart. A coord that drew its own rings would be a second drawing order.

It does not own a panel either. A coord belongs to a chart, and Coord.Frame hands back the coord positioned in one panel's rectangle rather than moving the receiver into it — panels are built concurrently, and a coord that remembered which panel it was in would be a data race.

Index

Constants

View Source
const FullTurn = 2 * math.Pi

FullTurn is the default sweep: a whole circle.

Variables

View Source
var ErrUnknownType = errors.New("figure/coord: unknown coord type")

ErrUnknownType reports a Desc naming a coord this package does not have and nobody registered.

Functions

func OppositeFurniture

func OppositeFurniture(cd Coord, dst *Furniture, req FurnitureRequest) bool

OppositeFurniture fills dst with cd's second axes, reporting whether cd has any to place.

It is the type assertion written once, so that a caller asks the question rather than knowing which coords answer it. Which edges are meant is in the request: YTicks for the right-hand one, XTicks for the top.

func Register

func Register(t Type, build func(Desc) (Coord, error))

Register makes a coordinate system this package does not define buildable by FromDesc, and therefore readable from a JSON spec.

The type is the one the coord's Describer reports, and the one a document carries as the coord's type. A type this package owns — every Type constant — is refused with a panic, because shadowing a built-in would change what every existing document means. Registering a type twice replaces the earlier builder.

A registered coord is rebuilt from the fields of Desc, which are the polar coord's. A coordinate system configured by something else — a projection's centre, say — round-trips its type and no more until the spec grows a place for it, which is additive.

Nothing iterates the registry and lookups are guarded by a lock, so registering from an init function is safe and registration order never changes what a chart draws.

func SmithZ

func SmithZ(re, im float64) (r, x float64)

SmithZ converts a reflection coefficient into the normalised impedance a Smith panel plots, which is the inverse of the map the coord applies:

z = (1 + Γ) / (1 − Γ)

It lives here rather than in a data package because it is the same three lines of arithmetic [smith.Invert] already needs, and having one copy is what keeps a tooltip and a column agreeing. A Γ of exactly 1 is an open circuit and has no finite impedance; both results are +Inf, which every scale treats as missing.

r, x := coord.SmithZ(re, im)    // the impedance chart's pair
g, b := coord.SmithZ(-re, -im)  // the admittance chart's, from the same Γ

Types

type Axis

type Axis uint8

Axis names one of a panel's two scales.

const (
	// FromX sweeps the X scale around the circle and reads Y as the radius.
	FromX Axis = iota
	// FromY sweeps the Y scale around the circle and reads X as the radius.
	FromY
)

The axes, as Theta reads them.

type Coord

type Coord interface {
	// Frame gives the coord a panel rectangle and that panel's scales, sets
	// the interval each scale maps into, and returns the coord positioned in
	// the rectangle. Cartesian sets the rectangle's edges; Polar sets an angle
	// range and a radius range.
	//
	// The receiver is not modified: the returned value is what the panel's
	// geoms are handed, so two panels drawn on two goroutines never share one
	// position.
	Frame(f Framing) Coord

	// Extent reports the interval each scale maps into — what Frame chose.
	// A mark that spans a whole axis needs it: the far end of a rule is where
	// the scale ends, and under a polar coord that is not where the rectangle
	// does.
	Extent() (x0, x1, y0, y1 float32)

	// Point turns one mapped pair into a device point.
	Point(x, y float32) ir.Point

	// Points is the batch form, and the one a geom on the hot path calls. It
	// appends into dst, which the caller owns and reuses between frames.
	//
	// It exists so that a per-row interface call does not reappear here: a
	// variadic or per-row method on this interface is the shape that cost a
	// million allocations on a million-row column once already.
	Points(dst []ir.Point, xs, ys []float32) []ir.Point

	// Straight reports whether an edge that is straight in data space is also
	// straight on screen. It is true for Cartesian and for a polar coord asked
	// for chords; it is false for one drawing arcs, where a geom has to build
	// a path instead of a polyline.
	Straight() bool

	// Edge appends the device path of an edge that is straight in data space,
	// continuing from p's current point. Cartesian appends one LineTo; Polar
	// appends the cubics of an arc.
	Edge(p *ir.Path, from, to ir.Point)

	// Area appends the closed device path of a data-space rectangle, given as
	// two mapped pairs. Cartesian appends four corners; Polar appends an
	// annular sector.
	Area(p *ir.Path, x0, y0, x1, y1 float32)

	// Clip appends the path a panel's data is clipped to: the rectangle, or
	// the disc inscribed in it.
	Clip(p *ir.Path, area ir.Rect)

	// Invert turns a device point back into a mapped pair, which is what a
	// tooltip needs before it asks the scales what the values were.
	Invert(pt ir.Point) (x, y float32)

	// Furniture reports the geometry of the grid lines, the axis lines and the
	// tick labels of one panel, one entry per tick in tick order. It fills dst
	// rather than returning a value so that a chart redrawn every frame does
	// not pay for its furniture again; render resets and reuses one.
	Furniture(dst *Furniture, req FurnitureRequest)

	// Decimates reports whether a reduction defined over pixel columns still
	// measures what it was defined to measure under this coord.
	//
	// It is true for Cartesian, where [github.com/timzifer/figure/stat.LTTB]
	// and MinMax bucket by a column of screen and that is exactly the unit
	// they were designed in. It is false for Polar, where a bucket of equal
	// angle is not a bucket of equal width — and nothing polar is a big-data
	// chart, so saying no costs nothing. See docs/adr/0011-decimation.md.
	Decimates() bool
}

Coord turns a pair of mapped positions into a device point, and reports the geometry of everything else that depends on what the pair means.

Implementing one is how a coordinate system is added. The set of methods is wider than a transform because a coordinate system is wider than a transform: what a straight edge is, what a data-space rectangle is, what a panel clips to, and where a grid line runs are all answers only the coord has.

Stability

Coord is implemented outside this module, so it never gains a method. What a coordinate system can additionally do — break a mark out of its middle, say what it is — is an optional interface beside it, as Exploder and Describer are, and a projection's own needs arrive the same way. A coord this package does not define is read back through Register.

func Cartesian

func Cartesian() Coord

Cartesian is the identity coord, and the default.

Its Point returns the pair it was given, its Edge is one LineTo and its Area is four corners — so a chart that never heard of this package draws exactly what it drew before there was one, and the golden files in the repository are the proof.

func Donut

func Donut(hole float64, opts ...PolarOption) Coord

Donut is Pie with a hole of the given fraction: the same chart with its middle left empty.

figure.Coord(coord.Donut(0.45))

The hole is where the radial scale starts rather than a disc of background painted over the middle, so nothing is drawn inside it and a pointer in it hits nothing — see Hole. A donut whose slices name their own inner and outer radius is that hole plus github.com/timzifer/figure/geom.X2: the radial axis is a dimension like any other, and the ring is only the slot a slice fills when the row does not say.

func FromDesc

func FromDesc(d Desc) (Coord, error)

FromDesc rebuilds a coord from its description. A type this package does not define is built by whoever registered it — see Register — and one nobody did is ErrUnknownType.

func Pie

func Pie(opts ...PolarOption) Coord

Pie is Polar with the angle taken from the Y axis: the recipe above, named.

It is sugar and nothing else — `Pie()` is `Polar(Theta(FromY))` — but it is the spelling that says what the chart is, and the one place the two things a pie needs beyond the coord can be written down where a reader will look for them. Those two are the layer and the scales:

p := figure.New(figure.Coord(coord.Pie()))
p.X(scale.Linear()) // one slot, filling the radius
p.Y(scale.Linear()) // the stacked total, filling the circle
p.Add(geom.Bar(src, geom.X("one"), geom.Y("share"), geom.GroupBy("browser")))

Neither scale is niced, and that is not an oversight: a niced angular domain rounds the total up and leaves a wedge of nothing at twelve o'clock.

Further options are applied after the default, so a half pie is `Pie(Sweep(math.Pi))` and a pie that turns the other way is `Pie(Counterclockwise(true))`.

func Polar

func Polar(opts ...PolarOption) Coord

Polar wraps one axis around a circle and reads the other as a radius.

It is what turns the marks that already exist into the family of charts that did not: a pie and a donut are a stacked github.com/timzifer/figure/geom.Bar with θ from the Y axis, a rose is the same bar with θ from an ordinal X, a radar is a github.com/timzifer/figure/geom.Line over one, and a gauge is a bar over a partial sweep. None of them is a new geom, which is the whole point of the stage.

Which axis is the angle

By default X sweeps the circle and Y is the radius, which is what a rose, a wind rose and a radar want: the category or the direction goes round and the magnitude goes out. Theta swaps them, which is what a pie wants: the value goes round and the single slot goes out.

A pie

p := figure.New(figure.Coord(coord.Polar(coord.Theta(coord.FromY))))
p.X(scale.Linear())   // one slot, filling the radius
p.Y(scale.Linear())   // the stacked total, filling the circle
p.Add(geom.Bar(src, geom.X("one"), geom.Y("share"), geom.GroupBy("browser")))

The ring closes into a full circle because a stacked Y domain ends at the total and starts at zero — so the angular scale must not be niced. A niced domain rounds the total up and leaves a wedge of nothing at twelve o'clock, which is why the recipe above spells scale.Linear without scale.Nice. Hole turns the pie into a donut.

Edges

An edge between two marks is an arc by default, because that is the edge that is straight in data space and it is what a rose petal and a polar band need. A radar is the exception — its sides are chords, and drawing them as arcs bows them outwards — so a radar asks for Chord.

func Smith

func Smith(opts ...SmithOption) Coord

Smith reads a panel's two axes as a complex impedance and places it on the unit disc, which is the chart every RF, microwave and antenna engineer works on and no general-purpose plotting library draws.

X is the normalised resistance r = R/Z₀ and Y the normalised reactance x = X/Z₀. The coord maps the pair through the reflection coefficient

Γ = (z − 1) / (z + 1),  z = r + jx

which carries the whole right half-plane — every passive impedance there is, including the infinite ones — into a disc a finger wide. That is the entire trick of the chart: a quantity with no bounds gets a picture with edges.

Why this is a coord and not a mark

A Smith chart's grid is two families of curves — a circle for each constant resistance, an arc for each constant reactance — and under Γ they are exactly the images of the two axes' own grid lines. So the grid is not drawn by anything: it is what the panel's X and Y ticks look like once the coord has had them, in the same way a polar coord turns a Y tick into a ring. Nothing in render changes, and no geom knows this chart exists.

p := figure.New(figure.Coord(coord.Smith()))
p.X(scale.Linear(scale.Domain(0, 20), scale.TickValues(0, 0.2, 0.5, 1, 2, 5)))
p.Y(scale.Linear(scale.Domain(-20, 20), scale.TickValues(-5, -2, -1, -0.5, -0.2, 0.2, 0.5, 1, 2, 5)))
p.Add(geom.Line(sweep, geom.X("r"), geom.Y("x")))

scale.TickValues is what asks for the grid a paper chart is printed with; an axis left to choose its own ticks draws a perfectly correct Smith chart with unfamiliar circles on it. The domains are pinned rather than trained because the chart's extent is the whole disc whatever the data does — a near-open reflection is an r in the thousands, and an axis that autoscaled to it would put every tick in the last pixel before the rim.

What the interval means

[Frame] gives each scale the range its own domain already is, so a linear scale's Map is the identity and the pair reaching [Smith.Point] is the impedance itself. That is this coord's answer to the question every coord answers — Cartesian says the interval is a distance along an edge, Polar says it is an angle and a radius, and Smith says it is the impedance — and it is why an axis here wants a linear scale. A log or symlog axis under this coord is not broken, it is a different chart, and the coord does not guess which one was meant.

A consequence worth knowing: a zoom moves nothing. scale.Zoomer.SetDomain changes the domain, the next Frame changes the range to match, and every point stays where it was. That is right — the disc is always the whole picture — but it means a Smith panel is not pannable, only relabelled.

A measured sweep

A vector network analyser reports S₁₁ as a reflection coefficient rather than as an impedance. SmithZ is the conversion, so a measured sweep is one line at the call site.

The admittance chart

SmithAdmittance turns the disc through half a turn and reads the pair as a conductance and a susceptance instead. Everything above still holds with y for z and the short circuit for the open; the grid is the same two families, mirrored.

type Desc

type Desc struct {
	// Type is which coord this is.
	Type Type

	// Theta is the axis a polar coord sweeps around the circle.
	Theta Axis
	// Hole is the inner radius as a fraction of the outer one, zero for a
	// coord with no hole.
	Hole float64
	// Radius is how much of the panel's shorter half-side the circle fills.
	Radius float64
	// Start is where the angular scale begins, in radians clockwise from
	// twelve o'clock, and Sweep how much of the circle it covers.
	Start, Sweep float64
	// Counterclockwise reverses the direction the angular scale runs in.
	Counterclockwise bool
	// Chord reports a coord drawing an edge between two marks as the straight
	// line between them rather than as an arc. It is a polar coord's choice,
	// whose default is the arc.
	Chord bool

	// Arc is the same choice made by a coord whose default is the other one:
	// it reports a Smith coord drawing an edge as the true image of a straight
	// data-space edge rather than as the chord it draws by default.
	//
	// The two are separate fields rather than one because a zero Desc has to
	// mean each coord's own default, and the two coords default opposite ways
	// — a rose petal's side is an arc, a measured locus is a chord. One field
	// would make a document that named a type and nothing else draw something
	// its constructor does not.
	Arc bool

	// Admittance reports a Smith coord mirrored through its centre — Γ ↦ −Γ —
	// so that the pair reads as a conductance and a susceptance. See
	// [SmithAdmittance].
	Admittance bool
}

Desc is a coord reduced to what configures it.

It is the bargain github.com/timzifer/figure/scale.Desc makes, for the same reason: a Coord is an interface over an unexported type, which is right for mapping positions and useless for writing one down. Nothing about a coord is a Go function, so unlike a scale, nothing here is lost.

func Describe

func Describe(c Coord) (Desc, bool)

Describe reports c's configuration, or ok == false if c cannot describe itself. A third-party coord that does not implement Describer still draws; it is simply not serializable.

func (Desc) Default

func (d Desc) Default() bool

Default reports whether d describes the coord a chart has when nobody chose one. A document does not carry a field for that: the absent coord and the Cartesian one draw the same chart, and writing `"coord": {"type": "cartesian"}` into every spec figure has ever produced would be noise.

type Describer

type Describer interface {
	Describe() Desc
}

Describer is implemented by a coord that can write itself down, so that a chart survives the round trip through the JSON spec. It sits beside github.com/timzifer/figure/scale.Describer and is optional for the same reason: a coord nobody serialises does not have to know what JSON is.

type Exploder

type Exploder interface {
	// Explode reports the device displacement of a mark whose extent in the
	// space the scales map into is the given pair of mapped positions, when it
	// is broken out by the fraction by of the coord's outer radius.
	Explode(x0, y0, x1, y1 float32, by float64) (dx, dy float32)
}

Exploder is implemented by a coord with a middle for a mark to be moved away from: what a slice broken out of a donut is doing.

It is an optional interface, like Describer, and Cartesian deliberately does not implement it. A rectangle on a Cartesian panel has no direction to be broken out in — every bar would move the same way, which is a translation of the layer rather than a reading of it — so a layer asking to break its marks out under a coord that has no middle draws exactly what it drew, and nothing is silently invented.

The displacement is answered rather than applied, because a coord does not draw: the geom that built the mark moves the path it built. A geom resolves the interface once per Build rather than per mark, exactly as it does github.com/timzifer/figure/scale.Band. See docs/adr/0026-breaking-a-mark-out.md.

type Framing

type Framing struct {
	// Area is the panel rectangle in device space.
	Area ir.Rect
	// X and Y are the panel's scales. Either may be nil, which asks for a
	// coord positioned in the rectangle without ranging anything: a frame
	// built by code that never heard of coordinate systems does that.
	X, Y scale.Scale
	// Z is the depth scale, and it is nil for every coord this package
	// defines.
	//
	// It is here because the seam is the one ADR 0056 widened for a third
	// dimension and this is the field that widening was for. Nothing reads it
	// yet: [github.com/timzifer/figure/three] projects above this stage and
	// uses no coord at all, so the coordinate system that will read it is the
	// spherical one — and when it arrives it finds a field rather than a
	// release.
	Z scale.Scale
}

Framing is what Coord.Frame is handed: the rectangle a panel occupies and the scales that map into it.

It is a struct rather than a parameter list for the reason geom.Training is one — a chart can gain a dimension, and a struct with exported fields gains a field where a method implemented outside this module cannot gain a parameter. ADR 0056 is the record.

type Furniture

type Furniture struct {
	// AxisX and AxisY are the two axis lines.
	AxisX, AxisY Shape

	// GridX is one shape per X tick and GridY one per Y tick, in tick order.
	// A tick with no grid line — a minor one, or one outside the panel — has
	// an empty shape.
	GridX, GridY []Shape

	// TickX and TickY are the tick marks, in tick order, empty where there is
	// none.
	TickX, TickY []Shape

	// LabelX and LabelY are where the tick labels go, in tick order.
	LabelX, LabelY []Label

	// InX and InY report, per tick, whether the tick falls inside the panel at
	// all. A Cartesian coord culls a tick that a float32 mapping put a hair
	// outside the plot rectangle; a polar one has nothing to fall off.
	InX, InY []bool

	// XLabelsShareARow reports whether the X tick labels sit along one
	// horizontal line and can therefore run into each other. render drops the
	// ones that would overlap when they do — and must not when they do not:
	// two labels on opposite sides of a ring can share an x and still be a
	// finger apart.
	XLabelsShareARow bool
}

Furniture is the geometry of one panel's grid lines, axis lines, tick marks and tick labels. A coord fills it; render strokes it.

Every per-tick slice is parallel to the tick list it came from, so index i is tick i whether or not that tick is drawn. That is what lets render keep the decisions that are its own — which grid lines the theme asked for, which labels would collide, whether this panel writes labels at all — while the coord answers only where things go.

It is filled rather than returned so that a chart redrawn every frame does not allocate its furniture again: Furniture.Reset keeps every buffer.

func (*Furniture) Reset

func (f *Furniture) Reset()

Reset empties f while keeping every buffer it has grown, including those of the shapes past its current length.

type FurnitureRequest

type FurnitureRequest struct {
	// Area is the panel rectangle in device space.
	Area ir.Rect
	// Metrics are the tick and label measurements the theme chose.
	Metrics Metrics
	// XTicks and YTicks are the ticks of the horizontal and vertical axes, in
	// ascending order, already mapped into Area.
	XTicks, YTicks []scale.Tick
}

FurnitureRequest is what Coord.Furniture — and Opposite beside it — is asked for: which rectangle, at what metrics, with which ticks on each axis.

The tick families are fields rather than parameters because a coord can acquire another one. A second axis on the far side of the panel already needed two more, and under the old positional signature the only way to add them was Opposite, a second method pair placing the same furniture. A depth axis, a second radial family or an axis of time would each have needed another. ADR 0060 is the record.

A nil tick slice means that axis was not asked for, which is how one call places one axis: Opposite reads YTicks for the right-hand edge and XTicks for the top, and fills only the side it was given.

type Label

type Label struct {
	At ir.Point
	H  ir.HAlign
	V  ir.VAlign
	// Rotation turns the label about its anchor, in radians clockwise. It is
	// zero for every label a Cartesian axis writes.
	Rotation float64
}

Label is where one tick label sits and how it is aligned about that point.

type Metrics

type Metrics struct {
	// TickLen is how far a major tick mark reaches out of the axis, and
	// MinorTickLen the same for a minor one.
	TickLen, MinorTickLen float32
	// LabelPad is the gap between the end of a full-length tick mark and the
	// label beyond it.
	LabelPad float32
}

Metrics are the theme lengths a coord needs in order to place furniture. They are passed in rather than read because a coord must not know what a theme is.

type Opposite

type Opposite interface {
	// FurnitureOpposite fills dst with the axis line, tick marks and tick
	// label positions of a second axis, opposite the one [Coord.Furniture]
	// places. [FurnitureRequest.YTicks] asks for the right-hand edge and
	// XTicks for the top; a nil slice fills nothing on that side.
	//
	// It fills one side of dst per direction asked for, so a caller keeps one
	// Furniture per axis rather than one with two of everything in it. The
	// slices are parallel to the ticks given, as they are everywhere in this
	// package.
	//
	// It places **no grid lines**. Two ladders of rules at different values
	// are a moiré rather than a reading, and which of the two scales a line
	// belongs to is unanswerable by looking — so the grid stays the primary
	// axis's, and the second axis is a line, its ticks and its labels.
	FurnitureOpposite(dst *Furniture, req FurnitureRequest)
}

Opposite is implemented by a coord that can place a second axis on the far side of the panel from the first: a vertical one down the right-hand edge, a horizontal one along the top.

It is an optional interface, for the reason every widening of this package since v0.8 has been one: Coord is implemented outside it and never gains a method ([CONCEPT §15](../CONCEPT.md#15-versioning--stability)). A coord that does not implement it draws no second axis, which is the honest answer for both of the others: a ring has one angular axis and one radial one and no far side to put another on, and a Smith chart's two axes are already drawn *inside* the disc as its grid, so a second one would be a fourth family of curves through the same ink. In each case a second axis drawn over the first would be two scales sharing one line.

It is one method rather than two because placing a second vertical axis and placing a second horizontal one are one capability — "this coord has edges opposite its axes" — and because a third such family would otherwise be a third method. Which edge is meant is read from the request: a nil tick slice is a direction that was not asked for. Cartesian implements it; Polar and Smith do not.

It answers the *furniture* only. Whether a chart has a second axis, which layers read it and whether the panel writes its labels are all decisions render already owns, exactly as they are for the first one — a coord reports where things go and does not draw. See [ADR 0037](../docs/adr/0037-secondary-axis.md).

type PolarOption

type PolarOption func(*polar)

PolarOption configures a polar coord. It is named for the coord it configures rather than for the package, so that a coordinate system added later can have options of its own without the two colliding.

func Arc

func Arc() PolarOption

Arc is the default edge policy, spelled out for a chart that wants to say so. See Chord for the other one.

func Chord

func Chord() PolarOption

Chord draws an edge between two marks as the straight line between them rather than as the arc through data space. It is what a radar wants: the sides of a spider chart are chords, and an arc between two axes bows the outline outwards into something the data does not say.

func Counterclockwise

func Counterclockwise(on bool) PolarOption

Counterclockwise runs the angular scale the other way round. The default is clockwise, which is the direction a pie, a clock and a compass all read in.

func Hole

func Hole(f float64) PolarOption

Hole leaves the middle of the circle empty, as a fraction of the outer radius in [0, 1). It is what makes a donut out of a pie and a ring gauge out of a gauge, and it is an explicit annulus rather than a white circle painted over the middle: the hole is where the radial scale starts, so a mark never enters it and a pointer in it hits nothing.

func Radius

func Radius(f float64) PolarOption

Radius sets how much of the panel the circle fills, as a fraction of half its shorter side. The default leaves room outside the ring for the tick labels that go round it; a chart with none — a pie usually has none — can ask for the whole of it.

func Start

func Start(radians float64) PolarOption

Start turns the whole coord about its centre, in radians clockwise from twelve o'clock. The default is zero: the first slice of a pie and the first axis of a radar both begin straight up, which is where a reader looks first.

func Sweep

func Sweep(radians float64) PolarOption

Sweep sets how much of the circle the angular scale covers, in radians. The default is a full turn; a half turn is a gauge.

func Theta

func Theta(a Axis) PolarOption

Theta chooses which scale sweeps the circle. The default is FromX.

type Shape

type Shape struct {
	// Pts is a straight run, empty when the shape is curved.
	Pts []ir.Point
	// Path is a curve, empty when the shape is straight. It is a value rather
	// than a pointer so that its buffers survive [Furniture.Reset].
	Path ir.Path
}

Shape is one piece of furniture: a straight run of points, or a path when the coord bends it.

The two forms are not interchangeable and the difference is deliberate. A Cartesian grid line is two points and reaches the backend as a Polyline, exactly as it did before there was a coord to ask — which is why every golden file in the repository still matches. A polar ring is cubics and reaches it as a stroked path.

func (*Shape) Empty

func (s *Shape) Empty() bool

Empty reports whether s holds nothing to draw.

type SmithOption

type SmithOption func(*smith)

SmithOption configures a Smith coord. It is named for the coord it configures rather than for the package, exactly as PolarOption is, so that two coords can have options of the same shape without colliding.

func SmithAdmittance

func SmithAdmittance(on bool) SmithOption

SmithAdmittance draws the admittance chart, whose two columns are a normalised admittance — conductance g on X and susceptance b on Y — rather than an impedance.

It is one sign. y = 1/z gives Γ_y = −Γ_z, so the Y chart is the Z chart turned through half a turn, and the grid that bunched towards an open now bunches towards a short. Because the turn is applied to the picture and not to the data, a load plotted as its admittance here lands on exactly the point its impedance lands on over there: the same physical reflection, read against the other grid. That is what makes the option useful rather than decorative, and it is what a shunt element wants, because a shunt admittance adds where a shunt impedance does not.

y := 1 / z                                     // in the caller's own numbers
g, b := coord.SmithZ(-re, -im)                 // or straight from a sweep

func SmithArc

func SmithArc() SmithOption

SmithArc draws an edge between two marks as the true image of the straight line between their impedances, which is an arc.

It is what a swept component traces: sweeping a series inductance draws the constant-resistance circle exactly, and four points along it joined by chords draw a quadrilateral instead. Use it for a locus computed from a component value; leave the default for a measured frequency sweep, where the samples are what is known and the line between two of them is a convention.

func SmithChord

func SmithChord() SmithOption

SmithChord is the default edge policy, spelled out for a chart that wants to say so: an edge between two marks is the straight line between them on screen, which is what every instrument draws for a measured trace. See SmithArc for the other one.

func SmithRadius

func SmithRadius(f float64) SmithOption

SmithRadius sets how much of the panel the disc fills, as a fraction of half its shorter side. The default leaves room outside the rim for the reactance labels that go round it.

type Type

type Type string

Type names a coordinate system. It is the word a written-down chart carries in place of the constructor that built the coord.

const (
	TypeCartesian Type = "cartesian"
	TypePolar     Type = "polar"
	TypeSmith     Type = "smith"
)

The coord types.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL