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.
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.
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"
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]
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
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;
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();
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
:
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();
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
Set the friendlyName
tag to change the way a column shows up in the UI (the real name stays the same)
Now, you can set default viewer settings for the specific dataframe:
let df = grok.data.demo.demog();
let defaultScatterPlotSettings = {
xColumnName: 'height',
yColumnName: 'weight',
sizeColumnName: 'age',
colorColumnName: 'race',
}
// now, all newly created viewers attached to df will inherit the specified settings:
df.tags['.Viewer Template: ' + DG.VIEWER.SCATTER_PLOT] = JSON.stringify(defaultScatterPlotSettings);
df.tags['.Viewer Template: ' + DG.VIEWER.HISTOGRAM] = JSON.stringify({valueColumnName: 'height'});
let view = grok.shell.addTableView(df);
view.scatterPlot();
view.histogram();
We’ve introduced a new feature for application developers. Now you can place hints for users in a form of indicators, popups, as well as describe a series of visual components in the wizard.
See examples: