JavaScript API updates

Let’s use this topic for reporting and discussing new Datagrok JavaScript API features.
They become available on http://dev.datagrok.ai/ at the time of the topic update.

We’ve added support for manipulating Datagrok .temp and .tags available for dataframes and columns. Now they can be manipulated just as one does with JavaScript’s Map or object properties (both API conventions are supported). For instance, here is how to iterate through the tags, print them to the browser’s console and then delete one key:

let demog = grok.data.demo.demog();
demog.tags.foo = 'bar';
for (const [key, value] of demog.tags)
  console.log(key, ':', value);
if ('foo' in demog.tags)
  delete demog.tags.foo;
grok.shell.info(`foo is present: ${demog.tags.has('foo')}`);

Try out the means to iterate and manipulate .tags and .temp collections in this example on Datagrok.

2 Likes

We’ve added functionality to manipulate files using grok.dapi.files.
At the moment you can use following list of file operations:

  • write(file, blob)

    await grok.dapi.files.write('Demo:TestJobs:Files:DemoFiles/testFile.dat', [0, 1, 2]);

  • writeAsText(file, data)

    await grok.dapi.files.writeAsText('Demo:TestJobs:Files:DemoFiles/testFile.txt', 'testString');

  • readAsBytes(file)

    await grok.dapi.files.readAsBytes('Demo:TestJobs:Files:DemoFiles/testFile.dat')

  • readAsText(file)

    await grok.dapi.files.readAsText('Demo:TestJobs:Files:DemoFiles/testFile.txt')

  • exists(file)

    await grok.dapi.files.exists('Demo:TestJobs:Files:DemoFiles/testFile.dat');

  • rename(file, newName)

    await grok.dapi.files.rename('Demo:TestJobs:Files:DemoFiles/forRename.txt', 'renamed.txt');

  • list(file, recursive, searchPattern)

    let recursive = true;
    let searchPattern = 'world';
    res = await grok.dapi.files.list('Demo:TestJobs:Files:DemoFiles/geo', recursive, searchPattern);
    
  • move(files, newPath)

    await grok.dapi.files.move(['Demo:TestJobs:Files:DemoFiles/testFile.txt'], 'geo');

  • delete(file)

    await grok.dapi.files.delete('Demo:TestJobs:Files:DemoFiles/testFile.dat');

Note, that these functions return promises you have to handle using async/await or .then chain.

You can take a look on JS API samples here to be sure you use API correctly and launch your own as js scripts on the platform.

1 Like

We have exposed the JS API for value matchers. See the following link for examples of all matchers (numerical, string, date, bool): https://datagrok.ai/help/explore/data-search-patterns

Or play around with the following example: https://dev.datagrok.ai/js/samples/data-frame/value-matching/value-matcher

let matcher = DG.ValueMatcher.numerical('> 30');

grok.shell.info(matcher.operator);      // '>'
grok.shell.info(matcher.match(40));     // true
grok.shell.info(matcher.validate(20));  // "> 30" does not match "20"
2 Likes

We added an event subscription API for Grid column and rows resize. Datagrok will be sending events on resize with properties to identify if resize is still in progress or just ended (ev.args.dragging) and which column gets resized (ev.args.column).

Code example:

let grid = grok.shell.addTableView(grok.data.demo.demog()).grid;

grid.onRowsResized.subscribe((ev) => {
  let status = ev.args.dragging ? "dragging" : "done";
  grok.shell.info("Resizing row height: " + status);
});

grid.onColumnResized.subscribe((ev) => {
  let status = ev.args.dragging ? "dragging" : "done";
  grok.shell.info(`Resizing ${ev.args.column.name}: ` + status);
});

[https://github.com/datagrok-ai/public/blob/master/packages/ApiSamples/scripts/grid/resize-events.js]

@dpetrov.gnf.org @andreas.gasser.novartis.com

2 Likes

Today we are introducing an important new feature - the ability to provide custom value comparison function that is used for sorting the column. The following script demonstrates the concept, all that is needed is to set column.valueComparer:

// Providing custom comparer function

// Let's say we have a string column where values have some logical order
let column = DG.Column.fromList(DG.TYPE.STRING, 'months', ['Feb', 'Jan', 'May', 'Mar']);

// Define and set a custom value comparer that is used for sorting later
let months = { Jan: 1, Feb: 2, Mar: 3, Apr: 4, May: 5 };
column.valueComparer = (s1, s2) => months[s1] - months[s2];

// It is possible to get the sorted order if needed
let order = column.getSortedOrder();
grok.shell.info(Array.from(order).map((i) => column.get(i)).join(', '));

// Or, simply double-click on the header to sort it visually
grok.shell.addTableView(DG.DataFrame.fromColumns([column]));

Once a value comparer is defined for a column, it gets used by all visualizations, and for other purposes such as aggregation as well. Check out the screenshot below (a result of running the script above) and note the proper sorted order on all charts:

@nico.pulver.novartis.com, @nikolaus.stiefl.novartis.com, @ptosco, @timothy.danford.novartis.com - I believe you have plenty of applications for that feature :slight_smile:

2 Likes

That’s really cool, Andrew, thanks a lot!
We will need that to support custom sorting by the user’s list of favorite compound ids.

We have currently added several scatter plot customizations:

  • method hitTest for row hit testing using canvas coords;
  • method zoom for zooming using world coords;
  • method worldToScreen for converting world coords to screen coords;
  • getters viewBox, xAxisBox, yAxisBox for view box and axes rectangles;
  • property viewport for the current viewport rectangle;
  • events onZoomed, onViewportChanged, onAfterDrawScene for the corresponding scatter plot events;
2 Likes

onMetadataChanged now fires MapChangeArgs events, allowing for a granular event handling. The following example intercepts the change of the column’s conditional color-coding settings:

demog.onMetadataChanged
  .pipe(rxjs.operators.filter(data => data.args.key === DG.TAGS.COLOR_CODING_CONDITIONAL))
  .subscribe((data) => info(`${data.args.change} - ${data.args.key} - ${data.args.value}`));

Here is the MapChangeArgs interface:

export interface MapChangeArgs<K, V> {
  sender: any;
  change: string;
  key: K;
  value: V;
}

We’ve added support for creating drag-n-drop objects.
Any object can be made draggable. And any object can be made a receiver of other objects. Some objects have built-in drag-n-drop support, such as table columns.
In the example, we are making a drag-and-droppable image.

let df = DG.DataFrame.fromColumns([
  DG.Column.fromList(DG.TYPE.STRING,
    'Character', ['Alf', 'Kate', 'Willie']),
  DG.Column.fromList(DG.TYPE.INT, 
    'Age', [229, 45, 42]),
  DG.Column.fromList(DG.TYPE.STRING,
    'Actor', ['Paul Fusco', 'Anne Schedeen', 'Max Wright'])
]);

let control = ui.searchInput('', 'Drag column header or image here...');

let grid = DG.Viewer.grid(df);

let image = ui.image(
  'https://datagrok.ai/img/heinlein.jpg',
  200, 200,
  {target: 'https://datagrok.ai', tooltipMsg: 'Drag Heinlein to Input field'}
);

// Allow dragging and dropping objects into the search field:
ui.makeDroppable(control.input, {
  acceptDrop: (o) => true,  // Allow to drag anything into the field
  doDrop: (o, _) => {       // Let's check what fell into the input field
    if (o instanceof DG.Column)
      control.value = o.name.toUpperCase() + ' was dropped';
    else if (o instanceof HTMLDivElement)
      control.value = o.style.backgroundImage;
  }
});

// Let's grab and drag the object (image):
ui.makeDraggable(image, {
  getDragObject: () => image, 
  getDragCaption: () => 'You are dragging Heinlein!'
});

ui.dialog()
  .add(control)
  .add(grid)
  .add(image)
  .show();
1 Like

Use the following functions to get the color of the cell (note that the parameter is a DataFrameCell, not GridCell)

DG.Color.getCellColorHtml(cell: Cell): string
DG.Color.getCellColor(cell: Cell): number

Working example: https://dev.datagrok.ai/js/samples/grid/color-coding/get-cell-color

HTML cells could easily be rendered now by setting column’s renderer to html:

image

Script: https://github.com/datagrok-ai/public/blob/master/packages/ApiSamples/scripts/grid/html-cells/html-table-cells.js

Now you can retrieve and reuse grid cell renderers like that:

// Reusing core grid renderers

let view = grok.shell.addTableView(grok.data.demo.molecules());
view.dataFrame.col('age').colors.setLinear();
let canvas = ui.canvas(300, 200);

view.grid.onCurrentCellChanged.subscribe((gridCell) =>
  gridCell.renderer.render(canvas.getContext('2d'), 20, 20, 200, 100, gridCell, gridCell.style));

ui.dialog()
  .add('Click on a cell to render it in the canvas below')
  .add(canvas)
  .show();

image

A new column type that stores binary data (UInt8Array) in cell has been added. Here’s how you would create such a column and set a 100-bytes array in the first row:


  const bytesCol = DG.Column.fromType(DG.COLUMN_TYPE.BYTE_ARRAY, 'bytes', 3);
  bytesCol.set(0, new UInt8Array(100));
 

When combined with semantic types, this feature allows storing and handling of arbitrary data types. For instance, this is used for rendering embedded images, check this out: link to image renderer

2 Likes