choiminyeong 5e5ef8c36f 플랫폼 최초 커밋 + 자동 업데이트 배포 폴더(dist)
소스(Core/Shell/Launcher)와 실행 PC가 받아갈 업데이트 파일(dist)을 함께 올린다.
Launcher가 켤 때 이 저장소의 dist와 D:\UbisamPlatform\bin을 비교해 새 버전이면 받아간다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 21:56:35 +09:00

155 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 패치 10 — LiveCharts2 차트 테마
시안 <a>11a</a>(라인 트렌드 · 막대 · 게이지)를 LiveCharts2로 옮기는 코드입니다.
색을 코드에 박지 않고 **현재 테마의 브러시 리소스에서 읽으므로** 라이트/다크 전환을 그대로 따라갑니다.
## 0. 패키지
WPF용 LiveCharts2 하나만 있으면 됩니다 (SkiaSharp는 의존성으로 따라옵니다).
```
dotnet add src/UbisamBase.Core package LiveChartsCore.SkiaSharpView.WPF
```
`.NET Framework 4.8` 타깃이므로 LiveCharts2가 net48을 지원하는 버전인지 확인하세요. 안 되면 `net8.0-windows`로 올리거나, 차트만 별도 프로젝트로 분리하는 방법이 있습니다 — 어느 쪽인지 알려주시면 그에 맞춰 정리해 드립니다.
## 1. 새 파일
| 이 프로젝트 | 리포지토리 |
| --- | --- |
| `code/UbisamBase.Core/Charting/ChartTheme.cs` | `src/UbisamBase.Core/Charting/ChartTheme.cs` (신규) |
축·시리즈를 만들어주는 정적 팩토리입니다. 화면마다 색을 다시 지정하지 마세요 — 이 클래스만 쓰면 톤이 유지됩니다.
## 2. 디자인 규칙 (코드에 반영된 것)
| 요소 | 규칙 |
| --- | --- |
| 격자 | **가로만** 헤어라인 1px (`Ubisam.Brush.Table.GridLine`). 세로 격자는 그리지 않음 |
| 데이터 선 | accent 밝은 단계, 두께 2, `LineSmoothness = 0` — 값을 매끄럽게 왜곡하지 않음 |
| 면적 | accent 12% (`0x1F` 알파) |
| 점 | 기본은 표시하지 않음 (`GeometrySize = 0`) — 밀도가 낮을 때만 켜세요 |
| 기준·목표선 | 회색 파선 `5 4`, 면적 없음, hover 비활성 |
| 막대 | 깊은 단계 채움 85%, **각진 모서리** (`Rx = Ry = 0`) |
| 축 라벨 | 11pt, `Ubisam.Brush.Muted`, Barlow |
| 값 라벨 | Barlow Condensed, 자릿수 고정 포맷 |
| 애니메이션 | 260ms — 탭 인디케이터와 같은 속도 |
색을 늘리지 않고 굵기와 불투명도로 구분하는 것이 이 시스템의 규칙입니다. 시리즈가 여러 개 필요하면 알려주세요 — 색을 더 만들지 않고 구분하는 방법을 따로 정리하겠습니다.
## 3. 라인 트렌드
```xml
<lvc:CartesianChart Series="{Binding TrendSeries}"
XAxes="{Binding TrendXAxes}"
YAxes="{Binding TrendYAxes}"
TooltipPosition="Top"
Height="240"/>
```
```csharp
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using UbisamBase.Core.Charting;
public ISeries[] TrendSeries { get; }
public Axis[] TrendXAxes { get; }
public Axis[] TrendYAxes { get; }
public MonitorViewModel()
{
var measured = new double[] { 121, 123, 119, 128, 126, 132, 130, 134 };
var baseline = Enumerable.Repeat(125d, measured.Length).ToArray();
TrendSeries = new ISeries[]
{
ChartTheme.ReferenceLine(baseline, "기준"),
ChartTheme.Line(measured, "측정")
};
TrendXAxes = new[] { ChartTheme.XAxis(labeler: v => $"-{(7 - v) * 15:0}s") };
TrendYAxes = new[] { ChartTheme.YAxis(format: "0", min: 100, max: 140) };
}
```
기준선을 먼저 넣어야 측정선이 위에 그려집니다.
## 4. 막대
```csharp
ProductionSeries = new ISeries[] { ChartTheme.Column(counts, "생산") };
ProductionXAxes = new[] { ChartTheme.XAxis(labeler: v => $"{8 + (int)v:00}") };
ProductionYAxes = new[] { ChartTheme.YAxis(format: "0", min: 0) };
```
시안처럼 최고값 막대만 진하게 하려면 `ColumnSeries`의 `Fill`을 하나로 두는 대신 값별 페인트를 주는 방식이 필요합니다 — 필요하시면 따로 만들어 드립니다.
## 5. 게이지
```xml
<lvc:PieChart Series="{Binding UtilizationSeries}"
InitialRotation="-180"
MaxAngle="180"
MinValue="0"
MaxValue="100"
Height="150"/>
```
```csharp
UtilizationSeries = ChartTheme.Gauge(82.4);
```
**목표선(파선)은 LiveCharts2가 제공하지 않습니다.** 시안의 75% 틱은 차트 위에 `Path`를 하나 얹어서 그려야 합니다. 반지름 r, 중심 (cx, cy), 목표 p(0~1)일 때 끝점은
```
x = cx + r · cos(180° − p · 180°) → p=0.75 이면 cx + r·cos(45°)
y = cy − r · sin(180° − p · 180°)
```
시안 값(cx=95, cy=100, r=77, p=0.75)으로는 (149.4, 45.6)입니다. 필요하시면 이 계산을 하는 작은 UserControl로 만들어 드립니다.
## 6. 테마 전환 대응
`ChartTheme`은 호출 시점의 브러시를 읽습니다. 테마를 바꾼 뒤 이미 만들어진 차트는 색이 그대로이므로, `UiSettingsService`가 테마를 교체한 직후 시리즈를 다시 만들어야 합니다.
```csharp
// 테마 적용 코드 끝에
ChartTheme.NotifyThemeChanged();
```
차트를 가진 ViewModel에서 이 이벤트를 구독해 `TrendSeries` 등을 다시 만들고 `OnPropertyChanged`를 올리세요.
## 7. 툴팁·범례
LiveCharts2 기본 툴팁은 밝은 판이라 다크에서 튑니다. 차트마다 지정하세요.
```xml
<lvc:CartesianChart TooltipBackgroundPaint="{Binding TooltipBackground}"
TooltipTextPaint="{Binding TooltipText}"
LegendTextPaint="{Binding LegendText}"
LegendPosition="Top"/>
```
```csharp
public IPaint<SkiaSharpDrawingContext> TooltipBackground => new SolidColorPaint(ChartTheme.Surface);
public IPaint<SkiaSharpDrawingContext> TooltipText => new SolidColorPaint(ChartTheme.Text);
public IPaint<SkiaSharpDrawingContext> LegendText => new SolidColorPaint(ChartTheme.Muted);
```
시안처럼 범례를 직접 그리는 편이 더 깔끔합니다 — 차트 위에 선 샘플 + 라벨을 `StackPanel`로 두면 됩니다.
## 8. 확인
1. 라이트에서 격자가 가로만, 헤어라인으로 보이는지
2. 다크로 바꾸고 차트를 다시 만들었을 때 선이 #4DA6F5, 격자가 #383D44인지
3. 막대 모서리가 각진지 (둥글면 `Rx`/`Ry`가 안 먹은 것)
4. 측정선이 기준 파선 위에 그려지는지
5. 게이지 숫자가 Barlow Condensed로 크게 나오는지
6. 툴팁 배경이 테마에 맞는지
## 주의
- `LineSmoothness = 0`을 유지하세요. 곡선 보간은 측정값을 실제와 다르게 보이게 합니다.
- Barlow가 대상 PC에 없으면 SkiaSharp도 기본 서체로 폴백합니다 — 폰트 임베드를 하시려면 알려주세요.
- LiveCharts2는 버전에 따라 속성명이 조금씩 다릅니다(`IPaint`/`Paint`, `TicksPaint` 등). 빌드 오류가 나면 해당 줄과 패키지 버전을 알려주시면 맞춰 드립니다.