TrendChart | CoUI
본문으로 건너뛰기
LogoCoUI

TrendChart

시계열 값을 선 또는 막대로 그리고 기간 칩 · 요약 · 기준선을 함께 보여 주는 추이 차트

목차

시간에 따라 변하는 값 하나를 선(line) 또는 막대(bar)로 그리는 컴포넌트입니다. 기간 칩(7일 · 30일 · 12개월), 요약 슬롯(보통 Stat), 목표 · 예산 같은 점선 기준선을 한 덩어리로 배치합니다. 데이터와 모든 표시 문자열은 호출자가 넘기고, 차트는 그 위치만 정합니다.

기본 사용법 (Basic Usage)#

TrendChart(
  semanticLabel: '월별 구독자 추이',
  summary: const Stat(label: '구독자', value: '12,480명'),
  periods: const ['7일', '30일', '12개월'],
  selectedPeriodIndex: period,
  onPeriodChanged: (index) => setState(() => period = index),
  referenceValue: 12000,
  referenceLabel: '목표 12,000명',
  points: [
    CoreTrendPoint(date: DateTime(2026, 8), value: 11200, label: '8월', valueLabel: '11,200명'),
    CoreTrendPoint(date: DateTime(2026, 9), value: 12480, label: '9월', valueLabel: '12,480명'),
  ],
)
TrendChart(
  semanticLabel: '월별 구독자 추이',
  summary: const Stat(label: '구독자', value: '12,480명'),
  periods: const ['7일', '30일', '12개월'],
  selectedPeriodIndex: period,
  onPeriodChanged: (index) => setState(() => period = index),
  referenceValue: 12000,
  referenceLabel: '목표 12,000명',
  points: [
    CoreTrendPoint(date: DateTime(2026, 8), value: 11200, label: '8월', valueLabel: '11,200명'),
    CoreTrendPoint(date: DateTime(2026, 9), value: 12480, label: '9월', valueLabel: '12,480명'),
  ],
)

빠른 오버라이드 (Chain)#

이미 만든 TrendChart 인스턴스에 스타일을 덧붙이려면 withStyle 체인을 씁니다. trendChartStyle 슬롯에 넘기는 것과 같은 결과입니다.

class TrendChartChainExample extends StatefulWidget {
  const TrendChartChainExample({super.key});

  @override
  State<TrendChartChainExample> createState() => _TrendChartChainExampleState();
}

class _TrendChartChainExampleState extends State<TrendChartChainExample> {
  @override
  Widget build(BuildContext context) {
    return TrendChart(
      semanticLabel: '월별 구독자 추이',
      points: [
        CoreTrendPoint(date: DateTime(2026, 4), value: 8200, label: '4월', valueLabel: '8,200명'),
        CoreTrendPoint(date: DateTime(2026, 5), value: 9100, label: '5월', valueLabel: '9,100명'),
        CoreTrendPoint(date: DateTime(2026, 6), value: 8800, label: '6월', valueLabel: '8,800명'),
        CoreTrendPoint(date: DateTime(2026, 7), value: 10400, label: '7월', valueLabel: '10,400명'),
        CoreTrendPoint(date: DateTime(2026, 8), value: 11200, label: '8월', valueLabel: '11,200명'),
        CoreTrendPoint(date: DateTime(2026, 9), value: 12480, label: '9월', valueLabel: '12,480명'),
      ],
    ).withStyle(
      const CoreTrendChartStyle(
        plotHeight: CoreSpace.space120,
        lineWidth: CoreStrokeWidth.stroke3,
        seriesColor: CoreColor.token(CoreColors.secondary),
      ),
    );
  }
}
class TrendChartChainExample extends StatefulComponent {
  const TrendChartChainExample({super.key});

  @override
  State<TrendChartChainExample> createState() => _TrendChartChainExampleState();
}

class _TrendChartChainExampleState extends State<TrendChartChainExample> {
  @override
  Component build(BuildContext context) {
    return TrendChart(
      semanticLabel: '월별 구독자 추이',
      points: [
        CoreTrendPoint(date: DateTime(2026, 4), value: 8200, label: '4월', valueLabel: '8,200명'),
        CoreTrendPoint(date: DateTime(2026, 5), value: 9100, label: '5월', valueLabel: '9,100명'),
        CoreTrendPoint(date: DateTime(2026, 6), value: 8800, label: '6월', valueLabel: '8,800명'),
        CoreTrendPoint(date: DateTime(2026, 7), value: 10400, label: '7월', valueLabel: '10,400명'),
        CoreTrendPoint(date: DateTime(2026, 8), value: 11200, label: '8월', valueLabel: '11,200명'),
        CoreTrendPoint(date: DateTime(2026, 9), value: 12480, label: '9월', valueLabel: '12,480명'),
      ],
    ).withStyle(
      const CoreTrendChartStyle(
        plotHeight: CoreSpace.space120,
        lineWidth: CoreStrokeWidth.stroke3,
        seriesColor: CoreColor.token(CoreColors.secondary),
      ),
    );
  }
}

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 구독자 · 사용량 · 환율 · 매출처럼 한 지표가 시간에 따라 어떻게 움직였는지 보여 줄 때
  • 기간을 바꿔 가며 같은 지표를 다시 볼 때(기간 칩)
  • 목표나 예산 대비 현재 위치를 함께 보여 줄 때(기준선)

대신 다른 컴포넌트를 사용하세요:

  • Stat: 추이 없이 현재 값과 변화율만 보여 줄 때
  • Tracker: 기간별 상태(정상 · 경고 · 장애)를 색 구간으로 보여 줄 때
  • Progress: 단일 작업의 완료 비율

Props / Parameters#

속성타입기본값설명
points List<CoreTrendPoint> 필수 표시할 시계열(오래된 것부터)
variant CoreTrendChartVariant line 선(line) 또는 막대(bar)
referenceValue double? null 점선 기준선 값(목표 · 예산)
referenceLabel String? null 기준선 범례 문구 — 값과 함께 있을 때만 범례가 보입니다
valueFormatter String Function(double)? null 값 축 눈금 포맷. 없으면 라틴 숫자 기본 포맷
periods List<String> [] 기간 칩 라벨. 비어 있으면 칩 줄을 그리지 않습니다
selectedPeriodIndex int? null 선택된 기간 칩 인덱스
onPeriodChanged void Function(int)? null 칩을 누르면 그 인덱스로 호출
summary Widget? / Component? null 차트 위 요약 슬롯(보통 Stat)
empty Widget? / Component? null points 가 비었을 때 그래프 대신 그릴 내용
semanticLabel String? null 그래프 영역의 접근 가능한 이름
trendChartStyle CoreTrendChartStyle? null 인스턴스 chrome / 치수 override

CoreTrendPoint#

속성타입설명
date DateTime 이 점이 가리키는 시점. 점은 목록 순서대로 같은 간격에 그려지고, 날짜는 대체 라벨에만 쓰입니다
valuedouble측정값(유한수, 음수 허용)
label String? 호출자가 로케일로 포맷한 날짜 라벨. 없으면 ISO yyyy-MM-dd
valueLabel String? 호출자가 포맷한 값 문자열. 툴팁과 스크린 리더가 읽습니다

CoreTrendChartStyle 필드#

필드타입설명
seriesColor CoreColor? Colour of the line, its end marker and the bars.
gridColor CoreColor? Colour of the horizontal grid lines at each axis tick.
referenceColor CoreColor? Colour of the dashed reference line and its legend swatch.
lineWidth double? Stroke width of the series line (logical px).
gridLineWidth double? Stroke width of each grid line (logical px).
referenceLineWidth double? Stroke width of the reference line (logical px).
referenceDashLength double? Length of one dash of the reference line (logical px).
referenceDashSpacing double? Gap between two dashes of the reference line (logical px).
markerSize double? Diameter of the marker on the last point of a line (logical px).
markerBorderRadius CoreBorderRadius? Corner radius of the last-point marker.
barSpacing double? Horizontal space between two adjacent bars (logical px).
barBorderRadius CoreBorderRadius? Corner radius of a bar's far end — the end away from the zero baseline. The baseline end stays square.
plotHeight double? Minimum height of the plot area (logical px). The plot grows when the axis labels need more room (large text).
sectionSpacing double? Vertical space between the summary, the period chips, the plot and the legend (logical px). Flows into Column.spacing / CSS gap .
axisLabelSpacing double? Space between the value axis labels and the plot, and between the plot and the date labels (logical px).
axisTickCount int? Target number of value-axis intervals. The axis rounds to a nice step, so the drawn count can differ by one.
axisLabelStyle CoreTextStyle? Typography of the value-axis and date-axis labels.
legendLabelStyle CoreTextStyle? Typography of the reference-line legend text.
legendSpacing double? Space between the legend swatch and its text (logical px).
legendSwatchWidth double? Width of the dashed legend swatch (logical px).
periodSpacing double? Space between period chips, both along a row and between wrapped rows (logical px).
periodChipStyle CoreChipStyle? Chrome forwarded to every period Chip.

스타일 시스템 (Style System)#

모든 chrome 은 trendChartStyle 슬롯 하나로 들어갑니다. 프로젝트 공통 값은 CoreComponentTheme.trendChart 의 style / variantStyles 에 둡니다.

CoreTrendChartStyle.default*
  → CoreTrendChartTheme.style                     // 프로젝트 공통
  → CoreTrendChartTheme.variantStyles[variant]    // variant 별
  → widget.trendChartStyle                        // 인스턴스별

기간 칩의 모양은 periodChipStyle(CoreChipStyle)로 Chip 에 그대로 전달됩니다.

변형 (Variants)#

line#

점마다 같은 폭의 구간 중앙을 지나는 선을 그리고, 마지막 점에 표식을 둡니다. 값 축은 데이터 범위에 맞춰 좁게 잡혀 작은 움직임도 보입니다.

bar#

점마다 막대 하나를 0 기준선에서 그립니다. 막대 길이가 곧 값이므로 값 축은 항상 0 을 포함합니다. 음수 막대는 아래로 자라고, 둥근 모서리는 기준선 반대쪽 끝에만 붙습니다.

동작 스펙 (Behavior)#

인터랙션#

각 점은 그래프 높이 전체를 차지하는 구간을 갖고, 그 구간에 마우스를 올리면 Tooltip 이 「날짜, 값」을 보여 줍니다. 기간 칩을 누르면 onPeriodChanged 가 그 인덱스로 호출됩니다 — 선택 상태와 데이터 교체는 호출자가 합니다.

상태 전환#

points 가 비면 그래프 자리에 empty 슬롯을 그립니다(없으면 아무것도 그리지 않음). 요약 슬롯과 기간 칩은 그대로 남아 다른 기간을 고를 수 있습니다. 로딩 중에는 호출자가 같은 자리에 Skeleton 을 넣습니다.

축#

값 축은 데이터와 기준선을 모두 담도록 1 · 2 · 5 × 10ⁿ 간격으로 반올림되고 눈금 수는 axisTickCount(기본 4 구간)를 목표로 합니다. 날짜 축은 첫 · 가운데 · 마지막 점의 라벨만 시작 · 가운데 · 끝에 붙입니다 — 어떤 폭과 글자 크기에서도 라벨이 겹치지 않고, 나머지 날짜는 툴팁과 스크린 리더로 읽을 수 있습니다. 값 축 라벨이 plotHeight 보다 더 많은 높이를 필요로 하면(큰 글자) 그래프가 그만큼 커집니다.

애니메이션#

전환 애니메이션이 없습니다. 데이터가 바뀌면 바로 다시 그립니다.

사용 가이드라인 (Usage Guidelines)#

✅ Do#

label · valueLabel 을 호출자 로케일로 포맷해서 넘기기

CoreTrendPoint(date: date, value: 12480, label: '9월', valueLabel: '12,480명')

이유: 차트는 숫자 · 날짜를 포맷하지 않습니다. 생략하면 ISO 날짜와 그룹 구분 없는 숫자가 툴팁과 스크린 리더에 그대로 나옵니다.


❌ Don't#

막대 차트에 0 에서 먼 값만 넣어 변화를 키워 보이게 하기

// ❌ 막대는 항상 0 에서 자라므로 9,980 과 10,020 의 차이가 거의 안 보인다
TrendChart(variant: CoreTrendChartVariant.bar, points: nearlyFlat)

이유: 막대의 길이는 값 자체를 뜻합니다. 작은 변동을 보여 주려면 축을 좁게 잡는 line 을 쓰세요.

접근성 (Accessibility)#

역할#

그래프 영역은 semanticLabel 을 이름으로 갖는 그룹입니다(Flutter Semantics(container: true, label:), Web role="group" + aria-label). 그 안에 점마다 하나씩 「날짜, 값」 이름을 가진 항목이 있습니다(Flutter 점별 Semantics(label:), Web role="list" 안의 role="listitem" + aria-label). 이 목록이 그래프의 데이터 표 대체 보기 역할을 합니다.

키보드#

처리하는 키가 없습니다. 기간 칩은 Chip 의 키보드 동작을 따릅니다.

스크린 리더#

그래프 이름을 읽은 뒤 점을 순서대로 「1월 1일, 3건」처럼 읽습니다. 값 축 · 날짜 축 라벨과 선 · 막대 · 격자는 장식이라 보조 기술에서 숨깁니다(Flutter ExcludeSemantics, Web aria-hidden). 기준선은 범례 문장으로 읽힙니다.

포커스 관리#

그래프 자체는 포커스를 받지 않습니다. 점별 툴팁은 hover 로 열리므로 키보드로는 열 수 없지만, 같은 내용이 각 점의 접근 가능한 이름에 있습니다.

알려진 제약#

  • 시리즈는 하나입니다. 여러 시리즈를 비교하려면 차트를 나란히 둡니다.
  • 점은 시간 간격과 무관하게 목록 순서대로 같은 간격에 놓입니다.

감속 모션 · 고대비 · 강제 색상 · 최소 터치 타겟처럼 전 컴포넌트에 공통으로 걸리는 축은 전역 접근성 축에서 다룹니다.

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명TrendChartTrendChart
그래프 CustomPaint 한 장(격자 · 기준선 · 선 · 막대) 절대 배치 <div> + 인라인 <svg>(선 · 점선)
위치 계산 CoreTrendChartGeometry → 픽셀 CoreTrendChartGeometry → 퍼센트
오른쪽→왼쪽 페인터가 x 를 뒤집고 구간 Row 가 저절로 뒤집힘 inset-inline-start 와 rtl:-scale-x-100
툴팁Tooltip 위젯Tooltip 컴포넌트
  • Stat: 요약 슬롯에 넣는 지표 카드
  • Tracker: 기간별 상태 구간
  • Chip: 기간 칩