* feat(region-routing): P1 tagging + region_routes primitive + read/write API + preview launcher - config.py: add Coverage.region_tagging (bool=False); add RegionRouteMatrix dataclass (enabled, cells) above NotificationsConfig; add region_routes field to NotificationsConfig; add explicit hydration branch for region_routes in _dict_to_dataclass mirroring destinations pattern. - coverage_area.py: add MonitoringArea.name (str|None=None, frozen); update areas_from_config to preserve name; refactor inline geom extraction from classify_event_areas into shared _event_geom_json helper; add matching_area_names(geom_json, areas)->list[str] (additive, all named matches, config-order, deduped; gate unchanged); add event_region_names convenience wrapper. - coverage_filter.py: add region_tagging ctor kwarg; stamp event.region/ regions before the gate when region_tagging=True and areas non-empty and not event.regions (never clobbers satpass preset). - pipeline/__init__.py: wire region_tagging into CoverageFilter construction. - notification_routes.py: add GET /notifications/regions (named coverage area names, config-order, deduped); GET /notifications/region-routing (matrix as JSON); POST /notifications/region-routing (explicit RMW — only region_routes changes, toggles/rules/destinations survive). - scripts/preview_dashboard.py: mesh-free launcher — dashboard API only, no mesh connector, no broadcast loop; vite runs separately. All 87 coverage tests pass; 300 total pass; 6 pre-existing failures unchanged (adapter config count mismatch + MeshCore EventType.NEW_CONTACT). * feat(region-routing): manual region x family matrix editor page Adds RegionRoutingMatrix.tsx — a plain editor over the region_routes config primitive. Rows = families (via useFamilies()), cols = regions (from GET /api/notifications/regions). Each cell exposes MT channel (ChannelPicker single + includeDisabled), MC channel name (text input), min_severity select (routine/priority/critical/immediate), and an enabled checkbox. Only cells where MT or MC is set are included in the sparse POST payload. Master enable toggle maps to top-level enabled. MT budget guard warns when more than 7 distinct MT indices are in use. Sticky family column; horizontal scroll for wide region sets. Registers route /region-routing in App.tsx and adds "Region Routing" nav entry (Map icon) under the Meshtastic section in Layout.tsx, immediately after Routing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(region-routing): regions endpoint reads saved (disk) coverage so routing columns are dynamic without a bot restart; preview reloads config after writes * feat(routing): unify MT/MC routing into per-family cards; region routing as an in-card expand; remove rules/destinations UI + standalone page * refactor(routing): move Meshtastic Routing from /notifications to /meshtastic/routing (mirror /meshcore/routing); redirect legacy path * feat(region-routing): dispatcher honors region_routes matrix (authoritative-on-match, per-region cooldown, per-channel dedup); non-matrix path unchanged Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(region-routing): matrix dedup key must match boot-restore 2-tuple form (prevents restart re-broadcast flood); regression test --------- Co-authored-by: Matt Johnson <mj@k7zvx.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
14 KiB
d3-axis
The axis component renders human-readable reference marks for scales. This alleviates one of the more tedious tasks in visualizing data.
Installing
If you use npm, npm install d3-axis. You can also download the latest release on GitHub. For vanilla HTML in modern browsers, import d3-axis from Skypack:
<script type="module">
import {axisLeft} from "https://cdn.skypack.dev/d3-axis@3";
const axis = axisLeft(scale);
</script>
For legacy environments, you can load d3-axis’s UMD bundle from an npm-based CDN such as jsDelivr; a d3 global is exported:
<script src="https://cdn.jsdelivr.net/npm/d3-axis@3"></script>
<script>
const axis = d3.axisLeft(scale);
</script>
API Reference
Regardless of orientation, axes are always rendered at the origin. To change the position of the axis with respect to the chart, specify a transform attribute on the containing element. For example:
d3.select("body").append("svg")
.attr("width", 1440)
.attr("height", 30)
.append("g")
.attr("transform", "translate(0,30)")
.call(axis);
The elements created by the axis are considered part of its public API. You can apply external stylesheets or modify the generated axis elements to customize the axis appearance.
An axis consists of a path element of class “domain” representing the extent of the scale’s domain, followed by transformed g elements of class “tick” representing each of the scale’s ticks. Each tick has a line element to draw the tick line, and a text element for the tick label. For example, here is a typical bottom-oriented axis:
<g fill="none" font-size="10" font-family="sans-serif" text-anchor="middle">
<path class="domain" stroke="currentColor" d="M0.5,6V0.5H880.5V6"></path>
<g class="tick" opacity="1" transform="translate(0.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.0</text>
</g>
<g class="tick" opacity="1" transform="translate(176.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.2</text>
</g>
<g class="tick" opacity="1" transform="translate(352.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.4</text>
</g>
<g class="tick" opacity="1" transform="translate(528.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.6</text>
</g>
<g class="tick" opacity="1" transform="translate(704.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">0.8</text>
</g>
<g class="tick" opacity="1" transform="translate(880.5,0)">
<line stroke="currentColor" y2="6"></line>
<text fill="currentColor" y="9" dy="0.71em">1.0</text>
</g>
</g>
The orientation of an axis is fixed; to change the orientation, remove the old axis and create a new axis.
Constructs a new top-oriented axis generator for the given scale, with empty tick arguments, a tick size of 6 and padding of 3. In this orientation, ticks are drawn above the horizontal domain path.
# d3.axisRight(scale) · Source
Constructs a new right-oriented axis generator for the given scale, with empty tick arguments, a tick size of 6 and padding of 3. In this orientation, ticks are drawn to the right of the vertical domain path.
# d3.axisBottom(scale) · Source
Constructs a new bottom-oriented axis generator for the given scale, with empty tick arguments, a tick size of 6 and padding of 3. In this orientation, ticks are drawn below the horizontal domain path.
Constructs a new left-oriented axis generator for the given scale, with empty tick arguments, a tick size of 6 and padding of 3. In this orientation, ticks are drawn to the left of the vertical domain path.
Render the axis to the given context, which may be either a selection of SVG containers (either SVG or G elements) or a corresponding transition.
# axis.scale([scale]) · Source
If scale is specified, sets the scale and returns the axis. If scale is not specified, returns the current scale.
# axis.ticks(arguments…) · Source
# axis.ticks([count[, specifier]])
# axis.ticks([interval[, specifier]])
Sets the arguments that will be passed to scale.ticks and scale.tickFormat when the axis is rendered, and returns the axis generator. The meaning of the arguments depends on the axis’ scale type: most commonly, the arguments are a suggested count for the number of ticks (or a time interval for time scales), and an optional format specifier to customize how the tick values are formatted.
This method has no effect if the scale does not implement scale.ticks, as with band and point scales. To set the tick values explicitly, use axis.tickValues. To set the tick format explicitly, use axis.tickFormat.
For example, to generate twenty ticks with SI-prefix formatting on a linear scale, say:
axis.ticks(20, "s");
To generate ticks every fifteen minutes with a time scale, say:
axis.ticks(d3.timeMinute.every(15));
This method is also a convenience function for axis.tickArguments. For example, this:
axis.ticks(10);
Is equivalent to:
axis.tickArguments([10]);
To generate tick values directly, use scale.ticks.
# axis.tickArguments([arguments]) · Source
If arguments is specified, sets the arguments that will be passed to scale.ticks and scale.tickFormat when the axis is rendered, and returns the axis generator. The meaning of the arguments depends on the axis’ scale type: most commonly, the arguments are a suggested count for the number of ticks (or a time interval for time scales), and an optional format specifier to customize how the tick values are formatted.
If arguments is specified, this method has no effect if the scale does not implement scale.ticks, as with band and point scales. To set the tick values explicitly, use axis.tickValues. To set the tick format explicitly, use axis.tickFormat.
If arguments is not specified, returns the current tick arguments, which defaults to the empty array.
For example, to generate twenty ticks with SI-prefix formatting on a linear scale, say:
axis.tickArguments([20, "s"]);
To generate ticks every fifteen minutes with a time scale, say:
axis.tickArguments([d3.timeMinute.every(15)]);
See also axis.ticks.
# axis.tickValues([values]) · Source
If a values iterable is specified, the specified values are used for ticks rather than using the scale’s automatic tick generator. If values is null, clears any previously-set explicit tick values and reverts back to the scale’s tick generator. If values is not specified, returns the current tick values, which defaults to null. For example, to generate ticks at specific values:
var xAxis = d3.axisBottom(x)
.tickValues([1, 2, 3, 5, 8, 13, 21]);
The explicit tick values take precedent over the tick arguments set by axis.tickArguments. However, any tick arguments will still be passed to the scale’s tickFormat function if a tick format is not also set.
# axis.tickFormat([format]) · Source
If format is specified, sets the tick format function and returns the axis. If format is not specified, returns the current format function, which defaults to null. A null format indicates that the scale’s default formatter should be used, which is generated by calling scale.tickFormat. In this case, the arguments specified by axis.tickArguments are likewise passed to scale.tickFormat.
See d3-format and d3-time-format for help creating formatters. For example, to display integers with comma-grouping for thousands:
axis.tickFormat(d3.format(",.0f"));
More commonly, a format specifier is passed to axis.ticks:
axis.ticks(10, ",f");
This has the advantage of setting the format precision automatically based on the tick interval.
# axis.tickSize([size]) · Source
If size is specified, sets the inner and outer tick size to the specified value and returns the axis. If size is not specified, returns the current inner tick size, which defaults to 6.
# axis.tickSizeInner([size]) · Source
If size is specified, sets the inner tick size to the specified value and returns the axis. If size is not specified, returns the current inner tick size, which defaults to 6. The inner tick size controls the length of the tick lines, offset from the native position of the axis.
# axis.tickSizeOuter([size]) · Source
If size is specified, sets the outer tick size to the specified value and returns the axis. If size is not specified, returns the current outer tick size, which defaults to 6. The outer tick size controls the length of the square ends of the domain path, offset from the native position of the axis. Thus, the “outer ticks” are not actually ticks but part of the domain path, and their position is determined by the associated scale’s domain extent. Thus, outer ticks may overlap with the first or last inner tick. An outer tick size of 0 suppresses the square ends of the domain path, instead producing a straight line.
# axis.tickPadding([padding]) · Source
If padding is specified, sets the padding to the specified value in pixels and returns the axis. If padding is not specified, returns the current padding which defaults to 3 pixels.
# axis.offset([offset]) · Source
If offset is specified, sets the offset to the specified value in pixels and returns the axis. If offset is not specified, returns the current offset which defaults to 0 on devices with a devicePixelRatio greater than 1, and 0.5px otherwise. This default offset ensures crisp edges on low-resolution devices.
