|
73 | 73 | USER_DAU: 'USER_DAU', |
74 | 74 | USER_WAU: 'USER_WAU', |
75 | 75 | USER_MAU: 'USER_MAU', |
| 76 | + USER_SIGNUP_SOURCE_TOTAL: 'USER_SIGNUP_SOURCE_TOTAL', |
| 77 | + USER_DEVICE_PLATFORM_TOTAL: 'USER_DEVICE_PLATFORM_TOTAL', |
76 | 78 | }; |
77 | 79 |
|
78 | 80 | // (아래부터는 기존 로직 그대로입니다. 색상/그룹/플롯/툴팁/박스플롯 렌더링 등 전체 원본 유지) |
|
167 | 169 | { key:'collection', name:'새록', metrics:[METRICS.TOTAL_COUNT, METRICS.PRIVATE_RATIO] }, |
168 | 170 | { key:'user', name:'유저', metrics:[ |
169 | 171 | METRICS.USER_COMPLETED_TOTAL, METRICS.USER_SIGNUP_DAILY, METRICS.USER_WITHDRAWAL_DAILY, |
170 | | - METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU |
| 172 | + METRICS.USER_DAU, METRICS.USER_WAU, METRICS.USER_MAU, |
| 173 | + METRICS.USER_SIGNUP_SOURCE_TOTAL, METRICS.USER_DEVICE_PLATFORM_TOTAL |
171 | 174 | ] }, |
172 | 175 | { key:'id', name:'동정 요청', metrics:[METRICS.PENDING_COUNT, METRICS.RESOLVED_COUNT, METRICS.RESOLUTION_STATS] }, |
173 | 176 | ]; |
|
683 | 686 | groupSet.add(id); |
684 | 687 | changed = true; |
685 | 688 | } |
| 689 | + } else if (opt.multiSeries) { |
| 690 | + // 일반 다중 시리즈: 컴포넌트별 라인 렌더링 |
| 691 | + const series = seriesMap.get(metric); |
| 692 | + const comps = Array.isArray(series?.components) ? series.components : []; |
| 693 | + const labelsMap = (componentLabels[metric] && typeof componentLabels[metric] === 'object') ? componentLabels[metric] : {}; |
| 694 | + const unit = String(opt.unit || '').toUpperCase(); |
| 695 | + const yAxis = axisByUnit(unit); |
| 696 | + |
| 697 | + comps.forEach(comp => { |
| 698 | + if (!comp || !comp.key) return; |
| 699 | + const compKey = comp.key; |
| 700 | + const id = datasetIdFor(metric, compKey); |
| 701 | + if (p.datasets.has(id)) return; |
| 702 | + |
| 703 | + const compColor = colorForMetric(id); |
| 704 | + const compLabel = labelsMap[compKey] || compKey; |
| 705 | + const points = (Array.isArray(comp.points) ? comp.points : []) |
| 706 | + .map(pt => { |
| 707 | + const x = toDateKST(pt.date); |
| 708 | + const y = normalizeValue(pt.value, unit); |
| 709 | + return (x && y != null) ? { x, y } : null; |
| 710 | + }) |
| 711 | + .filter(Boolean); |
| 712 | + |
| 713 | + const ds = { |
| 714 | + _id: id, |
| 715 | + label: `${opt.label || metric} (${compLabel})`, |
| 716 | + data: points, |
| 717 | + parsing: { xAxisKey: 'x', yAxisKey: 'y' }, |
| 718 | + borderColor: compColor, |
| 719 | + backgroundColor: compColor, |
| 720 | + tension: 0.25, |
| 721 | + pointRadius: 0, |
| 722 | + pointHoverRadius: 4, |
| 723 | + fill: false, |
| 724 | + spanGaps: true, |
| 725 | + yAxisID: yAxis, |
| 726 | + _saUnit: unit |
| 727 | + }; |
| 728 | + p.chart.data.datasets.push(ds); |
| 729 | + p.datasets.add(id); |
| 730 | + groupSet.add(id); |
| 731 | + changed = true; |
| 732 | + }); |
686 | 733 | } else { |
687 | 734 | const unit = String(optionMap.get(metric)?.unit || '').toUpperCase(); |
688 | 735 | const s = seriesMap.get(metric); |
|
0 commit comments