Skip to content

localize charts use fractional values on a percent axis and fail for sparse data #674

Description

@SuhasSrinivasan

localize charts use fractional values on a percent axis and fail for sparse data

Summary

modkit localize --chart labels its y-axis as percent modified but plots fractional values, so a TSV value of 50 is rendered at 0.5. Chart construction also rejects a one-datum input, gives an invalid zero-width x-axis when multiple retained data share one offset, hides one-point series, and can create or truncate the chart destination before rendering later fails.

Severity

Severity: Medium — scientific reporting and output reliability

Rationale: The chart presents modification levels 100-fold below the numeric percent scale stated by its label and TSV, which can mislead visual interpretation. The underlying aggregation and TSV values remain correct. Singleton/empty-data behavior is a bounded functional and file-preservation defect rather than loss of primary scientific results.

User and scientific impact

  • Affected result or workflow: HTML charts produced by localize, especially feature sets with one retained offset or one datum per series.
  • Direction of error: 100-fold plotted-scale mismatch, failed or invisible sparse chart, and premature creation/truncation of a chart file.
  • Likely exposure: the scale mismatch affects every nonzero chart; sparse rendering depends on retained data geometry.
  • Detectability or workaround: users can compare the chart with the correct TSV percentages; adding unrelated offsets can avoid the singleton-axis error but changes the visualization.

Affected versions and environment

  • Affected release: modkit 0.6.4.
  • Development revision: 5cecc3fb3a9336068d9e3c68d5c08d678153dd2c.
  • Operating system and architecture used for verification: macOS on Apple Silicon.
  • Input format: bgzip-compressed, tabix-indexed bedMethyl plus BED regions and genome sizes.

Steps to reproduce

Create a minimal two-position bedMethyl input:

printf 'chr1\t9\t10\tm\t10\t+\t9\t10\t255,0,0\t10\t50.00\t5\t5\t0\t0\t0\t0\t0\nchr1\t10\t11\tm\t10\t+\t10\t11\t255,0,0\t10\t20.00\t2\t8\t0\t0\t0\t0\t0\n' > counts.bed
bgzip -c counts.bed > counts.bed.gz
tabix -f -p bed counts.bed.gz

printf 'chr1\t100\n' > genome.sizes
printf 'chr1\t10\t11\n' > region.bed

modkit localize counts.bed.gz \
  --regions region.bed \
  --genome-sizes genome.sizes \
  --window 2 \
  --min-coverage 0 \
  --out-file out.tsv \
  --chart chart.html \
  --force

Observed behavior

The TSV reports percent-modified values of 50 and 20, while the HTML chart series contains fractional values 0.5 and 0.2 on an axis labeled as percent modified.

For the one-datum case, repeat the setup with only the first bedMethyl row. The affected command errors when minmax() returns OneElement, and chart creation may leave a zero-byte destination. With two or more retained entries/series sharing the sole offset, raw minimum and maximum bounds are equal, producing a zero-width axis. A one-datum series is also configured without a visible point symbol.

For the empty-data case, use this region:

printf 'chr1\t50\t51\n' > region.bed

If chart.html already exists, rendering failure can truncate it because the destination is opened before the chart has been constructed successfully.

Control or independent oracle

  • The TSV's percent_modified values are the chart's stated y-axis unit: 50 and 20 must be plotted as 50 and 20.
  • A singleton x value requires distinct padded integer bounds and a visible marker.
  • An empty data set should return a clear error without creating or modifying the requested chart file.

Expected behavior

  • Chart series use percent_modified() values, matching the TSV's numeric percent quantity and the y-axis label, allowing normal serialization-precision differences.
  • A shared singleton offset uses saturating one-unit padding so the x-axis is valid.
  • Every singleton series has a visible marker; multi-point line behavior remains unchanged.
  • Empty chart data returns an error before chart creation.
  • Chart rendering completes successfully in memory before the destination is created or truncated.
  • TSV bytes and scientific aggregation remain unchanged.

Root-cause evidence

  • modkit-core/src/localise/util.rs: chart series use frac_modified() despite a percent label; x bounds are the raw min/max; series symbols are disabled; empty values are not rejected before bound construction.
  • modkit-core/src/localise/subcommand.rs: the chart destination is opened before get_plot completes.
  • Focused parent-red/fix-green checks reproduce the 0.5-versus-50 mismatch, singleton failure/invisibility, and empty-data side effect while confirming identical TSV output.

Proposed fix scope

  • Plot percent values.
  • Validate nonempty chart input, pad singleton integer bounds, and show a point marker for one-datum series.
  • Render the HTML string before opening the chart destination.
  • Keep this chart-only correction separate from localize region parsing, regional-query propagation, and window/offset geometry fixes.

Non-goals

  • No aggregation, coordinate, strand, threshold, schema, or TSV-byte change.
  • No transactional rollback coupling the TSV and chart outputs.
  • No complete-write, explicit flush/fsync, atomic-replacement, or compressed-output guarantee.
  • Empty data still returns an error rather than producing a blank chart.
  • No performance redesign.

Acceptance criteria

  • Values plotted on the percent axis represent the same numeric percent quantity as the TSV, allowing normal serialization-precision differences.
  • Empty, one-offset, mixed-singleton, and ordinary multi-offset fixtures have deterministic outcomes.
  • Singleton bounds are valid and singleton series have visible markers.
  • Empty data returns a clear error without creating or changing the chart target.
  • Existing multi-offset bounds, multi-point behavior, TSV bytes, and aggregation remain unchanged.
  • Focused chart/localize tests and the applicable full workspace test suite pass.

Reproduction artifacts

All fixtures are generated inline by the commands above.

Related work

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions