Create custom charts
Warning
Rework pending, no delivery date. The Front App SDK (ReactJS) and the Front API SDK (NodeJS) are both being replaced. This page documents the current release, remains accurate, and is maintained until the replacement ships. Code written against it keeps working.
Data Platform makes it possible for you to create your own chart templates.
As a reminder, here is the configuration example of a chart:
{
"type": "chart",
"col": 0,
"row": 1,
"sizeX": 12,
"sizeY": 12,
"title": "",
"chart": {
"component": "my-component",
"options": {
"title": "My component"
},
"request": {
"data": {
"fields": {
"quantity": [
"sum"
]
}
},
"filter": {},
"scale": {
"fields": [
"city"
]
}
},
"dynamic-parameters": [
"dp-datepicker",
"dp-city"
],
"not-nullable-dynamic-parameters": [
"dp-datepicker"
]
}
}
This format is explained in detail in our Getting Further on App Development and lists the different templates provided by Data Platform (echart, table, jvectormap ....)
Step 1: Creating a "Hello World" component
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
render () {
return (
<div className='chart-my-component'>
Hello World
</div>
)
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
This declares a ReactJS component that will simply display "Hello World". For this example, save it in src/MyComponent.jsx.
Finally, you must import it into your Project by adding the highlighted line in the file src/index.jsx:
FpSdk.start()
.then(() => {
var FpAppTemplate = FpSdk.modules.sdk.templates.default
FpSdk.modules['chart-my-component'] = require('./MyComponent.jsx')
render(<FpAppTemplate />, document.getElementById('root'))
})
Once done, you should see your chart display "Hello World".
Warning
If you don't, it is perhaps because your request does not return results.
Step 2: Display data in your chart
Your template is called by the Data Platform SDK. You can access it via the props of your ReactJS component which holds a chart object with the following information:
this.props.chart.request: copy of the query made by your chart
this.props.chart.options: list of options that you have associated with your chart
this.props.chart.data: result of the query sent to the Analytics Manager
In this example, HelloWorld is replaced with a simple table:
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
render () {
return (
<div className='chart-my-component'>
<h1>{this.props.chart.options.title}</h1>
<table>
{this.props.chart.data.results.map((result, i) => {
return (
<tr key={i}>
<td>{JSON.stringify(result)}</td>
</tr>
)
})}
</table>
</div>
)
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
Thanks to this code, you now have a table with one row per result.
Now, let's go further and convert the JSON that appears into a real data row.
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
render () {
return (
<div className='chart-my-component'>
<h1>{this.props.chart.options.title}</h1>
<table>
<tr>
<th>City</th>
<th>Data</th>
</tr>
{this.props.chart.data.results.map((result, i) => {
return <tr key={i}>
<td>{result.scales.city}</td>
<td>{result.data.quantity.sum[0].value}</td>
</tr>
})}
</table>
</div>
)
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
Warning
In this example, there are several hard coded lines.
As a result, if you change the query for the associated JSON, the Chart will not work.
<td>{result.scales.city}</td>
<td>{result.data.quantity.sum[0].value}</td>
Now, let's create a function that will dynamically search the scales and data of your graph:
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
renderLine (result, i) {
let values = []
// Here we give a sample of loop that parses automatically a query result
// You have 3 levels:
// - data : It's the data you wan't to analyse, like turnover, quantity, etc....
// - computeMode : It's a compute mode like sum, count, avg ...
// - evol : You will always have 0 for 'now', but if you set an evol parameter like 'year', the previous year will be in 1
// Here you just need to convert QueryBuilder values to an easier format (an array)
for (let field in result.data) {
for (let computeMode in result.data[field]) {
for (let evol in result.data[field][computeMode]) {
values.push(result.data[field][computeMode][evol].value)
}
}
}
return (
<tr key={i}>
{/* Here we get the scales from the result (in this case 'city' */}
{Object.values(result.scales).map((scale, j) => {
return <td key={j}>{scale}</td>
})}
{/* Here we display our values */}
{values.map((d, j) => {
return <td key={j}>{d}</td>
})}
</tr>
)
}
render () {
return (
<div className='chart-my-component'>
<h1>{this.props.chart.options.title}</h1>
<table>
{this.props.chart.data.results.map(this.renderLine.bind(this))}
</table>
</div>
)
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
Finally, you can put the header line of your table dynamically from the query:
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
renderHeader () {
let values = []
// Here I loop over the request to create the column header
let request = this.props.chart.request
for (let field in request.data.fields) {
for (let computeMode of request.data.fields[field]) {
values.push(field + '-' + computeMode)
if (request.evol && request.evol.scale) {
values.push(field + '-' + computeMode + '-1')
}
}
}
return (
<tr>
{(request.scale.fields).map((scale, j) => {
return <th key={j}>{scale}</th>
})}
{values.map((d, j) => {
return <td key={j}>{d}</td>
})}
</tr>
)
}
renderLine (result, i) {
let values = []
// Here I give a sample of loop for parsing automatically a query result
// You have 3 level :
// - data : It's the data you wan't to analyse, like ca, qte, etc....
// - computeMode : Its a compute mode like sum, count, avg ...
// - evol : You will always have 0 for 'now', but if you set an evol parameter like 'year', the last year will be in 1
// Here we just need to convert QueryBuilder values to an easier format (an array)
for (let field in result.data) {
for (let computeMode in result.data[field]) {
for (let evol in result.data[field][computeMode]) {
values.push(result.data[field][computeMode][evol].value)
}
}
}
return (
<tr key={i}>
{/* Here we get the scales from the result (like in my case the 'siege_social' */}
{Object.values(result.scales).map((scale, j) => {
return <td key={i}>{scale}</td>
})}
{/* Here we display our values */}
{values.map((d, j) => {
return <td key={i}>{d}</td>
})}
</tr>
)
}
render () {
return (
<div className='chart-my-component'>
<h1>{this.props.chart.options.title}</h1>
<table>
{this.renderHeader.bind(this)()}
{this.props.chart.data.results.map(this.renderLine.bind(this))}
</table>
</div>
)
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
Here is the visual result of your code:

Step 3: Use a graphics library such as D3.js, Chart.js, or other
The first two steps have shown you how to display a simple table.
Now let's see how to plug the result with a library such as D3.js.
For this example, the previous result is connected with: http://bl.ocks.org/phuonghuynh/54a2f97950feadb45b07
First of all, let's add the dependencies requested by the plugin to index.html (this one does not seem compatible with Webpack, so simply add them to the <body> tag)
<!DOCTYPE html>
<html>
<head>
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="32x32">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>Data Platform dashboard</title>
</head>
<body>
<div id="root"></div>
<script src="http://phuonghuynh.github.io/js/bower_components/jquery/dist/jquery.min.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/d3/d3.min.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/d3-transform/src/d3-transform.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/cafej/src/extarray.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/cafej/src/misc.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/cafej/src/micro-observer.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/microplugin/src/microplugin.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/bubble-chart/src/bubble-chart.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/bubble-chart/src/plugins/central-click/central-click.js"></script>
<script src="http://phuonghuynh.github.io/js/bower_components/bubble-chart/src/plugins/lines/lines.js"></script>
</body>
</html>
Then, copy and paste the example provided on the link in the componentDidMount method of our ReactJS component.
There are some modifications to make:
- Remove the
$(document).ready from jQuery, because it will not be used by ReactJS
- Use the property
container to pass to d3.js the DOM node, which will allow it to draw in the div present in the render function.
import React from 'react'
import PropTypes from 'prop-types'
class ChartMyComponent extends React.Component {
componentDidMount () {
new window.d3.svg.BubbleChart({
supportResponsive: true,
container: this.refs.chart,
size: 600,
innerRadius: 600 / 3.5,
radiusMin: 50,
data: {
items: [
{ text: 'Java', count: '236' },
{ text: '.Net', count: '382' },
{ text: 'Php', count: '170' },
{ text: 'Ruby', count: '123' },
{ text: 'D', count: '12' },
{ text: 'Python', count: '170' },
{ text: 'C/C++', count: '382' },
{ text: 'Pascal', count: '10' },
{ text: 'Something', count: '170' }
],
eval: function (item) { return item.count },
classed: function (item) { return item.text.split(' ').join('') }
},
plugins: [
{
name: 'central-click',
options: {
text: '(See more detail)',
style: {
'font-size': '12px',
'font-style': 'italic',
'font-family': 'Source Sans Pro, sans-serif',
'text-anchor': 'middle',
'fill': 'white'
},
attr: { dy: '65px' },
centralClick: function () {
alert('Here is more details!!')
}
}
},
{
name: 'lines',
options: {
format: [
{// Line #0
textField: 'count',
classed: { count: true },
style: {
'font-size': '28px',
'font-family': 'Source Sans Pro, sans-serif',
'text-anchor': 'middle',
fill: 'white'
},
attr: {
dy: '0px',
x: function (d) { return d.cx },
y: function (d) { return d.cy }
}
},
{// Line #1
textField: 'text',
classed: { text: true },
style: {
'font-size': '14px',
'font-family': 'Source Sans Pro, sans-serif',
'text-anchor': 'middle',
fill: 'white'
},
attr: {
dy: '20px',
x: function (d) { return d.cx },
y: function (d) { return d.cy }
}
}
],
centralFormat: [
{// Line #0
style: { 'font-size': '50px' },
attr: {}
},
{// Line #1
style: { 'font-size': '30px' },
attr: { dy: '40px' }
}
]
}
}]
})
}
render () {
return <div ref='chart' />
}
}
ChartMyComponent.propTypes = {
chart: PropTypes.object
}
export default ChartMyComponent
So far, it displays the same graph as the example found online.
To display your own data, convert the this.props.chart.data object to the format requested by the chart.
Add a getItems method:
getItems () {
return this.props.chart.data.results.map(item => {
return {
text: item.scales.city,
count: item.data.quantity.sum[0].value
}
})
}
That you will call in the chart configuration:
radiusMin: 50,
data: {
items: this.getItems(),
eval: function (item) { return item.count },
classed: function (item) { return item.text.split(' ').join('') }
},
plugins: [
This is the final result:

Go further
If you need training or technical assistance to implement our solutions, contact your sales representative or click on this link to get a quote and ask our Professional Services experts for a custom analysis of your project.
Ask questions, give your feedback and interact directly with the team building the Data Platform on the dedicated Discord channel.
If you need support with your OVHcloud services, create a request in our Help Centre.
Join our community of users.