Skip to content

feat(charts): add In view filter to chart list#352

Open
msallin wants to merge 1 commit into
SignalK:masterfrom
msallin:claude/chart-bounds-filter
Open

feat(charts): add In view filter to chart list#352
msallin wants to merge 1 commit into
SignalK:masterfrom
msallin:claude/chart-bounds-filter

Conversation

@msallin

@msallin msallin commented Apr 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds an In view slide toggle to the chart list. When enabled, only charts whose bounds intersect the current map viewport (app.mapExtent) are shown.
  • The list updates reactively as the user pans/zooms the map, via a signal-driven effect that recomputes the filter while the toggle is on.
  • Combines with the existing text filter; charts without bounds metadata are treated as global and remain visible.

Changes

  • chart-utils.ts: new pure isChartInView(bounds, extent) helper (alongside extentFromBounds) encapsulating the bounds/viewport intersection and the "no bounds = global" rule. Both inputs are EPSG:4326 [minLon, minLat, maxLon, maxLat], compared via intersects from ol/extent with no re-projection.
  • chart-utils.spec.ts: unit tests for isChartInView (overlap, containment, edge-touching, disjoint, missing/malformed bounds) plus boundary tests for extentFromBounds.
  • chartlist.ts: new inViewOnly flag, toggleInViewOnly(), an overridden doFilter() combining the name filter with the viewport filter, and an effect() watching app.mapExtent.
  • chartlist.html: new In view mat-slide-toggle; the existing boundary toggle relabelled Bounds and the Re-order / Add buttons made icon-only to fit on one row.

Known limitation

The intersection is a plain min/max overlap test and does not handle viewports crossing the antimeridian (±180° longitude); this is documented in isChartInView.

Closes

Closes #351

Test plan

  • Install several charts covering different regions (e.g. NOAA US + OSM + a European chart)
  • Open chart list, confirm the In view toggle appears next to Bounds
  • Toggle on, pan/zoom the map — list shrinks to charts intersecting the viewport
  • Verify OSM (no bounds) stays visible regardless of viewport
  • Combine with the text filter — both predicates applied
  • Toggle off — full list returns

Summary by CodeRabbit

  • New Features

    • Added an “In view” filter to the chart list so you can show only charts visible in the current map area.
    • Updated chart-list actions for a cleaner, more compact toolbar.
  • Bug Fixes

    • Improved chart visibility filtering so results update as the map view changes.
    • Tightened boundary handling to better exclude charts that sit exactly on world edges while still including charts just inside them.

@joelkoz joelkoz self-assigned this Jun 26, 2026
@joelkoz

joelkoz commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the contribution! Are you still working on this (it's marked as a draft). Let me know...

@joelkoz joelkoz added awaiting response Requires further response from submitter. and removed awaiting response Requires further response from submitter. labels Jun 26, 2026
@
feat(charts): add "In view" filter to chart list

Adds a slide toggle to the chart list that filters the displayed charts
to only those whose bounds intersect the current map viewport. The list
updates reactively as the user pans/zooms the map. The text filter and
the in-view filter can be combined. Charts without bounds metadata are
treated as global and remain visible.

The viewport-intersection test lives in a pure isChartInView() helper in
chart-utils, alongside extentFromBounds, with unit tests covering
overlap, containment, edge-touching, disjoint and missing-bounds cases.
@
@msallin msallin force-pushed the claude/chart-bounds-filter branch from 40ce9c9 to d0ed9c9 Compare June 26, 2026 13:11
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds chart-list filtering to show only charts whose bounds intersect the current map viewport. It introduces isChartInView, re-filters when the map extent changes, and adds an in-view toggle alongside updated chart list controls.

Changes

Chart list viewport filtering

Layer / File(s) Summary
Chart visibility helper
src/app/modules/map/ol/lib/charts/chart-utils.ts, src/app/modules/map/ol/lib/charts/chart-utils.spec.ts
isChartInView(bounds, extent) is added, ol/extent imports intersects, and extentFromBounds tests add mid-range, edge, and near-edge cases.
Chart list filtering
src/app/modules/skresources/components/charts/chartlist.ts
ChartListComponent adds inViewOnly, reruns filtering on map extent changes, and filters by text and chart bounds before updating filteredList.
Chart list controls
src/app/modules/skresources/components/charts/chartlist.html
The chart list action area renames “Boundaries” to “Bounds”, adds the in-view toggle, and switches the reorder and add controls to icon buttons.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ChartListComponent
  participant MapExtent
  participant isChartInView
  User->>ChartListComponent: toggleInViewOnly(true)
  ChartListComponent->>ChartListComponent: doFilter()
  ChartListComponent->>MapExtent: read app.mapExtent()
  MapExtent-->>ChartListComponent: current extent
  ChartListComponent->>isChartInView: intersects(bounds, extent)
  isChartInView-->>ChartListComponent: visible / hidden
  MapExtent->>ChartListComponent: extent changes on pan or zoom
  ChartListComponent->>ChartListComponent: effect reruns doFilter()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

(_/)
I nibbled bounds and sniffed the view,
Then hopped where charty breezes blew.
The list now twitches as maps expand,
With carrot joy and tidy land. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an in-view chart list filter.
Linked Issues check ✅ Passed The PR appears to implement the viewport-based chart filter, reactive updates, global-chart handling, and text-filter combination requested in #351.
Out of Scope Changes check ✅ Passed The changes stay focused on the chart-list viewport filter, UI placement, and supporting tests without obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/app/modules/map/ol/lib/charts/chart-utils.spec.ts

(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files
(Use node --trace-warnings ... to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 10.5.0

/.eslintrc:2
"root": true,
^

SyntaxError: Unexpected token ':'
at wrapSafe (node:internal/modules/cjs/loader:1763:18)
at Module._compile (node:internal/modules/cjs/loader:1804:20)
at Object..js (node:internal/modules/cjs/loader:1961:10)
at Module.load (node:internal/modules/cjs/loader:1553:32)
at Module._load (node:internal/modules/cjs/loader:1355:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
at loadCJSModuleWithModuleLoad (node:internal/modules/esm/translators:326:3)
at ModuleWrap. (node:internal/modules/esm/translators:231:7)
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26

src/app/modules/map/ol/lib/charts/chart-utils.ts

(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files
(Use node --trace-warnings ... to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 10.5.0

/.eslintrc:2
"root": true,
^

SyntaxError: Unexpected token ':'
at wrapSafe (node:internal/modules/cjs/loader:1763:18)
at Module._compile (node:internal/modules/cjs/loader:1804:20)
at Object..js (node:internal/modules/cjs/loader:1961:10)
at Module.load (node:internal/modules/cjs/loader:1553:32)
at Module._load (node:internal/modules/cjs/loader:1355:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
at loadCJSModuleWithModuleLoad (node:internal/modules/esm/translators:326:3)
at ModuleWrap. (node:internal/modules/esm/translators:231:7)
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26

src/app/modules/skresources/components/charts/chartlist.ts

(node:2) ESLintIgnoreWarning: The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignore-files
(Use node --trace-warnings ... to show where the warning was created)

Oops! Something went wrong! :(

ESLint: 10.5.0

/.eslintrc:2
"root": true,
^

SyntaxError: Unexpected token ':'
at wrapSafe (node:internal/modules/cjs/loader:1763:18)
at Module._compile (node:internal/modules/cjs/loader:1804:20)
at Object..js (node:internal/modules/cjs/loader:1961:10)
at Module.load (node:internal/modules/cjs/loader:1553:32)
at Module._load (node:internal/modules/cjs/loader:1355:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
at loadCJSModuleWithModuleLoad (node:internal/modules/esm/translators:326:3)
at ModuleWrap. (node:internal/modules/esm/translators:231:7)
at ModuleJob.run (node:internal/modules/esm/module_job:437:25)
at async node:internal/modules/esm/loader:639:26


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@msallin msallin changed the title feat(charts): add In Bounds filter to chart list feat(charts): add In view filter to chart list Jun 26, 2026
@msallin msallin marked this pull request as ready for review June 26, 2026 22:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/modules/map/ol/lib/charts/chart-utils.ts`:
- Around line 41-57: isChartInView currently calls intersects directly, so
wrapped viewport extents crossing the antimeridian can be misclassified; update
this helper to detect a wrapped extent and split or normalize it into two
longitude ranges before testing overlap. Keep the existing global-chart fallback
for undefined bounds, and add a regression test around isChartInView covering a
dateline-crossing view to verify charts near +/-180 are classified correctly.

In `@src/app/modules/skresources/components/charts/chartlist.html`:
- Around line 107-117: The icon-only action buttons in chartlist.html no longer
have an explicit accessible name, so add an aria-label or visible text to both
button elements near showChartLayers and the adjacent action button. Keep the
existing matTooltip if desired, but ensure each control has a unique,
descriptive label for assistive tech that matches its action.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d12843b-161a-4c34-81e6-8f7644b50a2e

📥 Commits

Reviewing files that changed from the base of the PR and between b18f036 and d0ed9c9.

📒 Files selected for processing (4)
  • src/app/modules/map/ol/lib/charts/chart-utils.spec.ts
  • src/app/modules/map/ol/lib/charts/chart-utils.ts
  • src/app/modules/skresources/components/charts/chartlist.html
  • src/app/modules/skresources/components/charts/chartlist.ts

Comment on lines +41 to +57
* Limitation: this is a plain min/max overlap test and does not account for a
* viewport that crosses the antimeridian (+/-180 longitude); near the dateline a
* chart may be wrongly included or excluded.
*
* Example:
* bounds=[10, 40, 20, 50], extent=[15, 45, 30, 60] -> true (overlap)
* bounds=[10, 40, 20, 50], extent=[30, 45, 40, 60] -> false (disjoint)
* bounds=undefined, extent=[...] -> true (global chart)
*/
export function isChartInView(
bounds: number[] | undefined,
extent: Extent
): boolean {
if (!Array.isArray(bounds) || bounds.length !== 4) {
return true;
}
return intersects(bounds, extent);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle wrapped view extents before intersecting.

This helper is documented to return wrong results when the viewport crosses the antimeridian, so the new In view filter will misclassify charts for dateline-adjacent views. Split wrapped extents into two longitude ranges (or normalize them first) before calling intersects, and add a regression test for that case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/modules/map/ol/lib/charts/chart-utils.ts` around lines 41 - 57,
isChartInView currently calls intersects directly, so wrapped viewport extents
crossing the antimeridian can be misclassified; update this helper to detect a
wrapped extent and split or normalize it into two longitude ranges before
testing overlap. Keep the existing global-chart fallback for undefined bounds,
and add a regression test around isChartInView covering a dateline-crossing view
to verify charts near +/-180 are classified correctly.

Comment on lines 107 to +117
<button
mat-button
mat-icon-button
(click)="showChartLayers(true)"
matTooltip="Re-order Charts"
matTooltipPosition="above"
>
<mat-icon>import_export</mat-icon>
Re-order
</button>

<button
mat-button
mat-icon-button

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore an accessible name for the icon-only actions.

Changing these controls to icon-only buttons removes their explicit label for assistive tech. Add an aria-label (or visible text) for both actions; matTooltip is not enough here.

Suggested fix
       <button
         mat-icon-button
+        aria-label="Re-order Charts"
         (click)="showChartLayers(true)"
         matTooltip="Re-order Charts"
         matTooltipPosition="above"
       >
         <mat-icon>import_export</mat-icon>
       </button>

       <button
         mat-icon-button
+        aria-label="Add Chart"
         matTooltip="Add Chart"
         matTooltipPosition="above"
         [matMenuTriggerFor]="addchartmenu"
       >
         <mat-icon>add</mat-icon>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
mat-button
mat-icon-button
(click)="showChartLayers(true)"
matTooltip="Re-order Charts"
matTooltipPosition="above"
>
<mat-icon>import_export</mat-icon>
Re-order
</button>
<button
mat-button
mat-icon-button
<button
mat-icon-button
aria-label="Re-order Charts"
(click)="showChartLayers(true)"
matTooltip="Re-order Charts"
matTooltipPosition="above"
>
<mat-icon>import_export</mat-icon>
</button>
<button
mat-icon-button
aria-label="Add Chart"
matTooltip="Add Chart"
matTooltipPosition="above"
[matMenuTriggerFor]="addchartmenu"
>
<mat-icon>add</mat-icon>
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/modules/skresources/components/charts/chartlist.html` around lines
107 - 117, The icon-only action buttons in chartlist.html no longer have an
explicit accessible name, so add an aria-label or visible text to both button
elements near showChartLayers and the adjacent action button. Keep the existing
matTooltip if desired, but ensure each control has a unique, descriptive label
for assistive tech that matches its action.

@joelkoz

joelkoz commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Thanks @msallin — this is a clean, well-tested addition, and I really like that you documented the antimeridian limitation right in the JSDoc rather than papering over it. The isChartInView test coverage (overlap / containment / edge-touch / disjoint / malformed bounds) is exactly the kind of regression guard I like to see. This is a real quality-of-life win for users running large multi-region chart libraries.

Two things before I merge:

  1. Accessible labels on the toolbar buttons. Switching Re-order and Add to icon-only mat-icon-button to fit the new toggle on one row dropped their accessible names — a matTooltip isn't read as a label by assistive tech. Could you add an aria-label to both buttons (matching the action)? CodeRabbit flagged the same thing inline.

  2. Antimeridian — your call on scope. The ±180° gap you documented is fine to land as-is in my book, since the worst case is a chart being mis-listed near the dateline, not a bad line on the chart. If you'd rather close it out here, the seam is right where you noted in isChartInView — detect a wrapped extent and split it into two longitude ranges before the intersects test, plus a regression test for a dateline-crossing view. Equally happy to take it as a follow-up — just let me know which way you want to go.

Once the button labels are in I'm good to merge. Thanks again for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: filter chart list to charts within current map viewport

2 participants