Menu

Change language

GitHub repo

Area Chart

area-chartui

Area chart wrapper around Apache ECharts. Smooth/stacked fills, multi-series, point markers. Theme-aware via registry tokens.

Installation

$npx uipkge-ng@latest add area-chart
Manifest:https://uipkge.dev/r/angular/area-chart.jsonInstalls to:src/app/components/ui/charts/area-chart/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
dataRecord<string, unknown>[][]optional
xFieldstringxoptional
yFieldstring | string[]yoptional
curve

Line interpolation. Default 'smooth'.

'smooth''linear''step''stepStart''stepEnd'
smoothoptional
stacked

Stack series cumulatively. Default false.

booleanfalseoptional
markers

Show point markers. Default false.

booleanfalseoptional
dashed

Dashed stroke on all series (forecast look). Default false.

booleanfalseoptional
heightnumber | string300optional
optionRecord<string, unknown>—optional
ariaLabel

Accessible name announced for the chart image. Defaults to "Chart".

string—optional
classstring—optional

npm dependencies

Used by

Files installed (3)

  • src/app/components/ui/charts/area-chart/area-chart.component.ts7 kB
    import {
      AfterViewInit,
      Component,
      ElementRef,
      Input,
      OnChanges,
      OnDestroy,
      SimpleChanges,
      ViewChild,
      ChangeDetectionStrategy,
    } from '@angular/core'
    import type * as echarts from 'echarts/core'
    import { cn } from '@/lib/utils'
    import {
      getChartAxisColor,
      getChartColors,
      getChartSplitLineColor,
      getChartTextColor,
      getChartTooltipBg,
      getChartTooltipBorder,
      getChartTooltipText,
      mergeOptionBlock,
    } from '../use-chart-theme'
    
    export type AreaChartCurve = 'smooth' | 'linear' | 'step' | 'stepStart' | 'stepEnd'
    
    /**
     * Angular port of the UIPKGE AreaChart — filled line chart with smooth/
     * stacked fills, multi-series and point markers. Standalone, theme-aware.
     * Same inputs as the Vue `AreaChart`.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-area-chart, [ui-area-chart]',
      standalone: true,
      host: {
        '[attr.data-slot]': '"area-chart"',
        '[attr.data-uipkge]': '""',
        '[class]': 'hostClass',
        '[attr.role]': '"img"',
        '[attr.aria-label]': 'ariaLabel || "Chart"',
        '[attr.tabindex]': '0',
        '[style.height]': 'heightStyle',
      },
      template: `<div #chartEl class="size-full"></div>`,
    })
    export class UiAreaChartComponent implements AfterViewInit, OnChanges, OnDestroy {
      @Input() data: Record<string, unknown>[] = []
      @Input() xField = 'x'
      @Input() yField: string | string[] = 'y'
      /** Line interpolation. Default 'smooth'. */
      @Input() curve: AreaChartCurve = 'smooth'
      /** Stack series cumulatively. Default false. */
      @Input() stacked = false
      /** Show point markers. Default false. */
      @Input() markers = false
      /** Dashed stroke on all series (forecast look). Default false. */
      @Input() dashed = false
      @Input() height: number | string = 300
      @Input() option?: Record<string, unknown>
      /** Accessible name announced for the chart image. Defaults to "Chart". */
      @Input() ariaLabel?: string
      @Input('class') className?: string
    
      fields(): string[] {
        return Array.isArray(this.yField) ? this.yField : [this.yField]
      }
      @ViewChild('chartEl', { static: false }) chartEl?: ElementRef<HTMLDivElement>
    
      private chart: echarts.ECharts | null = null
      private unsubscribeTheme: (() => void) | null = null
    
      get hostClass(): string {
        return cn('block focus-visible:ring-ring w-full focus-visible:ring-2 focus-visible:outline-none', this.className)
      }
    
      get heightStyle(): string {
        return /^\d+$/.test(String(this.height)) ? `${this.height}px` : String(this.height)
      }
    
      /** Build the merged ECharts option (pure — no DOM needed, unit-testable). */
      getOption(): Record<string, unknown> {
        const fields = this.fields()
        const colors = getChartColors()
        const xData = this.data.map((d) => d[this.xField])
    
        const series = fields.map((field, i) => ({
          name: field,
          type: 'line',
          smooth: this.curve === 'smooth',
          step:
            this.curve === 'step'
              ? 'middle'
              : this.curve === 'stepStart'
                ? 'start'
                : this.curve === 'stepEnd'
                  ? 'end'
                  : false,
          stack: this.stacked ? 'areas' : undefined,
          symbol: this.markers ? 'circle' : 'none',
          symbolSize: 6,
          areaStyle: { opacity: this.stacked ? 0.5 : 0.15 },
          lineStyle: { width: 2, type: this.dashed ? 'dashed' : 'solid' },
          itemStyle: { color: colors[i % colors.length] },
          data: this.data.map((d) => d[field]),
        }))
    
        const userOption: Record<string, any> = this.option ?? {}
        const {
          series: userSeries,
          xAxis: userXAxis,
          yAxis: userYAxis,
          grid: userGrid,
          tooltip: userTooltip,
          legend: userLegend,
          ...userRest
        } = userOption
        const mergedSeries = Array.isArray(userSeries) ? series.map((s, i) => ({ ...s, ...(userSeries[i] ?? {}) })) : series
    
        return {
          color: colors,
          grid: mergeOptionBlock(
            {
              left: 16,
              right: 16,
              top: 24,
              bottom: fields.length > 1 ? 32 : 24,
              outerBoundsMode: 'same',
              outerBoundsContain: 'axisLabel',
            },
            userGrid,
          ),
          tooltip: mergeOptionBlock(
            {
              trigger: 'axis',
              backgroundColor: getChartTooltipBg(),
              borderColor: getChartTooltipBorder(),
              textStyle: { color: getChartTooltipText(), fontSize: 12 },
            },
            userTooltip,
          ),
          legend:
            userLegend?.show === false
              ? undefined
              : mergeOptionBlock(
                  fields.length > 1
                    ? {
                        bottom: 0,
                        icon: 'circle',
                        itemWidth: 8,
                        itemHeight: 8,
                        textStyle: { fontSize: 11, color: getChartTextColor() },
                      }
                    : { show: false },
                  userLegend,
                ),
          xAxis: mergeOptionBlock(
            {
              type: 'category',
              data: xData,
              axisLine: { lineStyle: { color: getChartAxisColor() } },
              axisLabel: { color: getChartTextColor(), fontSize: 11 },
              axisTick: { show: false },
            },
            userXAxis,
          ),
          yAxis: mergeOptionBlock(
            {
              type: 'value',
              splitLine: { lineStyle: { color: getChartSplitLineColor() } },
              axisLabel: { color: getChartTextColor(), fontSize: 11 },
              axisLine: { show: false },
              axisTick: { show: false },
            },
            userYAxis,
          ),
          series: mergedSeries,
          ...userRest,
        }
      }
    
      /** ECharts chart + component modules, resolved lazily so SSR never loads canvas code. */
      private chartModules(charts: Record<string, unknown>, comps: Record<string, unknown>): unknown[] {
        return [
          charts['LineChart'],
          comps['GridComponent'],
          comps['TooltipComponent'],
          comps['LegendComponent'],
          comps['MarkPointComponent'],
          comps['MarkLineComponent'],
          comps['MarkAreaComponent'],
        ]
      }
    
      async ngAfterViewInit(): Promise<void> {
        if (typeof window === 'undefined' || !this.chartEl?.nativeElement) return
        const [{ use }, { CanvasRenderer }, charts, comps] = await Promise.all([
          import('echarts/core'),
          import('echarts/renderers'),
          import('echarts/charts'),
          import('echarts/components'),
        ])
        use([CanvasRenderer, ...(this.chartModules(charts, comps) as never[])])
        const { init } = await import('echarts/core')
        this.chart = init(this.chartEl.nativeElement)
        this.chart.setOption(this.getOption())
        const { onChartThemeChange } = await import('../use-chart-theme')
        this.unsubscribeTheme = onChartThemeChange(() => this.chart?.setOption(this.getOption()))
        if (typeof ResizeObserver !== 'undefined') {
          new ResizeObserver(() => this.chart?.resize()).observe(this.chartEl.nativeElement)
        }
      }
    
      ngOnChanges(changes: SimpleChanges): void {
        if (this.chart && Object.keys(changes).length) {
          this.chart.setOption(this.getOption())
        }
      }
    
      ngOnDestroy(): void {
        this.unsubscribeTheme?.()
        this.unsubscribeTheme = null
        try {
          this.chart?.dispose()
        } catch {
          /* already disposed */
        }
        this.chart = null
      }
    }
    
  • src/app/components/ui/charts/area-chart/index.ts0.1 kB
  • src/app/components/ui/charts/use-chart-theme.ts7.4 kB

Raw manifest:https://uipkge.dev/r/angular/area-chart.json