In this tutorial, we will demonstrate how to create a Datasheet using the DataSheet widget. DataSheet widget is similar to TableView widget but it also includes features that allow the user to have more control of the content and styles of cells.
# Create Empty Datasheet
The following example shows how to create a basic DataSheet widget without any data. However, the user can click on any cell to edit its content.
This table also has customized even and odd rows and columns fill color. ['header']['headerdata'] is array of column names and will be automatically generated if empty. Likewise, ['index'] ['indexdata'] is array of row names.
import { DataSheet } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
function createScene(canvas) {
const dataSheet = new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"rows": 100,
"cols": 100,
"mincolumnwidth": 80,
"horizontalscroll": "floating",
"verticalscroll": "floating",
"header": {
"textstyle": {
"font": "12px Roboto"
}
},
"content": {
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
}
});
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Datasheet with sample data
The following example shows how to create a DataSheet widget with data and disable feature allows editing of table content. User can disable table content editing feature by using setContentEditable(false). header is an array of column names and is passed into ['header']['headerdata'].
Likewise, index is an array of row names, and is passed into ['index']['indexdata']. data is an array of data to supply ['data'] of DataSheet. ['autotablesize'] will calculate table size base on data array size and ignore ['cols'] and ['rows']. ['mincolumnwidth'] and ['minrowheight'] are optional and to define minimum column width and minimum row height respectively. User can also use styles like alignment, baseline, and font for cell content in ['content']['textstyle'].
import { DataSheet } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { WrapMode } from "@int/geotoolkit/widgets/datasheet/WrapMode.ts";
import { AlignmentStyle, BaseLineStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { data } from "/src/code/Carnac/Widgets/Datasheet/data.ts";
const THEME_COLOR = "gray";
function createScene(canvas) {
function createDataSheet() {
return new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"autotablesize": true,
"data": data,
"mincolumnwidth": 100,
"headervisible": false,
"indexvisible": false,
"content": {
"textstyle": {
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
},
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
},
"horizontalscroll": "floating",
"verticalscroll": "floating"
});
}
function adjustColumnsWidth(dataSheet2) {
dataSheet2.getColumn(0).setWidth(50);
dataSheet2.getColumn(1).setWidth(100);
dataSheet2.getColumn(4).setWidth(70);
dataSheet2.getColumn(8).setWidth(110);
}
function highlightRow(dataSheet2, columns, rowIndex, wrap) {
wrap = wrap == null ? WrapMode.Cut : wrap;
for (let i = 0; i < columns; ++i) {
const cell = dataSheet2.getCell(rowIndex, i);
cell.setWrapMode(wrap).setFillStyle(THEME_COLOR).setTextStyle(cell.getTextStyle().setColor("white"));
}
}
const dataSheet = createDataSheet();
dataSheet.setContentEditable(false);
adjustColumnsWidth(dataSheet);
highlightRow(dataSheet, data[0].length, 0, WrapMode.Wrap);
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Datasheet with Datatable
The following example shows how to create a DataSheet widget with data using DataTable.
.cg-tooltip-holder {
position: relative;
}
.cg-tooltip {
position: absolute;
display: block;
padding: 2px 12px 3px 7px;
overflow: visible !important;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 13px;
background: white !important;
opacity: 0.9;
color: #333333;
border: solid 1px gray;
border-radius: 5px;
text-align: left;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
border-radius: 5px;
margin: 0 !important;
z-index: 10000;
max-width: 400px;
text-wrap: normal !important;
white-space: normal !important;
}
/* Default setting for tooltip */
.cg-tooltip-container {
position: absolute;
display: block;
overflow: visible !important;
font-family: Roboto, Helvetica, Arial, sans-serif;
font-size: 12px;
padding: 3px 7px;
background: #f7f7f7;
color: #333333;
border: 1px solid #938e8e;
opacity: 0.8;
text-align: left;
box-shadow: 3px 3px 10px #888;
margin: 0 !important;
z-index: 10000;
max-width: 400px;
text-wrap: normal !important;
white-space: normal !important;
user-select: none;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.cg-tooltip-container {
border-radius: 0;
}
}
/* Default left arrow for tooltip */
.cg-tooltip-arrow-left::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 0;
top: 50%;
border: 5px solid transparent;
border-left: 0;
border-right: 5px solid #938e8e;
transform: translate(calc(-100%), -50%);
}
.cg-tooltip-arrow-left::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 0;
top: 50%;
border: 4px solid transparent;
border-left: 0;
border-right: 4px solid #f7f7f7;
transform: translate(calc(-100%), -50%);
}
/* Default top arrow for tooltip */
.cg-tooltip-arrow-top::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
top: 0;
border: 5px solid transparent;
border-top: 0;
border-bottom: 5px solid #938e8e;
transform: translate(-50%, -100%);
}
.cg-tooltip-arrow-top::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
top: 0;
border: 4px solid transparent;
border-top: 0;
border-bottom: 4px solid #f7f7f7;
transform: translate(-50%, -100%);
}
/* Default right arrow for tooltip */
.cg-tooltip-arrow-right::before {
content: '';
position: absolute;
display: block;
width: 0px;
right: 0;
top: 50%;
border: 5px solid transparent;
border-right: 0;
border-left: 5px solid #938e8e;
transform: translate(100%, -50%);
}
.cg-tooltip-arrow-right::after {
content: '';
position: absolute;
display: block;
width: 0px;
right: 0;
top: 50%;
border: 4px solid transparent;
border-right: 0;
border-left: 4px solid #f7f7f7;
transform: translate(100%, -50%);
}
/* Default bottom arrow for tooltip */
.cg-tooltip-arrow-bottom::before {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
bottom: 0px;
border: 5px solid transparent;
border-bottom: 0;
border-top: 5px solid #938e8e;
transform: translate(-50%, 100%);
z-index: 10000;
}
.cg-tooltip-arrow-bottom::after {
content: '';
position: absolute;
display: block;
width: 0px;
left: 50%;
bottom: 0;
border: 4px solid transparent;
border-bottom: 0;
border-top: 4px solid #f7f7f7;
transform: translate(-50%, 100%);
z-index: 10000;
}
/* Tooltip item name */
/* Tooltip item value */
/* .cg-tooltip-item-value */
/* Tooltip item value */
.cg-tooltip-item-unit {
text-transform: none;
}
.cg-tooltip-item-name {
text-transform: capitalize;
white-space: nowrap;
vertical-align: middle;
font-size: 13px;
}
.cg-tooltip-row {
display: flex;
flex-direction: row;
align-items: center;
white-space: pre-wrap;
font-size: 12px;
line-height: 100%;
margin: 1px 0;
}
.cg-tooltip-title {
font-size: 13px;
height: 14px;
text-transform: capitalize;
}
.cg-tooltip-title .cg-tooltip-symbol {
margin-right: 0 !important;
}
.cg-tooltip-title-name {
vertical-align: middle;
}
.cg-tooltip-row + .cg-tooltip-row {
margin-top: 4px;
}
.cg-tooltip-row.cg-tooltip-title + .cg-tooltip-row {
margin-top: 5px;
}
.cg-tooltip-item-value + .cg-tooltip-item-unit {
margin-left: 1px;
}
/* Tooltip symbol */
.cg-tooltip-symbol-cell {
display: inline-flex;
min-width: 13px; /* 10px size + 3px margin */
}
.cg-tooltip-symbol {
margin-right: 3px;
background-color: transparent;
display: block;
}
.cg-tooltip-symbol > img {
display: block;
}
.cg-tooltip-list-cell {
display: inline-flex;
}
.cg-tooltip-list-symbol {
display: block;
margin-right: 3px;
width: 6px;
height: 6px;
vertical-align: middle;
border-radius: 50%;
border: 1px solid rgba(0,0,0,.4);
}
.cg-tooltip-symbol-legacy {
border-radius: 4px;
margin-right: 5px;
height: 8px;
width: 8px;
display: inline-block;
}
.cg-tooltip-title-legacy {
font-weight: 900;
}
/* Tooltip symbol circle */
.cg-tooltip-symbol.circle {
height: 10px;
width: 10px;
display: inline-block;
border-radius: 50%;
border: 1px solid rgba(0,0,0,.4);
}
/* Tooltip symbol line */
.cg-tooltip-symbol.line {
height: 10px;
width: 10px;
display: inline-block;
transform: scale(1.2, 0.2);
}
/* Tooltip symbol diamond */
.cg-tooltip-symbol.diamond {
height: 10px;
width: 10px;
display: inline-block;
transform: rotate(45deg);
border-radius: 0px;
}
/* Tooltip symbol square */
.cg-tooltip-symbol.square {
height: 10px;
width: 10px;
display: inline-block;
border-radius: 0px;
border: 1px solid rgba(0,0,0,.4);
}
# Customize Datasheet
This example shows how to set fill color for rows and columns as well as set style for an individual cell.
setColumnsFillStylemethod andsetRowsFillStylecan be used to set fill color for rows and columns.setCellFillStylemethod can be used to set fill color of an individual cell.setCellBordermethod can be used to set border of an individual cell.
User can also set row height and column width by using setRowsHeight and setColumnsWidth methods.
In this example, the largest value of every column is highlighted.
import { DataSheet } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { WrapMode } from "@int/geotoolkit/widgets/datasheet/WrapMode.ts";
import { AlignmentStyle, BaseLineStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { LineStyle } from "@int/geotoolkit/attributes/LineStyle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { data } from "/src/code/Carnac/Widgets/Datasheet/data.ts";
const THEME_COLOR = "gray";
function createScene(canvas) {
function createDataSheet() {
return new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"autotablesize": true,
"data": data,
"mincolumnwidth": 100,
"headervisible": false,
"indexvisible": false,
"content": {
"textstyle": {
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
},
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
},
"horizontalscroll": "floating",
"verticalscroll": "floating"
});
}
function adjustColumnsWidth(dataSheet2) {
dataSheet2.getColumn(0).setWidth(50);
dataSheet2.getColumn(1).setWidth(100);
dataSheet2.getColumn(4).setWidth(70);
dataSheet2.getColumn(8).setWidth(110);
}
function highlightRow(dataSheet2, columns, rowIndex, wrap) {
wrap = wrap == null ? WrapMode.Cut : wrap;
for (let i = 0; i < columns; ++i) {
const cell = dataSheet2.getCell(rowIndex, i);
cell.setWrapMode(wrap).setFillStyle(THEME_COLOR).setTextStyle(cell.getTextStyle().setColor("white"));
}
}
function customizeDataSheet(dataSheet2) {
const borderLineStyle = new LineStyle({ "color": THEME_COLOR, "width": 2 });
for (let j = 1; j < data[0].length; j++) {
const maxCell = {
row: -1,
column: -1,
value: Number.MIN_VALUE
};
for (let i = 0; i < data.length; i++) {
if (parseFloat(data[i][j]) > maxCell.value) {
maxCell.row = i;
maxCell.column = j;
maxCell.value = parseFloat(data[i][j]);
}
}
dataSheet2.getCell(maxCell.row, maxCell.column).setTextStyle({
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
}).setBorder({
"left": borderLineStyle,
"right": borderLineStyle,
"top": borderLineStyle,
"bottom": borderLineStyle
});
}
}
const dataSheet = createDataSheet();
customizeDataSheet(dataSheet);
adjustColumnsWidth(dataSheet);
highlightRow(dataSheet, data[0].length, 0, WrapMode.Wrap);
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Cut and wrap text
DataSheet widget also supports Cut and Wrap mode for text.
Use setWrapMode method to change cell content text size mode.
import { DataSheet } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { WrapMode } from "@int/geotoolkit/widgets/datasheet/WrapMode.ts";
import { AlignmentStyle, BaseLineStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
function createScene(canvas) {
function createDataSheet() {
return new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"autotablesize": true,
"rows": 6,
"cols": 5,
"mincolumnwidth": 120,
"headervisible": false,
"indexvisible": false,
"content": {
"textstyle": {
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
},
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
},
"horizontalscroll": "floating",
"verticalscroll": "floating"
});
}
function addDataSheetData(dataSheet2) {
dataSheet2.getCell(0, 0).setContentData("This is wrap mode, it makes very long text line into another line", true);
dataSheet2.getCell(0, 1).setContentData("This is cut mode, it does NOT makes very long text line into another line", true);
dataSheet2.getCell(1, 2).setContentData("Either modes\nsupport multilines\ntext", true);
dataSheet2.getCell(1, 3).setContentData("Text<br>supports basic <b>HTML</b><br>text tags", true);
}
const dataSheet = createDataSheet();
dataSheet.getCell(0, 0).setWrapMode(WrapMode.Wrap);
addDataSheetData(dataSheet);
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Cell content validation
DataSheet widget also supports validation before submitting new cell text content after edit.
Initialize validation for the whole table by assigning a validator in ['content']['validator'].
Use setCellValidator method to set cell content validator. The method takes a function in the form function (cellContent) as parameter.
Validator must return a boolean = true to mark cell text content as valid, false otherwise. cellContent is new cell text content and it has type string.
In this example, we initialize validator to only allow number in DataSheet.
import { DataSheet, ValidationPreset } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { AlignmentStyle, BaseLineStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { data } from "/src/code/Carnac/Widgets/Datasheet/data.ts";
function createScene(canvas) {
function createDataSheet(data2, headerData2) {
return new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"autotablesize": true,
"data": data2,
"mincolumnwidth": 100,
"indexvisible": false,
"header": {
"headerdata": headerData2
},
"content": {
"validator": ValidationPreset.Number,
"textstyle": {
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
},
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
},
"horizontalscroll": "floating",
"verticalscroll": "floating"
});
}
function adjustColumnsWidth(dataSheet2) {
dataSheet2.getColumn(0).setWidth(50);
dataSheet2.getColumn(1).setWidth(100);
dataSheet2.getColumn(4).setWidth(100);
dataSheet2.getColumn(7).setWidth(120);
dataSheet2.getColumn(8).setWidth(110);
}
function processedData() {
const processedData2 = [];
for (let i = 1; i < data.length; ++i) {
processedData2[i - 1] = [];
for (let j = 0; j < data[i].length; ++j) {
let value = data[i][j];
value = value.replace(/[,\s%]/g, "");
processedData2[i - 1][j] = value;
}
}
return processedData2;
}
const headerData = data[0];
const dataSheet = createDataSheet(processedData(), headerData);
adjustColumnsWidth(dataSheet);
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
export { createScene };
createScene(document.querySelector('[ref="plot"]'));
# Merging of cells
DataSheet widget also supports merging of multiple cells. In this example, we merge some cells into one.
You can try merge cells by click and drag to select a group of cell, then click on Merge/Unmerge cell button. Merged cell always has properties and data from the top-left cell in its group of cell.
In this example, World Population (2020 and historical) cell properties and data is originally from cell at location 1A.
import { DataSheet } from "@int/geotoolkit/widgets/datasheet/DataSheet.ts";
import { WrapMode } from "@int/geotoolkit/widgets/datasheet/WrapMode.ts";
import { DataSheetSelectionEvents } from "@int/geotoolkit/widgets/tools/DataSheetSelection.ts";
import { AlignmentStyle, BaseLineStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { datatable } from "/src/code/Carnac/Widgets/Datasheet/data.ts";
const THEME_COLOR = "gray";
let selection;
function createScene(canvas) {
function createDataSheet() {
return new DataSheet({
"bounds": new Rect(0, 0, canvas.width, canvas.height),
"autotablesize": true,
"data": datatable,
"mincolumnwidth": 100,
"indexvisible": false,
"headervisible": false,
"content": {
"textstyle": {
"alignment": AlignmentStyle.Center,
"baseline": BaseLineStyle.Middle
},
"gridstyle": {
"color": "gray",
"pixelsnapmode": true
}
},
"horizontalscroll": "floating",
"verticalscroll": "floating"
});
}
function addDataSheetData(dataSheet2) {
dataSheet2.insertRow(1, ["", "", "", "", "", "", "", "", ""]);
dataSheet2.insertRow(0, ["World Population (2020 and historical)", "", "", "", "", "", "", "", ""]);
}
function adjustColumnsWidth(dataSheet2) {
dataSheet2.getColumn(0).setWidth(50);
dataSheet2.getColumn(1).setWidth(100);
dataSheet2.getColumn(4).setWidth(70);
dataSheet2.getColumn(8).setWidth(110);
}
function highlightRow(dataSheet2, columns, rowIndex, wrap) {
wrap = wrap == null ? WrapMode.Cut : wrap;
for (let i = 0; i < columns; ++i) {
const cell = dataSheet2.getCell(rowIndex, i);
cell.setWrapMode(wrap).setFillStyle(THEME_COLOR).setTextStyle(cell.getTextStyle().setColor("white"));
}
}
function customizeDataSheet(dataSheet2) {
dataSheet2.getCell(0, 0).setTextStyle(dataSheet2.getCell(0, 0).getTextStyle().setProperties({
"font": "18px Arial"
}));
highlightRow(dataSheet2, dataSheet2.getNumberOfColumns(), 1, WrapMode.Wrap);
}
function mergeCells(dataSheet2) {
const columns = dataSheet2.getNumberOfColumns();
dataSheet2.mergeCells(dataSheet2.getCell(0, 0), dataSheet2.getCell(0, columns - 1));
for (let i = 0; i < columns; ++i) {
const cell = dataSheet2.getCell(1, i);
dataSheet2.mergeCells(cell, dataSheet2.getCell(2, i));
}
}
function connectDataSheetSelectionEvents(dataSheet2) {
dataSheet2.on(DataSheetSelectionEvents.CellClicked, (source, eventName, selectionArgs) => {
selection = {
"first": selectionArgs.getFirstCellPosition(),
"last": selectionArgs.getLastCellPosition()
};
});
dataSheet2.on(DataSheetSelectionEvents.onPointerMoved, (source, eventName, selectionArgs) => {
selection = {
"first": selectionArgs.getFirstCellPosition(),
"last": selectionArgs.getLastCellPosition()
};
});
}
const dataSheet = createDataSheet();
adjustColumnsWidth(dataSheet);
addDataSheetData(dataSheet);
customizeDataSheet(dataSheet);
mergeCells(dataSheet);
connectDataSheetSelectionEvents(dataSheet);
return new Plot({
"canvaselement": canvas,
"root": dataSheet
});
}
function mergeSelectedCells(mergeCellDataSheet) {
if (mergeCellDataSheet == null || selection == null || selection["first"] == null || selection["last"] == null) {
return;
}
const firstCell = mergeCellDataSheet.getCell(selection["first"].getY(), selection["first"].getX());
const lastCell = mergeCellDataSheet.getCell(selection["last"].getY(), selection["last"].getX());
mergeCellDataSheet.mergeCells(firstCell, lastCell);
}
function unmergeSelectedCell(mergeCellDataSheet) {
if (mergeCellDataSheet == null || selection == null || selection["first"] == null) {
return;
}
const firstCell = mergeCellDataSheet.getCell(selection["first"].getY(), selection["first"].getX());
mergeCellDataSheet.unmergeCells(firstCell);
}
export { createScene, mergeSelectedCells, unmergeSelectedCell };
createScene(document.querySelector('[ref="plot"]'));