datagrok-viewers
Add a viewer, configure a viewer, change viewer options, find viewer, close viewer, view a scatter plot, bar chart, histogram, line chart, box plot, pie chart, heat map, correlation plot, 3D scatter,
SKILL.mdpackages/Grokky/dockerfiles/claude-runtime/plugin/skills/datagrok-viewers/SKILL.md
GitHubdatagrok-viewers
Add, configure, find, and close viewers on a DG.TableView via the
datagrok_exec tool. Globals injected by the runtime: grok, ui, DG,
view, t (the current DG.DataFrame, when the view is a TableView).
Quick reference
| Need | Call |
|---|---|
| Add a viewer | view.addViewer(DG.VIEWER.SCATTER_PLOT, {xColumnName: 'a', ...}) |
| Configure an existing viewer | viewer.setOptions({colorColumnName: 'logP'}) |
| All non-grid viewers | Array.from(view.viewers).slice(1) |
| First viewer of a type | Array.from(view.viewers).slice(1).find((v) => v.type === DG.VIEWER.SCATTER_PLOT) |
| Close one viewer | viewer.close() (wrap in try/catch — throws if never attached) |
| Close every non-grid viewer | Array.from(view.viewers).slice(1).forEach((v) => { try { v.close(); } catch {} }) |
| Inspect a viewer's option schema | viewer.getProperties() → DG.Property[] (each has .name) |
| Standalone (detached) viewer | DG.Viewer.fromType(DG.VIEWER.SCATTER_PLOT, t, {...}) |
THE viewer footgun: column-binding properties always end in ColumnName(s)
Users say "x is mw, y is logp". You must emit xColumnName / yColumnName —
never bare x / y / color. Bare aliases are inconsistent across viewer
types and may silently do nothing for the canonical addViewer/setOptions
path. Always use the full *ColumnName(s) form.
| User says | Emit (single column) | Emit (multiple columns) |
|---|---|---|
x is mw |
xColumnName: 'mw' |
xColumnNames: ['mw', ...] |
y is logp |
yColumnName: 'logp' |
yColumnNames: ['logp', ...] |
color by activity |
colorColumnName: 'activity' |
— |
size by mw |
sizeColumnName: 'mw' |
— |
label by smiles |
labelColumnName: 'smiles' |
— |
split by class |
splitColumnName: 'class' |
splitColumnNames: ['class'] |
value is ic50 |
valueColumnName: 'ic50' |
valueColumnNames: ['ic50'] |
category is class |
categoryColumnName: 'class' |
categoryColumnNames: ['class'] |
columns are a, b |
— | columnNames: ['a', 'b'] |
- Single column properties: singular
<role>ColumnName(string). - Multi-column properties: plural
<role>ColumnNames(array of strings). - Some viewers take both forms for different roles (Line chart has
xColumnNamefor the x axis andyColumnNamesfor the series). - When in doubt, inspect the live schema:
viewer.getProperties()returnsDG.Property[]. Each has.name(canonical),.propertyType,.choices.
Other naming conventions across viewers:
- Boolean toggles start with
show:showRegressionLine,showXAxis,showLegend,showStatistics,showColorSelector. NotregressionLine/legend/statistics. - Axis scale:
xAxisType: 'linear' | 'logarithmic'(andy,z,colorAxisType). NotlogX, notlogScale. - Axis bounds:
xMin,xMax,yMin,yMax. NotxRange: [a, b].
Unconfirmed options
Options not documented here must be confirmed against the live schema before
setting: viewer.getProperties() → match .name, respect .propertyType and
.choices. Values are scalars — never objects/arrays: a wrong-typed value is
stored unchecked.
The viewers array
view.viewers is a synchronous array snapshot:
view.viewers[0]is always the grid. Closing it breaks the table widget. Every iteration must.slice(1)(or filter outv.type === DG.VIEWER.GRID).- Every other entry is a non-grid viewer. Each has
.type(a string equal to one of theDG.VIEWER.*constants) and.dataFrame.
Viewer types catalog
Always use the DG.VIEWER.* constants, never literal strings:
| Constant | String value | Primary properties |
|---|---|---|
DG.VIEWER.SCATTER_PLOT |
'Scatter plot' |
xColumnName, yColumnName, colorColumnName, sizeColumnName, showRegressionLine |
DG.VIEWER.HISTOGRAM |
'Histogram' |
valueColumnName, splitColumnName, bins |
DG.VIEWER.LINE_CHART |
'Line chart' |
xColumnName, yColumnNames (array) |
DG.VIEWER.BAR_CHART |
'Bar chart' |
splitColumnName, valueColumnName, valueAggrType |
DG.VIEWER.PIE_CHART |
'Pie chart' |
categoryColumnName |
DG.VIEWER.BOX_PLOT |
'Box plot' |
valueColumnName, categoryColumnName |
DG.VIEWER.HEAT_MAP |
'Heat map' |
(returns a Grid subclass — column tag-driven color coding) |
DG.VIEWER.STATISTICS |
'Statistics' |
columnNames |
DG.VIEWER.CORR_PLOT |
'Correlation plot' |
columnNames (or xs / ys) |
DG.VIEWER.DENSITY_PLOT |
'Density plot' |
xColumnName, yColumnName |
DG.VIEWER.SCATTER_PLOT_3D |
'3d scatter plot' |
xColumnName, yColumnName, zColumnName, colorColumnName |
DG.VIEWER.TRELLIS_PLOT |
'Trellis plot' |
xColumnNames, yColumnNames, viewerType |
DG.VIEWER.PC_PLOT |
'PC Plot' |
columnNames |
DG.VIEWER.TREE_MAP |
'Tree map' |
splitColumnNames, sizeColumnName |
DG.VIEWER.MATRIX_PLOT |
'Matrix plot' |
xColumnNames, yColumnNames |
DG.VIEWER.FILTERS |
'Filters' |
columnNames (see datagrok-filtering for the real filtering API) |
DG.VIEWER.FORM |
'Form' |
columnNames |
DG.VIEWER.MARKUP |
'Markup' |
content (string) |
DG.VIEWER.NETWORK_DIAGRAM |
'Network diagram' |
node1ColumnName, node2ColumnName |
DG.VIEWER.WORD_CLOUD |
'Word cloud' |
column |
DG.VIEWER.TILE_VIEWER |
'Tile Viewer' |
per-row cards |
DG.VIEWER.PIVOT_TABLE |
'Pivot table' |
grouped aggregates |
DG.VIEWER.SHAPE_MAP |
'Shape Map' |
choropleth |
DG.VIEWER.CALENDAR |
'Calendar' |
dateColumnName |
DG.VIEWER.GLOBE |
'Globe' |
latitudeColumnName, longitudeColumnName |
DG.VIEWER.GOOGLE_MAP |
'Google map' |
latitudeColumnName, longitudeColumnName |
DG.VIEWER.TIMELINES |
'Timelines' |
startColumnName, endColumnName |
DG.VIEWER.RADAR_VIEWER |
'Radar' |
columnNames |
DG.VIEWER.GRID |
'Grid' |
(the main grid — view.grid is the typed accessor) |
Full canonical list at runtime: Object.values(DG.VIEWER).
Adding viewers
Use the in-scope view global, not grok.shell.tv (the globally active
view, which may be a different tab when the user has several open).
// Scatter plot of MW vs LogP, colored by activity, with regression line.
view.addViewer(DG.VIEWER.SCATTER_PLOT, {
xColumnName: 'MW',
yColumnName: 'LogP',
colorColumnName: 'activity',
showRegressionLine: true,
});
// Line chart with multiple y series — yColumnNames is the plural form (array).
view.addViewer(DG.VIEWER.LINE_CHART, {
xColumnName: 'date',
yColumnNames: ['revenue', 'expenses', 'profit'],
});
// Bar chart of count by category.
view.addViewer(DG.VIEWER.BAR_CHART, {
splitColumnName: 'category',
valueColumnName: 'count',
valueAggrType: 'sum',
});
Configuring an existing viewer
viewer.setOptions({...}) applies a batch of property changes and re-renders
once. Same property-name rules as addViewer.
// Recolor the existing scatter plot by a different column.
const sp = Array.from(view.viewers).slice(1)
.find((v) => v.type === DG.VIEWER.SCATTER_PLOT);
if (sp)
sp.setOptions({colorColumnName: 'logP'});
view.addViewer(...) returns the freshly attached viewer, so you can chain
create + configure:
const sp = view.addViewer(DG.VIEWER.SCATTER_PLOT, {
xColumnName: 'height',
yColumnName: 'weight',
showRegressionLine: true,
});
sp.setOptions({xMin: 150, xMax: 200, colorColumnName: 'age'});
To discover property names, ask the viewer:
const v = view.addViewer(DG.VIEWER.SCATTER_PLOT);
const names = v.getProperties().map((p) => p.name);
return ui.divText(names.join(', '));
Finding viewers
view.viewers is an array; index 0 is always the grid, so iteration always
starts with .slice(1).
// First scatter plot on the view (or undefined).
const sp = Array.from(view.viewers).slice(1)
.find((v) => v.type === DG.VIEWER.SCATTER_PLOT);
// All histograms.
const hs = Array.from(view.viewers).slice(1)
.filter((v) => v.type === DG.VIEWER.HISTOGRAM);
Closing viewers
viewer.close() closes and detaches the viewer. It throws if the viewer
was never attached to a view, so wrap in try/catch.
// Close every scatter plot on the view.
Array.from(view.viewers).slice(1)
.filter((v) => v.type === DG.VIEWER.SCATTER_PLOT)
.forEach((v) => { try { v.close(); } catch {} });
// Close every non-grid viewer (reset the view to just the grid).
Array.from(view.viewers).slice(1)
.forEach((v) => { try { v.close(); } catch {} });
Close-and-replace pattern for "show me X instead of Y":
// Close all scatter plots, then add a histogram.
Array.from(view.viewers).slice(1)
.filter((v) => v.type === DG.VIEWER.SCATTER_PLOT)
.forEach((v) => { try { v.close(); } catch {} });
view.addViewer(DG.VIEWER.HISTOGRAM, {valueColumnName: 'activity'});
Plugin viewers — they are functions, not types
Some "viewers" users name are actually package-registered functions that
create a viewer as a side effect. They aren't in DG.VIEWER. Route via
grok.functions.call:
| User phrase | Function call |
|---|---|
| "chem space" / "chemical space" | grok.functions.call('Chem:chemSpaceTopMenu', {table: t, molecules: t.col('smiles'), methodName: 'UMAP', similarityMetric: 'Tanimoto', plotEmbeddings: true}) |
| "sequence space" | grok.functions.call('Bio:sequenceSpaceTopMenu', {table: t, molecules: t.col('sequence'), methodName: 'UMAP', similarityMetric: 'Levenshtein', plotEmbeddings: true}) |
| "activity cliffs" | grok.functions.call('Chem:activityCliffs', {table: t, molecules: t.col('smiles'), activities: t.col('activity'), similarity: 80, methodName: 'UMAP'}) |
| "elemental analysis" | grok.functions.call('Chem:elementalAnalysis', {table: t, molecules: t.col('smiles')}) |
Each mutates the DataFrame and attaches a viewer to the current TableView.
Only the *Space functions return the freshly-attached viewer (Chem:chemSpaceTopMenu,
Bio:sequenceSpaceTopMenu). Chem:activityCliffs and Chem:elementalAnalysis
return void, so to keep configuring you must find the new viewer via
view.viewers:
const sp = await grok.functions.call('Chem:chemSpaceTopMenu', {
table: t, molecules: t.col('smiles'),
methodName: 'UMAP', similarityMetric: 'Tanimoto', plotEmbeddings: true,
});
if (sp)
sp.setOptions({colorColumnName: 'activity'});
Standalone (detached) viewers
Use DG.Viewer.fromType only when embedding a viewer in a custom UI
element you return from the block. The caller is responsible for placing the
result. For everything else, prefer view.addViewer.
// Build a scatter plot of t and embed its DOM root in the chat.
const sp = DG.Viewer.fromType(DG.VIEWER.SCATTER_PLOT, t, {
xColumnName: 'mw', yColumnName: 'logp',
});
return sp.root;
Anti-patterns
- Bare
x/y/colorin options —{x: 'mw', y: 'logp'}silently does nothing. The property isxColumnName. - Literal strings instead of
DG.VIEWER.*— useDG.VIEWER.SCATTER_PLOT, not'scatter plot'. view.scatterPlot(opts)/view.histogram(opts)/view.barChart(opts)— per-type shorthands are deprecated. Useview.addViewer(DG.VIEWER.*, opts).new DG.Viewer(...)— internal constructor over a Dart handle. Useview.addViewer(type, opts)orDG.Viewer.fromType(type, df, opts?).grok.shell.addViewer(...)— no such method. Useview.addViewer(...).grok.shell.tvwhenviewis in scope —viewis the in-scope TableView.grok.shell.tvis the globally active view and may be a different tab.- Treating "chem space" / "sequence space" / "activity cliffs" as viewer
types — they're functions called via
grok.functions.call. - Iterating
view.viewerswithout.slice(1)— index 0 is the grid.view.viewers.forEach(v => v.close())closes the grid. viewer.close()on a never-attached viewer — throws. Guard with try/catch.await view.addViewer(...)— synchronous for built-in types. Only plugin viewers viagrok.functions.call(...)are async.
Out of scope
- Row filtering (
df.filter) —datagrok-filtering. - Grid customization (visibility, widths, pinning) —
datagrok-grid-customization; column color coding —datagrok-df-and-columns. - The Filters viewer (
DG.VIEWER.FILTERS) —view.getFiltersGroup()is the right API and is covered bydatagrok-filtering. Only add a Filters viewer here if the user explicitly says "add a filter panel as a viewer".