grafana/docs/sources/tutorials/build-a-panel-plugin-with-d3/index.md
Jack Baldry 4563111180
Move Grafana tutorials from grafana/tutorials repository (#62309)
* Add Grafana tutorials originally from tutorials repository

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

* Replace tutorials/step shortcode with ordinary headings

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

* Fix typos reported by codespell

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

* Fix doc-validator linting and run prettier

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

* Specify version in tutorials lookup as non-rendered pages do not have a relative permalink used to infer the version

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

* Use latest version

Ensures CI passes and only breaks one website build as the backport to
v9.3.x will solve the missing "latest" pages on publishing.

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>

---------

Signed-off-by: Jack Baldry <jack.baldry@grafana.com>
2023-01-27 17:53:50 +00:00

7.2 KiB

title summary description id categories tags status authors Feedback Link weight
Build a panel plugin with D3.js Learn how to use D3.js in your panel plugins. how to use D3.js in your panel plugins. build-a-panel-plugin-with-d3
plugins
beginner
Published
grafana_labs
https://github.com/grafana/tutorials/issues/new 60

Introduction

Panels are the building blocks of Grafana, and allow you to visualize data in different ways. This tutorial gives you a hands-on walkthrough of creating your own panel using D3.js.

For more information about panels, refer to the documentation on Panels.

In this tutorial, you'll:

  • Build a simple panel plugin to visualize a bar chart.
  • Learn how to use D3.js to build a panel using data-driven transformations.

{{% class "prerequisite-section" %}}

Prerequisites

  • Grafana 7.0
  • NodeJS 12.x
  • yarn {{% /class %}}

Set up your environment

{{< docs/shared lookup="tutorials/set-up-environment.md" source="grafana" version="latest" >}}

Create a new plugin

{{< docs/shared lookup="tutorials/create-plugin.md" source="grafana" version="latest" >}}

Data-driven documents

D3.js is a JavaScript library for manipulating documents based on data. It lets you transform arbitrary data into HTML, and is commonly used for creating visualizations.

Wait a minute. Manipulating documents based on data? That's sounds an awful lot like React. In fact, much of what you can accomplish with D3 you can already do with React. So before we start looking at D3, let's see how you can create an SVG from data, using only React.

In SimplePanel.tsx, change SimplePanel to return an svg with a rect element.

export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => {
  const theme = useTheme();

  return (
    <svg width={width} height={height}>
      <rect x={0} y={0} width={10} height={10} fill={theme.palette.greenBase} />
    </svg>
  );
};

One single rectangle might not be very exciting, so let's see how you can create rectangles from data.

  1. Create some data that we can visualize.

    const values = [4, 8, 15, 16, 23, 42];
    
  2. Calculate the height of each bar based on the height of the panel.

    const barHeight = height / values.length;
    
  3. Inside a SVG group, g, create a rect element for every value in the dataset. Each rectangle uses the value as its width.

    return (
      <svg width={width} height={height}>
        <g>
          {values.map((value, i) => (
            <rect x={0} y={i * barHeight} width={value} height={barHeight - 1} fill={theme.palette.greenBase} />
          ))}
        </g>
      </svg>
    );
    
  4. Rebuild the plugin and reload your browser to see the changes you've made.

As you can see, React is perfectly capable of dynamically creating HTML elements. In fact, creating elements using React is often faster than creating them using D3.

So why would you use even use D3? In the next step, we'll see how you can take advantage of D3's data transformations.

Transform data using D3.js

In this step, you'll see how you can transform data using D3 before rendering it using React.

D3 is already bundled with Grafana, and you can access it by importing the d3 package. However, we're going to need the type definitions while developing.

  1. Install the D3 type definitions:

    yarn add --dev @types/d3
    
  2. Import d3 in SimplePanel.tsx.

    import * as d3 from 'd3';
    

In the previous step, we had to define the width of each bar in pixels. Instead, let's use scales from the D3 library to make the width of each bar depend on the width of the panel.

Scales are functions that map a range of values to another range of values. In this case, we want to map the values in our datasets to a position within our panel.

  1. Create a scale to map a value between 0 and the maximum value in the dataset, to a value between 0 and the width of the panel. We'll be using this to calculate the width of the bar.

    const scale = d3
      .scaleLinear()
      .domain([0, d3.max(values) || 0.0])
      .range([0, width]);
    
  2. Pass the value to the scale function to calculate the width of the bar in pixels.

    return (
      <svg width={width} height={height}>
        <g>
          {values.map((value, i) => (
            <rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
          ))}
        </g>
      </svg>
    );
    

As you can see, even if we're using React to render the actual elements, the D3 library contains useful tools that you can use to transform your data before rendering it.

Add an axis

Another useful tool in the D3 toolbox is the ability to generate axes. Adding axes to our chart makes it easier for the user to understand the differences between each bar.

Let's see how you can use D3 to add a horizontal axis to your bar chart.

  1. Create a D3 axis. Notice that by using the same scale as before, we make sure that the bar width aligns with the ticks on the axis.

    const axis = d3.axisBottom(scale);
    
  2. Generate the axis. While D3 needs to generate the elements for the axis, we can encapsulate it by generating them within an anonymous function which we pass as a ref to a group element g.

    <g
      ref={(node) => {
        d3.select(node).call(axis as any);
      }}
    />
    

By default, the axis renders at the top of the SVG element. We'd like to move it to the bottom, but to do that, we first need to make room for it by decreasing the height of each bar.

  1. Calculate the new bar height based on the padded height.

    const padding = 20;
    const chartHeight = height - padding;
    const barHeight = chartHeight / values.length;
    
  2. Translate the axis by adding a transform to the g element.

    <g
      transform={`translate(0, ${chartHeight})`}
      ref={(node) => {
        d3.select(node).call(axis as any);
      }}
    />
    

Congrats! You've created a simple and responsive bar chart.

Complete example

import React from 'react';
import { PanelProps } from '@grafana/data';
import { SimpleOptions } from 'types';
import { useTheme } from '@grafana/ui';
import * as d3 from 'd3';

interface Props extends PanelProps<SimpleOptions> {}

export const SimplePanel: React.FC<Props> = ({ options, data, width, height }) => {
  const theme = useTheme();

  const values = [4, 8, 15, 16, 23, 42];

  const scale = d3
    .scaleLinear()
    .domain([0, d3.max(values) || 0.0])
    .range([0, width]);

  const axis = d3.axisBottom(scale);

  const padding = 20;
  const chartHeight = height - padding;
  const barHeight = chartHeight / values.length;

  return (
    <svg width={width} height={height}>
      <g>
        {values.map((value, i) => (
          <rect x={0} y={i * barHeight} width={scale(value)} height={barHeight - 1} fill={theme.palette.greenBase} />
        ))}
      </g>
      <g
        transform={`translate(0, ${chartHeight})`}
        ref={(node) => {
          d3.select(node).call(axis as any);
        }}
      />
    </svg>
  );
};

Summary

In this tutorial you built a panel plugin with D3.js.