Skip to main content
Version: 2.4.1

Create a Bar Chart with React

info

This page uses the API before DRAW v1.17, please take a look at Coded Visual Component for the correct API. However the concept explained here are still accurate.

Welcome to this tutorial ! Let's say you want to create a bar chart in your application, but DRAW doesn't offer any ready-to-use brick that would do the job ! Well, now is the time to get your hands dirty.

We will create a bar chart with CODE. At the end of this tutorial, you will have a better understanding of RxJS and React integration with CODE. You will also be able to integrate a JS library of your choice with CODE.

To follow this tutorial, you need to create a new Olympe project and launch DRAW to then reach DRAW at the local address http://localhost:8888/.

All right, now that you are ready, buckle up and enjoy the show because there's a lot to cover. But don't be afraid, we'll go slowly.

Create a coded visual component signature in DRAW

Coded Visual Component will be the bread and butter of the applications you design using DRAW.

Navigate to your project's root in DRAW. Pick the brick called Coded Visual Component in the marketplace and drop it in your project. Name your new component Bar Chart and open it.

Properties and Events

You just landed in the context editor of visual components.

Visual components interact with their environment using two types of objects: properties and events (whereas functions use inputs and outputs). You should be familiar with them if you have seen a Visual Component before.

You can view a Coded Visual Component as a black box that communicates with other objects only through the properties and events you defined. Properties are attributes you want to expose to the users of your component that can be either read or set. Events are signals that can be sent to/from the component.

Here, create properties called legend, labels, data.

Generate the CODE template

You can generate the JS code template of your component using button Generate brick code in the screen top right corner.

You will be offered to download the JS file BarChart.jsx. Store it in the src/bricks folder of your project.

JSX: JavaScript Syntax Extension
JSX is a syntactic extension of JavaScript. It basically allows to use HTML markup inside JavaScript code. It comes with its own file extension: `.jsx`.

Let's analyse a bit the generated template: the files contains a single class BarChart, which contains a single method draw. Implementing a coded visual component consists in implementing this method.

import { VisualBrick, registerBrick } from 'olympe';

export default class BarChart extends VisualBrick {

/**
* @override
* @protected
* @param {!BrickContext} $
* @param {!Array<*>} properties
* @return {Element}
*/
render($, properties) {
// Write your code here. You have to implement this method !
// This method returns the rendered element that is attached to its parent with the overridable method `updateParent()`.
// Executed only once by default, override `setupExecution()` to change the behaviour and control the `properties` parameter.
}
}

registerBrick('017d6cbc79bf86dad51e', BarChart);

registerBrick is a function taking the tag of your coded visual component to associate it with the JavaScript class, creating a new Entry. It must be called for every brick used in an application.

Properties and events are not passed down to method draw. Your component's properties and events must be retrieved from the context object.

The second parameter passed down to method draw(), elementDom, represents the HTML div of your component. This element is the child of a DRAW container.

Implement your brick with CODE, RxJS, and React

In this section, we will see how we can use React with your coded visual component to actually produce something nice. If you are not familiar with React, it's okay. This tutorial does not dive into complex React features.

We will go through th following steps:

  1. Subscribe to the component properties.
  2. Write a bar chart renderer.
  3. Call React DOM processor.
React
React is a JavaScript library to build user interfaces. React will allow you to declare state variables that will control your views (in the sense of the MVC architecture). When a state changes, React will automatically update the DOM for you. This means that you no longer need to manipulate the DOM yourself. Instead, you manipulate a virtual DOM (a copy of the actual DOM) provided by React. This makes your code safer and less susceptible to slow down the DOM rendering.

Don't hesitate to check React's official documentation if you want to learn more about it: React Getting-started.

RxJS and Olympe Properties

As mentioned before, Coded Visual Component does not have inputs and outputs like Coded Action and Coded Function do. You need to access your properties and events directly from the Olympe Context that is passed down to the component methods. For example, to get property legend, you need to do

$.observe('legend')

You will get an Olympe Observable.

RxJS and Reactive Programming
Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using objects called _observables_ that makes it easier to compose asynchronous or callback-based code.

You can find more about RxJS here: RxJS official Doc.

In short, RxJS is responsible for all the cool real-time interactivity of DRAW and CODE. But in CODE, this is not done automatically for you. You have to actually work a bit and set it up.

We could subscribe to the RxJS observable using its subscribe. This consists in registering a callback function that is triggered any time legend gets a new value. But remember that your component has three properties. We can configure the component to update any time of those three properties gets a new value.

Updating the component

We want the component to be updated anytime one of the brick properties is updated. For this, we have to override method setupExecution. This method must return an Observable. We will combine the observables corresponding to the three component properties using RxJS combineLatest Operator. combineLatest gathers multiple observables into an observable array (not to confuse with an array of observables). When an observable emits a new value, combineLatest emit all current values.

You have to import this operator first:

import {combineLatest} from 'rxjs';

Then get an observable for each property, combine them, and use the resulting observable as the return value of setupExecution:

/**
* @override
*/
setupExecution($) {
return combineLatest([
$.observe('legend'),
$.observe('labels'),
$.observe('data')
]);
}

Bar chart renderer

We want a bar chart from Chart.JS library, working with React. We will use react-chartjs2, which is a React wrapper for Chart.js.

Install both dependencies with npm:

npm i chart.js react-chartjs-2

Import Chart.js and the React wrapper of th Bar component with:

import { Bar } from 'react-chartjs-2';
import Chart from 'chart.js/auto';

The render method of the component must return a DOM element So let us create a React element and use it as the return value of render method:

/**
* This method runs when the brick is ready in the HTML DOM.
*
* @override
* @param {!BrickContext} $
* @param {string} legend
* @param {string} label
* @param {string} data
*/
render($, [legend, labels, data]) {
return (<Bar
data={{
labels: labels.split(','),
datasets: [{label: legend, data: data.split(',')}]
}}
options={{ maintainAspectRatio: false }}
/>);
}

This is pure JSX code, it may feel unusual to JavaScript developers at first. The component can be rendered with HTML markup as follows: <ChartRenderer .../>. Your component is a function that returns a <Bar/> component. This could also return a <div><h1>Hello World!</h1></div> instead, it's the same principle.

React DOM processor

Finally, we must configure how the component parent is updated, since render returns a React element. Import React and React DOM processor:

import React from 'react';
import ReactDOM from 'react-dom';

Override the updateParent method:

/**
* @override
*/
updateParent(parent, element) {
ReactDOM.render(element, parent);
return () => {
ReactDOM.unmountComponentAtNode(parent);
};
}
Optimise React
`ReactDOM.render()` may be called multiple times while the title is being updated, which is quite bad (blame the tutorial maker). In another tutorial, we will actually see how we can let React handle the property directly so that the update is live instead of a complete re-render.

Summary: entire component

import { VisualBrick, registerBrick } from 'olympe';

//react imports
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';

import { Bar } from 'react-chartjs-2';
import Chart from 'chart.js/auto';

//RxJS imports
import {combineLatest} from 'rxjs';

export default class BarChart extends VisualBrick {

/**
* @override
*/
setupExecution($) {
return combineLatest([
$.observe('legend'),
$.observe('labels'),
$.observe('data')
]);
}

/**
* @override
*/
updateParent(parent, element) {
ReactDOM.render(element, parent);
return () => {
ReactDOM.unmountComponentAtNode(parent);
};
}

/**
* This method runs when the brick is ready in the HTML DOM.
*
* @override
* @param {!BrickContext} $
* @param {string} legend
* @param {string} label
* @param {string} data
*/
render($, [legend, labels, data]) {
return (<Bar
data={{
labels: labels.split(','),
datasets: [{label: legend, data: data.split(',')}]
}}
options={{ maintainAspectRatio: false }}
/>);
}
}

registerBrick('017d6cbc79bf86dad51e', BarChart);

That's it for this tutorial ! Feel free to explore on your own and try other libraries like Material UI, or try coding your own functions with the Coded Actions/Coded Function bricks. Thank you for reading !

Understand the libraries and tools

CODE UI API

Let's take a closer look the the UIBrick class and the methods it provides:

/**
* A UI Brick aims to display something on the screen of a UI application.
*/
export class UIBrick extends Brick {

/**
* @override
*/
protected onInit(context: UIContext);

/**
* @override
*/
protected onDestroy(context: UIContext);

/**
* @param context the brick context
* @param domElement the associated DOM Element
*/
draw(context: UIContext, domElement:Element);
}

onInit is called only once at the initialisation of your brick, before it is mounted in the DOM. Put all code that you want to be executed only once here.

onDestroy is called only once right before your brick is removed from the DOM. Add any clean-up code here.

draw is where you will actually code your brick. It is called when the DOM Element associated to that brick has been added to the document and is ready to be drawn. From that point you can be sure the corresponding HTML div is mounted and ready.

ReactDOM

ReactDOM uses a virtual DOM, a copy of the actual DOM. It is a package that provides DOM specific methods that can be used at the top level of a web app to enable an efficient way of managing DOM elements of the web page. ReactDOM provides the developers with an API containing the following methods and a few more:

  1. findDOMNode()
  2. unmountComponentAtNode()
  3. hydrate()
  4. createPortal()
  5. render(element, container, callback)
  • element: This parameter expects a JSX expression or a React Element to be rendered.
  • container: This parameter expects the container in which the element has to be rendered.
  • callback: This is an optional parameter that expects a function that is to be executed once the render is complete.

Basically, you should never manipulate the DOM when you use React. As we said before, React provides you with a Virtual DOM to protect the actual DOM from being slowed down by unnecessary calls.

Pipeable Operator

A Pipeable Operator is a function that takes an observable as its input and returns another observable. It is a pure operation: the previous Observable stays unmodified. :::

startWith()

Returns an observable that, at the moment of subscription, will synchronously emit all values provided to this operator, and then subscribe to the source and mirror all of its emissions to subscribers.

Webpack and his loaders friends

At its core, Webpack is a static module bundler for modern JavaScript applications. When Webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles. Those bundles are static assets which can be served or deployed.

So, we know that our browser expects simple static assets (.js, .css, .png/.jpg). What about .jsx ? Well we need to configure Wepback so that we can load jsx files and translate them as readable JavaScript for the browser. And guess what is readable JSX code for the Browser ? Well, React.createElement() of course ! Because React is just that, a JavaScript library.

So we are basically finding a way to automatically generate those boring nested createElement calls for us ! And for this, we will use a JSX loader called Babel.

Let's take a look at the common configuration in file webpack.config.js:

      const common = {
mode: 'development',
devtool: 'source-map',
module: {
rules: [
{
test: /\.js$/,
enforce: 'pre',
use: 'source-map-loader'
},
{
test: /\.css$/,
exclude: /(node_modules|bower_components)/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.s[ac]ss$/,
exclude: /(node_modules|bower_components)/,
use: ["style-loader", "css-loader", "sass-loader"]
},
{
test: /\.(png|woff|woff2|otf|eot|ttf|svg)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'url-loader'
}
}
],
},
resolve: {
alias: {
'@olympeio': path.resolve(__dirname, 'node_modules/@olympeio'),
'olympeio-extensions': path.resolve(__dirname, 'node_modules/@olympeio-extensions'),
},
},
plugins: [new CleanWebpackPlugin()],
}

Notice how for each different file extensions, we have different loaders. For .jsx files, we will need to add:

{
test: /\.jsx$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
options: { presets: ['@babel/env', '@babel/react'] },
}

This is basically telling Webpack "If you find .jsx files, please use Babel loader.". Of course, these loaders don't come from nowhere. You have to install them with npm and save them to your devDependencies. For example:

"devDependencies": {
"@babel/core": "^7.13.0",
"@babel/preset-env": "^7.13.0",
"@babel/preset-react": "^7.12.0",
"@olympeio/dev-tools": "~1.1.3",
"@olympeio/draw": "~1.15.0",
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "^7.0.0",
"generate-json-webpack-plugin": "^2.0.0",
"source-map-loader": "^2.0.1",
"webpack": "^5.28.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2",
"webpack-import-glob-loader": "^1.6.3",
"webpack-merge": "^5.7.3",
"webpack-node-externals": "^2.5.0",
"style-loader": "^2.0.0",
"babel-loader": "^8.2.0",
"css-loader": "^5.2.1",
"sass": "^1.32.8",
"sass-loader": "^11.0.1",
"url-loader": "^4.1.1"
}
What is Babel ?

Babel is a JavaScript compiler, sort of. It doesn’t compile JavaScript the same way gcc compiles C++. Instead, it compiles newer JavaScript into older JavaScript. Technically, it is a transpiler. It means that you can write JavaScript code using the latest features the language has to offer and Babel will compile your code into JavaScript that will run without issues on most browsers, even if they don’t support the cutting edge standards. Specifically in the case of React, your code will be in JSX format, which of course is not a standard supported by browsers. You’ll need Babel to compile it down to regular JavaScript.

Other loaders

In this example, we use other loaders like SASS for .scss files and URL-loader for font files. You don't need those loaders to make a project work with JSX files only.

Finally, please make sure main.js (located next to the src folder), which is used by webpack to bundle everything together, actually finds .jsx source files:

// This file imports Olympe and project and dependencies bricks source code, so that Webpack can bundle everything together.

// Import Olympe runtime or DRAW
import 'olympe';
import '@olympeio/core';

// Import project bricks. We use webpack-import-glob-loader to import all bricks.
import './web/**/*.js';

// Import .jsx source files
import './web/**/*.jsx';