Charts: puntos + labels de valor en todas las gráficas de líneas
- Nuevo ChartValueLabels.swift: ChartValueBubble (cápsula) y ChartSelectionCard
(tarjeta multi-serie), con umbral compartido (<=12 puntos → labels SIEMPRE
visibles; las gráficas cortas aportan mucho más con los valores a la vista)
- PointMark añadido donde faltaba (Compare, Year vs Year, Drawdown, Volatility);
agrandado donde ya existía (Evolution, Prediction, Period vs Period, Rolling)
- Selección por tap/arrastre (chartXSelection) en las 8 gráficas: RuleMark + burbuja
prominente (una serie) o tarjeta con el valor de cada serie (multi-serie)
- Evolution reutiliza su scrubbing existente y ahora muestra burbuja en el punto
- Period vs Period: labels A arriba / B abajo para evitar solapes
- Drawdown/Volatility migrados de Chart(data:) a Chart{ForEach} para poder añadir
marcas de selección
Verificado en iPad Pro 13" (labels visibles en Evolution y Period vs Period).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015qUZrBusG82T37R7PeokqJ
This commit is contained in:
@@ -3,6 +3,7 @@ import Charts
|
||||
|
||||
struct YearOverYearChartView: View {
|
||||
@ObservedObject var viewModel: ChartsViewModel
|
||||
@State private var selectedMonthIdx: Int?
|
||||
|
||||
private let monthAbbreviations = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
@@ -92,6 +93,18 @@ struct YearOverYearChartView: View {
|
||||
|
||||
// MARK: - Chart
|
||||
|
||||
/// Two selected years × 12 months stays readable with permanent labels.
|
||||
private var showAllLabels: Bool { selectedSeries.count <= 2 }
|
||||
|
||||
private func selectionRows(for month: Int) -> [(label: String, value: String, color: Color)] {
|
||||
selectedSeries.compactMap { series in
|
||||
let value = series.values[month]
|
||||
guard !value.isNaN else { return nil }
|
||||
let idx = allYears.firstIndex(where: { $0.id == series.id }) ?? 0
|
||||
return (String(series.year), ChartLabels.percent(value), lineColors[idx % lineColors.count])
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var chartBody: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
@@ -99,6 +112,7 @@ struct YearOverYearChartView: View {
|
||||
ForEach(selectedSeries) { yearSeries in
|
||||
let idx = allYears.firstIndex(where: { $0.id == yearSeries.id }) ?? 0
|
||||
let color = lineColors[idx % lineColors.count]
|
||||
let seriesPosition = selectedSeries.firstIndex(where: { $0.id == yearSeries.id }) ?? 0
|
||||
ForEach(0..<12, id: \.self) { monthIdx in
|
||||
let value = yearSeries.values[monthIdx]
|
||||
if !value.isNaN {
|
||||
@@ -109,10 +123,35 @@ struct YearOverYearChartView: View {
|
||||
)
|
||||
.foregroundStyle(color)
|
||||
.interpolationMethod(.catmullRom)
|
||||
|
||||
PointMark(
|
||||
x: .value("Month", monthIdx),
|
||||
y: .value("Return", value)
|
||||
)
|
||||
.foregroundStyle(color)
|
||||
.symbolSize(showAllLabels ? 32 : 18)
|
||||
.annotation(position: seriesPosition == 0 ? .top : .bottom, spacing: 2) {
|
||||
if showAllLabels {
|
||||
ChartValueBubble(text: ChartLabels.percent(value), color: color)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let selectedMonthIdx, selectedMonthIdx >= 0, selectedMonthIdx < 12 {
|
||||
RuleMark(x: .value("Selected", selectedMonthIdx))
|
||||
.foregroundStyle(Color.secondary.opacity(0.35))
|
||||
.lineStyle(StrokeStyle(lineWidth: 1, dash: [3, 3]))
|
||||
.annotation(position: .top, spacing: 4) {
|
||||
ChartSelectionCard(
|
||||
title: monthAbbreviations[selectedMonthIdx],
|
||||
rows: selectionRows(for: selectedMonthIdx)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.chartXSelection(value: $selectedMonthIdx)
|
||||
.chartXAxis {
|
||||
AxisMarks(values: Array(0..<12)) { value in
|
||||
AxisValueLabel {
|
||||
|
||||
Reference in New Issue
Block a user