Changelog¶
All notable changes to InsightDock Charts for C++ are documented here.
Format based on Keep a Changelog. Versioning follows Semantic Versioning.
[Unreleased]¶
Added¶
ChartExporter::capabilities()queries advertised native drawing support and compiled PDF availability without rendering. Built-in charts declareVectorCapability::Native; custom charts default toUnknownand may overridevector_capability().VectorFallback::Rejectrequires native SVG/PDF output:save()/encode()returnChartError::VectorUnsupportedwhendraw()declines, without rasterizing or replacing the destination file. Existing custom draw overrides continue to work. Void PDF embedding APIs throwstd::runtime_erroron strict rejection.- Const
ChartExporter::save()/encode()overloads, with documented exception contracts and installed-consumer/concurrent-export coverage.
Fixed¶
-
Documentation and public display names consistently use InsightDock Charts for C++ or InsightDock Charts. Existing API identifiers and package names retain their spelling.
-
Allowed custom-chart vector fallback now uses the regular raster supersampling, clone and allocation checks. Raster fallback render failures return
RenderFailure; partial standalone vector output is discarded. -
Supersampled raster exports preserve logical layout and scale all drawing primitives, including line widths, dash lengths, marker/bubble radii, rounded corners, borders, leader lines and independently sized data labels. Fonts retain their original TTF data. Exporting still uses a clone and does not change the caller's chart. Custom render-only charts retain the legacy path.
- Raster thick strokes now fill their interior with distance-based edge coverage instead of sparse parallel traces; translucent interiors blend once per segment. Gallery references reflect the corrected strokes.
-
Scatter/bubble now draw through
DrawTarget, enabling consistent supersampling and native SVG/PDF shapes and text instead of raster fallback. -
Pie callouts now use measured columns with vertical spacing, reserved title/legend areas and leaders routed outside the circle. Automatic radii reserve label width. Overfull columns keep larger slices first with stable input-order ties; blocks that cannot fit are omitted in full. Slice geometry and legend visibility remain independent of callout omission.
-
Linear nice axes now enclose the lower data extent as well as the upper extent, including one-tick hints. Positive scatter data no longer rounds inward and appears in the axis/legend margins; zero-based chart domains retain their zero baseline.
-
Pie, Cartesian and scatter/bubble legends measure individual entries, wrap labels/newlines, reserve right margins and pack top/bottom rows. Legends use at most one third of the canvas height and summarize excess entries as
+N more; their data stays visible. Scatter Top legends now render above the plot. Secondary-axis titles remain beside the axis with a right legend. -
Automatic pie center/radius options stay unresolved until rendering, reserving space for the title and legend and adapting to later canvas size changes. Existing gallery references updated for measured legend placement and automatic pie geometry.
-
Pie chart exports now share geometry and text wrapping through
DrawTarget, preserving explicit colors, alpha, strokes, gaps, legends, titles and in-slice labels in native SVG/PDF output. UTF-8 token wrapping preserves code points and labels longer than 159 bytes are no longer truncated. - Pie proportions normalize large finite values before summing; negative values count as zero. Fill opacity is clamped and multiplies the slice color alpha. Fully consumed gaps leave no stray geometry. Pie percentage labels and SVG coordinates ignore process locale.
- Legacy PDF report label wrapping also preserves UTF-8, and hidden small labels no longer shift subsequent callout angles.
- DateTime ticks now use UTC with checked timestamp conversion; Raw labels preserve fractions and value axes choose precision from tick spacing. Numeric tick/data labels ignore process locale.
- Top Cartesian legends render above the plot; category truncation removes whole UTF-8 code points.
- Exporters report failed writes as
BadPath, catch rendering/encoding exceptions, reject oversized vector dimensions and report unavailable raster/vector PDF asPdfUnsupported. Custom charts withoutclone()fail supersampling without modifying const input. - Font files exceeding 64 MiB are rejected before their contents are allocated/read.
- Installed CMake targets advertise C++17. pkg-config metadata is relocatable and includes feature
definitions and the optional
libpodofodependency. Third-party headers no longer trigger unused-function diagnostics in strict core builds.
Added¶
PieChart::draw()and a defaultDrawTarget::fill_sector()implementation for existing custom targets. The doughnut example exports PNG, native SVG and optional native PDF.CartesianOptions::custom_x_formatterfor continuous DateTime tick presentation.- Reliability regressions run in UTC, Karachi and New York process timezones.
- PDF CI job pinned to PoDoFo revision
00f86c8ad7b78430b25ad2c81edb704ae8daa6ea, with report examples and an installed PDF-enabled consumer. - Governance/acceptance guidance, corrected security trust boundaries, and bundled font license notices installed with the library. Roadmap reconciled with implemented features and expanded with reliability, axes/layout, analytics, accessibility and ecosystem milestones.
Changed¶
- Default vector PDF pie exports now use the chart's raster/SVG layout, including its background
and title. The explicit
PieVectorOptionsoverload ofpodofo::draw_chart_vector()and thedraw_pie()/draw_pie_slices()helpers retain the separate PDF report layout. SOVERSIONtracksmajor.minorat all versions to match the documented ABI policy. Rebuild applications when upgrading between minor releases; patch releases must preserve ABI.- CMake examples/tests default off when InsightDock Charts is included as a subdirectory.
- Date labels previously formatted in the machine's local timezone now default to UTC. Regenerate affected output baselines or supply a custom formatter for another timezone.
- Refresh the time-series gallery baseline for UTC labels.
1.0.0-rc1 — 2026-10-02 — first public release candidate¶
Release candidate for 1.0. The API is frozen for the feedback window except for fixes to problems found by outside users. Binary compatibility is not promised, only source compatibility.
Phase E — platforms and packaging¶
- CI matrix — Windows (MSVC, static and DLL), macOS and a shared-library Linux build, in addition to x86-64 and ARM64 Linux. (Added without a local MSVC/macOS run; the first GitHub run is the real test.)
- MSVC build flags —
/W4 /utf-8 /permissive-,/WXunderINSIGHTDOCK_WERROR; on Windows the DLL and all executables share one output directory so shared builds run from the build tree. - Gallery regression compares decoded pixels (≤ 8 per channel, ≤ 0.1 % of pixels) instead of encoded PNG bytes, so it no longer depends on the zlib/FPU of one platform. Test-only
tests/third_party/stb_image.h(never shipped). - Automated
find_packageconsumer test (find_package_consumerin CTest): installs the build to a scratch prefix and builds and runs an out-of-tree program, for static and shared builds. - Windows paths —
STBIW_WINDOWS_UTF8for PNG/JPEG output and a UTF-8-safeFont::load_from_file. - vcpkg port under
packaging/vcpkg/insightdock-charts(featuresfont-latin,podofo);SHA512is a placeholder until the first tag exists. scripts/check_version.sh(CI jobversion-consistency) fails if CMake, OpenWrtPKG_VERSIONand the vcpkg port disagree.
Phase D — chart features¶
- Step lines —
LineChartOptions::step/AreaChartOptions::step(StepMode::After,Before,Middle). - 100%-stacked bar (
BarMode::Stacked100, vertical and horizontal) and area (AreaChartOptions::stacked_percent): the value axis is fixed at 0–100% and labelled with%automatically. - Histogram —
compute_histogram()andmake_histogram_chart()(insightdock/histogram.h), aBarChartpreset with touching bars. - Colorblind-safe palette —
PaletteKind::ColorblindSafe(Okabe–Ito) and the newChartOptions::palette, which selects the color cycle for series added without an explicit color. - Reference lines and bands now work in horizontal bar charts (previously skipped silently):
Horizontalmeans a constant value (drawn vertically there),Verticala category position. - Multi-line text — chart titles, category labels and the x-axis title may contain
\n; each line is aligned independently and the margins grow to fit. - Non-finite values in stacked bars now count as 0 (they used to poison the stack).
- Not in 1.0: error bars (see ROADMAP).
Phase C — native vector output¶
- Native vector SVG and PDF for bar, line, area and combo charts (grouped/stacked/horizontal bars, splines, per-series dash and markers, translucent fills, data labels, reference lines/bands, secondary axis, rotated axis titles, legend).
ExportOptions::svg_backend = SvgBackend::Vectorandpdf_backend = PdfBackend::Vectornow apply to every Cartesian chart; only scatter still embeds a raster. Selectable text, scales to any size. New guide: Vector export. DrawTarget(include/insightdock/draw_target.h) — pixel-space drawing interface that Cartesian charts render through, withRasterTarget(PNG/JPEG; gallery output is byte-identical),detail::SvgTargetandpodofo::PdfTarget.Chart::draw(DrawTarget&)is a new virtual (default returnsfalse; all Cartesian charts implement it).- Doughnut cutout in vector pie output — SVG and PDF pies now honour
PieOptions::inner_radius_ratio(hole painted in the background color, as the raster path does).PieVectorOptionsgainsinner_radius_ratioandhole_color. Font::baseline_offset()andFont::ascent()are now public so non-raster targets place text exactly where the raster path does.
Changed (API, pre-1.0)¶
CartesianChart::PlotContext::buffer(ImageBuffer&) is nowtarget(DrawTarget&);detail::fill_bar/detail::draw_markertake aDrawTarget&;compute_plot_recttakes(width, height, font). CustomCartesianChartsubclasses draw withctx.target.draw_text(font, ...)instead offont.draw_text(ctx.buffer, ...).
Fixed¶
- Stacked
AreaChartclipped its top series — the y-axis was sized from the tallest single series instead of the per-category total (BarChartalready did this). Thearea_stackedgallery baseline was regenerated.
Added¶
- Per-series styling and missing-data handling (Phase B of the v1.0 plan).
DataSeries::style(SeriesStyle, newinclude/insightdock/series_style.h): optional per-seriesdash,marker,line_widthandfill_opacity; unset fields inherit the chart-wide options.PointMarkerShape/DashPatternmoved there fromchart_style.h(still included by it). New overloadsCartesianChart::add_series(label, values, color, SeriesStyle)andComboChart::add_line_series(label, values, color, SeriesStyle, secondary_axis). Legend swatches show the dash/marker for styled series. The hard-coded fill alphas (60/120/180) are now the defaults thatfill_opacityoverrides.GapPolicy(Gapdefault,Interpolate,Zero) onLineChartOptions/AreaChartOptions/ComboLineOptions: NaN, Inf and series shorter than the category list break the line (or are joined/zeroed) instead of always drawing as 0. Stacked areas treat a missing value as 0.lttb_decimatekeeps non-finite points so decimation doesn't hide gaps.- Fonts and text.
ChartOptions::font(FontDataPtrfrom the newload_font_data()/make_font_data()) makes font injection actually work for every chart (previouslyChart::font_was never read, soFONT_MODE=systemcould not render text). Text is decoded as UTF-8 (previously every byte above 127 was dropped); missing glyphs render as?;\nis handled inmeasure_width. New optional embedded Latin-1/Latin-Extended-A font (INSIGHTDOCK_FONT_LATIN/FONT_MODE=embedded-latin,embedded_font_latin.h). Font files are validated (≤ 64 MiB, parseable).CartesianOptions::x_label_mode(Auto/Rotate90/Truncate) for wide category labels. - Thread safety.
Chart::clone()(overridden by all built-in charts) letsChartExporterrender supersampled output from a scaled copy; exporting no longer mutates the chart, and concurrent exports of one chart are safe.Chartis now copyable (the unusedfont_member was removed). -
Versioning/ABI. Library
VERSION/SOVERSION(major.minorwhile 0.x), hidden default symbol visibility withINSIGHTDOCK_APIexport macros (Windows DLLs now export the API),SameMinorVersionpackage compatibility before 1.0,find_dependency(podofo)in the installed config when built with PoDoFo. Documented as source-compatible only. -
Log y-axis and continuous DateTime x-axis actually wired into charts —
Scale'sScaleType::Log/ScaleType::DateTimeinterpolation and tick generation (nice_ticks()) were already fully implemented and unit-tested, but no chart type ever exposed a way to use them:CartesianChart::render()always built a plain lineary_scale, and the x-axis was always evenly-spaced category slots. This left the "network traffic and time-series dashboards" use caseROADMAP.mdnames as the primary OpenWrt motivation forAreaChartunreachable —examples/time_series_line_example.ccfaked a time axis with hand-formatted date strings as category labels, which can't represent irregular/gapped real time deltas. CartesianOptions::y_scale_type(ScaleType, defaultLinear) — set toLogonBarChart/LineChart/AreaChart/ComboChartfor data spanning multiple orders of magnitude. NewScale::log_nice()(mirrorslinear_nice()) builds a power-of-10/1-2-5-mantissa "nice" domain; extends (never shrinks) to the actual data range, since log ticks don't necessarily reach the true min/max the way linear "nice" rounding does.CartesianOptions::begin_at_zerois ignored wheny_scale_type = Log(log(0) is undefined — forcing it would otherwise stretch the domain across hundreds of decades of empty ticks). Secondary (right) y-axis stays Linear-only in v1.CartesianOptions::x_scale_type(ScaleType, defaultLinear) plusCartesianChart::set_x_values()— setx_scale_type = DateTimeand supply Unix-timestampx_values()to position points by real value instead of index. OnlyLineChart/AreaChartopt in (newsupports_continuous_x_axis()hook, defaultfalse);BarChart/ComboChartsilently ignore it and keep category-slot positioning, since a fixed-width bar-per-category layout has no well-defined meaning for irregularly-spaced continuous x-positions.draw_axes_and_grid()draws niceTickFormat::DateTime-formatted ticks from the continuous domain instead of per-category labels when active.examples/time_series_line_example.ccand itsgallery_regression.cc/docs/gallery/time_series_line.pngcounterpart converted from hand-formatted date-string categories to real Unix timestamps viaset_x_values()+x_scale_type = DateTime.- New
examples/log_scale_example.cc. New unit tests:test_scale_log_nice_*(primitive),test_line_chart_log_y_axis_renders_and_spans_range,test_line_chart_datetime_x_axis_positions_by_value_not_index,test_bar_chart_ignores_datetime_x_scale_type,test_cartesian_x_values_roundtrip. - Horizontal bar chart —
BarOptions::orientation(BarOrientation::Verticaldefault,Horizontal) onBarChart: bars extend rightward from a category axis on the left instead of upward from one at the bottom, for rankings/leaderboards and long category names that would otherwise collide under a vertical x-axis. NewCartesianChart::is_horizontal()hook (defaultfalse, onlyBarChartoverrides it) makes the shared axis/grid/margin code indraw_axes_and_grid()/compute_plot_rect()orientation-aware: value ticks + vertical gridlines move to the bottom, category labels move to the left (left margin widens to fit the longest one), grouped/stacked bar geometry gets dedicateddraw_grouped_horizontal()/draw_stacked_horizontal()implementations (fill_bar()itself is already orientation-agnostic — just a rect fill).ReferenceLine/ReferenceBandoverlays are not supported in horizontal mode (v1) — their orientation semantics assume a vertical value axis, soBarChart::draw_overlay()is a no-op when horizontal rather than rendering them scrambled. Not available onComboChart(line-series geometry has no horizontal equivalent). Newexamples/bar_horizontal_example.cc; new unit teststest_bar_chart_horizontal_*. pkg-configsupport (cmake/insightdock.pc.in) —cmake --installnow also installslib/pkgconfig/insightdock.pc, for Meson and plain-Makefile consumers that don't use CMake'sfind_package(). Paths resolve viaCMAKE_INSTALL_FULL_*, so-DCMAKE_INSTALL_PREFIXmust be set at thecmake -Bconfigure step (not justcmake --install --prefix) for a custom prefix to be reflected correctly — documented in README.-
packaging/openwrt/Makefile— a standard OpenWrt feed package sample (package.mk-based,Build/Compileinvoking the project's own plain Makefile with cross toolchain variables), wired to the existingCHART_TYPES/EXPORT_FORMATS/FONT_MODEtrim knobs. Not built by this repo's CI; a starting point for downstream feeds. Seepackaging/openwrt/README.md. -
Data labels on marks — all 6 chart types —
DataLabelOptions(include/insightdock/chart_style.h:enabled,color,font_size,position,decimals,format) added as adata_labelsfield onPieOptions,BarOptions,LineChartOptions,AreaChartOptions,ComboLineOptions,ScatterOptions— defaultenabled = false, so existing renders are byte-identical until opted in. Bar/Line/Area/Combo reuse the sameTickFormat-based formatting as y-axis ticks (hoisted fromcartesian_chart.cc's file-localformat_y_tickintodetail::format_tick_value()/detail::format_label_value()in the newinclude/insightdock/detail/tick_format.h), so a label matches its axis's number formatting.PieChartgets a new in-slice value/percentage label atradius*0.65along each slice's midpoint angle, distinct from the existing external leader-line label.ScatterChartlabels the formatted Y value only (no per-point custom label in v1). - Bar rounded corners + border —
BarChart's previously-scattered private members (mode_/group_gap_/bar_gap_) promoted to aBarOptionsstruct (include/insightdock/bar_chart.h) with newcorner_radius,border_color(default transparent = no border), andborder_widthfields; oldset_mode()/set_group_gap()/set_bar_gap()setters kept as forwarders for source compatibility.ImageBuffer::fill_rounded_rect()(new primitive, mirrors the existingfill_circle/fill_sector4x4 supersample-AA technique via a rounded-box signed-distance-field) and a shareddetail::fill_bar()helper (include/insightdock/detail/bar_shape.h) apply the same rounding/border logic to bothBarChartandComboChart's bar series (which gained a parallelBarOptions bar_opts_). A rounded bar with a border draws a plain rectangular outline for v1 — a minor accepted overlap at the 4 corners. - Pie doughnut cutout —
PieOptions::inner_radius_ratio(0–1, default0) punches a hole in the pie's center down to the chart's background color after slices are drawn (PieChart::render()). Assumes a solid background, which is already the only backdrop this library supports. Raster (PNG/JPEG/PDF-raster-embed) only — the native vector PDF/SVG pie paths (podofo_vector.cc,svg_pie.cc) don't draw the cutout yet; tracked as a follow-up. - Line/area spline smoothing, dash patterns, point marker shapes —
LineChartOptions/AreaChartOptions/ComboLineOptionsgainspline(bool),dash(DashPattern=std::vector<float>of alternating on/off pixel lengths; empty = solid), andpoint_shape(PointMarkerShape:Circle/Square/Triangle/Cross);ScatterOptionsgainspoint_shape. These are chart-wide settings, not per-series. NewImageBuffer::draw_dashed_line()/draw_dashed_polyline()(dash phase carries continuously across polyline vertices) anddetail::catmull_rom_expand()(Catmull-Rom curve expansion ininclude/insightdock/detail/spline.h, used only for the stroke/fill geometry — markers and data labels still anchor to the original per-category points) anddetail::draw_marker()(include/insightdock/detail/marker.h) back all four chart types.AreaChartalso gains point markers (show_points, off by default) — it previously had none at all. - Reference lines & shaded bands —
CartesianOptions::reference_lines(std::vector<ReferenceLine>) andreference_bands(std::vector<ReferenceBand>) draw threshold lines/bands (e.g. an SLA line, a target range) behind a chart's own series, via a new shareddetail::draw_reference_lines()(include/insightdock/detail/reference_lines.h) called fromBarChart/LineChart/AreaChart's newdraw_overlay()overrides and from the top ofComboChart::draw_overlay()(ahead of its existing line-series drawing, so reference lines sit behind data but the combo's own lines still draw on top).ReferenceLine/ReferenceBandsupportHorizontal(y-value, optionally against the secondary axis) andVertical(fractional category-index) orientation. Required promotingCartesianChart::PlotContextfromprotectedtopublicso the shared overlay helper — outside the class hierarchy — can take it as a parameter. Not available onPieChart/ScatterChart, which have nodraw_overlay()hook. Pulls forward the Phase 1 "step lines and spline" and Phase 2 "basic annotations and trend lines" roadmap items — seeROADMAP.md. -
Doc site restructured from 5 flat nav entries into Concepts/Guides/Chart types (
mkdocs.yml,docs-site/) — closes the gap where the richest usage content in the repo (docs/API.md: error handling, theming, tick formatting, extending viadraw_overlay) existed only in the repo and was never reachable from the published MkDocs site. New pages:docs-site/concepts.md(class hierarchy, render pipeline,PlotContext/Scale, steps for adding a new chart type — narrative content that previously only existed inCLAUDE.md, which isn't a public doc);docs-site/guides/usage.md(transcludesdocs/API.md, same single-source-of-truth patterncomparison.mdalready used fordocs/COMPARISON.md);docs-site/guides/pdf-reports.md(newPdfReportBuilderwalkthrough — previously only a one-line type-table entry plus the example program);docs-site/guides/troubleshooting.md(real incidents: the stale-install ABI hazard, clangd false positives, MrDocs aarch64 emulation, gallery baseline updates — previously scattered acrossCHANGELOG.md/CLAUDE.md/DOCUMENTATION.mdwith no single discoverable FAQ);docs-site/chart-types/{pie,bar,line,area,combo,scatter}.md(full options table + rendered example per chart type, replacing the previous "raw MrDocs struct page or nothing" gap);docs-site/changelog.md(transcludesCHANGELOG.md).docs/API.mdtrimmed (doc-tooling setup steps now just point atDOCUMENTATION.mdinstead of duplicating them) and cross-linked to the new pages. -
CartesianOptions::min_y_labels/max_y_labels— two new integer fields (default0= unconstrained / default ~6) that control y-axis tick density on allCartesianChartsubclasses (BarChart,LineChart,AreaChart,ComboChart).max_y_labelspasses the value as the tick-count hint to bothScale::linear_nice(which sets the domain) andnice_ticks(which generates labels), giving approximately that many evenly-spaced, human-readable ticks.min_y_labelsdoes the same, and if the nice-rounding step produces fewer ticks than requested it retries with a doubled hint (best-effort; capped at that retry ifmax_y_labelsis also set). Setting both to the same value (e.g.min_y_labels = max_y_labels = 12) instructs the renderer to attempt exactly that many y-axis labels. Ifmin_y_labels > max_y_labelsthe values are swapped internally so the constraint is always valid. Works correctly with positive/negative/mixed data, float values,TickFormat,custom_y_formatter, andbegin_at_zero. Six new unit tests added (test_y_labels_*inunit_tests.cc). -
CartesianOptions::max_x_labels— integer field (default0= unconstrained) that caps the number of x-axis category labels rendered. When set, a second step ceilingceil(n / max_x_labels)is applied on top of the existing collision-avoidance skip, so the two constraints compose: collision avoidance prevents overlapping text;max_x_labelsindependently limits label count regardless of chart width. Useful for dense time-series categories (e.g. 100 daily date strings) where you want ~12 readable tick marks irrespective of pixel width. -
examples/time_series_line_example.cc— demonstrates a 100-point daily time-seriesLineChart: decimates 100 → 25 points withlttb_decimate_indexed, usesmax_x_labels = 12to show ~12 evenly-spaced date labels, and a standard y-axis label for pre-scaled GB values. Wired intoINSIGHTDOCK_BUILD_EXAMPLESin CMake; gallery image atdocs-site/img/gallery/time_series_line.png. -
ComboChartsecondary (right) y-axis —add_bar_series()/add_line_series()take an optionalsecondary_axisflag (defaultfalse, fully backward compatible); series flaggedtrueare scaled against an independent right-side axis instead of sharing the left one, for series with different units or magnitude (e.g. session count vs. average duration). Implemented as genericCartesianCharthooks (has_secondary_y_axis(),compute_y2_min()/compute_y2_max()) plusPlotContext::y_scale2/has_y2, so anyCartesianChartsubclass can opt in the same way later;BarChart/LineChart/AreaChartare unaffected since the hooks default to disabled. NewCartesianOptions::y2_axis_label/y2_tick_format/custom_y2_formattermirror the existing left-axis fields. Right margin widens automatically when a secondary axis is active. AddedFont::draw_text_rotated_cw()(right-axis label, reads top-to-bottom) alongside the existingdraw_text_rotated_ccw()(left-axis label); both now share oneblit_rotated()implementation. Covered by two newunit_testscases, demonstrated inexamples/all_charts_raster_pdf.cc's combo chart page, and added to the public gallery: newexamples/combo_chart_example.cc, acombo_chart.pngreference baseline indocs/gallery/(gallery_regressionCTest), and a section indocs-site/examples.md— closing the gap whereComboChart(added in Phase 0.6) had never been added to the gallery/regression baseline or docs site. ScatterChart(include/insightdock/scatter_chart.h,src/scatter_chart.cc) — scatter and bubble charts with continuous numeric x/y axes (not a category list, unlikeBarChart/LineChart). SetScatterPoint::radiusto enable the bubble variant; size scales betweenScatterOptions::min_bubble_radiusandmax_bubble_radius. Derives directly fromChartrather thanCartesianChartsince the x-axis must be continuous. Demonstrated inexamples/scatter_bubble_example.cc; covered bygallery_regressionandunit_tests.- Native SVG vector pie —
ExportOptions::svg_backend(SvgBackend::Rasterdefault,SvgBackend::Vector) mirrors the existingPdfBackendpattern.Vectormode emits real<path>/<circle>/<text>SVG forPieChart(src/svg_pie.cc) instead of a base64-inlined raster PNG; other chart types fall back to raster embed. Handles the single-100%-slice edge case with a<circle>(an arc with identical start/end points is degenerate). - LTTB decimation (
include/insightdock/decimate.h,src/decimate.cc) —lttb_decimate()and the category-index conveniencelttb_decimate_indexed()implement largest-triangle-three-buckets downsampling for reducing large line/area datasets to a target point count without losing visual shape (peaks/valleys), as a pre-processing step beforeLineChart/AreaChart. benchmarks/bench_render.cc— dependency-freestd::chronotiming harness (INSIGHTDOCK_BUILD_BENCHMARKSCMake option, defaultOFF). Covers line render at 1k/10k/100k points, bar render (50×10), pie render (20 slices), PNG encode, and PDF raster export. Baseline numbers recorded indocs/BENCHMARKS.md.docs-mrdocsCI job — runsmrdocs docs/mrdocs.ymland checksdocs/reference/index.htmlis produced (scripts/ci_docs_mrdocs.sh).- Gallery embedded in README — pie/bar/line/area sample images now render inline in
README.mdinstead of only being linked. docs/mrdocs-theme/wrapper.html.hbs— mrdocs 0.8.0's stock HTML generator ships with zero CSS (plain browser-default white-on-black text, no typography/table/code-block styling). This is a themed replacement for its one layout template (clean type, bordered/zebra-striped tables, code chips,prefers-color-schemedark mode) — copied over the installedmrdocsbinary's own template before each generation, both byscripts/ci_docs_mrdocs.shand the local steps indocs/DOCUMENTATION.md.--addonswould require vendoring mrdocs's entire internal helpers/partials tree just to change one file, so this overwrites the single layout in place instead.- One-stop MkDocs Material doc site (
mkdocs.yml,docs-site/,scripts/build_docs_site.sh) — pulled forward from the post-v1.0.0 plan inFUTURE.md: Home, Getting started, Examples (each example's full source transcluded fromexamples/*.ccviapymdownx.snippets, paired with its rendered output fromdocs/gallery/), API Reference (the MrDocs output copied in by the build script), and Comparison (transcludesdocs/COMPARISON.mddirectly — single source of truth). Built and published to GitHub Pages via.github/workflows/docs.yml. Verified visually with headless Chrome screenshots, not just a successfulmkdocs build.
Changed¶
-DBUILD_SHARED_LIBS=ON+-DINSIGHTDOCK_WITH_PODOFO=ONverified end-to-end on OpenWrt (x86_64/musl toolchain):find_package(podofo CONFIG)resolves,libinsightdock.solinks against PoDoFo, and headers/CMake export/.soall stage correctly for downstream packages.- Repository prepared for open-source release — removed non-public sample code and the previous CI configuration; added GitHub Actions CI (
.github/workflows/ci.yml: lint, clang-tidy, build-and-test, sanitizers;.github/workflows/docs.yml: MrDocs + MkDocs site published to GitHub Pages) and standard GitHub OSS scaffolding (CODE_OF_CONDUCT.md, issue/PR templates). CMake/Makefile output filename islibinsightdock.a/.so. - Embedded font subset regenerated to cover full printable ASCII (
include/insightdock/embedded_font.h) — the previous subset was missing glyphs for 20 punctuation codepoints, including$,!,",#,',*,;,=,?,@, brackets, backslash, backtick, braces,|, and~(each silently rendered as blank space via a.notdefglyph — not caught byFont::draw_text/measure_width's range check, which only validates the codepoint is in[32,127], not that the font actually maps it). Traced the embedded binary to DejaVu Sans via its TTF name table and regenerated the subset withfonttools'pyftsubset --unicodes="U+0020-007E"from a full DejaVu Sans install; all 95 printable-ASCII codepoints now resolve to a real glyph (verified with a standalonestbtt_FindGlyphIndexsweep). New subset is also smaller (~9.3 KB vs ~21 KB).NOTICEupdated with accurate DejaVu Sans / Bitstream-Vera-derived license attribution (previously a vague "verify font license before redistribution" placeholder).docs/gallery/*.pngbaselines regenerated (UPDATE_GALLERY=1) since text metrics shifted slightly with the new subset. - CMake
find_package(InsightDock)now exports the target asInsightDock::Charts(previouslyInsightDock::InsightDockCharts) — addedEXPORT_NAME Chartsto the library target. The in-treeInsightDock::ChartsALIAS masked this for anyone building this repo directly; every out-of-tree consumer following the README's ownfind_packageexample hit atarget_link_libraries"target was not found" configure error. Verified with a cleancmake --installto a temp prefix followed by a throwaway consumer project that configures, builds, links, and runs.
Fixed¶
-
ChartExporterno longer turns a PNG/JPEG/SVG export into a PDF whenExportOptions::pdf_backendisVector; the backend only applies to PDF output (save("x.pdf"),PdfRaster/PdfVector) and todraw_pdf().save("x.pdf")now keeps an explicitOutputFormat::PdfVector. -
Robustness against non-finite and hostile input (Phase A of the v1.0 plan).
- NaN/Inf/huge coordinates no longer hang or invoke undefined behaviour:
ImageBufferprimitives (draw_line,draw_dashed_*,fill_polygon,fill_rect,fill_circle,fill_sector,fill_rounded_rect) now reject non-finite geometry, clamp before every float→int cast, and clip very long segments to the buffer; dash cycles shorter than 0.25 px render solid.Font::draw_text/blit_rotatedignore non-finite anchors. Previouslydraw_dashed_segmentlooped forever on an Inf pixel andnice_tickson Inf cast to int (UB; on ARM64 it saturated and looped ~2 billion times). Scalesanitizes non-finite domains and caps tick generation (nice_ticks,linear_nice, DateTime/Log branches). Axis bounds in Bar/Line/Area/Combo/Scatter now come from a shared finite-only helper (detail/finite_range.h), so a NaN first value no longer poisons the domain; stacked-bar sums skip non-finite values.PieChart::add_slicestores non-finite values as 0.ImageBufferrefuses to allocate beyondImageBuffer::kMaxPixels(2^27 px) andChartExporterreturnsChartError::InvalidInputfor oversized charts instead of lettingbad_allocescape; the supersample factor is capped at 8.- Makefile build:
area_chart.ccwas missing fromSRCS(anyAreaChartuse failed to link); defaultCHART_TYPESis nowpie bar line area scatter(waspie bar line, which silently omitted scatter and area); the never-readINSIGHTDOCK_HAS_BAR/LINE/SCATTERdefines were removed; header dependency tracking (-MMD -MP) added;make testnow also builds and runs the unit tests. - CI: sanitizer job now also enables
float-cast-overflow,float-divide-by-zero(with-fno-sanitize-recover),build-and-testalso runs on an ARM64 runner, and a newmake-buildjob covers the Makefile (default and minimal OpenWrt configurations). New hostile-input unit tests plus a deterministic fuzz test cover every chart type. -
Docs honesty: README/CLAUDE.md/getting-started no longer claim a
ChartOptions::fontfield orFont::load_ttf()that don't exist yet (font injection is planned before 1.0); Make defaultCHART_TYPESdocumented correctly; OpenWrt samplePKG_VERSIONaligned with the CMake project version. -
Pie chart leader-line labels could render off-canvas for wide/near-horizontal slices —
PieOptions::radiusdefaulted to a fixed250.0, a non-zero value that reads as "explicitly configured" to the auto-fit logic inPieChart::render()(and the duplicated copy insrc/svg_pie.cc's native SVG vector path), so the branch that reserves ~35% margin for callout labels only ran when the bare circle literally overflowed the canvas — never when the circle fit but left no room for the leader line's knee/elbow segments. A slice whose midpoint angle points near-horizontal (e.g. an exact 50% slice) extends its leader line straight out from the circle's edge; with the old default on thedocs/gallery/pie_chart.pngexample (600×600, radius 250, only 50px margin), that leader line and its label landed past the canvas edge entirely — reproducible inexamples/main.cctoo, which mirrored the sameradius = 250.0. Fixed by changing the default to0.0, the same "auto" sentinel already used bycenter_x/center_y, so the existing margin-reserving auto-fit branch triggers for the common case of never callingset_pie_options(). An explicitly-configured non-zero radius that fits the canvas is still trusted as-is.docs/gallery/pie_chart.pngregenerated;examples/main.cc's redundant explicitradius = 250.0override removed. - Vertical
ReferenceLine/ReferenceBandrendered mirrored (right-to-left) on the category axis —CartesianChart::PlotContext::x_scalewas built asScale(0.0, n, plot.left(), plot.right()), butScale::to_pixel()uses the same "domain min → pixel_max, domain max → pixel_min" convention on every axis (the convention that makes higher y-values render higher on screen), sox_scale.to_pixel()mapped category index 0 to the right edge and index n to the left edge — backwards from howBarChart/LineChart/AreaChart/ComboChartactually position category geometry (plot.left() + slot*(i + 0.5), left-to-right).x_scalehad no other reader anywhere in the codebase before the reference-line/band feature above started callingto_pixel()on it directly, so the bug was latent until that new code path exercised it. Fixed by swapping thepixel_min/pixel_maxarguments atx_scale's single construction site incartesian_chart.cc(ScatterChartalready applies the same swap to its own x-axis scale, for the same reason). Found by manually renderingBarChart/LineChartwith both a horizontal and a vertical reference line and inspecting the output — the "renders without crashing" unit test added alongside the original feature didn't catch it. Two new regression tests (test_reference_line_vertical_not_mirrored_bar/_line_chart) assert a reference line near the low end of the category domain renders left of one near the high end; both fail against the pre-fix code. - Makefile (OpenWrt core build) was missing
src/svg_writer.ccfromSRCS— any build that actually calledChartExporter::save/encodewith SVG output (or just linkedexport.cc, which referenceswrite_svg/encode_svgunconditionally) would fail at link time. Only the CMake build had this source listed. Found while wiringsrc/svg_pie.ccinto the same conditional-pie block. - Diagnosed intermittent
std::bad_alloc/std::length_errorreported fromChartExporter::save()— not a defect inpie_chart.cc/export.cc/chart.h(verified clean across 4000+ ASan/UBSan render/save cycles, including with PoDoFo linked). Root cause was an ABI-incompatible stale install: acmake --installhad been re-run against a build directory whose library binary was never rebuilt afterExportOptionsgrew from 16 to 20 bytes (Phase 0.6'ssvg_backendfield). That crosses the x86-64 SysV ABI's struct-passing threshold (≤16 bytes passed in registers, >16 bytes passed on the stack): callers built against the newer 20-byte header passExportOptionson the stack, while the stale precompiledChartExporter::save()/render_mutable()(16-byte header) read it out of registers instead, soopts.supersamplecame back as register garbage. When that garbage was>1,SupersampleScopemultiplied the chart's width/height by it, andImageBuffer's backingstd::vectorallocation blew up. Root-caused with a GDB hardware watchpoint (ASLR had to be kept enabled — GDB disables it by default, which is why the bug never reproduced under a debugger or under ASan's allocator) and confirmed by relinking the same stale.aagainst its matching old header (0/60 failures) vs. the mismatched new header (~50% failures). No source change required; see the new "Installing for out-of-tree consumers" note inCLAUDE.md. docs/mrdocs.ymldefinedINSIGHTDOCK_HAS_PODOFO=0to skip PoDoFo includes during doc generation, butexport.ccguards with#ifdef INSIGHTDOCK_HAS_PODOFO(existence, not value) — so the=0define still satisfied the guard and pulled in<podofo/podofo.h>, which isn't available at doc-gen time, aborting the whole run. Fixed by not defining the macro at all. Latent since the job was introduced; never surfaced because it always short-circuited before mrdocs was installed on the runner.
0.7.0 — 2026-06-18 — Phase 0.5 / 0.6 foundations¶
Added¶
PdfReportBuilder::draw_chart()/draw_chart_flow()— embed anyChart(bar/line/pie) into aPdfReportBuilder-driven report at mm coordinates, viaChartExporter::draw_pdf().draw_chart_flow()adds an optional caption, border, and auto-pagination (ensure_space()), advancing the builder's vertical cursor likedraw_table(). Closes the Phase 0.5 blocker for the Phase 1 "Multi-page PDF report builder" roadmap item. Demonstrated inexamples/podofo_report_example.cc.insightdock/pdf/report.h—PdfReportBuilderfor multi-page PDF reports: tables, section headings, footers, pagination, and inline formatted text (PoDoFo build only).- Log and DateTime scale implementations —
ScaleType::Loguses base-10 mapping;ScaleType::DateTimeuses Unix-timestamp ticks with automatic time-step selection.TickFormat::DateTimeformats axis labels asYYYY-MM-DD HH:MM. OutputFormat::Svgandsvg_writer.cc— SVG export via PNG raster base64-inlined in SVG XML (ChartExporter::save("chart.svg")).AreaChart— area and stacked-area charts onCartesianChart(area_options().stacked).- Visual regression —
tests/gallery_regression.ccCTest target compares renders against reference PNGs indocs/gallery/. Regenerate withUPDATE_GALLERY=1. lint-tidyCI job — clang-tidy onsrc/*.ccwithcompile_commands.json(optional until toolchain is validated).- 32 unit tests (was 27) — log/datetime scale roundtrip and SVG encode coverage.
Added (prior Phase 0.5)¶
- Makefile feature-selection flags — three variables let you trim the library for size-constrained targets such as OpenWrt routers:
CHART_TYPES(defaultpie bar line) — omit chart types you don't need; each omitted type is not compiled, andcartesian_chart.ccis skipped automatically when neitherbarnorlineis selected.EXPORT_FORMATS(defaultpng jpeg) — omitjpegto strip the JPEG codec and recover ~15 KB.FONT_MODE(defaultembedded) — set tosystemto drop the 20 KB baked-in TTF and load a font from the filesystem at runtime instead (seeFont::load_from_file()).INSIGHTDOCK_NO_EMBEDDED_FONTpreprocessor guard infont.cc— when defined,Font::Font()is a no-op (font not loaded); caller must invokeFont::load_from_file()orFont::load_from_memory()before rendering.INSIGHTDOCK_NO_JPEGpreprocessor guard inpng_writer.cc— stubswrite_jpeg/encode_jpegto return false / empty so the linker can GC the stb JPEG codec via--gc-sections.INSIGHTDOCK_HAS_PIEpreprocessor guard inexport.cc—SupersampleScopeonly referencesPieChartandPieOptionswhen pie support is compiled in, preventing linker errors in pie-less builds.
Measured footprint reduction (pie + PNG + system font vs full default build):
| Configuration | Code section |
|---|---|
| Full (all types, embedded font, JPEG) | ~287 KB |
| Minimal (pie only, PNG only, system font) | ~90 KB |
With -Os -s strip (estimated) |
~45–55 KB |
insightdock/error.h—ChartErrorenum +ChartResult<T>result type.ChartExporter::save()now returnsChartResult<void>;encode()returnsChartResult<std::vector<uint8_t>>. Both are contextually convertible toboolso existingif (!save(...))callers compile unchanged.insightdock/theme.h—ChartThemestruct withlight()anddark()built-in presets.Chart::apply_theme(theme)batch-applies background, title, axis, grid, and text colors plus font size.ScaleTypeenum (Linear,Log,DateTime) inscale.hwithScale::set_type()/Scale::type()accessors. API shape is established; Log and DateTime implementations follow in Phase 0.5/0.6.TickFormatenum (Raw,SI,Currency,Percent) andCartesianOptions::y_tick_formatincartesian_chart.h. Custom formatting viaCartesianOptions::custom_y_formattercallback. Replaces the hard-coded%.0frendering incartesian_chart.cc.CartesianChart::draw_overlay(PlotContext&)— virtual hook called afterdraw_series(). Default no-op; override to add reference lines, annotations, watermarks, or combo-chart geometry without replacing the full render pipeline.Font::TextMetricsstruct (width,height) andFont::measure(text, size)infont.h/font.cc— measures text at an explicit pixel size without changing font state.- Label collision avoidance for Cartesian x-axis labels —
draw_axes_and_grid()now computes a skip step so adjacent labels do not overlap at any category count. tests/unit_tests.cc— 27 deterministic unit tests forScale,Palette,ImageBuffer,ChartResult,ChartTheme, andFont::measure. Registered asunit_testsCTest target alongside the existing smoke tests.- CI sanitizer jobs — ASan + UBSan build and test run (clang).
- PoDoFo CI made automatic — the PoDoFo build runs by default now instead of manual-only; checks that
podofo_report_exampleproduces a non-empty PDF.
Added¶
ComboChart— overlays grouped bar series and line series on the same Cartesian canvas; uses thedraw_overlay()hook introduced in Phase 0.5.add_bar_series()/add_line_series()API mirrors the existingBarChart/LineChartinterfaces. Registered inCMakeLists.txt,Makefile, andinclude/insightdock/charts.h.examples/all_charts_raster_pdf.cc— five-page A4 PDF (Bar, Line, Area, Combo, Pie) rendered at 2× supersample viaChartExporter::draw_pdf(). Demonstrates raster-quality zoom testing for all chart types in a single document.INSIGHTDOCK_HAS_PIE=1unconditionally defined onInsightDockChartsinCMakeLists.txt—pie_chart.ccis always compiled andSupersampleScopeinexport.ccneeds this guard to scale pie center/radius correctly at supersample > 1.
Fixed¶
- Pie supersample rendering —
INSIGHTDOCK_HAS_PIEwas never defined in the CMake build, so theSupersampleScopepie-scaling block insrc/export.ccwas dead code. At supersample > 1 the canvas doubled butpie_opts_.center_x/y/radiusstayed at original values, placing the pie in the upper-left corner of the scaled buffer before downsampling. Fixed by the unconditionalINSIGHTDOCK_HAS_PIE=1definition above. - Legend/x-axis label overlap in Cartesian charts —
CartesianChart::compute_plot_rect()used a fixed 36 px bottom margin that did not account for all three stacked layers (x-axis category labels, legend row, optional x-axis title). The margin is now computed dynamically fromfont.line_height():8 + lh + 8 + (lh + 10) + x_title_h.draw_legend()positioning updated to match. - Pie chart label / percentage overlap — long slice labels are word-wrapped at the chart edge, drawn one line at a time (PoDoFo
DrawTextdoes not honor\n), and the percentage stays anchored to the elbow callout with wrapped lines growing upward. Fixed in raster (pie_chart.cc) and PoDoFo vector (podofo_vector.cc). - Inverted Y-axis —
CartesianCharthadpixel_min/pixel_maxswapped in theScaleconstruction, causing bar and line charts to render with higher values at the bottom and bars growing downward from zero. Fixed by passing(plot.top(), plot.bottom())in the correct order toScale::linear_nice. Bar and line charts now render with the standard orientation (higher values higher on the chart). ImageBuffer::pixel()OOB — the header documented "returnsnullptrif out of bounds" but the implementation never checked bounds. Fixed with explicitx >= width_ || y >= height_guards.
0.6.0 — 2026-06-05¶
Added¶
- InsightDock Charts rebrand: namespace
insightdock::charts, headers underinclude/insightdock/ - Umbrella header
insightdock/charts.hand PDF headerinsightdock/pdf/podofo.h - CMake project
InsightDockCharts, targetInsightDock::Charts,find_package(InsightDock) - CI pipeline (core build + CTest)
- Smoke tests in
tests/(PNG/JPEG magic-byte checks) - Project metadata: LICENSE (MIT), NOTICE, CONTRIBUTING, SECURITY, brand clearance notes
- Documentation: API policy, release checklist, benchmarks strategy, future roadmap items
.clang-format,.editorconfig,.gitattributesinsightdock/detail/math_constants.hfor portableM_PI
Changed¶
- Generic examples use InsightDock Charts naming
- PoDoFo generated include dirs are
PRIVATEon the library target - Makefile produces
libInsightDockCharts.a
Removed¶
- Public
chartlibnamespace andinclude/chartlib/headers (clean break, no compatibility aliases)
[0.5.0] — prior release (chartlib)¶
ChartExporterunified export API (PNG, JPEG, PDF raster/vector)- Native vector PDF pies via
podofo_vector - JPEG export via stb_image_write
[0.3.0] — prior release¶
- PDF report example, vector PoDoFo pies, Tableau-10 palette