Class JLineGraph

All Implemented Interfaces:
SettingsChangeListener, ImageObserver, MenuContainer, Serializable, Accessible

public class JLineGraph extends JPanel implements SettingsChangeListener
Reusable JFreeChart-based line chart component for visualizing student assessment progress.

This component is shared across all assessment pages (Braille, Abacus, iOS, ScreenReader, etc.) to display time-series data showing skill progression over multiple sessions. It supports three primary visualization modes:

Visual Design and Rendering:

  • Background bands: Colored horizontal bands indicate score ranges to aid interpretation:
    • Red band: -0.25 to 0.5 (minimal/no proficiency)
    • Orange bands: 0.5–1.5, 1.5–2.5 (emerging skills)
    • Yellow band: 2.5–3.5 (developing proficiency)
    • Green band: 3.5–4.5 (mastery/proficient)
  • Rendering jitter: A configurable visual jitter of ±0.1 is applied to plotted points via addJitter(double) to reveal overlapping data points. This is a display-only transformation and does not modify persisted values. Jitter can be:
  • Color palette: Consistent color-blind friendly palette used for series rendering:
    • PALETTE_HEX: Hex color strings for HTML legend generation (8 colors)
    • PALETTE: AWT Color objects for JFreeChart rendering (8 colors matching PALETTE_HEX)

Typical Workflow for Assessment Pages:

  1. Page fetches recent sessions from database via Database.fetchLatestAssessmentResultsWithDates(java.lang.String, java.lang.String, int)
  2. Page calls updateWithGroupedDataByDate(java.util.List, java.util.List, String[], String[]) to populate chart
  3. On submit, page calls saveGroupedCharts(java.nio.file.Path, String, int, int) to export PNG images
  4. Page generates Markdown/HTML reports linking to the exported plots

Export and Persistence:

Accessibility:

  • ChartPanel accessible name set to "Skill progression chart"
  • Tooltips enabled showing coordinate values on hover
  • Keyboard navigation supported through JFreeChart's default ChartPanel behavior

Settings Integration: Implements SettingsChangeListener to respond to jitter configuration changes at runtime without requiring application restart.

See Also:
  • Field Details

    • PALETTE_HEX

      public static final String[] PALETTE_HEX
      Public color palette (hex) for HTML legends and consistency across pages.
    • PALETTE

      public static final Color[] PALETTE
      Public color palette as AWT Color objects for chart rendering.
  • Constructor Details

    • JLineGraph

      public JLineGraph()
      Create a new JLineGraph with default styling and an empty dataset.
  • Method Details

    • settingsChanged

      public void settingsChanged()
      Description copied from interface: SettingsChangeListener
      Invoked when application settings have been changed and persisted. Implementations should read the desired values from the Settings helper and update any runtime state accordingly.
      Specified by:
      settingsChanged in interface SettingsChangeListener
    • setJitterEnabled

      public void setJitterEnabled(boolean enabled)
      Enable or disable rendering jitter at runtime.
      Parameters:
      enabled - true to enable jitter, false to draw raw values
    • isJitterEnabled

      public boolean isJitterEnabled()
      Query whether rendering jitter is currently enabled.
      Returns:
      true when jitter is enabled, false otherwise
    • setJitterDeterministic

      public void setJitterDeterministic(boolean deterministic)
      Enable/disable deterministic (seeded) jitter. When enabled, jitter will be generated from a java.util.Random seeded with jitterSeed (or 0 when seed is null).
      Parameters:
      deterministic - true to use a seeded RNG, false to use non-deterministic RNG
    • isJitterDeterministic

      public boolean isJitterDeterministic()
      Query whether deterministic jitter is enabled.
      Returns:
      true when deterministic (seeded) jitter is enabled
    • setJitterSeed

      public void setJitterSeed(Long seed)
      Set the seed used when deterministic jitter is enabled. Pass null to clear the seed (will use 0 when a deterministic RNG is created).
      Parameters:
      seed - seed value or null to clear
    • getJitterSeed

      public Long getJitterSeed()
      Return the currently configured jitter seed or null when unset.
      Returns:
      configured seed value or null when not set
    • updateWithData

      public void updateWithData(List<List<Integer>> allSkillValues)
      Replace the current dataset with the provided list of skill value series. Each inner list represents a single session and must contain NUMBER_OF_SKILLS entries.
      Parameters:
      allSkillValues - list of sessions where each session is a list of integer skill values (older sessions first)
    • updateWithGroupedData

      public void updateWithGroupedData(List<List<Integer>> allSkillValues, String[] partCodes)
      Update the component with grouped plots. Each group is determined by the prefix of the part code (e.g. 'P1' from 'P1_1'). For each group we render a separate small chart stacked vertically.
      Parameters:
      allSkillValues - list of sessions (older first) where each session is a list of integer skill values
      partCodes - array of part codes aligned with columns in each session row
    • updateWithGroupedDataByDate

      public void updateWithGroupedDataByDate(List<LocalDate> dates, List<List<Integer>> rows, String[] partCodes)
      Plot grouped data over time. Dates are used as the X axis (oldest first). Each skill within a group is drawn as its own line (one series per skill) with point markers and a color-blind friendly palette. Legend placed in the upper-right corner.
      Parameters:
      dates - chronological list of session dates (oldest first)
      rows - list of session rows where each row is a list of integer scores
      partCodes - array of part codes aligned with the columns in each row
    • updateWithGroupedDataByDate

      public void updateWithGroupedDataByDate(List<LocalDate> dates, List<List<Integer>> rows, String[] partCodes, String[] partLabels)
      Plot grouped data over time with optional human-friendly labels. Each provided partCodes entry maps to a column index inside rows and (optionally) a friendly label supplied in partLabels. The dates list must be ordered oldest-first and must be parallel to the rows list.
      Parameters:
      dates - chronological list of session dates (oldest first)
      rows - list of session rows where each row is a list of integer scores
      partCodes - array of part codes aligned with the columns in each row
      partLabels - optional human friendly labels parallel to partCodes
    • saveGroupedCharts

      public Map<String,Path> saveGroupedCharts(Path dir, String baseName, int width, int heightPerGroup) throws IOException
      Save each grouped subchart as an individual PNG file. The method writes files named {baseName}-{group}.png into the provided directory and returns a map of group -> written path. Caller must ensure grouped data has been rendered (updateWithGroupedData called) prior to invoking this.
      Parameters:
      dir - directory to write files into
      baseName - base filename (no extension) to prefix each file
      width - image width in pixels
      heightPerGroup - per-group image height in pixels
      Returns:
      ordered map of group id to written file path
      Throws:
      IOException - on I/O error
    • showEmptyGrouped

      public void showEmptyGrouped(String[] partCodes)
      Show an empty grouped chart using the provided part codes. This will render one row of zeros sized to the number of parts so the UI shows grouped axes and placeholders even when no session data exists yet.
      Parameters:
      partCodes - array of part codes used to determine the number of columns
    • saveChart

      public void saveChart(Path outputPath, int width, int height) throws IOException
      Save the current chart to a PNG file. If the chart is empty this will still export the rendered chart panel contents.
      Parameters:
      outputPath - path to write the PNG file to
      width - image width in pixels
      height - image height in pixels
      Throws:
      IOException - if writing fails