Skip to content

Assorted scale fixes and improvements#90

Merged
jonmmease merged 29 commits into
mainfrom
jonmmease/integration_updates
Jul 21, 2025
Merged

Assorted scale fixes and improvements#90
jonmmease merged 29 commits into
mainfrom
jonmmease/integration_updates

Conversation

@jonmmease

Copy link
Copy Markdown
Owner

Assorted scale updates and improvements I made while working on integrating avenger-scales with vegafusion. Didn't get all the way there, but these are generally useful updates.

jonmmease and others added 29 commits June 28, 2025 17:24
- Renamed all scale type `new` methods that return `ConfiguredScale` to `configured`
- Renamed LinearScale::new_color to LinearScale::configured_color for consistency
- Removed #[allow(clippy::new_ret_no_self)] attributes as they're no longer needed
- Updated all usage sites including examples, tests, and internal macros
- Applied consistent naming convention across all scale types:
  - LinearScale::configured()
  - QuantizeScale::configured()
  - ThresholdScale::configured()
  - QuantileScale::configured()
  - BandScale::configured()
  - PointScale::configured()
  - SymlogScale::configured()
  - OrdinalScale::configured()
  - LogScale::configured()
  - PowScale::configured()

This change improves API clarity by making it explicit that these methods
return a configured scale instance rather than the scale type itself.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added comprehensive documentation as docstrings on each scale struct describing
all available configuration options, their types, defaults, and behavior:

- LinearScale: clamp, range_offset, round, nice
- QuantizeScale: nice
- ThresholdScale: no options
- QuantileScale: no options
- BandScale: align, band, padding_inner, padding_outer, round, range_offset
- PointScale: align, padding, round, range_offset
- SymlogScale: constant, clamp, range_offset, round, nice
- OrdinalScale: no options
- LogScale: base, clamp, range_offset, round, nice
- PowScale: exponent, clamp, range_offset, round, nice

This documentation makes the scale configuration options discoverable through
IDE tooltips and generated documentation, improving the developer experience.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement scale_type() method for all scale types, returning Vega-compatible
scale type names in lowercase:
- linear, pow, symlog, log
- quantize, quantile, threshold
- band, point, ordinal

This provides a standard way to identify scale types programmatically.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement invert() method on ScaleImpl trait and ConfiguredScale that:
- Accepts ArrayRef input and returns ArrayRef output
- Parallels the scale() method pattern for consistency
- Provides efficient array-based inverse transformations
- Handles type conversions internally (casts to Float32)

This addresses the feature request for VegaFusion's DataFusion UDF
implementation, eliminating the need for scalar loops and dependency
on avenger_common::ScalarOrArray.

Implemented for scales that support inversion:
- LinearScale
- PowScale
- LogScale
- SymlogScale

Added comprehensive tests to verify the new functionality works
correctly with different input types and scale configurations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ansformations

This commit adds a new normalize() method to ConfiguredScale that applies the nice
domain transformation and returns a new scale with the nice option disabled.

Changes:
- Add compute_nice_domain() method to ScaleImpl trait with default implementation
- Implement compute_nice_domain() for LinearScale, LogScale, PowScale, SymlogScale, and QuantizeScale
- Add normalize() method to ConfiguredScale that applies nice transformation and disables nice option
- Add comprehensive tests for normalize() method with different nice option values
- Update doctest imports to use correct module paths

The normalize() method enables callers to easily retrieve nice domain extents by calling
scale.normalize()?.numeric_interval_domain(), providing a scale with the nice domain
applied and the nice option disabled to avoid repeated calculations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add zero boolean option to all scale configurations (linear, log, pow, symlog, quantize)
- Implement apply_normalization methods that handle both zero and nice transformations
- Zero extension applied before nice calculations for proper ordering
- Zero behavior: both positive → set min to zero, both negative → set max to zero
- LogScale ignores zero option (invalid in logarithmic space)
- Power and Symlog scales delegate to LinearScale for normalization
- Update normalize() method to apply both zero and nice transformations
- Add comprehensive tests for zero functionality and combined zero+nice behavior
- Update documentation to include zero option for all scale types

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Create TimeScale struct implementing ScaleImpl trait
- Support Date32, Date64, and Timestamp Arrow types
- Implement temporal to numeric scaling with proper null handling
- Add timezone configuration option (not yet connected)
- Include basic tests for Date32 and Timestamp scaling
- Add new error types for temporal operations

This is the foundation for time scale support in avenger-scales,
addressing limitations in Vega's local/UTC-only approach by preparing
for configurable timezone support.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Implement timezone configuration and parsing (IANA timezones, UTC, local)
- Add nice interval generation with calendar-aware boundaries
- Implement tick generation algorithm with D3-inspired interval hierarchy
- Add comprehensive time interval system (millisecond to year)
- Support calendar arithmetic (month/year offsets, DST handling)
- Add tests for nice domains, tick generation, and timezone parsing

Calendar features:
- Interval hierarchy: ms, sec, min, hour, day, week, month, year
- Smart interval selection based on domain span and target tick count
- Floor/ceil operations to interval boundaries
- Proper handling of variable month lengths and leap years
- Week start configuration support (future enhancement)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add numeric to temporal inversion for all supported types
- Handle null values properly in inversion
- Support Date32, Date64, and all Timestamp variants
- Add comprehensive test for scale inversion
- Create helper function for optional millisecond arrays

The invert method reverses the scale operation, converting
numeric range values back to temporal domain values.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add safe time construction wrappers that never panic
- Handle spring-forward gaps by jumping to next valid hour
- Handle fall-back overlaps with configurable strategies
- Update interval operations (floor, offset) for DST safety
- Fix tick generation to avoid duplicates during fall-back
- Add DST transition detection utilities
- Ensure nice domain calculations are DST-safe

DST handling features:
- DstTransition enum to detect spring-forward/fall-back
- DstStrategy enum for ambiguous time resolution
- safe_with_hour() for DST-aware hour setting
- safe_and_hms() for creating times during transitions
- Actual duration calculation for intervals

Tests added:
- Spring forward transition (2:00 AM -> 3:00 AM)
- Fall back transition (repeated 1:00 AM hour)
- Tick generation across DST boundaries
- Non-DST timezone verification

This ensures TimeScale operations never panic due to DST transitions
and provides predictable behavior for all edge cases.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add compute_actual_duration_millis() to calculate true duration across DST transitions
- Update scale() method to use actual duration when DST transitions occur in domain
- Update invert() method with iterative refinement for accurate DST-aware inversion
- Update scale_timestamp_values() to handle all timestamp types with DST awareness
- Add comprehensive tests for spring-forward and fall-back scaling scenarios
- Ensure visual accuracy: 3-hour actual duration during spring-forward, 5-hour during fall-back

All DST tests passing. Scale operations now correctly handle domains spanning DST transitions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…mpletion

- Mark DST Phase 5 as complete (scale operations now DST-aware)
- Update checkboxes for completed calendar-aware algorithms
- Add summary of remaining work organized by priority
- Update status to reflect low risk level now that core functionality is complete

Remaining high priority work:
- Implement tick_format() method for formatting
- Handle remaining edge cases (leap years, month lengths)
- Add tests for European and Southern hemisphere DST

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
The plan should remain in the ignored tasks/ directory, not tracked in git.
- Create TemporalTickFormatter that adapts format based on tick interval
- Override scale_to_string() to use custom formatter for temporal values
- Format strings change based on interval: years show %Y, days show %b %d, hours show %H:%M
- Handle all temporal types: Date32, Date64, Timestamp with/without timezone
- Add comprehensive test for tick formatting at different time scales
- Integrate with existing coerce/format system by implementing DateFormatter, TimestampFormatter, and TimestamptzFormatter traits

The formatter automatically selects appropriate format strings:
- Milliseconds: %H:%M:%S%.3f
- Seconds: %H:%M:%S
- Minutes/Hours: %H:%M
- Days/Weeks: %b %d
- Months: %B
- Years: %Y

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix band scale rounding to match Vega's behavior for pixel-perfect rendering
- When round=true and range starts at 0 with no padding, keep start position at 0 to avoid sub-pixel shifts
- Update bandwidth calculation to use floored step value when rounding is enabled
- Add comprehensive tests to verify rounding behavior across various ranges and with padding
- Update existing test to reflect the new Vega-matching behavior

This fixes pixel-diff discrepancies between Vega and VegaFusion when using band scales
with the round option enabled.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
  - Remove special case that forced start position to 0 when round=true
  - Apply alignment offset consistently for all ranges, matching Vega's implementation
  - Fix 2-pixel offset and bandwidth discrepancy (38px vs 37px) reported in bug
  - Update tests to reflect correct Vega-matching positions
  - Add test case that verifies exact position match with Vega for 8-band scenario

  This fully resolves the pixel-diff discrepancies between Vega and VegaFusion
  band scales with round option enabled.
- Remove camelCase support from band scale (paddingInner, paddingOuter)
- Add comprehensive option validation infrastructure:
  - OptionConstraint enum for various validation rules (Boolean, Float, ranges, etc.)
  - OptionDefinition struct to define valid options with constraints
  - Required trait method option_definitions() returning valid options
  - Default validate_options() implementation using definitions
- Implement option_definitions() for all scale types with proper constraints:
  - band: align, band, padding, padding_inner, padding_outer, round, range_offset
  - linear: clamp, range_offset, round, nice, zero, default
  - log: base (custom validator), clamp, range_offset, round, nice, zero, default
  - point: align, padding, round, range_offset
  - pow: exponent, clamp, range_offset, round, nice, zero, default
  - symlog: constant, clamp, range_offset, round, nice, zero, default
  - quantile: default
  - quantize: nice, zero, default
  - ordinal: default
  - threshold: default
  - time: timezone, nice, interval, week_start, locale, default
- Add detailed documentation for validation system
- Fix clippy warnings and formatting issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
  Implements Vega-compatible padding for linear, log, pow, and symlog scales.
  Padding expands the scale domain by the specified number of pixels on each
  side of the range, applied before zero and nice transformations.

  - Add padding option (NonNegativeFloat) to all quantitative scales
  - Implement scale-specific padding algorithms:
    - Linear: Direct domain expansion from center
    - Log: Transform to log space, apply padding, transform back
    - Pow: Transform to power space, apply padding, transform back
    - Symlog: Transform to symlog space, apply padding, transform back
  - Update symlog transforms to use ln_1p/exp_m1 for numerical stability
  - Reset padding to 0 in normalized scales to prevent double application
  - Handle non-numeric ranges gracefully by ignoring padding
  - Add comprehensive tests for all scale types

  The implementation matches Vega's behavior where padding expands the domain
  to accommodate pixel margins before other domain adjustments.
- Modified normalize() to check scale's supported options before adding
- Only sets 'zero', 'nice', and 'padding' if they're in option_definitions()
- Prevents validation errors for scales that don't support these options
- Added comprehensive tests to verify correct behavior for different scale types

This fixes the issue where point, band, and ordinal scales were receiving
unsupported options like 'zero' and 'nice' during normalization, which
caused validation errors in VegaFusion.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Modified normalize() to check scale's supported options before adding
- Only sets 'zero', 'nice', and 'padding' if they're in option_definitions()
- Prevents validation errors for scales that don't support these options
- Added comprehensive tests to verify correct behavior for different scale types

This fixes the issue where point, band, and ordinal scales were receiving
unsupported options like 'zero' and 'nice' during normalization, which
caused validation errors in VegaFusion.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace individual directory builds with workspace build
- Remove references to non-existent directories (avenger-vega, scatter-panning)
- Use standard cargo build --workspace command

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace format!("Unsupported image URL: {}", s) with
format!("Unsupported image URL: {s}") to satisfy
clippy::uninlined_format_args lint

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Replace all format!() calls to use inline variable syntax
(e.g., format!("{}", var) -> format!("{var}")) to satisfy
clippy::uninlined_format_args lint in newer Rust versions.

Changes made in:
- avenger-scales: format_num, scalar, band, coerce, time, mod
- avenger-app: app.rs
- avenger-winit-wgpu: file_watcher.rs, lib.rs
- examples/wgpu-winit: util.rs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fix remaining clippy::uninlined_format_args warnings in:
- examples/wgpu-scales/src/util.rs
- examples/iris-pan-zoom/src/util.rs

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@jonmmease jonmmease merged commit c38e448 into main Jul 21, 2025
1 check passed
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.

1 participant