Introduction to Muze

What is Muze?

Muze is a data visualization library that uses an abstracted version of Layered Grammar of Graphics to create composable and interactive charts in JavaScript.

Muze is composable which helps you to build custom charts without being constrained by the chart type support of vendor. If you want a line plot on top of a bar plot and call it bar-line plot, you can do that using Muze's composability API.

Muze uses DataModel and its operators to transform and feed data to various components, generating interactivity and drawing side effects, thus allowing the overriding of behaviors and/or interactions on the chart.

Using Muze, you can easily compose complex and cross-connected charts, which work on desktops, tablets and mobile.

Understanding how Muze works

Muze consumes instance of DataModel which serves as data source and transforms that data into visualizations by assigning fields from Datamodel to x-axes, y-axes, facets, color, shape, size axes, etc. These are called encodings.

Next, layers are created for visualization based on the encodings. A Layer takes data and mark (ex: bar, line, area) and renders a visual representation of data using the mark.

logo

Note

X-axes, Y-axes and facets are called planer encodings. Color, shape and size are called retinal encodings. When we assign a field from DataModel to different encodings, we say we are assigning fields to encoding channels.

Thereafter, layers are created for the visualization based on the encoding. A Layer takes the data and a mark (ex: bar, line, area) and renders a visual representation of the data using that mark.

logo

Each layer gets data wich is just enough to render the layer properly using a mark. Thus you can assume

Layer = Data + Mark

Marks are analogous to plot types.

All the charts which you have seen so far can be conceptualized in terms of layers. For a simple bar chart, there is only one layer with a bar mark. For a multi series bar line chart there are two layers with bar and line mark placed on top of another. A set of layers can be composed on top of each other, or side-by-side, each of them having a unified data source, thus enabling complex visualizations.

Since each layer is controlled by the data and any interaction only affects the data, whenever such interaction occurs the relevant layers get affected automatically. Since all of the layers of other charts are also connected by a network of DataModels, every layer reacts to that change in data and adjusts its view accordingly. This enables charts that can seamlessly interact with one another. Checkout cross interaction for more details.

Even if you are not very clear on how the above concepts work, don't worry. We'll be explaining the same concepts as and when required. Also, if you're just looking to use the visualizations created by Muze, and not compose your own, head to the tutorials section for quick start guides. However, in case you want a deep dive of the core concepts behind Muze, you may want to continue reading.

What can you achieve with Muze?

  • Muze provides atomic marks (analogous to plot / chart type) using which you can create layers
    • Bar
    • Line
    • Area
    • Text
    • Point
    • Tick
    • Arc
  • Supports higher level visualizations or constructs like these, out-of-the-box:
    • Crosstab
    • SPLOM
    • Dual Axes
  • Add retinal encodings like color, size and shape to create color, shape, size axes (analogous to legend)
  • Composability: Layers can be composed to form complex visualization
  • Interactivity: Each layers react to data events automatically

Creating our First Visualization

If you call the muze export as function, it creates an environment.

const env = muze();

Environment is analogous to a space which stores the common settings. Any visualization created from the environment will automatically inherit those settings.

logo

Environment is like JavaScript closure

Environment is analogous to JavaScript closures. Just like in JavaScript, a function can have access to the state of any variable present in a closure, similarly all the canvases created from an environment have access to the properties set in the environment.

Next, we will be asking an instance of canvas from the environment.

const canvas = env.canvas();

So far, canvas is just a blank page where the visualization will be housed. We will be calling the property functions of the canvas instance to create visualizations.

Setting up the canvas dimensions

You can specify height and width of the canvas you need in order to get the desired space to render the visualization. Although this is optional, if not passed, muze tries to get the dimension of rendering space from the dom element on which it will be mounted.

canvas
    .width(600)
    .height(450)

We now have a blank canvas of size 600px * 450px.

Providing the data source

As mentioned earlier, Muze uses instance of DataModel to render the charts.

As explained in DataModel, Muze also needs to know about the schema of your data, apart from just the data.

We're going to use cars.csv data for all the illustrations in the page.

const dm = new DataModel(data, schema); // creates datamodel from raw data
canvas.data(dm) // feed data

Assigning fields to axes

Here we will be telling the canvas which field to use to create the y axis (rows) and which field to use to create the x axis (columns) for visualization.

canvas
    .rows(['Miles_per_Gallon'])
    .columns(['Origin'])

You might wonder why the name of the api is rows or columns instead of axes. In practice, using rows and columns you can create starting from a simple unit visualization to a complex visualization layout. Head here to see different layout variations which rows and columns enables you to achieve.

Mount the visualization on a dom element

The last and final step to create the visualization is to pass an dom element on which the visualization will be mounted. You can either pass an instance of HTMLElement or pass any css selectors like .chart-container or #unique-chart-containter.

canvas.mount('#container');

Putting it all together

Combining the steps we have learned so far,

main
run-button
run-button
reset-button
// Data is fetched from from https://www.charts.com/static/cars.json,
// schema is fetched from https://www.charts.com/static/cars-schema.json.
  
const DataModel = muze.DataModel;
const dm = new DataModel(data, schema);
  
const env = muze();
const canvas = env.canvas();

canvas
  	.data(dm)
  	.width(600)
  	.height(400)
  	.rows(['Miles_per_Gallon'])
  	.columns(['Origin'])
  	.mount('#chart-container');

Notice here we haven't mentioned chart type in our API. Muze by default understands which type of variable(s) (measure or dimension) that are plotted in x-axis and y-axis and based on that, it draws a suitable chart which you can override. The default mark (plot type) for a given combination is listed below

Y AxisX AxisMark (Plot)
MeasureDimension (Categorical)Bar
MeasureDimension (Temporal)Line
DimensionDimensionBar
MeasureMeasurePoint

Check out the tutorial for changing the plot type for rendering different layers than what muze draws by default. Also, if you want to know more about layers checkout composing layers.

Using the environment

We introduced an environment towards the beginning of the document but we have not used apart from creating a canvas entrance. We are going to see how this environment can help us eliminating redundancy.

The most frequently used methods of canvas are

  • width
  • height
  • rows
  • columns
  • data
  • layers
  • config
  • mount

We have discussed most of these methods in the last few sections. However, env (Environment) and canvas have methods which are common to both as can be seen below:

  • width
  • height
  • rows
  • columns
  • data
  • layers
  • config

If these methods are called on env instead of the canvas instance(s), then each of the canvases created from same environment will automatically inherit those properties from the env. In most of the cases you'll have one data source powering a page in your app which will contain multiple canvases. It then makes sense to pass the data to the env rather than the canvas instance.

// Creates two separate environments
const env1 = muze();
const env2 = muze();

// Creates a datamodel from data
const dm = new DataModel(data, schema);

// Configuration of charts
const conf = { ... };

// Set instance of datamodel to env1
env1.data(dm);
// Set configuration to config
env2.config(conf);

// Creates few canvas from two different environment
const canvas11 = env1.canvas();
const canvas12 = env1.canvas();
const canvas21 = env2.canvas();

canvas11 and canvas12 gets data from environment. There is no need to pass the model again to individual canvases created from env1. However, canvas21 does not have a data source since it has been created using a separate environment (env2) to which the data hasn't been provided. Even then, canvas21 still gets the config (conf) from env2.

Wrapping up

We have learnt how to make a simple chart using two fields from the data. In the next section we will understand how we can get different layout variations for different combination of rows and columns.