Create an Interactive Bar Chart

CoCube ❤️'s interactive diagrams.

This guide walks through using CoCube to build an editable bar chart from scratch. At the end of the tutorial, you will have an interactive component that allows you to add and remove bars and change their heights using simple visual controls. Like everything else you create in CoCube, this component can be embedded in other components to build even more complex tools, or you can make it public and share it using a link.

At the end of this tutorial, you will have this editable chart.

If you haven't built anything with CoCube yet, following this tutorial is a great way to learn the basics. The chart is simple, but creating it from scratch allows you to see how you can create tools for your own use cases. You can also reference the Quick Start Guide for a walkthrough of useful concepts to understand when building.

Getting started.

The first step is to open the console and create a new component.

You will be taken to the component editor after clicking the Create Component button. You will see a green square in the middle of the screen. This is how a component looks when no cells are set.

A new component. Very sad.

This component will be called the top-level component in the rest of the guide. It will contain everything needed to make the chart.

Let's turn this top-level component into something useful, the backing box (the gray background) for the chart.

The backing box.

Before getting into changing the component, let's understand the basics of cells. In CoCube, cells behave similar to spreadsheet cells, with one major difference: cells are not arranged in a grid. Instead, every component has a set of named cells that define how the component looks and behaves. Some components will have many cells, and some components will only have a few. Here is a Component with two cells:

Cell Name ->

Width
250
Height
250

<- Expression

This definition is what we want to follow for the component we are building. When the Width or Height cells are not created on a component, they default to 10. Setting these cells to 250 will make the component we just created larger.

On the right side of the editor you will see the cells panel. This panel shows all of the cells defined for this component. Since we just created this component, it is empty.

Let's create the Width cell first. Click the empty insert field in the cells panel to name the new cell, and type Width. Make sure to get the capitalization right, it matters. Then press the insert button to create the cell. If you see the component disappear, don't panic, new cells have a default value of 0.

You should see the cell appear in the cells panel. Click on it to open the cell editor. Then, change the number from 0 to 250.

Now do the same thing, except this time name the cell Height. After setting both the Width and Height cells, the component will be a larger green square.

Now it's time to change the color. Create the Color cell and click on the cell in the cells panel open the editor. When you edit this cell, you will notice that it is a Number. We want the value to be a Color. To do this, we need to change the type of the cell. Do this by clicking the dropdown box next to the value and selecting one of the options. Change the type from Number to Color. After selecting Color, the value will change to black , and you can click on the value to open a color picker. Click on the color then select a dark gray, something like .

One final change is to set is the Display cell. This cell defines the shape of the component. We will use it to add rounding to the corners of the backing box. Create the Display cell and open the cell editor. This cell expects a Shape, so click the type selector dropdown and select Shape. Then click on the shape selector and set it to RoundedRectangle(30) Shape.

With this, the backing box should be complete. Later in the guide we will make the backing box resize automatically based on the number and size of bars in the chart.

Understanding more about cells.

When you make a cell, you can name it anything you want. Most components will have a bunch of cells to manage their data and how they look. That being so, there are a few special cell names. These cells are called attribute cells, and setting them changes how the component looks and/or behaves. In the example so far every cell we have set is an attribute cell. For now you don't need to know every attribute, but if you want to see a list then take a look at the Quick Start.

Displaying the bars.

Now, let's add some bars. The first thing to consider is what data we want to keep with each bar. To start, each bar will only have a height. This means that an Array of Number's would be sufficient to store all of the bar data.

Create a cell named Data and set it to the following Array:

[40,60,120]

These are the heights of the bars we want to display. The chart will initially have three bars.

Creating a row layout.

The next step is to use a layout component to align each bar in a row. In the components panel, insert a DynamicRow and name the new component "Bars". You should see the component inserted in the center of the backing box. The DynamicRow component allows you to display multiple components in a row. It automatically resizes based on the width of each component being displayed. By default, it shows a row of simple components which are nothing more than colored boxes. Let's modify the DynamicRow to display components look like chart bars.

In the Components panel click the newly created Bars component to select it. Selecting a component allows you to override its cells. Overriding a cell allows you to set a new expression for the cell without changing the components actual definition. Overriding is what allows generic components to be customized for a given use-case. To see this, let's add spacing to the row by overriding the Spacing cell.

With the Bars component selected in the component editor, click the Spacing cell to override it. Set it to 20. You should see the space between the components in the DynamicRow grow.

Now, let's set the expression that determines which components are displayed in the row. With the DynamicRow still selected in the component editor, click on the Subcomponents cell. By default, this cell contains an Array of Component that define the current display. Instead of adding new components directly to this array, we want this cell to use the Data cell in the parent component. This is where we can use an expression.

Set the Subcomponents cell to ForEachArray(ParentCell("Data", 1), ParentCell("SingleBar", 1)). Doing this will first require changing the type of the cell to Fn. Then use the function selector to choose the ForEachArray function. You will see the arguments of the function appear under the function name selector. Use the same technique to set each function argument until the function matches what is defined above.

Let's understand what this function actually does. What the function says is that for every element in the Array contained in the Data cell, repeat some component, producing a new Array of Component's. To get our bars to display, we have to define one more cell in the parent component: SingleBar. Navigate back up to the parent component in the component editor by clicking the "Parent" button on the cells panel.

Create the SingleBar cell and set it to the following Component:

Width
30
Height
30

At this point, you should see three green boxes in a row in the center of the backing box. Each of these squares is associated to one element in the Data array. There is one more thing to understand before we can get them to use the correct height.

Using the Data cell.

The ForEachArray function is a powerful way to lay out components. Not only does it repeat the a component, it also sets two additional cells on it: ForEachIndex and ForEachValue.

The ForEachIndex cell contains the index (zero-based) of the component in the list.

The ForEachValue cell contains the data from the original array (in this case, the height of the bar).

Using this, we can change the repeated component definition in the SingleBar cell. We want to get each bars height using the ForEachValue cell which is implicitly set on each repeated component. In the Component definition in the SingleBar cell, set Height to Cell("ForEachValue").

At this point, you should see the bars taking the proper size. Now, let's fix the alignment of each bar so that the bottoms are even, instead of being vertically centered.

Time to edit the Component stored in the SingleBar cell again. This time we are adding two more cells.

  • Set the AlignFrom attribute cell to BottomCenter FixPoint.
  • Set the AlignTo attribute cell to BottomCenter FixPoint.

At this point, the bars should be aligned to the bottom of the row.

You can also set Color to to make the bars easier to look at.

The definition of the entire bar chart component at this point should be:

Data
[40,60,120]
SingleBar
Color
Width
30
Height
Cell("ForEachValue")
AlignTo
BottomCenter FixPoint
AlignFrom
BottomCenter FixPoint
Color
Width
250
Height
250
Display
RoundedRectangle(30) Shape
Components
"Bars"
DynamicRow ComponentRef
Subcomponents
ForEachArray(ParentCell("Data", 1), ParentCell("SingleBar", 1))
Spacing
20

Adding an "Insert Bar" button.

The next step for this component is to create a button that will allow users to insert new bars.

Create and position the button.

Select the top-level component, and in the Components panel click the insert-component button. Find the Button component and insert it using InsertButton as the name. You should see a button appear in the center of the bar chart.

Let's move this button to the right side of the backing box. Click the InsertButton component in the components panel to select it, and override the following cells.

  • Set AlignFrom to CenterLeft FixPoint.
  • Set AlignTo to CenterRight FixPoint.
  • Set OffsetX to 30.

At this point the button will be aligned to right edge of the backing box, with a little spacing.

Next let's change the text displayed in the button. With the button component selected, click the ButtonDisplay cell.

In the editor for the ButtonDisplay cell, click on the ComponentRef expression to edit the component reference. Inside the component ref editor click on the overrides list to expand it. This allows the us to override the cells of the Padding component for this specific usage. Let's increase the amount of padding slightly. In the overrides:

  • Set PaddingMin to 4.
  • Set PaddingLeft to 8.
  • Set PaddingRight to 8.

You should see that the button is slightly bigger, with more space around the text.

By default, the Button displays a Padding component which itself contains a reference to a Text component. With the component reference editor still open, you should see that the component has an overrideable cell named PaddedComponent. This is the default text displayed by the button. Click on this component reference in the PaddedComponent cell to open the editor on it. This opens the component reference editor on the Text component reference. At this point, you can override the Text cell to "Insert Bar".

As an aside - every one of the builtin components is created using the same cell based system you are using to create this component with. This means that you can create your own versions of all of those components if you do not like their behavior. It also means that once you have created a component you can use it anywhere, just like the builtin components.

Reacting to button presses.

Right now, the newly inserted button does not do much when it is clicked. To change this it is worthwhile to understand how the Button component works. By default, when the a button is clicked, it emits an event with the tag ButtonClicked and no data. This means you can react to button clicks using an event handler in the top-level bar chart component by matching on the ButtonClicked tag. We will set this event handler up next.

Select the top-level bar chart component. Create the EventHandler attribute cell. Set this cell to:

On
Tag "ButtonClicked"SetCell Data ArrayPush(Cell("Data"), 50)Capture

This action can be read as "set the Data cell to (whatever was already in the Data cell) + (a new element of 50)". After setting the event handler, clicking on the button should result in a new bar of height 50 being inserted into the chart.

Adding buttons to remove bars.

At this point, bars can be added to the chart, but they can't be removed. We can change this by modifying the Component stored in the SingleBar cell to include a button which removes the bar when it is clicked.

Create a new cell in the top-level component named RemoveButton and set it to:

Button ComponentRef

Then modify the Component stored in the SingleBar cell by setting Components to the following:

"RemoveButton"
ParentCell("RemoveButton", 3)

Why ParentCell("RemoveButton", 3)? The component needs to reference the remove button cell from the place where it is displayed, not from where it is defined.

You may notice that clicking one of these buttons causes a bar to be added to the chart. This is because these buttons are emitting the default event tag of ButtonClicked, just like the insert-bar button.

To change this, open the editor for the RemoveButton cell. Override the following cells:

  • Set AlignTo to BottomCenter FixPoint.
  • Set AlignFrom to TopCenter FixPoint.
  • Set OffsetY to 3.
  • Set EmitTag to "RemoveBar".
  • Set EmitData to ParentCell("ForEachIndex", 1).
  • Set ButtonDisplay to:
Padding ComponentRef
PaddingMin
2
PaddedComponent
Color
Width
10
Height
10
Display
Icon(Remove) Shape

There are a number of things happening here. Setting AlignTo, AlignFrom, and OffsetY align the buttons to the bottom of their bar. Setting EmitTag causes the button to emit a different tag when it is clicked, instead of using the default ButtonClicked tag. This will allow a different matcher to match on remove-button clicks, separately from insert-button clicks. The EmitData cell defines an expression which, when the button is clicked, is evaluated. The result of this evaluation is sent with the emitted event and can be accessed using Var("match") in the event handler. Overriding the ButtonDisplay cell allows the appearance of the button to be changed, in this case to a remove icon with some padding.

At this point, you can edit the EventHandler on the top-level component so that when an event to remove a button is matched, the associated data is removed from the Data cell. On the top-level component, modify the EventHandler cell to:

On
Tag "ButtonClicked"SetCell Data ArrayPush(Cell("Data"), 50)Capture
On
Tag "RemoveButton"SetCell Data ArrayRemove(Cell("Data"), Var("match"))Capture

Now, clicking on a remove button will remove that bar from the chart.

At this point, the full definition for the bar chart component is:

Data
[40,60,120]
RemoveButton
Button ComponentRef
EmitTag
"RemoveBar"
ButtonDisplay
Padding ComponentRef
PaddingMin
2
PaddedComponent
Color
Width
10
Height
10
Display
Icon(Remove) Shape
EmitData
ParentCell("ForEachIndex", 1)
AlignTo
BottomCenter FixPoint
AlignFrom
TopCenter FixPoint
OffsetY
3
SingleBar
Color
Width
30
Height
Cell("ForEachValue")
AlignTo
BottomCenter FixPoint
AlignFrom
BottomCenter FixPoint
Components
"RemoveButton"
ParentCell("RemoveButton", 3)
Color
Width
250
Height
250
Display
RoundedRectangle(30) Shape
EventHandler
On
Tag "ButtonClicked"SetCell Data ArrayPush(Cell("Data"), 50)Capture
On
Tag "RemoveButton"SetCell Data ArrayRemove(Cell("Data"), Var("match"))Capture
Components
"Bars"
DynamicRow ComponentRef
Subcomponents
ForEachArray(ParentCell("Data", 1), ParentCell("SingleBar", 1))
Spacing
20
"InsertButton"
Button ComponentRef
ButtonDisplay
Padding ComponentRef
PaddingMin
4
PaddingLeft
8
PaddingRight
8
PaddedComponent
Text ComponentRef
Text
"Insert Bar"

Adding editors to change bar heights.

At this point we can insert and remove bars, but it is not possible to change the height of existing bars. To change this, we can modify the SingleBar component to include a numeric editor. We will then modify the top-level event handler to react to number change events coming from these editors.

The first thing to do is define the number editor component. Create a new cell in the top-level component called HeightEditor. To start, set the content of this cell to the following ComponentRef.

NumberEdit ComponentRef

Having this definition in a top-level cell will make it easier to change.

Now we can add this component to the SingleBar component using a cell reference. Open the SingleBar cell editor. In the Components cell, we will add another reference, just like we did for the RemoveButton. The Components cell override should have the following definition when you are finished:

"RemoveButton"
ParentCell("RemoveButton", 3)
"HeightEditor"
ParentCell("HeightEditor", 3)

A number editor should be visible in the center of each bar. At this point modifying the numbers will not do anything. Before we handle events from the number editors though, let's get them aligned to the bottom of each bar.

Set the following overrides on the NumberEdit ComponentRef in the HeightEditor cell.

  • Set AlignTo to BottomCenter FixPoint.
  • Set AlignFrom to TopCenter FixPoint.
  • Set OffsetY to 30.
  • Set MinColumns to 1.

That looks better. Note that the MinColumns cell defines the minimum width (in characters) of the number editor. Now let's attach the number displayed by each editor to height of the bar it is under.

Then override the InputNumber cell to ParentCell("Height", 1)

This allows the number editor to use the current height of the bar as the number to display. The events from the editor need to be acted on for the edits to actually apply though. What is important to understand is that the edit event should not be handled by the bar itself, it should be handled by the top-level bar chart component. The reason for is that each bar is pulling its height from the Data cell in the top-level component.

Open the HeightEditor cell containing the ComponentRef defining each height editor. In the overrides for this component reference, set EventHandler to the following:

On
Tag "NumberModified"Emit "BarHeightModified" EvalContainer(
"Index"
ParentCell("ForEachIndex", 1)
"Height"
Var("match")
)
Capture

There are a few interesting functions here. What exactly is this event handler doing? The first thing to keep in mind is that this event handler is defined on the number editor component, which is itself included as a component of the SingleBar component. What this does is, when a number is modified, that NumberModified event is matched. Then instead of just emitting the matched value (the new height), the handler instead emits a value using the EvalContainer function. This function takes a container (a Map or an Array) and evaluates each of the expressions contained within. It then returns a new Map or Array containing the results of those evaluations.

When a number is modified, an event will be emitted that contains, for example, a Map like:

"Index"
2
"Height"
80

The Index field contains the index of the bar in the chart to modify the height of. The Height field contains the new height.

At this point, the last step is to react to these BarHeightModified events on the top-level bar chart component. On the top-level component, add a third matcher to the event handler in the EventHandler cell:

On
Tag "BarHeightModified"SetCell Data ArraySet(Cell("Data"), IndexMap("Index", Var("match")), IndexMap("Height", Var("match")))Bubble

Modifying a number in a height editor will now change the height of the associated bar.

Fitting the backing box.

Now, lets make the backing box resize automatically based on the number and height of bars.

We can start with the height. All of the data about the heights of the bars is in the Data cell. We want to use the maximum height bar + some padding. Set the Height cell to:

Add(80, ArrayMax(Cell("Data")))

The width should be based on the number of bars. Set the Width cell to:

Add(40, Multiply(50, ArrayLength(Cell("Data"))))

Conclusion

Congratulations! At this point you have a fully functional bar chart that you have built from scratch. This component can be shared or embedded in other components.

Hopefully, going through these concepts helps you see how you can use CoCube to build components for more specific use-cases. Learning how to leverage this flexibility will allow you to create fully custom systems that work in the way you need them to.