How to create a Custom element in Grafana Canvas Panel

I’m currently working on creating a new element in canvas panel such as circle. After saving the file in Grafana Installation directory “C:\Program Files\GrafanaLabs\grafana\public\app\features\canvas\elements” & imported the function in registry.ts. After saving the files, restarting the service & rebooting the system I cannot see any change in elements dropdown in grafana canvas panel.

Just to be sure, i removed a existing file “droneTop.tsx” from system & also removed the path from “registry.ts” & then restarted the service & rebooted the system but there is still no update in elements dropdown in grafana canvas panel.

Can anyone provide me suggestion on why I’m facing this issue & How to create a new element in canvas panel?

I’m currently using this blog for reference: Pizzeria observability on Grafana Canvas panel | Volkov Labs

@rsm You need to rebuild Grafana to remove or add advanced elements as we demonstrated in the tutorial. Updating internal files won’t make a difference:

Hi Mikhail, thanks for responding.

I also tried creating a new element “a circle” in grafana but i was not able to see any changes elements dropdown in canvas panel.

I also provided the code which i used to create a circle:

import { css } from '@emotion/css';
import React from 'react';

import { GrafanaTheme2 } from '@grafana/data';
import { ScalarDimensionConfig } from '@grafana/schema';
import { useStyles2 } from '@grafana/ui';
import { DimensionContext } from 'app/features/dimensions';
import { ScalarDimensionEditor } from 'app/features/dimensions/editors';

import { CanvasElementItem, CanvasElementProps, defaultBgColor } from '../element';

interface CircleData {
  radius?: number;
}

interface CircleConfig {
  radius?: ScalarDimensionConfig;
}

const CircleDisplay = ({ data }: CanvasElementProps<CircleConfig, CircleData>) => {
  const styles = useStyles2(getStyles);

  const circleRadius = data?.radius ?? 50; // Default radius if not provided

  return (
    <svg
      className={styles.circle}
      width={circleRadius * 2}
      height={circleRadius * 2}
      viewBox={`0 0 ${circleRadius * 2} ${circleRadius * 2}`}
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <circle cx={circleRadius} cy={circleRadius} r={circleRadius} fill={defaultBgColor} />
    </svg>
  );
};

export const circleItem: CanvasElementItem = {
  id: 'circle',
  name: 'Circle',
  description: 'Circle element',

  display: CircleDisplay,

  defaultSize: {
    width: 100,
    height: 100,
  },

  getNewOptions: (options) => ({
    ...options,
    background: {
      color: {
        fixed: 'transparent',
      },
    },
    placement: {
      width: options?.placement?.width ?? 100,
      height: options?.placement?.height ?? 100,
      top: options?.placement?.top,
      left: options?.placement?.left,
    },
  }),

  prepareData: (ctx: DimensionContext, cfg: CircleConfig) => {
    const data: CircleData = {
      radius: cfg?.radius ? ctx.getScalar(cfg.radius).value() : 50,
    };

    return data;
  },

  registerOptionsUI: (builder) => {
    const category = ['Circle'];
    builder.addCustomEditor({
      category,
      id: 'radius',
      path: 'config.radius',
      name: 'Radius',
      editor: ScalarDimensionEditor,
    });
  },
};

const getStyles = (theme: GrafanaTheme2) => ({
  circle: css({
    transition: 'transform 0.4s',
  }),
});

1 Like

I also have one more query:

In grafana, while a rectangle is created as an element as rectangle.tsx. The function which exported is “droneFrontItem()” in registry.ts but they imported the function “rectangleItem()” from same file where “rectangleItem()” doesn’t exist.

How the rectangle element is working perfectly if the function which doesn’t even exist is being imported registry.ts?

@rsm It’s not clear from your answer. Did you build Grafana or just updating on the fly?

I’m creating a new element in already installed grafana.

@rsm That would not work. All elements are compiled into JavaScript during the build process.

Please re-read our blog post and watch the video.