# DataChat – Free, zero-dependency Java tool ask questions with Claude/Offline AI

> Source: <https://github.com/RDMachinery/DataChat>
> Published: 2026-10-20 11:00:42+00:00

**Ask your CSV questions in plain English and get an answer plus a chart.**

No pivot tables, no remembering which formula does what. Load a CSV and type things like:

- *"What's driving the drop in March?"*
- *"Chart the financial transactions by region"*
- *"Monthly revenue by category, excluding refunded"*
- *"Share of revenue by channel as a pie chart"*

DataChat is a single-file Java Swing desktop app with **no external dependencies**. It works with Claude (Anthropic API) for natural-language understanding, and it also ships with a built-in **offline engine and demo data** so you can try it without an API key.

**Requirements:** Java 17 or newer (JDK for the commands below).

```
# Run directly from source (Java 11+ source launcher)
java DataChat.java

# or compile first
javac DataChat.java
java DataChat
```

The app opens with **demo data already loaded**, so you can click an example question straight away.

Either paste your API key into the **Claude API key** field in the toolbar, or set environment variables before launching:

```
export ANTHROPIC_API_KEY="sk-ant-..."
export ANTHROPIC_MODEL="claude-sonnet-5-5"   # optional; this is the default
java DataChat.java
```

The key is held in memory only and is never written to disk. Without a key (or with **Offline mode** ticked) the built-in rule-based engine answers instead.

| Area | What it does | 
|---|---|
| **Toolbar** | Two rows. Top: **Load CSV...** ,**Demo data** and**Export PDF report...** (greyed out until you've asked a question). Bottom: Claude API key, model name, Offline mode and the sample-rows privacy toggle | 
| **Chat (left)** | Your questions, the analyst's plain-English answers, the auto-insights for the loaded file, and clickable example questions | 
| **Chart tab** | The chart(s) for the latest answer. Hover for exact values. **Save chart as PNG...** exports it | 
| **Result table tab** | The exact numbers behind the answer. **Save result as CSV...** exports them (see[Exporting results](#exporting-results) ) | 
| **Your data tab** | A scrollable view of the loaded CSV | 
| **How it was computed tab** | The JSON plan that was executed, how many rows were used, and the computed facts | 

The status bar shows the loaded file, row and column counts, and which engine is active (Claude or offline).

- Click **Load CSV...** , or**drag and drop** a`.csv` ,`.tsv` or`.txt` file onto the window.
- Click **Demo data** to load (or reload) the built-in sample.

The demo is 2,317 synthetic financial transactions from January to June 2026 with these columns: `TransactionID, Date, Region, Category, Channel, Status, Amount`.

It contains a deliberate story: **March dips** because the North region (store closures) and the Hardware category (supply shortage) sell much less. Ask *"What's driving the drop in March?"* and the analysis should surface exactly those two.

As soon as a file loads, DataChat scans it and posts a short list of things worth a closer look in the chat. Each line ends with an **explore** link: click it and the full analysis (table, charts and explanation) runs immediately, with no typing.

| Insight | What triggers it | 
|---|---|
| **Biggest drop** | The largest period-on-period fall in the main measure, if it is at least 10%. *Explore* runs the driver analysis for exactly those two periods | 
| **Biggest rise** | The same for the largest increase | 
| **Over-indexing segment** | A segment whose share of the measure differs from its share of rows by at least 10 points, for example *"Services is 16% of rows but 33% of Total Amount"* . When the table has no numeric column it flags a segment that makes up at least half of all rows | 
| **Unusual values** | Values above Q3 + 3 x IQR in the main numeric column (shown only if they are under 5% of rows) | 
| **Overall trend** | The change from the first to the last period, if it is at least 5% | 
| **Missing data** | A column that is at least 20% empty | 

At most five insights are shown, in that priority order. Details:

- The "main measure" is the first numeric column whose name looks like money (*amount, revenue, sales, price, cost, profit...* ), otherwise the first numeric column. With no numeric columns, insights count rows instead.
- A last month that stops part-way through is ignored when looking for drops, so an incomplete month never shows up as a fake slump.
- Insights are computed locally, so they never call the API. Clicking *explore* does use Claude for the explanation if a key is set.
- The insights are also printed in the [PDF report](#pdf-report) .

Click **Export PDF report...** (top row of the toolbar, next to Demo data) to save everything you've asked in the current session as a single A4 document. The button is greyed out until you've asked at least one question.

The report contains:

1. A title block with the dataset name, row and column counts, the date, and the column list.
2. The **Key insights** found on load.
3. For every question, in order: the question, the plain-English answer, how many rows were used and any filters applied, the chart(s), and the result table (first 25 rows).

Notes:

- Loading a new file starts a fresh session, so the report only covers the current dataset.
- Charts are embedded as images and tables are real selectable text. Long tables continue across pages with the header repeated.
- The writer is built into the program (no PDF library), using the standard Helvetica fonts. Characters outside Western European text, such as Chinese, Cyrillic or Arabic, appear as `?` .
- For the complete table behind an answer, use **Save result as CSV...** .

Open the **Result table** tab and click **Save result as CSV...** to save the table behind the latest answer. The button is greyed out until you've asked a question.

- The suggested file name comes from the answer's title.
- Numbers are written as plain values (`12503.58` , not`+12,503.58` ) so they open cleanly in Excel, Numbers or Google Sheets. Percentage columns such as`-59.4%` stay as text.
- The file is UTF-8 with a byte-order mark, which Excel needs to display non-ASCII characters correctly, and fields containing commas, quotes or line breaks are quoted.
- The chart isn't part of the CSV. Use **Save chart as PNG...** for that.

Type in the box at the bottom of the chat and press Enter (or click **Ask**). With Claude enabled, follow-ups can build on the previous question, for example *"now split that by channel"* or *"only for North"*. The last three turns are sent as context.

| Ask | You get | 
|---|---|
| `Chart the financial transactions by region` | Bar chart of total Amount per Region | 
| `Monthly revenue by category` | Multi-line chart, one line per Category | 
| `What's driving the drop in March?` | Trend chart plus a ranked breakdown of which segments caused the change | 
| `Why did revenue rise in May?` | Same analysis for an increase | 
| `Compare February and March` | Driver analysis between two named periods | 
| `Share of revenue by channel as a pie chart` | Pie chart with percentages | 
| `Average amount by category, excluding refunded` | Filtered average per Category | 
| `How many transactions per month?` | Row counts over time | 
| `Revenue by weekday` | Totals for Mon to Sun | 
| `Show amount by region for Q2` | Date-filtered totals | 
| `Top 5 largest transactions` | The five biggest rows, as a table | 
| `Describe this data` | Column-by-column overview | 

DataChat splits the problem so that the language model never has to do arithmetic:

```
 your question
      |
      v
 1. PLAN      Claude reads the question + a compact schema of your table
              and replies with a small JSON "analysis plan".
      |
      v
 2. EXECUTE   The app runs that plan itself on the FULL dataset
              (filter, group, aggregate, compare), so numbers are exact.
      |
      v
 3. EXPLAIN   Claude writes a short plain-English answer from the
              computed result table; the app draws the chart.
```

Why this design:

- **Accuracy.** Every number comes from Java code running over all your rows, not from a model's estimate.
- **Privacy.** Claude sees the schema and the small aggregated result, not your raw rows (see[Privacy](#privacy) ).
- **Transparency.** The*How it was computed* tab shows the exact plan that ran, so you can verify the answer.
- **Graceful fallback.** If the API call fails, the app falls back to the offline engine and tells you.

For the planning call:

- The table name, row count and, for each column, its **type** :`NUMBER` ,`DATE` or`TEXT` .
- Per column: numeric min, max and mean; date range; or the distinct values of text columns (up to 25, otherwise a few examples).
- Optionally the first 3 rows (only if **Send sample rows to AI** is ticked).
- The last three question/plan pairs, and today's date.

For the explanation call: your question, the executed plan, the computed facts, and at most the first 30 rows of the result table.

Both calls go to `https://api.anthropic.com/v1/messages` using Java's built-in `HttpClient`.

Claude (or the offline planner) produces one JSON object. The engine executes it.

```
{
  "intent": "aggregate",
  "title": "Total Amount by month and Category",
  "metric": "Amount",
  "agg": "sum",
  "x": "Date",
  "bucket": "month",
  "series": "Category",
  "filters": [{ "column": "Status", "op": "!=", "value": "Refunded" }],
  "sort": "none",
  "limit": null,
  "chart": "line"
}
```

| Field | Meaning | 
|---|---|
| `intent` | `aggregate` (group + chart),`drivers` (explain a change),`rows` (list individual rows),`describe` (column overview) | 
| `metric` | Numeric column to measure, or `null` to count rows | 
| `agg` | `sum` ,`avg` ,`count` ,`min` ,`max` ,`median` | 
| `x` | Column for the x-axis or grouping ( `null` = one overall number) | 
| `bucket` | For date columns: `day` ,`weekday` ,`week` ,`month` ,`quarter` ,`year` (auto-chosen from the date span if omitted) | 
| `series` | Optional second grouping, drawn as separate colours (max 8 series) | 
| `filters` | `column` ,`op` (`=` ,`!=` ,`>` ,`<` ,`>=` ,`<=` ,`contains` ,`in` ,`not in` ) and`value` . Date filters accept partial ISO dates like`2026-03` | 
| `sort` /`limit` | Ordering and top-N. Categories default to descending by value; large category sets are capped at 20 | 
| `chart` | `bar` ,`line` ,`pie` ,`none` (omitted means auto) | 
| `from` /`to` /`direction` | For `drivers` : the periods to compare (for example`2026-02` ,`2026-03` ) and whether to look for a`drop` or a`rise` | 

Chart choice when `chart` is auto: time on the x-axis gives a line (or bars for very few points), categories give bars (horizontal when there are many or the labels are long), and pie is used only when requested with up to 10 positive slices.

If a column name in a plan doesn't match exactly, the engine tries case-insensitive and fuzzy matching, and otherwise reports the available columns.

For questions like *"what's driving the drop in March?"* the engine:

1. Buckets the data by period (month by default) and totals the measure for each.
2. Picks the periods to compare. A single named period is compared with the one before it. If none is named, it finds the **largest period-on-period drop** (or rise).
3. For every text column with 2 to 40 distinct values, computes each segment's value in both periods.
4. Ranks segments by their **excess movement** : how far they moved beyond what they'd have done if they had followed the overall trend. This stops large, stable segments (such as`Status = Completed` , which is most of the data) from topping the list just because they are big.
5. Also checks **two-way combinations** (for example`Region = North & Category = Hardware` ) and keeps only those that moved clearly more (at least 10 percentage points) than both of their parts.
6. Reports segments that lagged the trend or moved the opposite way, and splits the change into **row count vs average value per row** .

You get two charts: the trend with both periods highlighted, and a horizontal bar chart of each segment's change (green up, red down). The result table shows each segment's change, percentage change and **share of the total change**. Shares are computed within each dimension separately, so they can overlap across dimensions.

If the final period in the data stops mid-month, the answer warns that it may be incomplete.

- Your CSV is read and processed **locally** . Aggregation, filtering and charting all happen in Java on your machine.
- Claude only ever receives the schema summary, the aggregated result table (30 rows at most), and, **only if you tick "Send sample rows to AI"** , the first three data rows.
- Text-column summaries include distinct values (up to 25 per column), so avoid using Claude with columns whose *values* are sensitive, or use**Offline mode** .
- In **Offline mode** nothing leaves your machine.

With no API key (or **Offline mode** ticked), a built-in rule-based planner turns your question into the same JSON plan. It understands:

- **Measures:** column names mentioned in the question, plus words like*average, median, max, min, how many, count* . If you don't name a measure, it picks the most money-like numeric column and says so.
- **Grouping:*** by, per, each, across* , column names (and their plurals), and time words such as*monthly, per week, over time, trend, weekday* .
- **Filters:** category values appearing in the question (`North` ,`Hardware` ), exclusions (*excluding refunded* ,*without retail* ), and dates (*in March* ,*Q2* ,*2026* ).
- **Ranking and charts:*** top 5, bottom 3, lowest, best* , and*pie / line / bar* .
- **Change questions:*** drop, fell, decline, rise, why, driving, compare X and Y* .

It's good for exploring and for demos. It is keyword-based, so unusual phrasing and conversational follow-ups work best with Claude enabled.

- **Delimiters** are auto-detected: comma, semicolon, tab or pipe. Quoted fields and escaped quotes are supported.
- **Encoding:** UTF-8 (BOM handled), with a fallback to Windows-1252 if invalid bytes are found.
- **Numbers:** currency symbols, thousands separators and percent signs are stripped (`$1,200.50` ,`£99` ,`12%` ), and`(20)` is read as`-20` .
- **European number formats** are detected per column:`1.234,56` ,`12,5` ,`1 234,5 €` and`(300,40)` are all read correctly. A value like`1.234.567,89` or`12,5` counts as European evidence and one like`1,234.56` or`1,234,567` as US evidence. Values that could be either (`1,234` or`1.200` on their own) follow the rest of the column, then the rest of the file, and default to US style if there is no evidence anywhere. Dotted dates such as`01.03.2026` are still recognised as dates, not numbers. If an ambiguous column is read the wrong way, give it one unambiguous value (for example`1.200,00` ) or reformat the file.
- **Dates:** auto-detected from`yyyy-MM-dd` ,`yyyy-MM` ,`yyyy/MM/dd` ,`d/M/yyyy` (day first is tried before month first),`M/d/yyyy` ,`d-M-yyyy` ,`d.M.yyyy` , and`d MMM yyyy` /`MMM d, yyyy` styles. A time portion is ignored.
- **Type detection:** a column is`NUMBER` or`DATE` if at least 95% of its non-empty values parse. Columns named like IDs (`Customer ID` ,`OrderID` ,`zip` ,`phone` ) are kept as text so they aren't summed.
- Blank headers become `Column N` , and duplicate headers get a numeric suffix.

- The data is held in memory, so very large files (millions of rows) will need a bigger heap (`java -Xmx4g ...` ).
- Only one table at a time. There are no joins across files.
- Day-first dates are assumed when a column is ambiguous (for example `03/04/2026` with no day above 12).
- Number columns whose every value is ambiguous (only `1,234` -style or`1.200` -style values, in a file with no other evidence) are read as US numbers.
- Stacked charts and multiple y-axes aren't supported. Multi-series data is drawn as grouped bars or multiple lines.
- Driver analysis considers text columns (up to 40 distinct values each) and two-way combinations of columns with up to 12 values. Numeric columns aren't used as dimensions.
- The offline engine can't resolve follow-ups that depend on the previous question.
- The PDF report uses built-in fonts and only covers Western European characters.
- A language model can occasionally misread a question. Check the **How it was computed** tab if an answer surprises you.

| Problem | Fix | 
|---|---|
| Status bar says *offline engine* | No API key entered, or **Offline mode** is ticked | 
| Note says *Claude's plan could not be used* | The API returned an error (bad key, wrong model name, no network) or an invalid plan. The message includes the HTTP error. The answer came from the offline engine | 
| `I couldn't find a column called ...` | The plan referenced a column that doesn't exist. Rephrase using the column names listed in the chat | 
| `... isn't numeric` | You asked to sum or average a text or date column. Use a numeric column, or count instead | 
| Dates not recognised | Check the **Your data** tab and the*Describe this data* answer to see the detected type. Reformat the column as`yyyy-MM-dd` | 
| Garbled characters | Re-save the file as UTF-8 | 
| Out of memory | Launch with a larger heap: `java -Xmx4g DataChat.java` | 
| Numbers read 1000x too small or large | A `1.200` or`1,234` column was read in the wrong style. See*European number formats* under[CSV handling](#csv-handling) | 
| No auto-insights appear | Nothing in the data crossed the thresholds (for example fewer than 3 time periods), or the file has no date or numeric columns. Ask questions as usual | 
| `?` characters in the PDF | The text contains characters outside Western European encodings, which the built-in PDF fonts can't show | 

Everything lives in `DataChat.java`, organised as nested classes:

| Class | Responsibility | 
|---|---|
| `DataChat` | Swing UI: toolbar, chat transcript, tabs, drag and drop, background `SwingWorker` tasks | 
| `DataTable` ,`Column` | In-memory table, CSV parsing, type detection ( `NUMBER` /`DATE` /`TEXT` ), schema description for the model | 
| `Plan` ,`Filter` | The analysis plan, with JSON conversion | 
| `Engine` | Executes a plan: filtering, grouping, aggregation, driver analysis, fact generation | 
| `Result` ,`ChartData` | Output of the engine: table rows, chart specs, plain-English facts | 
| `LocalPlanner` | Offline natural-language to plan translation, and offline answer text | 
| `Claude` | Anthropic Messages API client plus the planner and explainer system prompts | 
| `Json` | Minimal JSON reader and writer (no libraries) | 
| `ChartPanel` | Java2D chart rendering: bar, horizontal bar, line, pie, with tooltips. Also used to render charts into the PDF | 
| `Insights` ,`Insight` | The on-load scan: biggest drop and rise, over-indexing segments, outliers, trend and missing data. Each insight carries a ready-made `Plan` | 
| `Pdf` ,`ReportBuilder` | Minimal PDF 1.4 writer (text, rules, JPEG images, Flate-compressed pages) and the report layout | 
| `DemoData` | Synthetic transactions generator and example questions | 

- **New aggregation:** add it to`Acc.get` ,`Engine.aggOf` and`measureLabel` , and mention it in`Claude.PLAN_SYSTEM` .
- **New time bucket:** add a case in`Engine.bucketLabel` and the allowed set in`Engine.bucketOf` .
- **New chart type:** add a`ChartType` , a paint method in`ChartPanel` , and a rule in`Engine.chartType` .
- **New insight:** add a method to`Insights` that builds an`Insight(headline, plan)` and call it from`Insights.generate` . The plan runs through the normal engine when the user clicks*explore* .
- **Report layout:** edit`ReportBuilder.build` ; the`Pdf` helper offers`para` ,`table` ,`image` ,`rule` and automatic page breaks.
- **Different model:** change the**Model** field or set`ANTHROPIC_MODEL` .
- **Your own demo data:** edit`DemoData.create()` and`DemoData.examplesFor()` .
