![require(esm) Backported to Node.js 20, Paving the Way for ESM-Only Packages](https://cdn.sanity.io/images/cgdhsj6q/production/be8ab80c8efa5907bc341c6fefe9aa20d239d890-1600x1097.png?w=400&fit=max&auto=format)
Security News
require(esm) Backported to Node.js 20, Paving the Way for ESM-Only Packages
require(esm) backported to Node.js 20, easing the transition to ESM-only packages and reducing complexity for developers as Node 18 nears end-of-life.
@thoughtspot/ts-chart-sdk
Advanced tools
ThoughtSpot Charts SDK allows developers to integrate custom charts into ThoughtSpot. Developers can create custom charts in Javascript using charting libraries such as HighCharts and upload them to ThoughtSpot.
ts-chart-sdk
with TypeScript to enable static type checking.This tutorial demonstrates how to create a Gantt chart using HighCharts.
Before you begin, check for the following requirements:
To create and test the application, this tutorial uses a Vite project setup.
Open a terminal window and run the following commands:
md gantt
cd gantt
Create a Vite project.
$ npm create vite@latest
Configure the project name and development framework for your chart application. In this tutorial, we will use the Vanilla framework with TypeScript.
✔ Project name: … gantt demo
✔ Package name: … gantt-demo
✔ Select a framework: › Vanilla
✔ Select a variant: › TypeScript
Initialize your application.
npm install
npm run dev
Go to the localhost link and check if the following page shows up.
npm install --save highcharts lodash
npm install --save @thoughtspot/ts-chart-sdk
Render a chart in the application created from the preceding steps.
This tutorial uses the chart from the Highcharts library to create a custom Gantt chart.
This tutorial uses the implementation code of the Gantt chart from the Highcharts site.
To implement the chart code in your application, complete these steps:
To copy the implementation code from the Highcharts library, select a chart type and click Copy JS Code.
Paste this code into the main.ts
file in your src
folder.
Import the dependencies for Highcharts and Highcharts Gantt module into your application code as shown in this example:
import Highcharts from 'highcharts/es-modules/masters/highcharts.src';
import 'highcharts/es-modules/masters/modules/gantt.src';
Note the order of import.
Replace the content of index.html
with the following snippet:
Note that we have replaced the div id app
with container
because the code copied from Highcharts points to container
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gantt Chart Demo</title>
</head>
<body>
<div id="container"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
Refresh the application URL and check if the chart imported into your application code is rendered:
Delete the unwanted files from your project folder The folder structure in your project at this point includes many files as shown in this figure.
You can remove the following files.
/public/vite.svg
/src/counter.ts
/src/typescript.svg
The chart imported into your application has static data. To add ThoughtSpot capabilities such as drill, you need to fetch data from your ThoughtSpot application. To add ThoughtSpot capabilities and data to the chart code in your application, integrate ThoughtSpot Chart SDK and complete these steps:
Chart Context is the main context object that helps in orchestrating ThoughtSpot APIs to render charts. It also acts as a core central point of all interactions on the charts.
To initialize the chart context, call getChartContext
:
const init = async () => {
const ctx = await getChartContext({
getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => {
const columns = chartModel.columns;
// Here we assume that the columns are always coming in the
// following order.
// [Project Name, Task, Start Date, End Date, Completion]
// TBD: do basic validation here to ensure that the chart is renderable
if (columns.length < 4) {
// not possible to plot a chart
return [];
}
const chartConfig: ChartConfig = {
key: 'default',
dimensions: [
{
key: 'project-name',
columns: [columns[0]],
},
{
key: 'task',
columns: [columns[1]],
},
{
key: 'start-date',
columns: [columns[2]],
},
{
key: 'end-date',
columns: [columns[3]],
},
{
key: 'completion',
columns: columns[4] ? [columns[4]] : [],
},
],
};
return [chartConfig];
},
getQueriesFromChartConfig: (
chartConfig: ChartConfig[],
): Array<Query> => {
// map all the columns in the config to the query array
return chartConfig.map(
(config: ChartConfig): Query =>
_.reduce(
config.dimensions,
(acc: Query, dimension) => ({
queryColumns: [
...acc.queryColumns,
...dimension.columns,
],
}),
{
queryColumns: [],
} as Query,
),
);
},
renderChart: (context) => {},
});
};
init();
NOTE: For more information about the chart context component, refer to the following documentation resources:
The custom chart context component must include the following mandatory properties to function:
This function takes in a ChartModel object and returns a well-formed point configuration definition.
The point for the Gantt chart used in this tutorial looks like this:
// Project 1 - Project Name
{
name: 'Start prototype', // Task
start: Date.UTC(2014, 10, 18), // Start Date
end: Date.UTC(2014, 10, 25), // End Date
completed: {
amount: 0.25, // Completion
},
}
The above data can be represented as a table in CSV format as shown here:
Project Name, Task, Start Date, End Date, Completion
"Project 1", "Start prototype", "2014-10-18", "2014-10-25", 0.25
To create a Highcharts version of the data set, the above-mentioned headers must be presented as columns from ThoughtSpot. The query on the ThoughtSpot Answer page should have all the above columns to plot a Gantt chart.
Ensure that the getDefaultChartConfig
method is included in chartContext to define the configuration of the columns that are required to map the dataset into the chart. We assume that the order of the column is maintained in the chartModel.
To render the chart, the default configuration is required.
getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => {
const columns = chartModel.columns;
// Here we assume that the columns are always coming in the
// following order.
// [Project Name, Task, Start Date, End Date, Completion]
// TBD: do basic validation here to ensure that the chart is renderable
if (columns.length < 4) {
// not possible to plot a chart
return [];
}
const chartConfig: ChartConfig = {
key: 'default',
dimensions: [
{
key: 'project-name',
columns: [columns[0]],
},
{
key: 'task',
columns: [columns[1]],
},
{
key: 'start-date',
columns: [columns[2]],
},
{
key: 'end-date',
columns: [columns[3]],
},
{
key: 'completion',
columns: columns[4] ? [columns[4]] : [],
},
],
};
return [chartConfig];
},
This method defines the data query that is required to fetch the data from ThoughtSpot to render the chart. For most use cases, you do not require the data outside of the columns listed in your chart.
This example maps all the columns in the configuration as an array of columns in the arguments.
getQueriesFromChartConfig: (
chartConfig: ChartConfig[],
): Array<Query> => {
// map all the columns in the config to the query array
return chartConfig.map(
(config: ChartConfig): Query =>
_.reduce(
config.dimensions,
(acc: Query, dimension) => ({
queryColumns: [
...acc.queryColumns,
...dimension.columns,
],
}),
{
queryColumns: [],
} as Query,
),
);
},
This renderChart (Doc)
function is required to render the chart implemented in your code. This function ensures that every time chartContext
tries to re-render the chart due to the changes in data or chart model, the chart rendered in your application is updated.
Note: You can control render and re-render by implementing more granular control for the updates on data, visual props, or chart model.
But we haven't yet implemented this. So let's proceed and implement this.
To implement renderChart, complete the following steps:
renderChart
function.const renderChart = (ctx) => {
// THE CHART
Highcharts.ganttChart('container', {
//....
} as any);
return Promise.resolve();
};
renderChart
function in getChartContext
as shown in this example:...
renderChart: (context) => renderChart(context),
...
At this point, you will notice that the chart is gone on the link.
[vite] connecting...
[vite] connected.
Chart Context: initialization start
To run the chart and test your implementation, you need a Playground.
https://ts-chart-playground.vercel.app/
NOTE
You can check out the playground code on the GitHub repository to your local environment and modify the data set to test your charts effectively.
<your localhost url with port>
Gantt - 3 Attribute - 2 Date - 1 Measure
In the next step, you need to bring your data into the chart.
The data model is unique to every chart. It defines how each point will be plotted on the chart.
For the Gantt chart, this tutorial uses the following format for data points:
{
name: 'Start prototype', // Task
start: Date.UTC(2014, 10, 18), // Start Date
end: Date.UTC(2014, 10, 25), // End Date
completed: {
amount: 0.25, // Completion
},
}
However, the data retrieved from the chart model includes an array of column types as shown in this example:
// inside chart model
{
...
"data": [
{
"data": [
{
"columnId": "79344559-4c71-45c6-be33-450316eab54d",
"columnDataType": "CHAR",
"dataValue": [
"Project 1",
"Project 1",
"Project 1",
"Project 1",
"Project 2",
"Project 2",
"Project 2",
"Project 2"
]
},
{
"columnId": "bce26c20-1335-4357-9d3f-b15a6a27237d",
"columnDataType": "CHAR",
"dataValue": [
"Start prototype",
"Test prototype",
"Develop",
"Run acceptance test",
"Start prototype",
"Test prototype",
"Develop",
"Run acceptance test"
]
},
{
"columnId": "cdd0329d-9ae6-41d2-b036-aa565eb18bc9",
"columnDataType": "CHAR",
"dataValue": [
1413570600000,
1414348200000,
1413743400000,
1414002600000,
1413570600000,
1414348200000,
1413743400000,
1414002600000
]
},
{
"columnId": "59a5893c-f487-46f6-ab08-8cd7672c283d",
"columnDataType": "CHAR",
"dataValue": [
1414175400000,
1414521000000,
1414175400000,
1414261800000,
1414175400000,
1414521000000,
1414175400000,
1414261800000
]
},
{
"columnId": "69a5893c-f487-46f6-ab08-8cd7672c283d",
"columnDataType": "FLOAT",
"dataValue": [
0.25,
null,
null,
null,
0.4,
null,
null,
null
]
},
{
"columnId": "79a5893c-f487-46f6-ab08-8cd7672c283d",
"columnDataType": "CHAR",
"dataValue": [
null,
"Develop",
"Start prototype",
"Test prototype",
null,
"Develop",
"Start prototype",
"Test prototype"
]
}
]
}
],
...
}
You can transform the above structure to the point and series format that Highcharts can understand and interpret. Use the following code snippet to create a data model and plug data values into the chart.
const getDataModel = (chartModel: any) => {
const dataArr = chartModel.data[0].data;
// create point from data
const points = dataArr[0].dataValue.map((_val: string, idx: number) => {
return {
id: `${dataArr[0].dataValue[idx]} ${dataArr[1].dataValue[idx]}`,
parent: dataArr[0].dataValue[idx],
name: dataArr[1].dataValue[idx],
start: new Date(dataArr[2].dataValue[idx]).getTime(),
end: new Date(dataArr[3].dataValue[idx]).getTime(),
completed: {
amount: dataArr[4].dataValue[idx],
},
dependency: `${dataArr[0].dataValue[idx]} ${dataArr[5].dataValue[idx]}`,
};
});
// create projects from points & data
const projects = _.uniq(dataArr[0].dataValue);
const dataSeries = projects.map((project) => {
const filteredPoints = points.filter(
(point: any) => point.parent === project,
);
return {
name: project,
data: [
...filteredPoints,
{
id: project,
name: project,
},
],
};
});
// get max and min date
const maxDate = _.max([...dataArr[2].dataValue, ...dataArr[2].dataValue]);
const minDate = _.min([...dataArr[2].dataValue, ...dataArr[2].dataValue]);
return {
dataSeries,
maxDate,
minDate,
};
};
Use the data model created from the above function and plug the values into the Highchart configuration to render the chart.
Create a data model object.
In your renderChart
code, add the following line:
const dataModel = getDataModel(chartModel);
Replace X Axis min and max values.
xAxis: {
min: dataModel.minDate,
max: dataModel.maxDate,
},
series: dataModel.dataSeries,
The chart implemented in your code now shows up on the playground with the data values you just plugged in.
The following example shows the entire chart code for the Gantt chart implementation described in this tutorial:
/* eslint-disable simple-import-sort/imports */
import {
ChartConfig,
ChartModel,
ColumnType,
Query,
getChartContext,
} from '@thoughtspot/ts-chart-sdk';
import Highcharts from 'highcharts/es-modules/masters/highcharts.src';
import 'highcharts/es-modules/masters/modules/gantt.src';
import _ from 'lodash';
const getDataModel = (chartModel: any) => {
const dataArr = chartModel.data[0].data;
// create point from data
const points = dataArr[0].dataValue.map((_val: string, idx: number) => {
return {
id: `${dataArr[0].dataValue[idx]} ${dataArr[1].dataValue[idx]}`,
parent: dataArr[0].dataValue[idx],
name: dataArr[1].dataValue[idx],
start: new Date(dataArr[2].dataValue[idx]).getTime(),
end: new Date(dataArr[3].dataValue[idx]).getTime(),
completed: {
amount: dataArr[4].dataValue[idx],
},
dependency: `${dataArr[0].dataValue[idx]} ${dataArr[5].dataValue[idx]}`,
};
});
// create projects from points & data
const projects = _.uniq(dataArr[0].dataValue);
const dataSeries = projects.map((project) => {
const filteredPoints = points.filter(
(point: any) => point.parent === project,
);
return {
name: project,
data: [
...filteredPoints,
{
id: project,
name: project,
},
],
};
});
// get max and min date
const maxDate = _.max([...dataArr[2].dataValue, ...dataArr[2].dataValue]);
const minDate = _.min([...dataArr[2].dataValue, ...dataArr[2].dataValue]);
return {
dataSeries,
maxDate,
minDate,
};
};
const renderChart = (ctx: any) => {
const chartModel = ctx.getChartModel();
console.log('chartModel:', chartModel);
console.log('data:', chartModel.data);
const dataModel = getDataModel(chartModel);
console.log('dataModel:', dataModel);
// THE CHART
Highcharts.ganttChart('container', {
title: {
text: 'Gantt Chart with Progress Indicators',
align: 'left',
},
xAxis: {
min: dataModel.minDate,
max: dataModel.maxDate,
},
accessibility: {
point: {
descriptionFormat:
'{yCategory}. ' +
'{#if completed}Task {(multiply completed.amount 100):.1f}% completed. {/if}' +
'Start {x:%Y-%m-%d}, end {x2:%Y-%m-%d}.',
},
},
lang: {
accessibility: {
axis: {
xAxisDescriptionPlural:
'The chart has a two-part X axis showing time in both week numbers and days.',
},
},
},
series: dataModel.dataSeries,
} as any);
return Promise.resolve();
};
const init = async () => {
const ctx = await getChartContext({
getDefaultChartConfig: (chartModel: ChartModel): ChartConfig[] => {
const columns = chartModel.columns;
// Here we assume that the columns are always coming in the
// following order.
// [Project Name, Task, Start Date, End Date, Completion]
// TBD: do basic validation here to ensure that the chart is renderable
if (columns.length < 4) {
// not possible to plot a chart
return [];
}
const chartConfig: ChartConfig = {
key: 'default',
dimensions: [
{
key: 'project-name',
columns: [columns[0]],
},
{
key: 'task',
columns: [columns[1]],
},
{
key: 'start-date',
columns: [columns[2]],
},
{
key: 'end-date',
columns: [columns[3]],
},
{
key: 'completion',
columns: columns[4] ? [columns[4]] : [],
},
],
};
return [chartConfig];
},
getQueriesFromChartConfig: (
chartConfig: ChartConfig[],
): Array<Query> => {
// map all the columns in the config to the query array
return chartConfig.map(
(config: ChartConfig): Query =>
_.reduce(
config.dimensions,
(acc: Query, dimension) => ({
queryColumns: [
...acc.queryColumns,
...dimension.columns,
],
}),
{
queryColumns: [],
} as Query,
),
);
},
renderChart: (context) => renderChart(context),
});
};
init();
If the chart creation is successful, you can host it on a server and make it available for use:
To deploy your charts, you can use Vercel, Netlify, or any server that can render an HTML page. For information, see Vite documentation.
To deploy the chart on a test domain in Vercel, install Vercel CLI and run the following command:
vercel;
For more information about Vercel deployment, see Vercel documentation.
To allow the use of Vercel application content in Thoughtspot, add the Vercel domain URL to the CSP allow-list. For more information, see the Security settings section in ThoughtSpot documentation.
You can use ChartToTSEvent.UpdateVisualProps
eventType inside ctx.emitEvent()
. Since the payload type for this event is unkown
you can just add a key value pair naming clientState
.
Sample -
ctx.emitEvent(ChartTOTSEvent.UpdateVisualProps,{
visualProps:{
clientState:"<req_state_in_string_format>"
...rest_of_visualProp
}
})
JSON.stringify(somelocalstate)
Probably you are implementing update client state
logic inside the the render
function of getChartContext
. Since it render
will be calling update client state
logic and this logic might again cause render
this will cause a cyclic call of render
. Hence,it is advised not to implement it inside render
function.
NOTE: This can be called inside render function just that to avoid having an infinite loop clientState updates should be handled by chart developer properly.
Since in our previous implementation of visualPropEditorDefintion
we provided this as an static object of type VisualPropEditorDefinition
but with the resent update this is converted function of type VisualEditorDefinitonSetter
along with VisualEditorDefintion
. So currently you can provide static config or dynamic config based on use case.
In getQueriesFromChartConfig
along with QueryColumn
you can provide additional optional key queryParams
. In queryParams
you can provide size
to deal with the number of data points that need to fetched. Also there is hard limit of 100K data points to be fetched from the backend.
ThoughtSpot Chart SDK, © ThoughtSpot, Inc. 2023
FAQs
Unknown package
We found that @thoughtspot/ts-chart-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 13 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
require(esm) backported to Node.js 20, easing the transition to ESM-only packages and reducing complexity for developers as Node 18 nears end-of-life.
Security News
PyPI now supports iOS and Android wheels, making it easier for Python developers to distribute mobile packages.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.