Skip to content
Back to skills

Create Custom Viewer

ASecurity

Create a custom JavaScript viewer extending DG.JsViewer with properties and rendering

  • 74 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 12, 2026
developmentjavascripttypescriptgojavashellapiperformance

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 12, 2026

npx -y skills add datagrok-ai/public --skill create-custom-viewer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Create Custom Viewer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Create Custom Viewer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/datagrok-ai-create-custom-viewer/badge)](https://www.skillsdirectory.com/skills/datagrok-ai-create-custom-viewer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: create-custom-viewer
description: Create a custom JavaScript viewer extending DG.JsViewer with properties and rendering
when-to-use: When user asks to create a viewer, chart, visualization, or custom rendering component
context: fork
effort: high
argument-hint: "[viewer-name] [package-path]"
---

# Create a Custom Viewer

Help the user develop a custom interactive viewer for Datagrok by extending `DG.JsViewer`.

## Usage
```
/create-custom-viewer [viewer-name] [--library <d3|echarts|plotly>]
```

## Instructions

### 1. Scaffold the viewer

From the package directory:
```shell
grok add viewer <ViewerName>
```

This creates a viewer class file. The naming convention is to add a `Viewer` postfix to the class name (e.g., `AwesomeViewer`).

### 2. Define the viewer class

Create a subclass of `DG.JsViewer` in a separate file (e.g., `src/awesome-viewer.ts`):

```typescript
import * as DG from 'datagrok-api/dg';
import * as ui from 'datagrok-api/ui';

export class AwesomeViewer extends DG.JsViewer {
  constructor() {
    super();
    // Register properties (appear in the context panel)
    this.splitColumnName = this.string('splitColumnName', 'site');
    this.valueColumnName = this.int('valueColumnName', 'age');
    this.valueAggrType = this.string('valueAggrType', 'avg', { choices: ['avg', 'count', 'sum'] });
    this.color = this.string('color', 'steelblue', { choices: ['darkcyan', 'seagreen', 'steelblue'] });
    this.initialized = false;
  }

  onTableAttached() {
    this.init();
    this.subs.push(DG.debounce(this.dataFrame.selection.onChanged, 50).subscribe((_) => this.render()));
    this.subs.push(DG.debounce(this.dataFrame.filter.onChanged, 50).subscribe((_) => this.render()));
    this.subs.push(DG.debounce(ui.onSizeChanged(this.root), 50).subscribe((_) => this.render(false)));
    this.render();
  }

  detach() {
    this.subs.forEach(sub => sub.unsubscribe());
  }

  onPropertyChanged(property) {
    super.onPropertyChanged(property);
    if (this.initialized)
      this.render();
  }

  render(computeData = true) {
    // Rendering logic here
  }
}
```

### 3. Register the viewer

In `src/package.ts`, add the annotated function:

```typescript
import {AwesomeViewer} from './awesome-viewer';

//name: AwesomeViewer
//description: Creates an awesome viewer
//tags: viewer
//meta.icon: images/icon.svg
//meta.toolbox: true
//meta.trellisable: true
//output: viewer result
export function awesome() {
  return new AwesomeViewer();
}
```

Or use the decorator approach (requires datagrok-tools >= 4.12.x):

```typescript
@grok.decorators.viewer({
  icon: 'images/icon.png',
  toolbox: true,
})
export class AwesomeViewer extends DG.JsViewer { /* ... */ }
```

### 4. Property types and naming conventions

Available property types in the constructor:
- `this.int(name, defaultValue, options)` -- integer
- `this.float(name, defaultValue, options)` -- floating point
- `this.string(name, defaultValue, options)` -- string
- `this.stringList(name, defaultValue, options)` -- string array
- `this.bool(name, defaultValue, options)` -- boolean
- `this.dateTime(name, defaultValue, options)` -- datetime

Property grouping in the UI is determined by naming:
- `Data` tab: properties ending with `ColumnName`
- `Colors` tab: properties ending with `color`
- `Axes` tab: properties containing `axis`
- `Legend` tab: properties starting with `legend`
- `Margins` tab: properties containing `margin`
- `Misc` tab: everything else

A viewer registered with `//meta.trellisable: true` can also name the properties that belong on the
trellis plot's control panel — the strip beside the inner-viewer selector, where a scatter plot
offers X, Y, Color and Size:

```typescript
get trellisProperties(): string[] { return ['mergeColumnSeries', 'logX', 'logY']; }
```

The first four are shown, in the order given, and unknown names are skipped. A viewer that declares
none gets no strip; its properties are still editable in the context panel, under a tab named after
the viewer.

### 5. Data preparation with filter support

Always respect the dataframe filter when preparing data:

```typescript
render(computeData = true) {
  if (computeData) {
    this.data.length = 0;
    this.aggregatedTable = this.dataFrame
      .groupBy([this.splitColumnName])
      .whereRowMask(this.dataFrame.filter)
      .add(this.valueAggrType, this.valueColumnName, 'result')
      .aggregate();
    // Process aggregated data...
  }
  // Render using this.root as the container
}
```

### 6. Events and interactivity

Add tooltips and selection handling to visual elements:

```typescript
// Row group tooltips on hover
element.on('mouseover', (event, d) => ui.tooltip.showRowGroup(this.dataFrame, i => {
  return d.category === this.dataFrame.getCol(this.splitColumnName).get(i);
}, event.x, event.y));
element.on('mouseout', () => ui.tooltip.hide());

// Selection on click
element.on('mousedown', (event, d) => {
  this.dataFrame.selection.handleClick(i => {
    return d.category === this.dataFrame.getCol(this.splitColumnName).get(i);
  }, event);
});
```

### 7. External dependencies

Add libraries (e.g., D3, ECharts) to `package.json` dependencies. Do NOT add platform-provided externals (datagrok-api, rxjs, cash-dom, dayjs, wu, openchemlib/full) to your bundle.

### 8. Build and test

```shell
npm run build
grok publish dev
```

Test with: `grok.shell.addTableView(grok.data.demo.demog()).addViewer('AwesomeViewer');`

## Behavior

- Ask for the viewer name and what it should visualize if not specified.
- Always include filter and selection event subscriptions for proper interactivity.
- Add subscriptions to `this.subs` so they are cleaned up when the viewer is detached.
- Use `DG.debounce` on frequently firing events (selection, filter, resize) for performance.
- Separate data computation from rendering to avoid recomputing on resize.
- Follow Datagrok coding conventions: no excessive comments, no curly brackets for one-line if/for, catch/else-if on new line.
- Suggest the decorator approach for registration when using datagrok-tools >= 4.12.x.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.