# How to build a customizable dashboard with tremor, recharts and react-grid-layout

Looking to craft a personalized dashboard that perfectly suits your data needs? You're in the right place! In this blog post, I'll guide you through the process of building a customizable dashboard using [tremor](https://www.tremor.so/) and [react-grid-layout](https://github.com/react-grid-layout/react-grid-layout).

To initiate our project, let's scaffold it using Vite with the command:

```bash
npm create vite@latest my-project
```

When prompted to select a framework, opt for 'React'.

Next, you'll be prompted to choose a variant. I opted for 'TypeScript'

Afterward, execute the following commands:

```bash
cd my-project
npm install
npm run dev
```

Congratulations! You now have an initial working project. Pretty straightforward, isn't it?

![Animated GIF](https://media3.giphy.com/media/v1.Y2lkPTc5MGI3NjExYmVmc3pqZDgzaHN2OWd5ZzBuaGtndWhjeXAzZDFlMzlrMWw5NHRrMiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/5hPVjnPv8LkRJ4bGXw/giphy.gif align="center")

For further instructions follow this guide: [https://www.tremor.so/docs/getting-started/installation](https://www.tremor.so/docs/getting-started/installation)

Now let's install `react-grid-layout` by running the following command:

```bash
npm install react-grid-layout
```

And... we're all set! Let's dive right into working on our project!

---

For each chart component, I'll utilize the **Card** and **Title** components to contain the chart itself. Depending on the desired chart type (all sourced from *tremor*), I'll integrate either a **BarChart** or a **PieChart**. Additionally, I'm gonna use the **ResponsiveContainer** component from the *recharts* library (which *tremor* relies on as a dependency) to ensure the chart fits neatly with the respective card.

```typescript
export enum ChartVisualization {
  BarChart = "bar",
  PieChart = "pie",
  // Add other visualization types as needed
}

export interface Chart {
  title: string;
  eventSchemaId: string;
  visualization: ChartVisualization;
  property: string;
  x: number;
  y: number;
}

type ChartProps = ComponentProps<"div"> & {
  chart: Chart,
  data: { name: string; value: number; }[],
};
```

Let's craft a chart widget tailored to each specific type of chart we aim to showcase:

```typescript
function BarDashboardChart({ data }: ChartProps) {
  return (
    <BarChart
      className="h-[90%]"
      data={data}
      index="name"
      categories={["value"]}
      colors={["blue"]}
      showLegend={false}
    />
  )
}

function PieDashboardChart({ data }: ChartProps) {
  return (
    <DonutChart
      className="h-[90%]"
      variant="pie"
      data={data}
      category="value"
      index="name"
      colors={[
        "slate",
        "violet",
        "indigo",
        "rose",
        "cyan",
        "amber",
      ]}
      showLabel={true}
    />
  )
}
```

In our chart component, we can map each visualization type to its corresponding component:

```typescript
const visualizationComponents: Record<ChartVisualization, React.ComponentType<ChartProps>> = {
  [ChartVisualization.BarChart]: BarDashboardChart,
  [ChartVisualization.PieChart]: PieDashboardChart,
  // Map other visualization types to their components
};

function DashboardChart(props: ChartProps) {
  const SpecificChart = visualizationComponents[props.chart.visualization];
  return (
    <Card>
      <Title className="mb-6">{props.chart.title}</Title>
      <ResponsiveContainer>
        <SpecificChart {...props} />
      </ResponsiveContainer>
    </Card>
  )
}
```

Notice that `visualizationComponents` corresponds not to the *recharts* components directly, but rather to our custom components that **encapsulate and interact** with *recharts* elements.

Once I have the list of charts, I'll iterate over it and render each chart as described:

```typescript
{charts.map(({ chart, data }) => (
    <DashboardChart chart={chart} data={data} />
))}
```

To add a touch of responsiveness and style to our charts, let's make them customizable using the **GridLayout** component.

I enclose the entire preceding block, which I attached earlier, with a `GridLayout` tag:

```typescript
<GridLayout
  className="items-center w-[1200px]"
  layout={charts.map((chart) => ({
    i: chart.chart.id.toString(),
    x: chart.chart.x,
    y: chart.chart.y,
    w: 1,
    h: 1,
  }))}
  cols={2}
  rowHeight={400}
  width={1200}
  isResizable={false}
  onLayoutChange={(layout) =>
    mutate(layout.map((l) => ({ id: parseInt(l.i), x: l.x, y: l.y })))
  }
>
  {charts.map(({ chart, data }) => (
      <DashboardChart chart={chart} data={data} />
  ))}
</GridLayout>
```

And... Voilà! Once you have enough data to display everything, it should look something like this:

![Animated GIF](https://media1.giphy.com/media/v1.Y2lkPTc5MGI3NjExcW8xMHNibWs3YW5sOW12YnRreWVtNWNqMnIzb2JxenE1Zmc1c240MiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/uT3DC6xjkFT5ZQU7HV/giphy.gif align="center")

---

To make things a bit more interesting, how about we throw in an "Add Chart" button so we can dynamically pop in a chart based on the data we have?

I'll be using a new library called [*Floating UI*](https://floating-ui.com/) for the dialog.

In the dialog, I'll allow the user to select the chart title, visualization type (bar/pie chart), the event type from the database, and one of the properties associated with this event.

In a new component that I've named **AddChartDialog**, I'll incorporate this code:

```typescript
<FloatingPortal>
  {isOpen && (
    <FloatingOverlay className="Dialog-overlay" lockScroll>
      <FloatingFocusManager context={context}>
        <div
          className="bg-white p-6 rounded w-[35rem] flex flex-col"
          ref={refs.setFloating}
          {...getFloatingProps()}
        >
          <Title className="text-2xl">Add Chart</Title>
          <Subtitle>Visualize your events</Subtitle>
        </div>
      </FloatingFocusManager>
    </FloatingOverlay>
  )}
</FloatingPortal>
```

We can now integrate **Select** components (also sourced from *tremor*) inside your dialog. For instance, we could include a **Select** component for choosing an event schema:

```typescript
<div className="mt-4">
  <div className="mb-2 text-sm">Event Type</div>
  <Select
    value={eventSchema?.id}
    onValueChange={(schemaID) =>
      setEventSchema(
        eventSchemas?.find((schema) => schema.id === schemaID)
      )
    }
  >
    {eventSchemas.map((schema) => (
      <SelectItem key={schema.id} value={schema.id}>
        {schema.id}
      </SelectItem>
    ))}
  </Select>
</div>
```

Note: I could have implemented a more robust form validation system using the `react-hook-form` library, for the dialog of the "Add Chart" button.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1696347244005/d7f720dc-d49d-43f7-b295-167a50fad62a.jpeg align="center")

For the complete context of what I've discussed here, and to get a glimpse of the entire project (including the backend), take a look at the repository of [MagicInsight](https://github.com/ozzs/magicinsights).
