Files
InvestmentTrackerApp/PortfolioJournal/Views/Charts/ChartValueLabels.swift
T
alexandrev-tibco f7d4fdbe2d 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
2026-07-03 23:09:35 +02:00

70 lines
2.4 KiB
Swift

import SwiftUI
import Charts
// MARK: - Point value labels for line charts
//
// Shared pieces for the "points + value labels" behavior across every line chart
// in the Charts tab: permanent labels when the series is short (they carry a lot
// of value at a glance), and a tap/drag selection bubble when it's dense.
enum ChartLabels {
/// A series with at most this many points shows its value labels permanently.
static let alwaysShowThreshold = 12
static func compactCurrency(_ value: Decimal) -> String {
value.compactCurrencyString
}
static func percent(_ value: Double, decimals: Int = 1) -> String {
String(format: "%+.\(decimals)f%%", value)
}
}
/// Small capsule bubble used both for permanent point labels and the selection popover.
struct ChartValueBubble: View {
let text: String
var color: Color = .appPrimary
var prominent: Bool = false
var body: some View {
Text(text)
.font(prominent ? .caption.weight(.bold) : .caption2.weight(.semibold))
.foregroundColor(prominent ? .white : color)
.padding(.horizontal, prominent ? 8 : 5)
.padding(.vertical, prominent ? 4 : 2)
.background(
Capsule().fill(prominent ? color : color.opacity(0.12))
)
.fixedSize()
}
}
/// Multi-line selection card (one row per series) used by multi-series charts.
struct ChartSelectionCard: View {
let title: String
let rows: [(label: String, value: String, color: Color)]
var body: some View {
VStack(alignment: .leading, spacing: 3) {
Text(title)
.font(.caption2.weight(.semibold))
.foregroundColor(.secondary)
ForEach(Array(rows.enumerated()), id: \.offset) { _, row in
HStack(spacing: 5) {
Circle().fill(row.color).frame(width: 6, height: 6)
Text(row.label)
.font(.caption2)
.foregroundColor(.secondary)
Text(row.value)
.font(.caption2.weight(.semibold))
.foregroundColor(.primary)
}
}
}
.padding(8)
.background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.gray.opacity(0.2), lineWidth: 0.5))
.fixedSize()
}
}