ApexCharts

A modern charting library that helps developers to create beautiful and interactive visualizations for web pages

ApexCharts is a modern charting library that helps developers to create beautiful and interactive visualizations for web pages. It is highly customizable and supports a wide range of chart types, making it a great choice for data visualization in web applications.

Getting Started

Create the component

Start by adding the following code to your project. Preferably in the components folder.

<template>
  <VueApexCharts v-bind="forwarded" ref="chart" />
</template>

<script lang="ts">
  import { useForwardPropsEmits } from "reka-ui";
  import VueApexCharts from "vue3-apexcharts";
  import type { ApexOptions } from "apexcharts";

  declare global {
    interface Window {
      Apex: ApexOptions;
    }
  }

  /**
   * This is the default configuration for ApexCharts, which can be overridden by the `options` prop.
   *
   * It sets the default styles, colors, and other properties for the charts.
   */
  window.Apex = {
    chart: {
      animations: { enabled: true },
      fontFamily: "var(--font-sans)",
      foreColor: "var(--color-foreground)",
      selection: {
        fill: { color: "var(--color-blue-500)", opacity: 0.1 },
        stroke: { color: "var(--color-blue-500)", width: 1, opacity: 0.5 },
      },
      toolbar: {
        show: false,
        tools: {
          download: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4m4-5l5 5l5-5m-5 5V3"/></svg>`,
          reset: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M21 12a9 9 0 0 0-9-9a9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5m-5 4a9 9 0 0 0 9 9a9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></g></svg>`,
          pan: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2m0 4V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2m0 4.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8"/><path d="M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15"/></g></svg>`,
          zoom: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21l-4.3-4.3"/></g></svg>`,
          zoomout: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21l-4.35-4.35M8 11h6"/></g></svg>`,
          zoomin: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21l-4.35-4.35M11 8v6m-3-3h6"/></g></svg>`,
          selection: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><!-- Icon from Lucide by Lucide Contributors - https://github.com/lucide-icons/lucide/blob/main/LICENSE --><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12.034 12.681a.498.498 0 0 1 .647-.647l9 3.5a.5.5 0 0 1-.033.943l-3.444 1.068a1 1 0 0 0-.66.66l-1.067 3.443a.5.5 0 0 1-.943.033zM5 3a2 2 0 0 0-2 2m16-2a2 2 0 0 1 2 2M5 21a2 2 0 0 1-2-2M9 3h1M9 21h2m3-18h1M3 9v1m18-1v2M3 14v1"/></svg>`,
        },
      },
      zoom: { enabled: false },
    },
    stroke: { curve: "smooth", lineCap: "round" },
    dataLabels: { enabled: false },
    grid: { borderColor: "var(--color-border)" },
    legend: {
      show: false,
      itemMargin: { horizontal: 8 },
      markers: { strokeWidth: 0, size: 6, offsetX: -3 },
    },
    markers: { strokeWidth: 0 },
    yaxis: {
      axisBorder: { color: "var(--color-border)" },
      axisTicks: { color: "var(--color-border)" },
      crosshairs: { stroke: { width: 1, color: "var(--color-border)" } },
      labels: { style: { colors: "var(--color-muted-foreground)", fontSize: "12px" } },
      title: { style: { color: "var(--color-muted-foreground)", fontSize: "12px" } },
    },
    xaxis: {
      axisBorder: { color: "var(--color-border)" },
      axisTicks: { color: "var(--color-border)" },
      crosshairs: { stroke: { width: 1, color: "var(--color-border)" } },
      labels: {
        style: { colors: "var(--color-muted-foreground)", fontSize: "12px" },
        hideOverlappingLabels: true,
        rotateAlways: false,
        rotate: 0,
      },
      title: { style: { color: "var(--color-muted-foreground)", fontSize: "12px" } },
    },
    plotOptions: {
      radialBar: {
        track: { background: "var(--color-muted)" },
        hollow: { size: "30px" },
      },
      polarArea: {
        rings: { strokeColor: "var(--color-border)" },
        spokes: { connectorColors: "var(--color-border)" },
      },
      radar: {
        polygons: {
          strokeColors: "var(--color-border)",
          connectorColors: "var(--color-border)",
        },
      },
    },
    // Hex values are best. If you need to convert your oklch values, use the `culori` package.
    colors: ["#93c5fd", "#3b82f6", "#2563eb", "#1d4ed8", "#1e40af"],
  };

  export type ApexChartProps = {
    /**
     * All the optional configuration of the chart goes in this property
     *
     * @default {}
     */
    options?: ApexOptions;
    /**
     * The chart type
     *
     * @default line
     */
    type?:
      | "line"
      | "area"
      | "bar"
      | "histogram"
      | "pie"
      | "donut"
      | "radialBar"
      | "rangeBar"
      | "scatter"
      | "bubble"
      | "heatmap"
      | "candlestick"
      | "radar"
      | "polarArea"
      | "treemap";
    /**
     * The data which you want to display in the chart
     *
     * @default []
     */
    series?: ApexOptions["series"];
    /**
     * Width of the chart
     *
     * @default '100%'
     */
    width?: string | number;
    /**
     * Height of the chart
     *
     * @default '100%'
     */
    height?: string | number;
  };
  export type ChartInstanceMethods = {
    init(): Promise<void>;
    refresh(): Promise<void>;
    destroy(): void;
    updateOptions(
      options: any,
      redrawPaths?: boolean,
      animate?: boolean,
      updateSyncedCharts?: boolean
    ): Promise<void>;
    updateSeries(newSeries: any, animate?: boolean): Promise<void>;
    toggleSeries(seriesName: string): any;
    highlightSeries(seriesName: string): any;
    showSeries(seriesName: string): void;
    hideSeries(seriesName: string): void;
    resetSeries(): void;
    zoomX(min: number, max: number): void;
    toggleDataPointSelection(seriesIndex: number, dataPointIndex?: number): any;
    appendData(newData: any): Promise<void>;
    appendSeries(newSeries: any, animate?: boolean): Promise<void>;
    addXaxisAnnotation(options: any, pushToMemory?: boolean, context?: any): void;
    addYaxisAnnotation(options: any, pushToMemory?: boolean, context?: any): void;
    addPointAnnotation(options: any, pushToMemory?: boolean, context?: any): void;
    removeAnnotation(id: string, options?: any): void;
    clearAnnotations(): void;
    dataURI(options?: { scale?: number; width?: number }): Promise<void>;
  };
</script>

<script lang="ts" setup>
  const props = withDefaults(defineProps<ApexChartProps>(), {
    series: () => [],
    type: "line",
    width: "100%",
    height: "100%",
    options: () => ({}),
  });

  /**
   * The ApexCharts instance.
   *
   * You can use this to call methods on the chart instance, such as `chart.updateOptions()`, `chart.refresh()`, etc.
   */
  const chart = useTemplateRef<ChartInstanceMethods>("chart");

  const emits = defineEmits([
    "animationEnd",
    "beforeMount",
    "mounted",
    "updated",
    "click",
    "mouseMove",
    "mouseLeave",
    "legendClick",
    "markerClick",
    "selection",
    "dataPointSelection",
    "dataPointMouseEnter",
    "dataPointMouseLeave",
    "beforeZoom",
    "beforeResetZoom",
    "zoomed",
    "scrolled",
    "brushScrolled",
  ]);

  const forwarded = useForwardPropsEmits(props, emits);

  defineExpose({ chart });
</script>

<style scoped>
  :deep(.apexcharts-tooltip) {
    border: 1px solid --alpha(var(--color-border) / 50%) !important;
    background: var(--color-background) !important;
    box-shadow: var(--shadow-xl);
    border-radius: var(--radius-lg) !important;

    .apexcharts-tooltip-title {
      padding: 8px 12px !important;
      background: var(--color-popover) !important;
      border-bottom: 1px solid --alpha(var(--color-border) / 50%) !important;
      font-weight: var(--font-weight-semibold);
    }
  }

  :deep(.apexcharts-xaxistooltip),
  :deep(.apexcharts-yaxistooltip) {
    background: var(--color-popover) !important;
    border: 1px solid var(--color-border) !important;
    box-shadow: var(--shadow-xl);
    color: var(--color-popover-foreground) !important;
    border-radius: var(--radius-md);
  }
  :deep(.apexcharts-yaxistooltip-left:before) {
    border-left-color: var(--color-border) !important;
  }
  :deep(.apexcharts-yaxistooltip-left:after) {
    border-left-color: var(--color-popover) !important;
  }
  :deep(.apexcharts-xaxistooltip-bottom:after) {
    border-bottom-color: var(--color-popover) !important;
  }
  :deep(.apexcharts-xaxistooltip-bottom:before) {
    border-bottom-color: var(--color-border) !important;
  }

  :deep(.apexcharts-toolbar) {
    gap: 10px;
    align-items: center;
    max-width: fit-content;

    > [class*="icon"] {
      width: auto;
      height: auto;
      transform: scale(1);
      margin: auto;
      transition: all 0.2s ease-in-out;
      color: var(--color-muted-foreground);
      &:hover {
        color: var(--color-blue-400);
      }

      &.apexcharts-selected {
        color: var(--color-blue-500);
      }

      svg {
        fill: var(--color-muted-foreground);
        width: 16px;
        height: 16px;
        transform: scale(1);
      }
    }

    .apexcharts-menu {
      background: var(--color-popover);
      border: 1px solid --alpha(var(--color-border) / 50%);
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-lg);
      width: auto;
      min-width: 110px;
      text-align: center;
      display: flex;
      flex-direction: column;
      gap: 2px;

      .apexcharts-menu-item {
        color: var(--color-popover-foreground);
        border-radius: var(--radius-sm);
        cursor: pointer;
        padding: 4px;
        font-size: 12px;
        font-weight: var(--font-weight-normal);
        &:hover {
          background: var(--color-muted);
        }
      }
    }
  }
</style>