diff --git a/dist/README.txt b/dist/README.txt index b126fe2..f2e3e2e 100644 --- a/dist/README.txt +++ b/dist/README.txt @@ -1,5 +1,5 @@ UbisamBase 플랫폼 배포 폴더 (빌드할 때 자동 생성 - 직접 고치지 마세요) -갱신 시각 : 2026-09-21 22:13:45 +갱신 시각 : 2026-09-21 22:17:54 빌드 구성 : Release 원본 소스 : D:\업무\03_개발\Source\용접기\08_UbisamPlatform\ ---- diff --git a/dist/UbisamBase.Core.dll b/dist/UbisamBase.Core.dll index f5de9e5..b311abf 100644 Binary files a/dist/UbisamBase.Core.dll and b/dist/UbisamBase.Core.dll differ diff --git a/dist/UbisamBase.Launcher.exe b/dist/UbisamBase.Launcher.exe index 70d3822..9980dd4 100644 Binary files a/dist/UbisamBase.Launcher.exe and b/dist/UbisamBase.Launcher.exe differ diff --git a/dist/UbisamBase.Shell.exe b/dist/UbisamBase.Shell.exe index 64bdd79..8ff640a 100644 Binary files a/dist/UbisamBase.Shell.exe and b/dist/UbisamBase.Shell.exe differ diff --git a/dist/platform-version.json b/dist/platform-version.json index aa5ca2c..af90d0a 100644 --- a/dist/platform-version.json +++ b/dist/platform-version.json @@ -1,4 +1,4 @@ { -"version": "2026-09-21 22:13:45", +"version": "2026-09-21 22:17:54", "configuration": "Release" } diff --git a/doc/history/changelog.md b/doc/history/changelog.md index f12ebb4..a5a0542 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -15,3 +15,4 @@ | 2026-09-21 | 플랫폼 자동 업데이트 추가 — Launcher가 켤 때 깃 저장소의 `dist`와 비교해 새 버전이면 받아서 실행(UI Setting에서 사용/자동 여부 선택) | | 2026-09-21 | 업데이트 안내창을 플랫폼 디자인으로 교체(Launcher 자체 XAML, 시스템 테마 따름) | | 2026-09-21 | `ApiService` 추가 — 사내 API를 앱이 대신 호출(토큰은 설정 파일에만, 호출 간격 제한·타임아웃 포함) | +| 2026-09-21 | 차트 종류별 디자인 정리 — 면적·계단선·누적/가로 막대·원형·도넛·산점 추가, 계열 팔레트(Chart.Series1~6)와 툴팁·범례 테마 적용 | diff --git a/doc/platform-guide.md b/doc/platform-guide.md index 08a8e6d..a7268e2 100644 --- a/doc/platform-guide.md +++ b/doc/platform-guide.md @@ -299,7 +299,32 @@ private void Rebuild() ``` - **`Background="Transparent"`를 꼭 지정** — 기본값은 흰 배경이라 다크 테마에서 튐. -- `ChartTheme.Column(values, name)`, `ChartTheme.Gauge(percent)`(도넛 게이지, `ISeries[]` 반환)도 있음. +- **툴팁·범례는 따로 입혀야 한다** — 지정하지 않으면 LiveCharts 기본(밝은 바탕 + 검은 글자)이 나와 + 다크 테마에서 튄다. 차트를 만든 뒤와 테마가 바뀐 뒤에 `ChartTheme.Apply(chart)`를 한 번 부른다 + (`CartesianChart` / `PieChart` 둘 다 있음). + +### 4-1. 차트 종류별 — 무엇을 언제 쓰나 + +| 만들기 | 생김새 | 쓰는 자리 | +| --- | --- | --- | +| `Line(values, name)` | accent 선 2px, 보간 없음, 면적 12% | 단일 측정값 추이(기본) | +| `Line(values, name, i)` | 팔레트 i번 선, 면적 없음 | 여러 계열을 한 그래프에 겹칠 때 | +| `ReferenceLine(values, name)` | 회색 파선, hover 없음 | 기준·목표·규격 상하한. **측정선보다 먼저** 넣어야 아래에 깔린다 | +| `SmoothLine(values, name, i)` | 곡선 보간(0.65) | 추세만 보여줄 때. **공정 측정값에는 쓰지 말 것** — 없던 중간값을 지어낸다 | +| `Area(values, name, i)` | 선 + 채움 24% | 누적량·점유량처럼 "쌓인 양" | +| `StepLine(values, name, i)` | 계단선 | ON/OFF·단계값처럼 다음 값까지 유지되는 신호 | +| `Column(values, name[, i])` | 각진 세로 막대, 채움 85% | 항목별 비교(수량·횟수) | +| `StackedColumn(values, name, i)` | 누적 세로 막대 | 전체 대비 구성비를 시간축으로 | +| `Bar(values, name[, i])` | 가로 막대 | 항목 이름이 길어 세로 막대 라벨이 겹칠 때(설비명·불량 유형) | +| `Pie(values[, names])` | 원형, 조각 사이 판 색 경계 | 구성비. 조각 6개 이하일 때만 | +| `Donut(values[, names])` | 가운데 빈 원형 | 구성비 + 가운데에 총계 표시 | +| `Gauge(percent)` | 0~100 도넛 게이지 | 가동률·달성률 한 값 | +| `Scatter(points, name, i)` | 점 9px, 판 색 테두리 | 두 값의 상관 | + +- **색은 직접 고르지 않는다.** 단일 계열은 accent 하나, 여러 계열은 `seriesIndex`를 0,1,2…로 넘기면 + `Ubisam.Brush.Chart.Series1~6` 팔레트를 순서대로 쓴다(라이트/다크 각각 따로 정의돼 있다). + 7개째부터는 색이 처음으로 돌아가므로, 계열이 그보다 많으면 차트를 나누는 편이 낫다. +- 팔레트 색이 필요하면 `ChartTheme.SeriesColor(i)`로 꺼내 쓴다(범례 표식 등). - 소비 프로젝트에서 차트를 직접 쓰려면 `LiveChartsCore`, `LiveChartsCore.SkiaSharpView`, `LiveChartsCore.SkiaSharpView.WPF`, `SkiaSharp` — 4개를 `$(UbisamPlatformDir)`에서 HintPath로 참조 추가해야 함(TestApp.csproj 예시 참고). diff --git a/src/UbisamBase.Core/Charting/ChartTheme.cs b/src/UbisamBase.Core/Charting/ChartTheme.cs index b6edaf1..2353f31 100644 --- a/src/UbisamBase.Core/Charting/ChartTheme.cs +++ b/src/UbisamBase.Core/Charting/ChartTheme.cs @@ -2,7 +2,9 @@ using System; using System.Windows; using System.Windows.Media; using LiveChartsCore; +using LiveChartsCore.Defaults; using LiveChartsCore.SkiaSharpView; +using LiveChartsCore.SkiaSharpView.WPF; using LiveChartsCore.SkiaSharpView.Painting; using LiveChartsCore.SkiaSharpView.Painting.Effects; using SkiaSharp; @@ -117,4 +119,204 @@ public static class ChartTheme LabelsPaint = new SolidColorPaint(Muted) { SKTypeface = BodyTypeface }, SeparatorsPaint = new SolidColorPaint(GridLine, 1) }; + + // ───────────────────────────────────────────────────────────────────────── + // 시리즈 색 + // 단일 계열 차트는 지금까지처럼 accent 하나만 쓴다. 여러 계열을 한 그래프에 그릴 때만 + // 아래 팔레트를 순서대로 쓴다 — 색을 화면마다 고르면 프로그램마다 톤이 달라진다. + // ───────────────────────────────────────────────────────────────────────── + + /// 계열 순번(0부터)에 해당하는 색. 6개를 넘어가면 처음으로 돌아간다. + public static SKColor SeriesColor(int index) + => ReadColor("Ubisam.Brush.Chart.Series" + ((Math.Abs(index) % 6) + 1)); + + /// 꺾은선 — 여러 계열용. 색만 팔레트에서 가져오고 나머지는 단일 계열과 같다(두께 2, 보간 없음). + public static ISeries Line(double[] values, string name, int seriesIndex) + { + var color = SeriesColor(seriesIndex); + return new LineSeries + { + Values = values, + Name = name, + Stroke = new SolidColorPaint(color, 2), + Fill = null, + GeometrySize = 0, + LineSmoothness = 0, + AnimationsSpeed = AnimationSpeed + }; + } + + /// 부드러운 꺾은선 — 추세용. 공정 측정값처럼 "튄 지점"이 중요한 데이터에는 쓰지 말 것 + /// (보간이 실제로 없던 중간값을 지어낸다). + public static ISeries SmoothLine(double[] values, string name, int seriesIndex = 0) + { + var color = SeriesColor(seriesIndex); + return new LineSeries + { + Values = values, + Name = name, + Stroke = new SolidColorPaint(color, 2), + Fill = null, + GeometrySize = 0, + LineSmoothness = 0.65, + AnimationsSpeed = AnimationSpeed + }; + } + + /// 면적 — 누적량·점유량처럼 "쌓인 양"을 보여줄 때. 겹쳐 그릴 수 있도록 채움을 24%로 둔다. + public static ISeries Area(double[] values, string name, int seriesIndex = 0) + { + var color = SeriesColor(seriesIndex); + return new LineSeries + { + Values = values, + Name = name, + Stroke = new SolidColorPaint(color, 2), + Fill = new SolidColorPaint(color.WithAlpha(0x3D)), + GeometrySize = 0, + LineSmoothness = 0, + AnimationsSpeed = AnimationSpeed + }; + } + + /// 계단선 — ON/OFF나 단계값처럼 "다음 값까지 그 상태가 유지되는" 신호용. + /// 이런 데이터를 꺾은선으로 그리면 없는 중간값이 있는 것처럼 보인다. + public static ISeries StepLine(double[] values, string name, int seriesIndex = 0) + { + var color = SeriesColor(seriesIndex); + return new StepLineSeries + { + Values = values, + Name = name, + Stroke = new SolidColorPaint(color, 2), + Fill = null, + GeometrySize = 0, + AnimationsSpeed = AnimationSpeed + }; + } + + /// 세로 막대 — 여러 계열용. 각진 모서리는 단일 계열과 같다. + public static ISeries Column(double[] values, string name, int seriesIndex) + => new ColumnSeries + { + Values = values, + Name = name, + Fill = new SolidColorPaint(SeriesColor(seriesIndex).WithAlpha(217)), + Rx = 0, + Ry = 0, + AnimationsSpeed = AnimationSpeed + }; + + /// 누적 막대 — 전체 대비 구성비를 볼 때. 같은 차트에 넣은 누적 막대끼리 쌓인다. + public static ISeries StackedColumn(double[] values, string name, int seriesIndex) + => new StackedColumnSeries + { + Values = values, + Name = name, + Fill = new SolidColorPaint(SeriesColor(seriesIndex).WithAlpha(217)), + Rx = 0, + Ry = 0, + AnimationsSpeed = AnimationSpeed + }; + + /// 가로 막대 — 항목 이름이 길어 세로 막대에서 라벨이 겹칠 때(설비명·불량 유형 등). + public static ISeries Bar(double[] values, string name, int seriesIndex = 0) + => new RowSeries + { + Values = values, + Name = name, + Fill = new SolidColorPaint(SeriesColor(seriesIndex).WithAlpha(217)), + Rx = 0, + Ry = 0, + AnimationsSpeed = AnimationSpeed + }; + + /// 원형 — 구성비. 조각은 팔레트 순서대로, 경계는 판 색으로 얇게 갈라 서로 붙어 보이지 않게 한다. + /// 조각이 6개를 넘으면 색이 돌아오므로 그때는 가로 막대(Bar)가 낫다. + public static ISeries[] Pie(double[] values, string[] names = null) + => BuildPie(values, names, innerRadius: 0); + + /// 도넛 — 원형과 같되 가운데를 비워 총계·제목을 얹을 수 있다. + public static ISeries[] Donut(double[] values, string[] names = null) + => BuildPie(values, names, innerRadius: 52); + + private static ISeries[] BuildPie(double[] values, string[] names, double innerRadius) + { + var series = new ISeries[values.Length]; + + for (var i = 0; i < values.Length; i++) + { + series[i] = new PieSeries + { + Values = new[] { values[i] }, + Name = names != null && i < names.Length ? names[i] : string.Empty, + Fill = new SolidColorPaint(SeriesColor(i)), + Stroke = new SolidColorPaint(Surface, 2), + InnerRadius = innerRadius, + AnimationsSpeed = AnimationSpeed + }; + } + + return series; + } + + /// 산점 — 두 값의 상관을 볼 때. 점이 겹쳐도 개수를 셀 수 있도록 판 색 테두리를 두른다. + public static ISeries Scatter(ObservablePoint[] points, string name, int seriesIndex = 0) + { + var color = SeriesColor(seriesIndex); + return new ScatterSeries + { + Values = points, + Name = name, + Fill = new SolidColorPaint(color.WithAlpha(0xC8)), + Stroke = new SolidColorPaint(Surface, 1), + GeometrySize = 9, + AnimationsSpeed = AnimationSpeed + }; + } + + // ───────────────────────────────────────────────────────────────────────── + // 툴팁 · 범례 — 차트 컨트롤에 직접 넣어야 하는 값들. + // 지정하지 않으면 LiveCharts 기본(밝은 바탕 + 검은 글자)이 나와서 다크 테마에서 튄다. + // ───────────────────────────────────────────────────────────────────────── + + public static SolidColorPaint TooltipTextPaint => new SolidColorPaint(Text) { SKTypeface = BodyTypeface }; + + public static SolidColorPaint TooltipBackgroundPaint => new SolidColorPaint(Surface); + + public static SolidColorPaint LegendTextPaint => new SolidColorPaint(Muted) { SKTypeface = BodyTypeface }; + + public static SolidColorPaint LegendBackgroundPaint => new SolidColorPaint(Surface); + + /// 툴팁·범례 색을 이 차트에 입힌다. 차트를 만든 뒤와 테마가 바뀐 뒤에 한 번씩 불러주면 된다. + public static void Apply(CartesianChart chart) + { + if (chart == null) + { + return; + } + + chart.TooltipTextPaint = TooltipTextPaint; + chart.TooltipBackgroundPaint = TooltipBackgroundPaint; + chart.TooltipTextSize = 12; + chart.LegendTextPaint = LegendTextPaint; + chart.LegendBackgroundPaint = LegendBackgroundPaint; + chart.LegendTextSize = 12; + } + + /// 원형·도넛 차트에도 같은 처리. + public static void Apply(PieChart chart) + { + if (chart == null) + { + return; + } + + chart.TooltipTextPaint = TooltipTextPaint; + chart.TooltipBackgroundPaint = TooltipBackgroundPaint; + chart.TooltipTextSize = 12; + chart.LegendTextPaint = LegendTextPaint; + chart.LegendBackgroundPaint = LegendBackgroundPaint; + chart.LegendTextSize = 12; + } } diff --git a/src/UbisamBase.Core/Themes/Colors.Dark.xaml b/src/UbisamBase.Core/Themes/Colors.Dark.xaml index 447997d..2c0421f 100644 --- a/src/UbisamBase.Core/Themes/Colors.Dark.xaml +++ b/src/UbisamBase.Core/Themes/Colors.Dark.xaml @@ -65,4 +65,14 @@ + + + + + + + + diff --git a/src/UbisamBase.Core/Themes/Colors.Light.xaml b/src/UbisamBase.Core/Themes/Colors.Light.xaml index 1025935..9ec1781 100644 --- a/src/UbisamBase.Core/Themes/Colors.Light.xaml +++ b/src/UbisamBase.Core/Themes/Colors.Light.xaml @@ -60,4 +60,12 @@ + + + + + + + +