#666's minor gridlines stepped in value units, but #669 scales a log axis in
exponents of its base; map each minor step back through the base.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#662 gives scatter and bubble charts one x scale over every series; #603's
trendlines still mapped x over each series' own extent, so a fit drifted off
its points. Use the chart's x scale where there is one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#640 orders category slots bottom-to-top on horizontal bars (or reversed via
c:orientation) while #603 pads the axis for trendline forecasts. Slots now
include the forecast padding on the right side, and trendlines map their
category positions through the same slot order as the bars.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
f64::clamp panics when the plot is narrower than the caption's minimum width;
use main's clamp_soft, as the rest of the chart code now does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Radar and filled radar charts are drawn polar: spokes per category,
polygon gridlines and value labels up the first spoke. Stock charts
draw high-low lines with close dashes, or up/down bars between open
and close, and are saved as c:stockChart (hiLowLines, upDownBars)
instead of a line chart; c:upDownBars is read, and chart.options takes
upDownBars. New stock charts start as high-low-close and new combo
charts as columns with a line.
Co-authored-by: Cursor <cursoragent@cursor.com>
Read the regionMap chartEx layout as a RegionMap chart (regions and
their colour values), write the part back as read while unchanged, and
when edited rebuild it with the original geography (projection, culture
and cached boundaries) carried over. With no map data in the repo, the
renderer draws each region as a value-coloured tile with a colour scale
and a note that the map itself is not drawn.
Co-authored-by: Cursor <cursoragent@cursor.com>
Read, keep and write every cx:lvl of a chartEx category dimension,
build a category tree from runs of parent labels, and draw treemaps as
nested squarified groups with banners and sunbursts as one ring per
level. Box & whisker honours quartileMethod="inclusive" (QUARTILE.INC)
on import, render, save and the chart.options command.
Co-authored-by: Cursor <cursoragent@cursor.com>
Histograms and Paretos bin their observations (bin count, bin width or
Scott's rule) and draw as touching columns, a Pareto with its cumulative
line on a percentage axis. Box and whisker charts draw exclusive-quartile
boxes, 1.5 IQR whiskers, means and outliers per category group.
Edited and new Office 2016 charts are written as chartEx parts from the
model (with the plain-chart fallback), so a waterfall stays a waterfall.
The chart menus list all seven types, and chart.options sets bins and
waterfall totals.
Co-authored-by: Cursor <cursoragent@cursor.com>
These Office 2016 charts were drawn as plain columns. Now:
- waterfall: bars float from the running total, total points
(cx:subtotals, now read into Chart.subtotals) stand on the axis and
reset it, increases/decreases/totals take accents 1-3 with a matching
legend, and connectors join each bar to the next;
- funnel: centred bars as wide as their value, category labels on the
left and values inside;
- treemap: squarified tiles in proportion to their value, labelled;
- sunburst: the innermost level as a ring with an open centre.
Co-authored-by: Cursor <cursoragent@cursor.com>
soak_tests.rs drives every non-file command with plausible and hostile
parameters over a ~100-slide deck, checking after each step that the
document validates, the selection is in range, the undo stack is bounded,
undo then redo restores the exact document, and every 100 steps that the
deck saves and reopens as .deckcraft and .pptx. Four seeds x 400 steps run
in CI (under a second); DECKCRAFT_SOAK_STEPS/SEED run it long.
The long soak found that a chart a few points wide panicked in f64::clamp
(label gutter min above max), and the render guard then blanked the whole
slide. chart.rs now uses a non-panicking clamp_soft for the gutters and the
zero line; a render test draws tiny charts next to a shape without the
guard.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pie and doughnut data labels still went through fmt_num, which rounded
anything non-integral to one decimal (999.99 drew as "1000.0"). They now
use Chart::data_label like the other label paths (series format, else
axis format, else General), and fmt_num is gone.
Co-authored-by: Cursor <cursoragent@cursor.com>
Upstream ce1c7b7 ("Charts: axis number formats, automatic major unit,
hidden axes") replaced the value-axis scaling this branch changed, and
already draws #144's chart as 0-45 in steps of 5. Take its structure and
API as they are (axis_scale with fixed bounds/unit and a room-based
interval count, Chart::axis_scale(secondary, max_intervals), axis_range,
combo charts' secondary axis) and drop this branch's auto_scale.
On top of it, the Office rules upstream lacked:
- Automatic minimum: zero only when every value is >= 0 and the smallest
is below 5/6 of the largest; otherwise the first major unit at or below
min - (max - min)/20. All-negative data mirrors it (the axis ends at
zero only when the largest is above 5/6 of the smallest). Headroom is
5 % of the data range, as documented. Chart::axis_scale feeds it the
plotted extremes (stacked sums still count from zero, 100 % series are
0-1); axis_range and value_range keep returning zero-inclusive ranges.
- A fixed limit beyond the data makes the automatic end give way instead
of producing an inverted axis.
- The renderer stops values past the axis limits at the plot edge, on
both value axes.
The model tests from this branch are ported to the new signature with
ten intervals of room.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Value axes showed raw numbers ("800000") with a fixed ~5-interval scale and
fixed label gutters, deleted axes were still drawn, and data labels ignored
their number format, so Russian business charts read "516600000" where
PowerPoint shows "516 600 000 ₽".
- New deckcraft_model::numfmt: Excel number format codes (sections for
positive/negative/zero, quoted and escaped literals, _x padding, *x fill,
[$sym-lcid] currency, [colour]/conditions dropped, 0/#/? placeholders,
grouping with ',' or the locale's literal space, trailing-comma scaling,
%, General) with digit grouping and decimal mark from the chart's
language (c:lang: ru-RU -> "800 000", "12,5%").
- Reading: each value axis' numFmt, c:delete, scaling min/max and
majorUnit, for the primary and a combo chart's secondary axis; data
labels' numFmt (series, then group, else the source data's formatCode);
the chart's c:lang. The primary value axis is the one on the left
(bottom) and the secondary the one on the right/top or crossing at max,
whatever the plot groups' order.
- Scale (Chart::axis_scale): fixed bounds and unit where given, else the
data plus 5% headroom rounded out to the smallest 1-2-5 unit that leaves
as many intervals as the axis has room for labels, as PowerPoint does
(0-800 000 by 100 000, 0-7 000 by 1 000 on the sample deck).
- Rendering: labels in the axis format, gutters sized to the longest label,
deleted axes draw no labels and take no room, data labels in their own
format.
A combo chart (columns plus lines, often with the lines on a second value
axis) was read with each series' type but drawn as lines only, on one
scale, so a spend-and-clicks chart showed the clicks as a flat line along
the bottom and no columns.
- Reading: a plot group whose value axis isn't the first group's is on the
secondary axis (Series::secondary_axis, now set).
- Model: Chart::series_kind, on_secondary and axis_range (per axis; a combo
stacks only its stacked series).
- Rendering: bar-like series are drawn as bars (clustered or stacked among
themselves), the others as lines/areas over them, each against its own
axis; the secondary axis' labels go on the right. Line series use their
line's colour and width (where PowerPoint keeps them), also in the legend.
Long category labels wrap onto two lines.
The value axis used a generic "nice" scale: about five intervals and no
headroom, so data with a maximum of 42.1 got 0-50 in steps of 10, while
PowerPoint and LibreOffice draw 0-45 in steps of 5.
`auto_scale` now follows the rule Office documents for automatic limits:
the maximum is the first major unit at or above max + (max - min)/20, the
minimum is zero when all values are positive and the smallest is below 5/6
of the largest (otherwise the first major unit at or below min - the same
headroom), and all-negative data mirrors that. The major unit is the
smallest 1, 2 or 5 x 10^n that gives at most 10 intervals. Fixed
`<c:min>`/`<c:max>` limits are kept as they are and no longer get rounded
or padded; values beyond them stop at the plot edge. 100% charts get
0-1 in steps of 0.1.
Fixes#144
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>