Skip to content

DailyAndHourlyChart

개요

DailyAndHourlyChart는 서비스 메트릭 데이터를 일간 및 시간별로 시각화하는 차트 컴포넌트입니다. 메트릭 선택, 탭 전환, 데이터 선택 등의 인터랙티브 기능을 제공하여 시계열 데이터 분석을 지원합니다.

주요 기능

  • 일간/시간별 메트릭 데이터 시각화
  • 메트릭 타입 선택 (서비스 개수, 응답시간 등)
  • 차트 타입 전환 (Bar, Column)
  • 날짜 및 시간 선택 기능
  • v-model을 통한 양방향 데이터 바인딩
  • 커스텀 툴팁 포맷터 지원

Props 인터페이스

typescript
interface DailyAndHourlyChartProps {
    width: number; // 차트 너비
    height: number; // 차트 높이
    tabItems: TabItem[]; // 차트 타입 탭 항목
    metricItems: MetricItem[]; // 메트릭 선택 항목
    items: ChartItem[]; // 차트 데이터
    hourlyItemMap: Record<number, ChartItem[]>; // 시간별 데이터 맵
    nameWidth?: number; // 이름 영역 너비 (기본값: 32)
    tooltipFormatter?: (item: ChartItem, type: ChartType) => string; // 툴팁 포맷터
}

interface TabItem {
    text: string; // 탭 표시 텍스트
    value: ChartType; // 차트 타입 값
}

interface MetricItem {
    text: string; // 메트릭 표시 텍스트
    value: string; // 메트릭 값 (형식: "daily:MxDef" 또는 "hourly:MxDef")
}

interface ChartItem {
    time: number; // 시간 (timestamp)
    name: string; // 표시명
    value: number; // 값
}

type ChartType = 'bar' | 'column';

v-model Props

typescript
// 양방향 바인딩 지원 props
selectedMetric: string; // 선택된 메트릭 (기본값: "daily:service_count")
selectedDate: number; // 선택된 날짜 (기본값: -1)
selectedHour: number; // 선택된 시간 (기본값: -1)
selectedTab: ChartType; // 선택된 차트 타입 (기본값: "bar")

이벤트

typescript
interface DailyAndHourlyChartEmits {
    (e: 'tab', item: ChartType): void;
    (e: 'select', time: number, hour: number, duplicate: boolean): void;
    (e: 'metric', metric: MxDef, interval: number): void;
    (e: 'update:selectedMetric', selectedMetric: string): void;
    (e: 'update:selectedDate', selectedDate: number): void;
    (e: 'update:selectedHour', selectedHour: number): void;
    (e: 'update:selectedTab', selectedTab: ChartType): void;
}
  • tab: 차트 타입 탭 변경 시 발생
  • select: 차트 데이터 선택 시 발생
  • metric: 메트릭 변경 시 발생
  • update:* : v-model 업데이트 이벤트

기본 사용법

vue
<template>
    <daily-and-hourly-chart
        :width="800"
        :height="400"
        :tab-items="tabItems"
        :metric-items="metricItems"
        :items="chartData"
        :hourly-item-map="hourlyDataMap"
        v-model:selected-metric="selectedMetric"
        v-model:selected-date="selectedDate"
        v-model:selected-hour="selectedHour"
        @tab="onTabChange"
        @select="onDataSelect"
        @metric="onMetricChange"
    />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { DailyAndHourlyChart } from '@jennifersoft/apm-components';
import { MxDef } from '@jennifersoft/apm-apis';
import type {
    ChartItem,
    ChartType,
    TabItem,
    MetricItem,
} from '@jennifersoft/apm-components';

// 탭 항목 정의
const tabItems: TabItem[] = [
    { text: '막대형', value: 'bar' },
    { text: '세로막대형', value: 'column' },
];

// 메트릭 항목 정의
const metricItems: MetricItem[] = [
    { text: '일간 서비스 개수', value: `daily:${MxDef.service_count}` },
    {
        text: '일간 평균 응답시간',
        value: `daily:${MxDef.service_responseTime}`,
    },
    { text: '시간별 서비스 개수', value: `hourly:${MxDef.service_count}` },
];

// 상태 관리
const selectedMetric = ref<string>(`daily:${MxDef.service_count}`);
const selectedDate = ref<number>(-1);
const selectedHour = ref<number>(-1);

// 차트 데이터
const chartData: ChartItem[] = [
    { time: 1640995200000, name: '01/01', value: 1250 },
    { time: 1641081600000, name: '01/02', value: 980 },
    { time: 1641168000000, name: '01/03', value: 1450 },
];

// 시간별 데이터 맵
const hourlyDataMap: Record<number, ChartItem[]> = {
    1640995200000: [
        { time: 1640995200000, name: '00:00', value: 45 },
        { time: 1640998800000, name: '01:00', value: 52 },
        // ... 24시간 데이터
    ],
};

// 이벤트 핸들러
const onTabChange = (tab: ChartType) => {
    console.log('차트 타입 변경:', tab);
};

const onDataSelect = (time: number, hour: number, duplicate: boolean) => {
    console.log('데이터 선택:', { time, hour, duplicate });
};

const onMetricChange = (metric: MxDef, interval: number) => {
    console.log('메트릭 변경:', { metric, interval });
    // API 호출하여 새 데이터 로드
};
</script>

인터랙티브 데모

현재 상태

선택된 메트릭:daily:301
선택된 날짜:없음
선택된 시간:없음
차트 타입:bar

데이터 구조

ChartItem

typescript
interface ChartItem {
    time: number; // 시간 (Unix timestamp)
    name: string; // 표시명 (예: "01/01", "14:00")
    value: number; // 메트릭 값
}

MetricItem & TabItem

typescript
interface MetricItem {
    text: string; // 사용자에게 표시될 텍스트
    value: string; // 메트릭 식별자 (형식: "daily:MxDef" 또는 "hourly:MxDef")
}

interface TabItem {
    text: string; // 탭 텍스트
    value: ChartType; // 차트 타입 ('bar' | 'column')
}

고급 사용법

커스텀 툴팁 포맷터

vue
<template>
    <daily-and-hourly-chart
        v-bind="chartProps"
        :tooltip-formatter="customTooltipFormatter"
    />
</template>

<script setup lang="ts">
const customTooltipFormatter = (item: ChartItem, type: ChartType): string => {
    const dateFormat = type === 'bar' ? 'YYYY/MM/DD' : 'HH:mm';
    const formattedDate = dayjs(item.time).format(dateFormat);
    return `${formattedDate}: ${item.value.toLocaleString()}건`;
};
</script>

동적 데이터 로딩

vue
<script setup lang="ts">
import { ref, watch } from 'vue';

const selectedMetric = ref<string>(`daily:${MxDef.service_count}`);
const chartData = ref<ChartItem[]>([]);
const hourlyDataMap = ref<Record<number, ChartItem[]>>({});

const onMetricChange = async (metric: MxDef, interval: number) => {
    // API 호출하여 새 데이터 로드
    const data = await fetchChartData(metric, interval);
    chartData.value = data.daily;
    hourlyDataMap.value = data.hourly;
};

// 메트릭 변경 감지하여 자동 데이터 로드
watch(selectedMetric, async (newMetric) => {
    const [type, metricId] = newMetric.split(':');
    const interval = type === 'daily' ? 86400000 : 3600000;
    await onMetricChange(parseInt(metricId), interval);
});
</script>

반응형 차트 크기

vue
<template>
    <div ref="containerRef" class="chart-container">
        <daily-and-hourly-chart
            :width="containerWidth"
            :height="containerHeight"
            v-bind="chartProps"
        />
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { useResizeObserver } from '@vueuse/core';

const containerRef = ref<HTMLElement>();
const containerWidth = ref<number>(800);
const containerHeight = ref<number>(400);

useResizeObserver(containerRef, (entries) => {
    const entry = entries[0];
    containerWidth.value = entry.contentRect.width;
    containerHeight.value = Math.max(300, entry.contentRect.height);
});
</script>

의존성

  • @jennifersoft/apm-components
  • @jennifersoft/apm-apis
  • @jennifersoft/vue-components-v2
  • @vueuse/core (유틸리티 함수 사용 시)