Я относительно новичок в SwiftUI, совершенно новичок в Swift Charts, и, кажется, наткнулся на стену. Я пытаюсь заставить свой график отражать цвета, присутствующие в entryObservation.subject.color, но если я использую простой .foregroundStyle(entryObservation.subject.color) , то легенда не отображается, даже когда я явно указываю это с помощью .chartLegend(.visible), и если я использую .foregroundStyle(by: .value("Subject", enterObservation.subject.name)) тогда диаграмма не отражает цвет в моих данных, но легенда показывает.
Я немного покопался и, похоже, мне придется использовать chartForegroundStyleScale(mapping:), но я совершенно не понимаю, как это сделать. Имейте в виду, что данные динамические, и код должен это поддерживать, поэтому .chartForegroundStyleScale(["Men": .blue, "Boys": .red, "Girls": .teal, "Women": .mint]) не будет работать.
Это вообще правильный путь, или мне просто следует создать собственную легенду?
Любая помощь будет принята с благодарностью,
Джош
import SwiftUI
import Charts
struct EntryObservation {
var subject: Subject
var sectionGroup: SectionGroup
var time: Date
}
struct Subject {
var name: String
var color: Color
}
struct SectionGroup {
var name: String
}
struct ClickerView: View {
var entryObservations: [EntryObservation]
@State private var categorization = 1
var body: some View {
Chart (entryObservations, id: \.time) {entryObservation in
Plot {
switch categorization {
case 2:
BarMark(x: .value("Section", entryObservation.sectionGroup.name), y: .value("Count", 1))
.foregroundStyle(by: .value("Subject", entryObservation.subject.name))
.cornerRadius(10)
.annotation(position: .top) {
Text("\(entryObservations.filter { $0.sectionGroup.name == entryObservation.sectionGroup.name }.count)")
.foregroundColor(Color.gray)
.font(.system(size: 12, weight: .bold))
}
default:
BarMark(x: .value("Subject", entryObservation.subject.name), y: .value("Count", 1)
.cornerRadius(10)
.foregroundStyle(by: .value("Subject", entryObservation.subject.name))
.annotation(position: .top) {
Text("\(entryObservations.filter { $0.subject.name == entryObservation.subject.name }.count)")
.foregroundColor(Color.gray)
.font(.system(size: 12, weight: .bold))
}
}
}
}.padding()
.chartLegend(.visible)
.chartYAxisLabel("Count")
.safeAreaInset(edge: .top) {
VStack {
Picker("Categorisation", selection: $categorization) {
Text("Subject").tag(1)
Text("Section").tag(2)
}
.pickerStyle(.segmented)
}
}
}
}
let entryObservations: [EntryObservation] = [
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1000)),
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1300)),
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2000)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1900)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2200)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 2500)),
EntryObservation(subject: Subject(name: "Girls", color: .teal), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2800)),
EntryObservation(subject: Subject(name: "Girls", color: .teal), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 3100)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 3400)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 3700)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 4000)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 4300)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 4600)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 4900)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 5200))
]
#Preview {
ClickerView(entryObservations: entryObservations)
}





Сначала я бы создал такой [String: Color], чтобы сопоставить имена предметов с соответствующими цветами:
@State private var colors: [String: Color] = [:]
Вы можете установить это в onChange:
// this assumes that each name only corresponds to one color
// there must not be two subjects with the same name but different colours
.onChange(of: entryObservations, initial: true) { _, newValue in
colors = Dictionary(uniqueKeysWithValues: Set(newValue.map(\.subject)).map { ($0.name, $0.color) })
}
onChange требует, чтобы аргумент of: соответствовал Equatable, поэтому вам следует сделать это для всех задействованных структур - EntryObservation, Subject и SectionGroup.
Затем вы можете использовать chartForegroundStyleScale, который принимает параметр функции, чтобы сопоставить каждое имя с цветом, используя словарь colors.
.chartForegroundStyleScale { (name: String) in
colors[name] ?? .clear
}
Я бы рекомендовал проделать то же самое с двумя разными категориями. Создайте два массива ChartData, где ChartData содержит все необходимые каждому BarMark данные, и обновите массивы в onChange. Таким образом, вам не нужно filter при каждом обновлении просмотра.
Минимальный воспроизводимый пример:
@State private var colors: [String: Color] = [:]
@State private var categorization = 1
var body: some View {
Chart (entryObservations, id: \.time) {entryObservation in
Plot {
switch categorization {
case 2:
BarMark(x: .value("Section", entryObservation.sectionGroup.name), y: .value("Count", 1))
.foregroundStyle(by: .value("Subject", entryObservation.subject.name))
.cornerRadius(10)
.annotation(position: .top) {
Text("\(entryObservations.filter { $0.sectionGroup.name == entryObservation.sectionGroup.name }.count)")
.foregroundColor(Color.gray)
.font(.system(size: 12, weight: .bold))
}
default:
BarMark(x: .value("Subject", entryObservation.subject.name), y: .value("Count", 1))
.cornerRadius(10)
.foregroundStyle(by: .value("Subject", entryObservation.subject.name))
.annotation(position: .top) {
Text("\(entryObservations.filter { $0.subject.name == entryObservation.subject.name }.count)")
.foregroundColor(Color.gray)
.font(.system(size: 12, weight: .bold))
}
}
}
}
.padding()
.chartLegend(.visible)
.chartYAxisLabel("Count")
.safeAreaInset(edge: .top) {
VStack {
Picker("Categorisation", selection: $categorization) {
Text("Subject").tag(1)
Text("Section").tag(2)
}
.pickerStyle(.segmented)
}
}
.onChange(of: entryObservations, initial: true) { _, newValue in
colors = Dictionary(uniqueKeysWithValues: Set(newValue.map(\.subject)).map { ($0.name, $0.color) })
}
.chartForegroundStyleScale { (name: String) in
colors[name] ?? .clear
}
}
let entryObservations: [EntryObservation] = [
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1000)),
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1300)),
EntryObservation(subject: Subject(name: "Men", color: .blue), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2000)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 1900)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2200)),
EntryObservation(subject: Subject(name: "Boys", color: .red), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 2500)),
EntryObservation(subject: Subject(name: "Girls", color: .teal), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 2800)),
EntryObservation(subject: Subject(name: "Girls", color: .teal), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 3100)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 3400)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 3700)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 4000)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 4300)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 4600)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "Mall"), time: Date(timeIntervalSince1970: 4900)),
EntryObservation(subject: Subject(name: "Women", color: .mint), sectionGroup: SectionGroup(name: "School"), time: Date(timeIntervalSince1970: 5200))
]