How a value spreads across the range a channel is drawn over.
Each names what is spread evenly as the value runs from the low end of the domain to the high end, and each is read in the mark's own quantity -- a circle's radius, a stroke's width:
:linear -- the quantity itself. A value twice another is drawn
twice as wide, which on a circle is four times the ink. ggplot2
spells this scale_radius.:sqrt -- the quantity against the root of the value, the root
being the quantity's ink exponent. The default, and what ggplot2's
scale_size does. The smallest value still draws at the low end
of the range rather than vanishing, and growth is compressed the
way area demands; it is exactly area-proportional when the range
starts at zero.:area -- the ink. Equal steps in value are equal steps in area,
which is the strict reading of what a size encoding claims.On a quantity whose ink grows linearly -- a stroke's width, an opacity -- all three are one function, because there is no area to correct for.
How a value spreads across the range a channel is drawn over. Each names what is spread evenly as the value runs from the low end of the domain to the high end, and each is read in the mark's own quantity -- a circle's radius, a stroke's width: - `:linear` -- the quantity itself. A value twice another is drawn twice as wide, which on a circle is four times the ink. ggplot2 spells this `scale_radius`. - `:sqrt` -- the quantity against the root of the value, the root being the quantity's ink exponent. The default, and what ggplot2's `scale_size` does. The smallest value still draws at the low end of the range rather than vanishing, and growth is compressed the way area demands; it is exactly area-proportional when the range starts at zero. - `:area` -- the ink. Equal steps in value are equal steps in area, which is the strict reading of what a size encoding claims. On a quantity whose ink grows linearly -- a stroke's width, an opacity -- all three are one function, because there is no area to correct for.
(categorical-domain? dom)True if domain is a sequence of non-numeric values (categorical).
True if domain is a sequence of non-numeric values (categorical).
What a channel's :range may span, where the quantity it is drawn
as has limits of its own.
An opacity outside 0 to 1 is not a fainter or a stronger colour -- it is not a colour at all, and the PNG path throws from inside AWT where the SVG path silently emits an out-of-range attribute. A negative radius is not a smaller mark; it emits a negative width and draws nothing. Both used to pass every check.
What a channel's `:range` may span, where the quantity it is drawn as has limits of its own. An opacity outside 0 to 1 is not a fainter or a stronger colour -- it is not a colour at all, and the PNG path throws from inside AWT where the SVG path silently emits an out-of-range attribute. A negative radius is not a smaller mark; it emits a negative width and draws nothing. Both used to pass every check.
(channel-domain spec d-min d-max)The [lo hi] a channel's values are read against, given its scale
spec and the range the data covers.
:domain on the spec replaces the data's own range -- a way to fix
what a size means across plots that would otherwise each scale to
their own extremes. :from-zero anchors the low end at zero, which
is what turns a spread across a range into a proportion: with the
range anchored there too, twice the value is twice the ink.
Anchored at zero it is the distance from zero that decides the ink,
so the domain runs to the value furthest from zero in either
direction. Taking the high end alone put every value at or below
zero at the bottom of the range: a column of -5, 0 and 10 drew one
mark of three and said nothing, and an all-negative column drew
every mark at the maximum beside a legend running the other way.
ggplot2's scale_size_area reads the magnitude here too, though it
divides by the maximum rather than by the widest magnitude, which on
an all-negative column draws marks larger than the range's own top.
The `[lo hi]` a channel's values are read against, given its scale spec and the range the data covers. `:domain` on the spec replaces the data's own range -- a way to fix what a size means across plots that would otherwise each scale to their own extremes. `:from-zero` anchors the low end at zero, which is what turns a spread across a range into a proportion: with the range anchored there too, twice the value is twice the ink. Anchored at zero it is the distance from zero that decides the ink, so the domain runs to the value furthest from zero in either direction. Taking the high end alone put every value at or below zero at the bottom of the range: a column of -5, 0 and 10 drew one mark of three and said nothing, and an all-negative column drew every mark at the maximum beside a legend running the other way. ggplot2's `scale_size_area` reads the magnitude here too, though it divides by the maximum rather than by the widest magnitude, which on an all-negative column draws marks larger than the range's own top.
(channel-mapper spec d-min d-max default-range ink-exponent)A function from a data value to the quantity a mark draws it as -- a radius, a width, an opacity.
spec is the channel's scale spec: :type (:linear or :log),
:domain, :range, :by and :from-zero, each optional.
default-range is what the channel spans where the spec names no
:range. ink-exponent comes from the mark's declared quantity --
2 for a radius or a side, 1 for a width -- and is what lets :by
mean the same thing whatever shape draws it.
Stated once because the marks and the legend have to agree: the mark
drawn beside a value in the legend is the size a mark of that value
is drawn at on the panel. impl.plan builds the legend from this and
render.mark draws from it, and they held separate copies of the
arithmetic until both were found to share a defect.
A value outside the domain is clamped to its nearer end rather than
drawn outside the range. A :domain narrower than the data is a
statement about what the reader should compare, not an instruction
to drop rows -- and a dropped row leaves no trace on the panel.
A domain of one distinct value has no spread to map, so every value
takes the middle of the range. ggplot2's scales::rescale answers a
zero range the same way, and for the same reason: with nothing to
compare a value against, the midpoint is the only unprejudiced
answer. Collapsing to the low end instead drew
{:size {:value 7 :scale true}} at radius 2.0 -- smaller than the
default 3.0 -- beside a legend reading 7, and did the same to any
size column whose values happen to be equal.
A function from a data value to the quantity a mark draws it as -- a
radius, a width, an opacity.
`spec` is the channel's scale spec: `:type` (`:linear` or `:log`),
`:domain`, `:range`, `:by` and `:from-zero`, each optional.
`default-range` is what the channel spans where the spec names no
`:range`. `ink-exponent` comes from the mark's declared quantity --
2 for a radius or a side, 1 for a width -- and is what lets `:by`
mean the same thing whatever shape draws it.
Stated once because the marks and the legend have to agree: the mark
drawn beside a value in the legend is the size a mark of that value
is drawn at on the panel. `impl.plan` builds the legend from this and
`render.mark` draws from it, and they held separate copies of the
arithmetic until both were found to share a defect.
A value outside the domain is clamped to its nearer end rather than
drawn outside the range. A `:domain` narrower than the data is a
statement about what the reader should compare, not an instruction
to drop rows -- and a dropped row leaves no trace on the panel.
A domain of one distinct value has no spread to map, so every value
takes the middle of the range. ggplot2's `scales::rescale` answers a
zero range the same way, and for the same reason: with nothing to
compare a value against, the midpoint is the only unprejudiced
answer. Collapsing to the low end instead drew
`{:size {:value 7 :scale true}}` at radius 2.0 -- smaller than the
default 3.0 -- beside a legend reading 7, and did the same to any
size column whose values happen to be equal.The method a channel spreads by when its scale does not say.
ggplot2's default for a point, adopted here: the perceptual correction is on, and the smallest mark stays visible.
The method a channel spreads by when its scale does not say. ggplot2's default for a point, adopted here: the perceptual correction is on, and the smallest mark stays visible.
(format-log-ticks ticks)Format log scale tick values. Values are always clean 1-2-3-5 multiples of powers of 10, so formatting is straightforward: integers >= 1 shown without decimals, sub-1 values use minimal decimal places.
Format log scale tick values. Values are always clean 1-2-3-5 multiples of powers of 10, so formatting is straightforward: integers >= 1 shown without decimals, sub-1 values use minimal decimal places.
(format-range-endpoints lo hi separators)The two labels a continuous legend prints at the ends of its bar.
Each end is written to six significant digits in plain notation, so
what a legend reads matches what its axis reads. %.4g did neither
half of that: four significant digits switch to scientific notation
at 10000, so a legend for a count read 1.235e+05 beside an axis
reading 100,000, and two ends as far apart as 123456 and 123999 read
alike. Below the switch it padded rather than truncated, so a span of
0.1 to 2.5 read 0.1000 and 2.500.
Precision follows each value's own magnitude rather than the span between them: a legend from 0.001 to 1000 has to show its low end as 0.001, which a span-derived step would have rounded to 0.
separators is the map defaults/number-separators reads off a
config, as for format-ticks.
The two labels a continuous legend prints at the ends of its bar. Each end is written to six significant digits in plain notation, so what a legend reads matches what its axis reads. `%.4g` did neither half of that: four significant digits switch to scientific notation at 10000, so a legend for a count read 1.235e+05 beside an axis reading 100,000, and two ends as far apart as 123456 and 123999 read alike. Below the switch it padded rather than truncated, so a span of 0.1 to 2.5 read 0.1000 and 2.500. Precision follows each value's own magnitude rather than the span between them: a legend from 0.001 to 1000 has to show its low end as 0.001, which a span-derived step would have rounded to 0. `separators` is the map `defaults/number-separators` reads off a config, as for `format-ticks`.
(format-ticks sx ticks)(format-ticks sx ticks separators)Format tick values: integers shown without decimals, floats rounded to the precision implied by the tick step size (avoids floating-point noise like 0.30000000000000004). Falls back to wadogo formatting only when the step cannot be determined (< 2 ticks).
Every value is formatted under Locale/ROOT, so what a tick reads
does not depend on the JVM it renders on.
The 3-arity writes each formatted tick with separators, the
{:thousands ... :decimal ...} map defaults/number-separators
reads off a config. Layout measures label widths through this same
function, so a grouped axis reserves room for the separators it will
draw.
Format tick values: integers shown without decimals, floats rounded to the
precision implied by the tick step size (avoids floating-point noise like
0.30000000000000004). Falls back to wadogo formatting only when the step
cannot be determined (< 2 ticks).
Every value is formatted under `Locale/ROOT`, so what a tick reads
does not depend on the JVM it renders on.
The 3-arity writes each formatted tick with `separators`, the
`{:thousands ... :decimal ...}` map `defaults/number-separators`
reads off a config. Layout measures label widths through this same
function, so a grouped axis reserves room for the separators it will
draw.(invert sc v)Read a drawing-space position back as a data value. On a band scale this answers with the category whose band contains the position, and with nil outside every band.
The band case is computed here rather than through ws/inverse.
Wadogo's bands-inverse-fn destructures :start and :end from
band maps whose keys are :rstart and :rend, so both locals are
nil and its ^double hint throws a NullPointerException for every
input. The band data it needs is on the scale either way. Reported
in dev-notes/wadogo-band-inverse-issue.md.
Read a drawing-space position back as a data value. On a band scale this answers with the category whose band contains the position, and with nil outside every band. The band case is computed here rather than through `ws/inverse`. Wadogo's `bands-inverse-fn` destructures `:start` and `:end` from band maps whose keys are `:rstart` and `:rend`, so both locals are nil and its `^double` hint throws a NullPointerException for every input. The band data it needs is on the scale either way. Reported in `dev-notes/wadogo-band-inverse-issue.md`.
(log-ticks [lo hi] n)Generate clean log-scale tick values for a [lo hi] domain, targeting approximately n ticks. Uses ggplot2-style 1-2-5 nice numbers instead of wadogo's linear-in-log-space approach (which produces irrational values like 3.162...). Returns a vector of tick values (doubles).
Strategy:
Generate clean log-scale tick values for a [lo hi] domain, targeting approximately n ticks. Uses ggplot2-style 1-2-5 nice numbers instead of wadogo's linear-in-log-space approach (which produces irrational values like 3.162...). Returns a vector of tick values (doubles). Strategy: - Powers of 10 only when they give >= 3 ticks (strongly preferred) - 1-2-5 intermediates per decade when more ticks are needed - 1-2-3-5 intermediates for dense sub-decade ranges - Bounding powers of 10 are included when they fall within a small margin (15% of log-span) of the domain edges
Create a wadogo scale mapping domain values to a pixel range.
Create a wadogo scale mapping domain values to a pixel range.
(numeric-color-domain spec d-min d-max)The [lo hi] a numeric colour or fill column is read against.
:domain on the spec replaces the range the data covers, so a
gradient can mean the same thing across plots and across the panels
of a facet. A value outside it is drawn at the nearer end of the
gradient rather than dropped, which is what normalize-continuous
clamps for, and matches what :size and :alpha answer.
Answers nil where the data gave no range, so a caller that had
nothing to normalize against still has nothing. A categorical
:domain -- a list of category names -- is not a numeric range and
is ignored here; order-by-domain in impl.plan reads that one.
Stated once because four places normalize a colour and a fifth builds the legend, and each held its own min and max.
The `[lo hi]` a numeric colour or fill column is read against. `:domain` on the spec replaces the range the data covers, so a gradient can mean the same thing across plots and across the panels of a facet. A value outside it is drawn at the nearer end of the gradient rather than dropped, which is what `normalize-continuous` clamps for, and matches what `:size` and `:alpha` answer. Answers nil where the data gave no range, so a caller that had nothing to normalize against still has nothing. A categorical `:domain` -- a list of category names -- is not a numeric range and is ignored here; `order-by-domain` in `impl.plan` reads that one. Stated once because four places normalize a colour and a fifth builds the legend, and each held its own min and max.
(pad-domain domain scale-spec)(pad-domain [lo hi] scale-spec padding)Add padding to a numeric domain. When lo == hi (constant data), pads by ±1 or ±5% of |lo|, whichever is larger. For log scales, callers must supply positive lo and hi -- the responsibility for excluding non-positive values lives upstream (filter-log-nonpositive for raw data, the scale-aware branch of compute-global-y-domain for stat-derived ranges).
padding is the resolved :domain-padding. The 2-arity falls back
to the library default, for a caller with no configuration in hand;
every caller inside draft->plan passes the resolved value, which is
how pj/options and pj/with-config reach this.
Add padding to a numeric domain. When lo == hi (constant data), pads by ±1 or ±5% of |lo|, whichever is larger. For log scales, callers must supply positive lo and hi -- the responsibility for excluding non-positive values lives upstream (filter-log-nonpositive for raw data, the scale-aware branch of compute-global-y-domain for stat-derived ranges). `padding` is the resolved `:domain-padding`. The 2-arity falls back to the library default, for a caller with no configuration in hand; every caller inside `draft->plan` passes the resolved value, which is how `pj/options` and `pj/with-config` reach this.
(scale-kind domain scale-spec)Determine the wadogo scale type (:categorical, :log, or :linear) from domain and spec. Passes through vector dispatch values like [:linear :doc] for doc defmethods.
Determine the wadogo scale type (:categorical, :log, or :linear) from domain and spec. Passes through vector dispatch values like [:linear :doc] for doc defmethods.
(spec-keys aesthetic)The keys a scale spec may carry for aesthetic. :type and
:domain belong to every scale; the rest are per aesthetic, from
defaults/channel-scale-options.
The keys a scale spec may carry for `aesthetic`. `:type` and `:domain` belong to every scale; the rest are per aesthetic, from `defaults/channel-scale-options`.
(tick-count pixel-range scale-spec spacing)How many ticks an axis asks for across pixel-range drawing units.
:n-ticks on the spec names the count outright. Failing that,
spacing names about how much room a tick should have and the count
is how many fit, never fewer than two. The count is then rounded to
a step a reader can read off, which is why the room each tick ends
up with can come out under spacing. One answer for both column
types -- each key used to be read on one of them and ignored on the
other.
How many ticks an axis asks for across `pixel-range` drawing units. `:n-ticks` on the spec names the count outright. Failing that, `spacing` names about how much room a tick should have and the count is how many fit, never fewer than two. The count is then rounded to a step a reader can read off, which is why the room each tick ends up with can come out under `spacing`. One answer for both column types -- each key used to be read on one of them and ignored on the other.
(validate-drawn-range-options! channel spec where)Throw when a scale spec names a drawn-range option the channel does not read, or names one Plotje cannot carry out.
Called from both places a spec can be written -- pj/scale and a
mapping's :scale -- because either can name a key the channel has
no use for, and a key that is read by nothing is the defect this
release exists to remove. where names the caller for the message.
Only the drawn-range options are policed here. The rest of the spec is checked where it is written.
Throw when a scale spec names a drawn-range option the channel does not read, or names one Plotje cannot carry out. Called from both places a spec can be written -- `pj/scale` and a mapping's `:scale` -- because either can name a key the channel has no use for, and a key that is read by nothing is the defect this release exists to remove. `where` names the caller for the message. Only the drawn-range options are policed here. The rest of the spec is checked where it is written.
(validate-spec-keys! aesthetic spec where)Throw when a scale spec names a key aesthetic does not read.
Called from both places a spec can be written, so pj/scale and a
mapping's :scale refuse the same keys. pj/scale used to take any
key and read what it understood, so a misspelled :rnge set nothing
and said nothing.
Throw when a scale spec names a key `aesthetic` does not read. Called from both places a spec can be written, so `pj/scale` and a mapping's `:scale` refuse the same keys. `pj/scale` used to take any key and read what it understood, so a misspelled `:rnge` set nothing and said nothing.
(validate-spec-values! channel spec where)Throw when a scale spec's :breaks, :tick-labels or :values
holds something the channel cannot carry out.
Called from both places a spec can be written -- pj/scale and a
mapping's :scale -- because a spec means the same thing wherever
it is written. These four checks lived only in pj/scale, so
{:shape {:column :c :scale {:values [:blob :thing :whatsit]}}}
planned without a word: every mark drew the fallback symbol while
the legend advertised three symbols that do not exist.
where names the caller for the message. Which channels read these
keys at all is settled by validate-spec-keys!, off the published
table, so this checks only what a key holds.
Throw when a scale spec's `:breaks`, `:tick-labels` or `:values`
holds something the channel cannot carry out.
Called from both places a spec can be written -- `pj/scale` and a
mapping's `:scale` -- because a spec means the same thing wherever
it is written. These four checks lived only in `pj/scale`, so
`{:shape {:column :c :scale {:values [:blob :thing :whatsit]}}}`
planned without a word: every mark drew the fallback symbol while
the legend advertised three symbols that do not exist.
`where` names the caller for the message. Which channels read these
keys at all is settled by `validate-spec-keys!`, off the published
table, so this checks only what a key holds.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |