Skip to content

Commit b9ebc1b

Browse files
committed
feat(customized example summaries)
1 parent 7e496ad commit b9ebc1b

6 files changed

Lines changed: 134 additions & 8 deletions

File tree

‎apps/web/src/App.tsx‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { editor as MonacoEditor } from 'monaco-editor';
1414
const DEFAULT_MARKDOWN = `## How to use this notebook
1515
1616
- **Markdown cell (this cell):** Double-tap or double-click to edit. Use **Shift+Enter** or the **Render** button to see the rendered version and (with Shift+Enter) move to the code cell.
17-
- **Code cell:** Write Python using \`Table\` and \`make_array\` from the \`datascience\` library. Press **Run** or **Ctrl+Enter** (**Cmd+Enter** on Mac) to execute. The right panel shows step-by-step table operations.
17+
- **Code cell:** Write Python using the \`datascience\` library. Press **Run** or **Ctrl+Enter** (**Cmd+Enter** on Mac) to execute. The right panel shows step-by-step table operations.
1818
- **Visualization:** After running, use the arrows to step through operations and **Export** to save as PDF or **Share** to copy a link.`;
1919

2020
const DEFAULT_CODE = `from datascience import *
@@ -407,10 +407,20 @@ function App() {
407407
})();
408408
}, [isRunning]);
409409

410+
// Back to the starter notebook (what you see on first visit)
411+
const handleResetNotebook = () => {
412+
setCode(DEFAULT_CODE);
413+
setMarkdown(DEFAULT_MARKDOWN);
414+
setCurrentExample('');
415+
setOutput({ stdout: '', stderr: '' });
416+
switchMobileView('notebook');
417+
};
418+
410419
const handleSelectExample = (example: Example) => {
411420
console.log('Loading example:', example.title);
412421
console.log('Example code:', example.code);
413422
setCode(example.code);
423+
setMarkdown(example.markdown);
414424
setCurrentExample(example.title);
415425
setOutput({ stdout: '', stderr: '' }); // Clear previous output
416426
switchMobileView('notebook');
@@ -596,6 +606,7 @@ function App() {
596606
{showGallery && (
597607
<ExamplesGallery
598608
onSelectExample={handleSelectExample}
609+
onReset={handleResetNotebook}
599610
onClose={() => setShowGallery(false)}
600611
/>
601612
)}

‎apps/web/src/components/ExamplesGallery.css‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@
120120
cursor: pointer;
121121
}
122122

123+
.example-card-reset {
124+
border-style: dashed;
125+
border-color: var(--border-strong);
126+
background: transparent;
127+
}
128+
123129
.example-card:hover {
124130
background: var(--bg-card);
125131
border-color: var(--text-tertiary);

‎apps/web/src/components/ExamplesGallery.tsx‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import './ExamplesGallery.css';
44

55
interface ExamplesGalleryProps {
66
onSelectExample: (example: Example) => void;
7+
/** Restore the starter notebook */
8+
onReset: () => void;
79
onClose: () => void;
810
}
911

10-
export default function ExamplesGallery({ onSelectExample, onClose }: ExamplesGalleryProps) {
12+
export default function ExamplesGallery({ onSelectExample, onReset, onClose }: ExamplesGalleryProps) {
1113
const [selectedCategory, setSelectedCategory] = useState<string>('all');
1214

1315
const filteredExamples = selectedCategory === 'all'
@@ -45,6 +47,27 @@ export default function ExamplesGallery({ onSelectExample, onClose }: ExamplesGa
4547
</div>
4648

4749
<div className="examples-grid">
50+
{selectedCategory === 'all' && (
51+
<button
52+
type="button"
53+
className="example-card example-card-reset"
54+
onClick={() => {
55+
onReset();
56+
onClose();
57+
}}
58+
>
59+
<div className="example-thumbnail">
60+
<div className="operation-badges">
61+
<span className="operation-badge">start here</span>
62+
</div>
63+
</div>
64+
<div className="example-info">
65+
<h3>Blank notebook</h3>
66+
<p>The starter notebook you see on first visit, with instructions and a small table to edit.</p>
67+
<div className="example-category">Reset</div>
68+
</div>
69+
</button>
70+
)}
4871
{filteredExamples.map(example => (
4972
<button
5073
type="button"

‎apps/web/src/index.css‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
-moz-osx-font-smoothing: grayscale;
2525
font-feature-settings: 'pnum' on, 'lnum' on, 'liga' on;
2626

27-
/* Jupyter — light (default) */
27+
/* Jupyter: light (default) */
2828
color-scheme: light;
2929
--bg-app: #faf9f5;
3030
--bg-header: #faf9f5;
@@ -75,7 +75,7 @@
7575
background: var(--bg-app);
7676
}
7777

78-
/* Berkeley — always dark */
78+
/* Berkeley: always dark */
7979
[data-theme='berkeley'] {
8080
color-scheme: dark;
8181
--bg-app: radial-gradient(circle at top left, rgba(0, 50, 98, 0.92) 0%, rgba(0, 20, 45, 0.96) 55%, #000a1a 100%);

‎apps/web/src/lib/examples.ts‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface Example {
99
description: string;
1010
category: 'basics' | 'filtering' | 'sorting' | 'grouping' | 'joining' | 'transforming';
1111
operations: string[]; // List of operations demonstrated
12+
markdown: string; // Note shown in the markdown cell above the code
1213
code: string;
1314
thumbnail?: string;
1415
}
@@ -18,6 +19,11 @@ export const examples: Example[] = [
1819
id: 'select-columns',
1920
title: 'Selecting Columns',
2021
description: 'Choose specific columns from a table',
22+
markdown: `## Selecting columns
23+
24+
\`select\` builds a new table containing only the columns you name, in the order you name them. The original table is left untouched. That is true of every \`Table\` method.
25+
26+
Step through the visualization and notice that the number of rows never changes; only the columns do.`,
2127
category: 'basics',
2228
operations: ['select'],
2329
code: `from datascience import *
@@ -45,6 +51,11 @@ result.show()
4551
id: 'filter-rows',
4652
title: 'Filtering Rows',
4753
description: 'Keep only rows that match a condition',
54+
markdown: `## Filtering rows with \`where\`
55+
56+
\`where(column, value)\` keeps only the rows whose value in that column matches. Here we keep the CS majors.
57+
58+
As you step through, watch each row get checked against the condition: kept rows are highlighted, and the ones that don't match are struck through before they disappear from the result.`,
4859
category: 'filtering',
4960
operations: ['where'],
5061
code: `from datascience import *
@@ -71,6 +82,11 @@ cs_students.show()
7182
id: 'sort-values',
7283
title: 'Sorting by Values',
7384
description: 'Order rows by a column',
85+
markdown: `## Sorting rows
86+
87+
\`sort(column)\` orders the rows by a column, smallest first. Passing \`descending=True\` flips that, so the highest GPA comes out on top.
88+
89+
Every row survives a sort. Only their order changes. Watch the rows move in the step-through.`,
7490
category: 'sorting',
7591
operations: ['sort'],
7692
code: `from datascience import *
@@ -96,6 +112,11 @@ sorted_students.show()
96112
id: 'add-column',
97113
title: 'Adding a Column',
98114
description: 'Add a new column to the table',
115+
markdown: `## Adding a column
116+
117+
\`with_column(label, values)\` returns a copy of the table with one more column. The array of values must have exactly one entry per row, in row order.
118+
119+
Here \`grades\` is an array we build first with \`make_array\`, then attach. Watch the new column appear on the right of the result.`,
99120
category: 'transforming',
100121
operations: ['with_column'],
101122
code: `from datascience import *
@@ -122,6 +143,11 @@ result.show()
122143
id: 'drop-column',
123144
title: 'Dropping Columns',
124145
description: 'Remove columns from a table',
146+
markdown: `## Dropping columns
147+
148+
\`drop\` is the mirror image of \`select\`: you name the columns you want to get rid of, and everything else stays.
149+
150+
Use it when a table has many columns and it is easier to say what you don't need.`,
125151
category: 'basics',
126152
operations: ['drop'],
127153
code: `from datascience import *
@@ -149,6 +175,11 @@ result.show()
149175
id: 'group-aggregate',
150176
title: 'Grouping and Aggregating',
151177
description: 'Group rows and compute statistics',
178+
markdown: `## Grouping and aggregating
179+
180+
\`group(column, function)\` collects the rows that share a value in the column, then applies the function to each group's values. With \`sum\`, every product ends up with the total of its amounts.
181+
182+
Step through it: first the rows are gathered into groups, then the values in each group are collapsed into one number. Notice the new column is named \`Amount sum\`.`,
152183
category: 'grouping',
153184
operations: ['group'],
154185
code: `from datascience import *
@@ -175,6 +206,11 @@ totals.show()
175206
id: 'join-tables',
176207
title: 'Joining Tables',
177208
description: 'Combine two tables on a common column',
209+
markdown: `## Joining two tables
210+
211+
\`join(column, other_table)\` matches rows from two tables that share a value in the named column and lines them up side by side.
212+
213+
Here both tables have an \`ID\` column. Watch the second table appear under the first, with matching keys highlighted as each row is paired up.`,
178214
category: 'joining',
179215
operations: ['join'],
180216
code: `from datascience import *
@@ -210,6 +246,11 @@ result.show()
210246
id: 'chain-operations',
211247
title: 'Chaining Operations',
212248
description: 'Combine multiple operations in sequence',
249+
markdown: `## Chaining operations
250+
251+
Because every \`Table\` method returns a new table, you can call another method on the result right away. Wrapping the expression in parentheses lets you put each step on its own line.
252+
253+
Read it top to bottom: keep the CS majors, keep only name and GPA, then sort. The visualization shows one step per method call.`,
213254
category: 'basics',
214255
operations: ['select', 'where', 'sort'],
215256
code: `from datascience import *
@@ -240,6 +281,11 @@ result.show()
240281
id: 'multiple-filters',
241282
title: 'Multiple Filters',
242283
description: 'Apply multiple where conditions',
284+
markdown: `## Filtering twice
285+
286+
A second \`where\` on the result of the first narrows things down further. \`are.above(3.5)\` is a *predicate*, a reusable condition, from the \`are\` collection.
287+
288+
Compare the two filter steps: the first checks the Major column, the second checks GPA against a threshold.`,
243289
category: 'filtering',
244290
operations: ['where'],
245291
code: `from datascience import *
@@ -274,6 +320,11 @@ high_gpa.show()
274320
id: 'pivot-table',
275321
title: 'Pivot Tables',
276322
description: 'Reshape data with pivot operations',
323+
markdown: `## Pivoting
324+
325+
\`pivot(columns, rows, values, function)\` turns one column's values into column headers and another's into row labels, then fills each cell by aggregating the \`values\` column.
326+
327+
Here every Product becomes a column and every Region a row, and each cell is the sum of Sales for that pair. Step through to see the source rows feed each cell.`,
277328
category: 'transforming',
278329
operations: ['pivot'],
279330
code: `from datascience import *
@@ -300,6 +351,11 @@ pivoted.show()
300351
id: 'group-multiple',
301352
title: 'Group with Multiple Aggregates',
302353
description: 'Group by column and compute multiple statistics',
354+
markdown: `## Different aggregates on the same groups
355+
356+
The function you pass to \`group\` decides what each group collapses to. \`np.mean\` gives the average score per major; \`max\` gives the highest.
357+
358+
The groups are identical in both calls. Only the number in the second column changes, and so does its name: \`Score mean\` versus \`Score max\`.`,
303359
category: 'grouping',
304360
operations: ['group'],
305361
code: `from datascience import *
@@ -332,6 +388,11 @@ max_scores.show()
332388
id: 'join-multiple-keys',
333389
title: 'Joining on Multiple Keys',
334390
description: 'Join tables with multiple matching columns',
391+
markdown: `## Joining on an ID
392+
393+
\`StudentID\` is the key that links a student's record to their enrollment. \`join\` pairs each row in \`students\` with the row in \`enrollment\` that has the same ID.
394+
395+
Watch the keys get matched one at a time, and notice the joined table keeps the columns from both.`,
335396
category: 'joining',
336397
operations: ['join'],
337398
code: `from datascience import *
@@ -368,6 +429,11 @@ result.show()
368429
id: 'complex-workflow',
369430
title: 'Complex Workflow',
370431
description: 'Combine multiple advanced operations',
432+
markdown: `## A full pipeline
433+
434+
This is the shape of most real analyses: filter down to the rows you care about, select the useful columns, group to summarise, and sort to rank.
435+
436+
Watch how the table changes character at each step, from individual transactions to one row per product. The sort uses \`Amount sum\`, the column that \`group\` created.`,
371437
category: 'basics',
372438
operations: ['select', 'where', 'group', 'sort', 'join'],
373439
code: `from datascience import *
@@ -400,6 +466,11 @@ result.show()
400466
id: 'take-sample',
401467
title: 'Taking Sample Rows',
402468
description: 'Select a specific number of rows from a table',
469+
markdown: `## Taking rows by position
470+
471+
\`take(n)\` returns the first \`n\` rows. You can also pass a specific row number, or a range of them, using \`take(np.arange(start, stop))\`.
472+
473+
Row positions start at 0. Watch the first three rows get picked out and the rest fall away.`,
403474
category: 'basics',
404475
operations: ['take'],
405476
code: `from datascience import *
@@ -426,6 +497,11 @@ sample.show()
426497
id: 'pivot-complex',
427498
title: 'Complex Pivot Operation',
428499
description: 'Reshape data with multiple dimensions',
500+
markdown: `## Pivoting scores
501+
502+
Each student took each subject once, so the pivot has one row per student and one column per subject, and each cell holds that student's score.
503+
504+
Step through and check that every (student, subject) pair from the original table lands in exactly one cell.`,
429505
category: 'transforming',
430506
operations: ['pivot'],
431507
code: `from datascience import *
@@ -452,6 +528,11 @@ pivoted.show()
452528
id: 'group-with-aggregate',
453529
title: 'Group with Custom Aggregate',
454530
description: 'Group data and apply custom aggregation functions',
531+
markdown: `## Total versus average
532+
533+
Grouping by Region with \`sum\` gives each region's total; grouping with \`np.mean\` gives its average. The groups are the same, the summary differs.
534+
535+
Notice the group step happens first in both calls, and the aggregation happens after.`,
455536
category: 'grouping',
456537
operations: ['group'],
457538
code: `from datascience import *
@@ -484,6 +565,11 @@ avg_by_region.show()
484565
id: 'multi-step-analysis',
485566
title: 'Multi-Step Data Analysis',
486567
description: 'Complete workflow: filter, group, sort, and select',
568+
markdown: `## Filter, group, sort, select
569+
570+
A four-step analysis, one method at a time. Keep employees with at least three years (\`are.above_or_equal_to\`), average each department's scores, rank the departments, then keep just the two columns worth reporting.
571+
572+
Watch the \`Years\` column disappear after the group step: once rows are collapsed into groups, only the aggregated columns remain.`,
487573
category: 'basics',
488574
operations: ['where', 'group', 'sort', 'select'],
489575
code: `from datascience import *

‎apps/web/src/lib/tracer.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def _sub_steps_group(table, args, kwargs, input_state, output_state):
264264
})
265265

266266
steps.append({
267-
"message": f"Find the unique values in '{label}' — each one becomes exactly one row of the result.",
267+
"message": f"Find the unique values in '{label}'. Each one becomes exactly one row of the result.",
268268
"detail": f"Unique values (sorted): {_fmt_list(uniq)}",
269269
"input_highlights": {"columns": [label]},
270270
"output_state": _make_state(out_cols, []),
@@ -501,13 +501,13 @@ def _sub_steps_sort(table, args, kwargs, input_state, output_state):
501501
if descending:
502502
order = order[::-1]
503503
steps.append({
504-
"message": "Rows keep all their values — only their order changes.",
504+
"message": "Rows keep all their values. Only their order changes.",
505505
"detail": f"New order of original row positions: {_fmt_list([int(i) + 1 for i in order])}",
506506
"output_highlights": {"columns": [label]},
507507
})
508508
except Exception:
509509
steps.append({
510-
"message": "Rows keep all their values — only their order changes.",
510+
"message": "Rows keep all their values. Only their order changes.",
511511
"output_highlights": {"columns": [label]},
512512
})
513513
return steps
@@ -547,7 +547,7 @@ def _sub_steps_join(table, args, kwargs, input_state, output_state):
547547
"output_state": _make_state(output_state["columns"], []),
548548
},
549549
{
550-
"message": "Find the key values that appear in BOTH tables — only those rows will be joined.",
550+
"message": "Find the key values that appear in BOTH tables. Only those rows will be joined.",
551551
"detail": f"Keys found in both tables: {_fmt_list(shared)}",
552552
"input_highlights": {"rows": left_match, "columns": [label]},
553553
"aux_table": {"label": "Second table", "state": aux_state,

0 commit comments

Comments
 (0)