Revision history for HTML-D3 - Visualise various charts using D3

0.20	Wed Oct 14 08:01:05 PM EDT 2026
	[ Enhancements ]
	- render_histogram_snippet: new 'binned' option for pre-computed bins, so a
	  caller can bin server-side and have the exact edges it chose rendered
	  without the browser re-binning. Each element of the data arrayref is a
	  hashref with a count plus either a label or a numeric lo/hi pair (the
	  label is then derived as "[lo,hi)"); d3.bin() is not called on this path
	  and the supplied counts are authoritative. New 'scheme' option supplies
	  the bar fill (first colour of the D3 categorical scheme) when 'color' is
	  absent. New errors: 'Data must contain at least one bin', 'Each
	  pre-binned entry must be a hash reference', 'Bin count must be numeric',
	  'Bin count must not be negative', 'Each pre-binned entry must have a
	  label or numeric lo and hi'. The non-binned path is unchanged, including
	  its 'Data must be an array of numeric values' error, and the bin-label
	  tooltip now escapes the label.
	- render_scatter_chart_snippet: new per-point colour and size dimensions.
	  An optional fourth element per point may carry { series => Str, size => Num };
	  the new opts colour_by_series (colour from the series value, with a
	  legend), scheme (D3 categorical scheme, default 'tableau10'), series_col
	  and size_col (tooltip row labels) and radius_range ([min, max] pixel
	  radii mapped by area with d3.scaleSqrt over the observed size domain)
	  render them. Colour and size are independent: a point may carry both.
	  A point with no size value keeps a fixed radius and a point with no
	  series value keeps the default colour, so output for data with neither is
	  unchanged.
	- render_multi_series_line_chart_snippet: new snippet method that generates
	  an embeddable multi-series line chart fragment rather than a whole page,
	  so a host application can keep its own page chrome. Accepts an arrayref
	  of { name, data => [ { label, value }, ... ] } series and an optional
	  \%opts hashref: scheme, x_label, y_label, animated, responsive, id.
	  Series need not share labels (the X domain is the union, in first-seen
	  order), each series gets a line and its own dots, and a clickable legend
	  toggles each series. Returns { svg_id => 'chart', html => Str }.
	[ Documentation ]
	- The three capabilities above are documented in the POD, README.md and the
	  formal specification, with new tests in t/snippet_histogram.t,
	  t/snippet_scatter.t and t/snippet_multi_series_line.t.

0.19	Tue Oct 13 03:35:05 PM EDT 2026
	[ Enhancements ]
	- render_stacked_bar_snippet: new snippet method that generates an embeddable
	  D3 v7 stacked bar chart. Accepts an arrayref of hashrefs (one row per
	  category/segment combination) and an optional \%opts hashref. Opts: cat_col,
	  seg_col, val_col (row-hash key names, defaults 'cat'/'seg'/'val'), orient
	  ('v' vertical / 'h' horizontal, default 'v'), scheme (D3 categorical colour
	  scheme: tableau10/category10/set2/set3/paired, default 'tableau10'), x_label
	  (axis label, default ''), sort (undef / 'value' desc-total / 'label' alpha).
	  Multiple rows sharing the same cat+seg are summed on the Perl side before
	  encoding. Uses d3.stack() with d3.stackOrderNone / d3.stackOffsetNone.
	  Includes an interactive legend: clicking a segment label toggles that layer
	  on/off and re-stacks the visible segments. Tooltip shows category, segment,
	  and value on mouseover. Returns { svg_id => 'stacked_bar', html => Str }.
	- render_combo_snippet: new snippet method that generates an embeddable combo
	  chart — bars for one measure on the left Y axis, a line for another on the
	  right Y axis, sharing the same X (category/band) axis. Accepts an arrayref
	  of hashrefs and \%opts: x_col, bar_col, line_col (row-hash key names,
	  defaults 'x'/'bar_val'/'line_val'), bar_label, line_label (legend/axis
	  labels, defaults 'Bar'/'Line'), bar_color (default 'steelblue'), line_color
	  (default '#e15759'). Two independent d3.scaleLinear domains (left for bars,
	  right for line); bars rendered first, line path and dots on top. Both axes
	  labelled. Tooltip on bar and dot mouseover shows all three measures.
	  Returns { svg_id => 'combo_chart', html => Str }.
	- render_treemap_snippet: new snippet method that generates an embeddable D3
	  v7 treemap using d3.treemapSquarify tiling. Accepts two data formats:
	  (1) flat-path arrayref of hashrefs [{path => 'Root/A/X', value => N}, ...]
	  converted to a stratify hierarchy via d3.stratify() inside the generated JS;
	  (2) a nested hashref tree with name/children/value structure passed directly
	  to d3.hierarchy(). Format is detected by ref() of the first argument.
	  \%opts: path_sep (separator string for flat paths, default '/'), scheme
	  (categorical colour scheme, default 'tableau10'), label (bool, default 1;
	  shows node name inside each cell, clipped to cell width). Tooltip shows node
	  name and value. Returns { svg_id => 'treemap', html => Str }.
	- render_histogram_snippet: new snippet method that generates an embeddable
	  frequency histogram. Accepts an arrayref of numeric scalars (pre-extracted
	  by the caller — no column-extraction logic inside the module) and \%opts:
	  bins (positive integer, default 20; pass undef for automatic Sturges count:
	  ceil(log2(n)+1)), x_label (axis label, default ''), color (bar fill colour,
	  default 'steelblue'). Uses d3.bin() with xScale.ticks(numBins) thresholds.
	  Tooltip shows bin range and count on mouseover.
	  Returns { svg_id => 'histogram', html => Str }.

0.18	Wed Sep 23 11:10:09 AM EDT 2026
	[ Enhancements ]
	- render_bar_chart_snippet: new snippet method that generates an embeddable
	  D3 v7 bar chart. Accepts an array of [$label, $value] (or
	  [$label, $value, \%extra]) pairs and an optional \%opts hashref with keys:
	  orientation ('vertical'/'horizontal', default 'vertical'),
	  sort_bars ('value'/'label'/'none', default 'none'),
	  max_bars (int, default 0; collapses tail into "Other"),
	  color (CSS colour or 'categorical' for Tableau-10 palette, default
	  'steelblue'), show_values (bool, default 0; prints values on bars),
	  animated (bool, default 0; bars grow from baseline with 800 ms stagger;
	  respects prefers-reduced-motion), value_label (tooltip label, default
	  'Value'), x_label (bottom-axis label, default ''). Returns
	  { svg_id => 'bar_chart', html => Str }. Negative values are silently
	  converted to their absolute value; undef values are silently skipped.
	  X-axis labels are rotated -45 degrees when more than 8 bars are shown in
	  vertical orientation. Extra \%extra fields appear as additional tooltip
	  rows. Returns a Perl character string (UTF-8 flag set).
	- render_scatter_chart_snippet: new snippet method that generates an
	  embeddable D3 v7 scatter chart. Accepts an array of [$x, $y] (or
	  [$x, $y, \%extra]) pairs; both x and y must be numeric. Optional \%opts:
	  id (default 'scatter_chart'), color (CSS colour, default 'steelblue'),
	  x_label, y_label, value_label (tooltip label, default 'Value'), animated
	  (bool; circles fade in from opacity 0; respects prefers-reduced-motion),
	  responsive. Returns { svg_id => Str, html => Str }. Uses d3.scaleLinear
	  for both axes; includes esc() XSS helper in generated JavaScript.
	- render_table_snippet: new snippet method that generates an embeddable HTML
	  table with optional D3-powered column sorting. Accepts an array of array
	  references; the first row is the header. Optional \%opts: id (default
	  'data_table'), sortable (bool, default 1; adds clickable column headers
	  with D3 sort), caption (string; emits a <caption> element). Returns
	  { table_id => Str, html => Str } (note: table_id, not svg_id). All header
	  and cell content is HTML-escaped.
	- id opt: all five existing snippet methods (render_line_chart_snippet,
	  render_zoomable_line_chart_snippet, render_pie_chart_snippet,
	  render_heatmap_snippet, render_bar_chart_snippet) now accept an id key in
	  \%opts that overrides the default svg_id. The returned svg_id reflects the
	  override. Derived element ids (tooltip div, legend div, etc.) are computed
	  from the svg_id string. Fully backward-compatible: omitting id gives the
	  same default id as before.
	- responsive opt: all render methods now support a responsive display mode.
	  Pass responsive => 1 to the constructor to make all full-page renders emit
	  viewBox="0 0 W H" width="100%" height="auto" instead of fixed pixel
	  dimensions on the SVG element. Snippet methods additionally accept
	  responsive in their per-call \%opts (takes precedence over the constructor
	  setting). Default is 0 (fixed dimensions), so existing callers are
	  unaffected. Uses new private helper _svg_attrs().

	[ Bug Fixes ]
	- render_pie_chart_snippet: tooltip handler was missing HTML sanitisation.
	  Added esc() JavaScript helper (same as bar/heatmap/scatter snippets) and
	  applied it to slice labels and extra-field keys and values in the
	  mouseover handler. Prevents XSS when label or extra fields contain HTML
	  special characters.
	- render_line_chart_snippet: completed API documentation (Arguments, Options,
	  Return value, Errors, Side effects, API SPECIFICATION). Now accepts an
	  optional second argument \%opts with id and responsive keys.


0.17	Tue Sep 22 08:45:56 PM EDT 2026
	[ Bug Fixes ]
	- All render_*_snippet methods now return a Perl character string (UTF-8
	  flag set) in the {html} value instead of a UTF-8 byte string.  The
	  previous use of encode_json() produced octets; any caller that
	  concatenated the result with a character string (e.g. a Mojolicious
	  Template Toolkit response) would silently double-encode non-ASCII
	  characters, producing mojibake.  Fixed by replacing encode_json() with
	  a module-level JSON::MaybeXS instance configured with utf8 => 0, which
	  serialises to a character string.

0.16	Mon Sep 21 07:34:35 PM EDT 2026
	[ Enhancements ]
	- render_heatmap_snippet: new snippet method that generates an embeddable
	  D3 v7 grid heatmap. Accepts an array of [$x, $y, $value] triples and an
	  optional \%opts hashref with keys: color_scheme (YlOrRd/Blues/Greens/
	  Purples/RdPu/YlGnBu, default YlOrRd), x_label, y_label, val_label
	  (tooltip label, default 'Value'), show_values (print value in each cell,
	  auto-suppressed when cell < 28px), cell_padding (0-8 px gap, default 2),
	  legend (colour-scale bar, default on), animated (fade-in with
	  prefers-reduced-motion support, default off). Returns
	  { svg_id => 'heatmap', html => Str }. undef values are silently skipped;
	  duplicate (x, y) pairs: last write wins. Requires Scalar::Util
	  looks_like_number (already in core distribution).

0.15	Wed Sep 16 01:51:38 PM EDT 2026
	[ Enhancements ]
	- render_pie_chart, render_animated_pie_chart, render_pie_chart_snippet: new
	  optional C<separator> key in the C<%opts> second argument controls the
	  character shown between label and value in the legend (default C<'/'>).
	  render_pie_chart and render_animated_pie_chart now accept the optional
	  second hashref argument for the first time; existing callers passing only
	  C<$data> are unaffected.

0.14	Tue Sep 15 09:12:17 PM EDT 2026
	[ Enhancements ]
	- render_pie_chart_snippet: major upgrade. Now accepts an optional second
	  argument \%opts with six keys:
	  - animated (bool, default 0): fan slices in from arc-length 0 using
	    attrTween / d3.easeBackOut.overshoot(1.2) with 800 ms staggered
	    transitions; legend fades in after; respects prefers-reduced-motion
	  - donut (bool, default 0): inner radius 38% of outer; total sum shown
	    in the centre hole
	  - sort_slices ('value'|'label'|'none', default 'none'): sort order
	  - max_slices (int, default 0): collapse tail into an "Other" slice
	  - legend (bool, default 1): HTML legend panel with colour swatches
	  - color_scheme (string, default 'tableau10'): supports tableau10,
	    category10, set2, set3, paired
	  Negative values are silently converted to their absolute value; zero-
	  value slices are silently omitted. Optional \%extra hashref per data
	  point is shown as additional rows in the hover tooltip.
	  svg_id changes from 'chart' to 'pie_chart' for namespace clarity.
	  (Breaking change in svg_id value; all other behaviour is backward-
	  compatible when opts are omitted.)

0.13	Tue Sep 15 06:05:45 PM EDT 2026
	[ Enhancements ]
	- render_zoomable_line_chart_snippet: accept an optional second argument
	  { animated => 1 } to animate the initial line draw on page load.
	  The line traces itself left-to-right via stroke-dashoffset / d3.easeLinear
	  (1200 ms); data-point circles then fade in from opacity 0 over 300 ms
	  (after a 1200 ms delay so they appear only once the line reaches them).
	  Subsequent zoom and reset redraws are never animated.
	  Respects prefers-reduced-motion: when the user has requested reduced
	  motion the chart draws immediately at full opacity.
	  Omitting the second argument or passing { animated => 0 } gives the
	  existing behaviour unchanged (backward-compatible).

0.12	Sun Sep 13 08:14:05 AM EDT 2026
	[ Enhancements ]
	- Added render_animated_bar_chart(): bar chart with on-load grow animation —
	  each bar rises from the baseline using d3.transition() with an 800 ms
	  duration and a 100 ms per-bar stagger, so bars appear one after another
	  from left to right
	- Added render_animated_line_chart(): line chart with on-load draw animation —
	  the line traces itself left-to-right via the stroke-dashoffset technique with
	  d3.easeLinear (1500 ms), followed by data-point circles fading in from
	  opacity 0 once the line is complete
	- Added render_pie_chart(): full-page pie chart with percentage labels inside
	  each slice and a colour legend; slices coloured with d3.schemeCategory10
	- Added render_animated_pie_chart(): same as render_pie_chart() but slices fan
	  out from zero on page load via attrTween/d3.interpolate (1000 ms), with
	  percentage labels fading in once drawing is complete
	- Added render_pie_chart_snippet(): embeddable pie chart fragment returning
	  {svg_id, html} — no page shell, no D3 CDN tag; caller loads D3
	[ Tests ]
	- Test::HTML::T5 doesn't build on a number of platforms, so
	  remove the dependancy.  Hopefully this will be fixed at sometime.

0.11 Thu Sep 10 09:32:08 PM EDT 2026
	[ Bug Fixes ]
	- render_zoomable_line_chart_snippet: the y-axis lower bound was
	  hard-coded to 0, causing data points with negative Y values to be
	  plotted off-screen below the x-axis.  Changed to
	  Math.min(0, d3.min(newData, d => d.value)) so the domain extends
	  downward when the data contains negative values (e.g.
	  accounting-notation amounts).

0.10 Fri Aug 28 08:57:09 PM EDT 2026
	[ Enhancements ]
	- Added render_zoomable_line_chart_snippet(): embeddable line chart with brush-to-zoom — drag to select an x-axis range, animated transition to the zoomed view, incremental re-zoom on the zoomed view, and a Reset zoom button to restore the full dataset

0.09	Thu Aug 27 09:16:02 PM EDT 2026
	[ Enhancements ]
	- render_line_chart_snippet: accept an optional third element per data pair — a hashref of extra key/value pairs serialised as an "extra" property in the D3 data binding and appended as additional rows in the mouseover tooltip

0.08	Thu Aug 27 01:30:10 PM EDT 2026
	[ Enhancements ]
	- Use Params::Get
	- Added test dashboard
	- Added support for Object::Configure
	- Added render_line_chart_snippet() returning an embeddable {svg_id, html} fragment for use in Mojolicious TT (and similar) layouts without post-processing
	[ Bug Fixes ]
	- Escape </b> as <\/b> in all JavaScript tooltip .html() strings to fix html-tidy '<' + '/' + letter failures on cpantesters
	  https://www.cpantesters.org/cpan/report/8321add6-d178-11ef-8e88-00fe6d8775ea

0.07	Fri Jan 10 21:16:49 EST 2025
	Added support for interactive legends

0.06	Thu Jan  9 21:51:36 EST 2025
	Added support for legends

0.05	Wed Jan  8 21:04:21 EST 2025
	Added support for animated tooltips

0.04	Tue Jan  7 21:26:42 EST 2025
	Added support for line charts with tooltips
	Added support for multiple line charts with tooltips

0.03	Mon Jan  6 20:55:12 EST 2025
	Added line chart support
	Use JSON::MaybeXS instead of JSON

0.02	Mon Jan  6 10:13:11 EST 2025
	Fix CI breakage

0.01	Mon Jan  6 09:47:22 EST 2025
        First draft - POC: generates bar charts only
