mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-03 00:41:56 -06:00
Data: Analysis ToolPak two-factor Anova, z-Test, Fourier Analysis, Random Number Generation and Sampling (#495)
* Data: Analysis ToolPak two-factor Anova (with and without replication) and z-Test Builds on the ToolPak of #320. data.analysisToolPak gains three tools: Anova: Two-Factor Without Replication (rows and columns as factors, the residual as the error term), Anova: Two-Factor With Replication (samples of rowsPerSample rows, with the interaction and within-cell terms) and z-Test: Two Sample for Means (known variances, normal tails and critical values from NORM.S.DIST and NORM.S.INV). Each has a form in the Data Analysis dialog. Tests check the reports against Montgomery's hardness-testing and battery-life examples. * Data: Analysis ToolPak Fourier Analysis data.analysisToolPak gains Fourier Analysis: the discrete Fourier transform or its inverse of one row or column of 2^k values (at most 4096), by an iterative radix-2 FFT. Input cells are read as IMREAL and IMAGINARY read them, so complex text such as 3+4i works, and the results are written as COMPLEX text. Tests check known transforms (1, 2, 3, 4; a sampled cosine), the inverse round trip and the input limits. * Data: Analysis ToolPak Random Number Generation and Sampling data.analysisToolPak gains the last two of Excel's 19 tools. Random Number Generation draws uniform, normal, Bernoulli, binomial, Poisson and discrete numbers, or writes a patterned sequence, in one or more columns; binomial and Poisson draws invert tables built from BINOM.DIST and POISSON.DIST. Sampling takes every period-th value or a random sample with replacement. Both use a seeded xoshiro256** generator: the same seed repeats the numbers, and a run without one reports the seed it chose. Output is capped at 1,000,000 numbers. * Data: Analysis ToolPak two-factor Anova without replication: Labels caption names the first column With Labels ticked, Anova: Two-Factor Without Replication takes the column names from the first row and the row names from the first cell of every data row. The form's caption read "Labels in first row", so a user with column headers but no row names who ticked it would lose the first data column. The caption now reads "Labels in first row and first column", and the command's params doc says the same. A dialog test checks the caption. * Data: Analysis ToolPak docs: timestamp lines and revision history for the 19 tools docs/gaps.md (§13) and docs/target-app-parity.md (what-if and analysis) gain their Last updated line and a revision-history row for the Analysis ToolPak reaching all 19 of Excel's tools. * Data: Analysis ToolPak new tools refuse options that are not numbers Random Number Generation (from, to, mean, standardDeviation, p, lambda, step, and the whole-number options) and z-Test (hypothesizedDifference) read their options with a default for anything that did not parse, so a form value typed as "0,3" or a typo quietly ran with the default. A blank or absent option still takes the default; any other value that is not a finite number is now an error naming the option. * Data: Analysis ToolPak refuses an Alpha that is not a number alpha() took any value that did not parse as the default 0.05, so a typed "0,01" ran z-Test, both two-factor Anovas (and the existing t-tests, F-Test and single-factor Anova, which share it) at the wrong significance level without a word. A blank or absent Alpha still means 0.05; any other value that is not a finite number is now an error. * Data: Analysis ToolPak Random Number Generation form hides the count for a patterned sequence A patterned sequence takes its length from From, To, step and the two repeat counts and ignores Number of Random Numbers, so a count typed there did nothing. The form shows the count only for the random distributions, and the patterned options carry a note that the repeats set how many values are written. * Data: Analysis ToolPak Random Seed caption says a blank seed draws a new sequence Random Number Generation and random Sampling labelled the seed "blank for none", but a blank seed makes the engine choose a new one, so each run draws different numbers. The caption now reads "blank for a new sequence". * Data: Analysis ToolPak form scrolls its options in a short window The Data Analysis window cannot be resized, and the patterned Random Number Generation form (seven distribution choices and five pattern fields, each label above its box) is taller than a 600-point window, so OK ended below the window's edge. The tool's options now sit in a vertical scroll area capped to the window's height less the title, output options and buttons, which always stay in view. * Data: Analysis ToolPak docs: Forecast Sheet is no longer listed as missing docs/target-app-parity.md's what-if and analysis row still listed Forecast Sheet as missing, while data.forecastSheet is in the catalog and docs/gaps.md §13 lists it as present. The row now lists it with the other analysis tools, and today's revision-history row says so. * Data: Analysis ToolPak seed applies only where numbers are drawn A patterned sequence and periodic sampling draw nothing, but the form showed the Random Seed field for both, and Random Number Generation read the seed for every distribution: a seed left in the form that was not a whole number refused a patterned run, and a valid one was reported as the seed used. The seed field now shows only for the random distributions and random sampling, and a patterned run neither reads nor reports a seed. * Data: Analysis ToolPak Fourier Analysis keeps small parts beside large ones The cleanup after the transform zeroed every part at or below 1e-13 of the largest output, so a one-value input such as 1e14 + 1i lost its imaginary part although a transform of one value performs no arithmetic, and a small harmonic beside a large DC term was lost the same way. The cutoff is now the rounding bound of the transform itself, 8 · ε · log2(N) · Σ(|re_n| + |im_n|), divided by N for the inverse (whose outputs are divided by N): it depends on the input and the number of butterfly stages, never on the largest output, and is zero for N = 1. An input sum that overflows disables the cleanup instead of zeroing everything. Two regression tests cover the one-value case and an eight-point round trip of 1e6 + 1e-7 cos(2πn/8). * Data: Analysis ToolPak binomial and Poisson tables grow with the distribution, not the trials Random Number Generation tabulated a binomial or Poisson distribution by evaluating BINOM.DIST or POISSON.DIST through worksheet-function dispatch for every outcome from 0 upward, so a million trials with p = 1 meant 1,000,001 calls and a table of as many entries. The table is now built from one value at the mode and the recurrence pmf(k + 1) = pmf(k) · (n − k)/(k + 1) · p/(1 − p) (λ/(k + 1) for Poisson), outward in both directions: the left tail ends where its terms vanish, the right tail once the cumulative probability reaches 1 within f64 resolution, so the table is as wide as the outcomes that matter (about 23,000 entries for Binomial(1,000,000, 0.5)). p = 0 and p = 1 are exact cases with one outcome. The trials limit and the draws are unchanged. Tests check the recurrence against BINOM.DIST and POISSON.DIST to 1e-12 relative, the table width at a million trials, and that the largest inputs complete in milliseconds with the right means. --------- Co-authored-by: FredApps <FredApps@users.noreply.github.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -32,7 +32,7 @@ pub(super) fn goal_seek(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, c
|
||||
}
|
||||
|
||||
/// The fields of one Analysis ToolPak tool's form (input and options; output is drawn apart).
|
||||
fn toolpak_fields(tool: &str) -> Vec<Field> {
|
||||
fn toolpak_fields(tool: &str, values: &Map<String, Json>) -> Vec<Field> {
|
||||
use Field::*;
|
||||
let grouped = || Choice { key: "groupedBy", label: "Grouped By:", options: vec![("columns", "Columns"), ("rows", "Rows")] };
|
||||
let labels = || Check { key: "labels", label: "Labels in first row" };
|
||||
@@ -57,6 +57,17 @@ fn toolpak_fields(tool: &str) -> Vec<Field> {
|
||||
],
|
||||
"correlation" | "covariance" | "rankPercentile" => vec![range(), grouped(), labels()],
|
||||
"anovaSingle" => vec![range(), grouped(), labels(), Text { key: "alpha", label: "Alpha:" }],
|
||||
// With Labels ticked this tool reads row names from the first column as well as column
|
||||
// names from the first row, so the caption says both.
|
||||
"anovaTwoFactorWithout" => {
|
||||
vec![range(), Check { key: "labels", label: "Labels in first row and first column" }, Text { key: "alpha", label: "Alpha:" }]
|
||||
}
|
||||
"anovaTwoFactorWith" => vec![
|
||||
Note("The input range holds column labels in its first row and sample labels in its first column.".into()),
|
||||
range(),
|
||||
Text { key: "rowsPerSample", label: "Rows per sample:" },
|
||||
Text { key: "alpha", label: "Alpha:" },
|
||||
],
|
||||
"regression" => vec![
|
||||
Text { key: "inputY", label: "Input Y Range:" },
|
||||
Text { key: "inputX", label: "Input X Range:" },
|
||||
@@ -70,6 +81,65 @@ fn toolpak_fields(tool: &str) -> Vec<Field> {
|
||||
"movingAverage" => {
|
||||
vec![range(), labels(), Text { key: "interval", label: "Interval:" }, Check { key: "standardErrors", label: "Standard Errors" }]
|
||||
}
|
||||
"randomNumberGeneration" => {
|
||||
let dist = values.get("distribution").and_then(Json::as_str).unwrap_or("uniform");
|
||||
let mut v = vec![Text { key: "variables", label: "Number of Variables:" }];
|
||||
// A patterned sequence sets its own length, so it has no count to type.
|
||||
if dist != "patterned" {
|
||||
v.push(Text { key: "count", label: "Number of Random Numbers:" });
|
||||
}
|
||||
v.push(Choice {
|
||||
key: "distribution",
|
||||
label: "Distribution:",
|
||||
options: vec![
|
||||
("uniform", "Uniform"),
|
||||
("normal", "Normal"),
|
||||
("bernoulli", "Bernoulli"),
|
||||
("binomial", "Binomial"),
|
||||
("poisson", "Poisson"),
|
||||
("patterned", "Patterned"),
|
||||
("discrete", "Discrete"),
|
||||
],
|
||||
});
|
||||
v.extend(match dist {
|
||||
"normal" => vec![Text { key: "mean", label: "Mean:" }, Text { key: "standardDeviation", label: "Standard deviation:" }],
|
||||
"bernoulli" => vec![Text { key: "p", label: "p Value:" }],
|
||||
"binomial" => vec![Text { key: "p", label: "p Value:" }, Text { key: "trials", label: "Number of trials:" }],
|
||||
"poisson" => vec![Text { key: "lambda", label: "Lambda:" }],
|
||||
"patterned" => vec![
|
||||
Note("Each number is repeated, then the whole sequence; together they set how many values are written.".into()),
|
||||
Text { key: "from", label: "From:" },
|
||||
Text { key: "to", label: "To:" },
|
||||
Text { key: "step", label: "In steps of:" },
|
||||
Text { key: "repeatEach", label: "Repeating each number (times):" },
|
||||
Text { key: "repeatSequence", label: "Repeating each sequence (times):" },
|
||||
],
|
||||
"discrete" => vec![Text { key: "valueRange", label: "Value and Probability Input Range:" }],
|
||||
_ => vec![Text { key: "from", label: "Between:" }, Text { key: "to", label: "And:" }],
|
||||
});
|
||||
// A patterned sequence draws nothing, so it has no seed.
|
||||
if dist != "patterned" {
|
||||
v.push(Text { key: "seed", label: "Random Seed (blank for a new sequence):" });
|
||||
}
|
||||
v
|
||||
}
|
||||
"sampling" => {
|
||||
let mut v = vec![
|
||||
range(),
|
||||
labels(),
|
||||
Choice { key: "method", label: "Sampling Method:", options: vec![("periodic", "Periodic"), ("random", "Random")] },
|
||||
];
|
||||
// Periodic sampling takes every period-th value and draws nothing, so only random
|
||||
// sampling has a seed.
|
||||
if values.get("method").and_then(Json::as_str) == Some("random") {
|
||||
v.push(Text { key: "samples", label: "Number of Samples:" });
|
||||
v.push(Text { key: "seed", label: "Random Seed (blank for a new sequence):" });
|
||||
} else {
|
||||
v.push(Text { key: "period", label: "Period:" });
|
||||
}
|
||||
v
|
||||
}
|
||||
"fourierAnalysis" => vec![range(), labels(), Check { key: "inverse", label: "Inverse" }],
|
||||
"exponentialSmoothing" => {
|
||||
vec![range(), labels(), Text { key: "damping", label: "Damping factor:" }, Check { key: "standardErrors", label: "Standard Errors" }]
|
||||
}
|
||||
@@ -78,6 +148,17 @@ fn toolpak_fields(tool: &str) -> Vec<Field> {
|
||||
v.extend([labels(), Text { key: "alpha", label: "Alpha:" }]);
|
||||
v
|
||||
}
|
||||
"zTest" => {
|
||||
let mut v = two();
|
||||
v.extend([
|
||||
Text { key: "hypothesizedDifference", label: "Hypothesized Mean Difference:" },
|
||||
Text { key: "variance1", label: "Variable 1 Variance (known):" },
|
||||
Text { key: "variance2", label: "Variable 2 Variance (known):" },
|
||||
labels(),
|
||||
Text { key: "alpha", label: "Alpha:" },
|
||||
]);
|
||||
v
|
||||
}
|
||||
_ => {
|
||||
let mut v = two();
|
||||
v.extend([
|
||||
@@ -96,8 +177,32 @@ pub(super) fn toolpak_defaults(sel: &str, tool: &str) -> Map<String, Json> {
|
||||
"tool": tool, "range": sel, "inputY": "", "inputX": "", "range1": sel, "range2": "", "bins": "",
|
||||
"groupedBy": "columns", "labels": false, "summary": true, "confidence": "", "kthLargest": "", "kthSmallest": "",
|
||||
"interval": "3", "damping": "0.3", "alpha": "0.05", "hypothesizedDifference": "0",
|
||||
"rowsPerSample": "", "variance1": "", "variance2": "", "inverse": false,
|
||||
"outputTo": "newSheet", "output": "", "newSheet": "",
|
||||
});
|
||||
// Random Number Generation and Sampling.
|
||||
for (k, x) in [
|
||||
("variables", "1"),
|
||||
("count", ""),
|
||||
("distribution", "uniform"),
|
||||
("from", "0"),
|
||||
("to", "1"),
|
||||
("mean", "0"),
|
||||
("standardDeviation", "1"),
|
||||
("p", "0.5"),
|
||||
("trials", "10"),
|
||||
("lambda", "1"),
|
||||
("step", "1"),
|
||||
("repeatEach", "1"),
|
||||
("repeatSequence", "1"),
|
||||
("valueRange", ""),
|
||||
("seed", ""),
|
||||
("method", "periodic"),
|
||||
("period", ""),
|
||||
("samples", ""),
|
||||
] {
|
||||
v[k] = json!(x);
|
||||
}
|
||||
if tool == "regression" {
|
||||
v["confidence"] = json!("95");
|
||||
v["inputY"] = json!(sel);
|
||||
@@ -105,6 +210,10 @@ pub(super) fn toolpak_defaults(sel: &str, tool: &str) -> Map<String, Json> {
|
||||
v.as_object().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Height a ToolPak form needs besides the tool's own options: title bar, output options, OK
|
||||
/// and the window's margins.
|
||||
const TOOLPAK_CHROME: f32 = 240.0;
|
||||
|
||||
/// Data › Data Analysis: pick a tool, then fill in its form. OK runs `data.analysisToolPak`
|
||||
/// (through the dialog's command); a report that would replace data asks first.
|
||||
pub(super) fn analysis_toolpak(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Dialog, confirm: &mut bool, open: &mut bool) {
|
||||
@@ -149,8 +258,11 @@ pub(super) fn analysis_toolpak(app: &mut SheetApp, ui: &mut egui::Ui, d: &mut Di
|
||||
return;
|
||||
};
|
||||
d.title = label.to_string();
|
||||
d.fields = toolpak_fields(&tool);
|
||||
form_fields(ui, d);
|
||||
d.fields = toolpak_fields(&tool, &d.values);
|
||||
// The tool's options scroll in a short window (the patterned Random Number Generation form
|
||||
// is taller than 600 points); the output options and OK below always stay in view.
|
||||
let room = (ui.ctx().content_rect().height() - TOOLPAK_CHROME).max(120.0);
|
||||
egui::ScrollArea::vertical().id_salt("toolpakForm").max_height(room).show(ui, |ui| form_fields(ui, d));
|
||||
ui.separator();
|
||||
ui.label(egui::RichText::new("Output options").strong());
|
||||
let to = d.values.get("outputTo").and_then(Json::as_str).unwrap_or("newSheet").to_string();
|
||||
|
||||
@@ -763,6 +763,117 @@ fn data_analysis_dialog_runs_a_tool_and_asks_before_overwriting() {
|
||||
assert!(h.state().message.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_analysis_dialog_opens_every_tool_form() {
|
||||
let mut s = blank();
|
||||
s.execute("range.setValues", json!({"range": "A1", "values": [[1, 2], [2, 4], [3, 6]]})).unwrap();
|
||||
let mut h = harness(s);
|
||||
for (id, label) in gridcraft_engine::cmd::analysis::TOOLS {
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": id}));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().dialog.as_ref().map(|d| d.title.clone()).as_deref(), Some(*label), "{id}");
|
||||
h.state_mut().dialog = None;
|
||||
}
|
||||
// The z-Test form runs with the known variances typed in.
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": "zTest"}));
|
||||
{
|
||||
let d = h.state_mut().dialog.as_mut().unwrap();
|
||||
for (k, v) in [("range1", "A1:A3"), ("range2", "B1:B3"), ("variance1", "1"), ("variance2", "4"), ("outputTo", "range"), ("output", "D1")] {
|
||||
d.values.insert(k.into(), json!(v));
|
||||
}
|
||||
}
|
||||
h.run_steps(2);
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert!(h.state().dialog.is_none(), "OK runs the tool and closes the dialog");
|
||||
assert_eq!(value(&h, "D1"), Value::from("z-Test: Two Sample for Means"));
|
||||
// Random Number Generation draws from the chosen distribution with the typed seed.
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": "randomNumberGeneration"}));
|
||||
{
|
||||
let d = h.state_mut().dialog.as_mut().unwrap();
|
||||
for (k, v) in [("count", "3"), ("distribution", "bernoulli"), ("seed", "1"), ("outputTo", "range"), ("output", "H1")] {
|
||||
d.values.insert(k.into(), json!(v));
|
||||
}
|
||||
}
|
||||
h.run_steps(2);
|
||||
key(&mut h, Key::Enter, Modifiers::NONE);
|
||||
assert!(h.state().dialog.is_none());
|
||||
assert!(matches!(value(&h, "H3"), Value::Number(x) if x == 0.0 || x == 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_factor_anova_without_replication_labels_caption_names_the_first_column() {
|
||||
// Ticked, Labels makes the tool take the first cell of every data row as the row's name, so
|
||||
// the caption must not say the labels are only in the first row.
|
||||
let mut h = harness(blank());
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": "anovaTwoFactorWithout"}));
|
||||
h.run_steps(2);
|
||||
assert!(h.query_by_label("Labels in first row and first column").is_some());
|
||||
assert!(h.query_by_label("Labels in first row").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_number_generation_form_hides_the_count_for_a_patterned_sequence() {
|
||||
// A patterned sequence ignores the count (its from, to, step and repeats set the length), so
|
||||
// the form offers the count only for the random distributions.
|
||||
let mut h = harness(blank());
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": "randomNumberGeneration"}));
|
||||
h.run_steps(2);
|
||||
assert!(h.query_by_label("Number of Random Numbers:").is_some());
|
||||
h.state_mut().dialog.as_mut().unwrap().values.insert("distribution".into(), json!("patterned"));
|
||||
h.run_steps(2);
|
||||
assert!(h.query_by_label("Number of Random Numbers:").is_none());
|
||||
assert!(h.query_by_label("Repeating each sequence (times):").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_seed_caption_says_blank_draws_a_new_sequence() {
|
||||
// A blank seed doesn't mean "no seed": the engine picks a new one, so the numbers differ on
|
||||
// each run. The caption says so for both tools that draw random numbers.
|
||||
let mut h = harness(blank());
|
||||
for tool in ["randomNumberGeneration", "sampling"] {
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": tool}));
|
||||
h.state_mut().dialog.as_mut().unwrap().values.insert("method".into(), json!("random"));
|
||||
h.run_steps(2);
|
||||
assert!(h.query_by_label("Random Seed (blank for a new sequence):").is_some(), "{tool}");
|
||||
assert!(h.query_by_label("Random Seed (blank for none):").is_none(), "{tool}");
|
||||
h.state_mut().dialog = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn data_analysis_form_keeps_ok_on_screen_in_a_short_window() {
|
||||
// The patterned Random Number Generation form is the tallest; in a 600-point-high window the
|
||||
// tool's options scroll and the output options and OK stay reachable.
|
||||
let mut h = crate::common::harness_with(crate::common::builder().with_size(egui::vec2(1280.0, 600.0)), crate::common::app(blank()), 4);
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": "randomNumberGeneration"}));
|
||||
h.state_mut().dialog.as_mut().unwrap().values.insert("distribution".into(), json!("patterned"));
|
||||
h.run_steps(4);
|
||||
// OK's label is padded; a button clipped off the window has no node at all.
|
||||
let ok = h.query_by_label(" OK ").map(|n| n.rect());
|
||||
assert!(ok.is_some_and(|r| r.bottom() <= 600.0), "OK at {ok:?} in a 600-point window");
|
||||
let output = h.query_by_label("New Worksheet Ply:").map(|n| n.rect());
|
||||
assert!(output.is_some_and(|r| r.bottom() <= 600.0), "the output options at {output:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_seed_shows_only_where_numbers_are_drawn() {
|
||||
// Patterned generation and periodic sampling draw nothing, so their forms have no seed.
|
||||
let seed = "Random Seed (blank for a new sequence):";
|
||||
let mut h = harness(blank());
|
||||
for (tool, key, value, shown) in [
|
||||
("randomNumberGeneration", "distribution", "uniform", true),
|
||||
("randomNumberGeneration", "distribution", "patterned", false),
|
||||
("sampling", "method", "random", true),
|
||||
("sampling", "method", "periodic", false),
|
||||
] {
|
||||
h.state_mut().open_dialog("analysisToolPak", json!({"tool": tool}));
|
||||
h.state_mut().dialog.as_mut().unwrap().values.insert(key.into(), json!(value));
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.query_by_label(seed).is_some(), shown, "{tool} {value}");
|
||||
h.state_mut().dialog = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solver_dialog_loads_the_saved_model_and_results_can_restore() {
|
||||
let mut s = blank();
|
||||
|
||||
+6
-6
@@ -1,6 +1,6 @@
|
||||
# Gaps
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (§4: chart data tables implemented with XLSX round-trip and print/PDF; earlier: §13: Solver Options; earlier: §13: Solver Load/Save Model and Reset All) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (§13: Analysis ToolPak has all 19 tools; earlier: §4: chart data tables implemented with XLSX round-trip and print/PDF; earlier: §13: Solver Options; earlier: §13: Solver Load/Save Model and Reset All) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
|
||||
Every known shortfall against Excel, one entry each, ranked by user impact (what blocks someone
|
||||
using GridCraft for real work first). This is the work list: pick from the top, check the open
|
||||
@@ -118,8 +118,8 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
|
||||
data model (`data.fromWeb`, `data.relationships` missing). **Estimate:** 60–100 h.
|
||||
|
||||
### 13. Analysis tools: Solver, Analysis ToolPak, Forecast Sheet (F)
|
||||
- `data.solver`, `data.analysisToolPak` missing. Forecast Sheet (`data.forecastSheet`) and the
|
||||
FORECAST.ETS family are in, including Forecast Start (hindcast, #785). **Estimate:** 15–25 h.
|
||||
- Forecast Sheet (`data.forecastSheet`) and the FORECAST.ETS family are in, including Forecast
|
||||
Start (hindcast, #785).
|
||||
- Solver (`data.solver`, #327) has Simplex LP with integer/binary branch and bound, a local
|
||||
nonlinear method (augmented Lagrangian, not GRG), the Evolutionary method, and Answer and
|
||||
Limits reports. Report refusal leaves the solution, saved model and history unchanged; a
|
||||
@@ -127,9 +127,8 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
|
||||
model on cells of its sheet and reads it back; Reset All removes it. Solver Options sets the
|
||||
evaluation limit, Integer Optimality and the Evolutionary options, which are saved with the
|
||||
model. AllDifferent constraints and the Sensitivity and Population reports remain.
|
||||
The Analysis ToolPak
|
||||
(`data.analysisToolPak`) has 13 of 19 tools. Forecast Sheet (`data.forecastSheet`) and the
|
||||
FORECAST.ETS family are in; the sheet has no Forecast Start (hindcast) option yet. **Estimate:** 12–20 h.
|
||||
- The Analysis ToolPak (`data.analysisToolPak`) has all 19 of Excel's tools; their chart output
|
||||
remains. **Estimate:** 12–20 h.
|
||||
|
||||
### 14. Page Layout view and print polish (U, F)
|
||||
- Page Layout view landed (#329): paper view with rulers and in-place header/footer editing.
|
||||
@@ -186,6 +185,7 @@ No alpha blockers: every core workflow passes the [alpha gate](roadmap.md#alpha-
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-01 | minor | §13: the Analysis ToolPak has all 19 of Excel's tools (two-factor Anova with and without replication, z-Test, Fourier Analysis, Random Number Generation, Sampling); its chart output remains. The stale "`data.solver`, `data.analysisToolPak` missing" and "no Forecast Start" sentences removed |
|
||||
| 2026-11-01 | minor | §4: chart data tables implemented for column, bar, line, area and compatible combo charts, with legend keys and border settings, live source values, XLSX round-trip and print/PDF; other chart gaps remain. |
|
||||
| 2026-10-31 | minor | §13: Solver Options (Max Evaluations, Integer Optimality and the Evolutionary options), saved with the model |
|
||||
| 2026-10-31 | minor | §13: Solver Load/Save Model (`data.solverSaveModel`, `data.solverLoadModel`) and Reset All (`data.solverReset`), with buttons in the Solver Parameters dialog |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Target-app parity: GridCraft vs Microsoft Excel
|
||||
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (page layout and print: Page Setup's page order; no score change; earlier: charts: data tables with XLSX round-trip and print/PDF; no score change; earlier: what-if and analysis: Solver Options; the Insert Function dialog is named as in Excel; no score change; earlier: Solver Load/Save Model and Reset All; automation: macro interface; catalog count 276 / 292, as `cargo xtask parity` measures; automation: typed macros in the engine; several windows: a native window per workbook window, New Window on the same workbook, Arrange All; no score change) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-11 · **Last updated:** 2026-11-01 · **Change:** minor (what-if and analysis: Analysis ToolPak has all 19 tools; earlier: page layout and print: Page Setup's page order; no score change; earlier: charts: data tables with XLSX round-trip and print/PDF; no score change; earlier: what-if and analysis: Solver Options; the Insert Function dialog is named as in Excel; no score change; earlier: Solver Load/Save Model and Reset All; automation: macro interface; catalog count 276 / 292, as `cargo xtask parity` measures; automation: typed macros in the engine; several windows: a native window per workbook window, New Window on the same workbook, Arrange All; no score change) · **Target:** Microsoft Excel (Microsoft 365)
|
||||
|
||||
This is the authoritative parity assessment. [`ROADMAP.md`](../ROADMAP.md) summarizes it,
|
||||
[`gaps.md`](gaps.md) lists every shortfall, and the area checklists hold the detail:
|
||||
@@ -89,7 +89,7 @@ performance 8, stability 8, localization 4, hardware 2, platforms 2, ecosystem 2
|
||||
| Page layout, print, PDF | 5 | ~80% | ~55% | 12–20 | Page setup, print titles, breaks, page order, Page Break Preview, headers/footers, PDF export and printing. Paper sizes match Japanese Excel: B4/B5/B6 are the JIS sizes, ISO B4/B5/B6 stay reachable by name, and Japanese Postcard (はがき), Japanese Double Postcard (往復はがき), the chou/kaku envelopes and A6 are available. No true Page Layout view; the PDF writer uses only the 14 standard fonts without embedding (no CJK or other non-Latin text in PDF); web print only exports (#141, #143) |
|
||||
| Charts and sparklines | 8 | ~50% | ~30% | 40–60 | 25 chart kinds, all 2-D. The chart model holds title, legend position, a data-labels flag, gridlines flag, axis titles and chart data tables (legend keys, borders, XLSX round-trip and print/PDF); no axis scaling, error bars, per-point formatting, chart sheets, 3-D, surface, map, Pareto. Kinds survive XLSX round trip (#158, #161). [chart-parity.md](chart-parity.md) |
|
||||
| Page layout, print, PDF | 5 | ~80% | ~55% | 12–20 | Page setup, print titles, breaks, page order, Page Break Preview, headers/footers, PDF export and printing. No true Page Layout view; the PDF writer uses only the 14 standard fonts without embedding (no CJK or other non-Latin text in PDF); web print only exports (#141, #143) |
|
||||
| What-if and analysis | 4 | ~50% | ~40% | 22–35 | Goal Seek, Scenario Manager, data tables, Analysis ToolPak (13 of 19 tools, no charts), Solver (Simplex LP, a local nonlinear method and Evolutionary; Answer and Limits reports; Load/Save Model, Reset All and Solver Options; no Sensitivity report), Forecast Sheet. Missing: Analyze Data |
|
||||
| What-if and analysis | 4 | ~50% | ~40% | 22–35 | Goal Seek, Scenario Manager, data tables, Analysis ToolPak (all 19 tools, no charts), Solver (Simplex LP, a local nonlinear method and Evolutionary; Answer and Limits reports; Load/Save Model, Reset All and Solver Options; no Sensitivity report), Forecast Sheet. Missing: Analyze Data |
|
||||
| Get & Transform, external data, data model | 5 | ~15% | ~10% | 60–100 | Import of CSV/TSV, JSON, HTML. No Power Query editor, no From Web, database or folder sources, no connections or refresh, no relationships or data model, no external workbook links (dropped with a warning) |
|
||||
| Insert objects | 3 | ~55% | ~45% | 25–40 | Pictures, 8 shape kinds (Excel has ~170), text boxes, own icon set, ink with Ink to Shape, hyperlinks, checkboxes. Missing: SmartArt, WordArt, equations, signature line, objects, 3-D models, screenshot, pictures in cells (PR #105) |
|
||||
| Review, comments, protection, collaboration | 4 | ~55% | ~40% | 35–55 | Threaded comments, notes, spelling (system dictionary), thesaurus, sheet and workbook protection, Allow Edit Ranges with Excel-compatible password hashes (#307). Missing: comment navigation, Show Changes, notes-to-comments conversion, accessibility checker, co-authoring, opening password-encrypted files (refused with a message since #17) |
|
||||
@@ -334,6 +334,7 @@ The inventory carried over from the 2026-10-07 ROADMAP.md, updated for what land
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-01 | minor | What-if and analysis: Analysis ToolPak has all 19 tools (was 13 of 19; no charts); no score change |
|
||||
| 2026-11-01 | minor | Charts: data tables for column, bar, line, area and compatible combo charts, with legend keys and borders; live source values, native XLSX round-trip and shared print/PDF rendering. Readiness estimates unchanged. |
|
||||
| 2026-11-01 | minor | Page layout and print: Page Setup's page order (Down, then over or Over, then down), kept in XLSX; no score change |
|
||||
| 2026-10-31 | minor | The Insert Function dialog is named as in Excel (it was called Formula Builder); no score change |
|
||||
|
||||
Reference in New Issue
Block a user