@@ -10,9 +10,12 @@ import SwiftUI |
||
| 10 | 10 |
|
| 11 | 11 |
struct MeasurementChartView: View {
|
| 12 | 12 |
private let minimumTimeSpan: TimeInterval = 1 |
| 13 |
- private let minimumVoltageSpan = 0.1 |
|
| 14 |
- private let minimumCurrentSpan = 0.1 |
|
| 15 |
- private let minimumPowerSpan = 0.1 |
|
| 13 |
+ private let minimumVoltageSpan = 0.5 |
|
| 14 |
+ private let minimumCurrentSpan = 0.5 |
|
| 15 |
+ private let minimumPowerSpan = 0.5 |
|
| 16 |
+ private let axisColumnWidth: CGFloat = 46 |
|
| 17 |
+ private let chartSectionSpacing: CGFloat = 8 |
|
| 18 |
+ private let xAxisHeight: CGFloat = 28 |
|
| 16 | 19 |
|
| 17 | 20 |
@EnvironmentObject private var measurements: Measurements |
| 18 | 21 |
var timeRange: ClosedRange<Date>? = nil |
@@ -34,91 +37,172 @@ struct MeasurementChartView: View {
|
||
| 34 | 37 |
) |
| 35 | 38 |
|
| 36 | 39 |
Group {
|
| 37 |
- VStack {
|
|
| 38 |
- HStack {
|
|
| 39 |
- Button( action: {
|
|
| 40 |
- self.displayVoltage.toggle() |
|
| 41 |
- if self.displayVoltage {
|
|
| 42 |
- self.displayPower = false |
|
| 43 |
- } |
|
| 44 |
- } ) { Text("Voltage") }
|
|
| 45 |
- .asEnableFeatureButton(state: displayVoltage) |
|
| 46 |
- Button( action: {
|
|
| 47 |
- self.displayCurrent.toggle() |
|
| 48 |
- if self.displayCurrent {
|
|
| 49 |
- self.displayPower = false |
|
| 50 |
- } |
|
| 51 |
- } ) { Text("Current") }
|
|
| 52 |
- .asEnableFeatureButton(state: displayCurrent) |
|
| 53 |
- Button( action: {
|
|
| 54 |
- self.displayPower.toggle() |
|
| 55 |
- if self.displayPower {
|
|
| 56 |
- self.displayCurrent = false |
|
| 57 |
- self.displayVoltage = false |
|
| 58 |
- } |
|
| 59 |
- } ) { Text("Power") }
|
|
| 60 |
- .asEnableFeatureButton(state: displayPower) |
|
| 61 |
- } |
|
| 62 |
- .padding(.bottom, 5) |
|
| 63 |
- if let primarySeries {
|
|
| 64 |
- VStack {
|
|
| 65 |
- GeometryReader { geometry in
|
|
| 66 |
- HStack {
|
|
| 67 |
- Group { // MARK: Left Legend
|
|
| 68 |
- if self.displayPower {
|
|
| 69 |
- self.yAxisLabelsView(geometry: geometry, context: powerSeries.context, measurementUnit: "W") |
|
| 70 |
- .withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .red, opacity: 0.5) |
|
| 71 |
- } else if self.displayVoltage {
|
|
| 72 |
- self.yAxisLabelsView(geometry: geometry, context: voltageSeries.context, measurementUnit: "V") |
|
| 73 |
- .withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .green, opacity: 0.5) |
|
| 74 |
- } |
|
| 75 |
- else if self.displayCurrent {
|
|
| 76 |
- self.yAxisLabelsView(geometry: geometry, context: currentSeries.context, measurementUnit: "A") |
|
| 77 |
- .withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .blue, opacity: 0.5) |
|
| 78 |
- } |
|
| 79 |
- } |
|
| 80 |
- ZStack { // MARK: Graph
|
|
| 81 |
- if self.displayPower {
|
|
| 82 |
- Chart(points: powerSeries.points, context: powerSeries.context, strokeColor: .red) |
|
| 83 |
- .opacity(0.5) |
|
| 84 |
- } else {
|
|
| 85 |
- if self.displayVoltage{
|
|
| 86 |
- Chart(points: voltageSeries.points, context: voltageSeries.context, strokeColor: .green) |
|
| 87 |
- .opacity(0.5) |
|
| 88 |
- } |
|
| 89 |
- if self.displayCurrent{
|
|
| 90 |
- Chart(points: currentSeries.points, context: currentSeries.context, strokeColor: .blue) |
|
| 91 |
- .opacity(0.5) |
|
| 92 |
- } |
|
| 93 |
- } |
|
| 94 |
- |
|
| 95 |
- // MARK: Grid |
|
| 96 |
- self.horizontalGuides() |
|
| 97 |
- self.verticalGuides() |
|
| 98 |
- } |
|
| 99 |
- .withRoundedRectangleBackground( cornerRadius: 0, foregroundColor: .primary, opacity: 0.06 ) |
|
| 100 |
- Group { // MARK: Right Legend
|
|
| 101 |
- self.yAxisLabelsView(geometry: geometry, context: currentSeries.context, measurementUnit: "A") |
|
| 102 |
- .foregroundColor(self.displayVoltage && self.displayCurrent ? .primary : .clear) |
|
| 103 |
- .withRoundedRectangleBackground(cornerRadius: 0, foregroundColor: .blue, opacity: 0.5) |
|
| 40 |
+ if let primarySeries {
|
|
| 41 |
+ VStack(alignment: .leading, spacing: 12) {
|
|
| 42 |
+ chartToggleBar |
|
| 43 |
+ |
|
| 44 |
+ GeometryReader { geometry in
|
|
| 45 |
+ let plotHeight = max(geometry.size.height - xAxisHeight, 140) |
|
| 46 |
+ |
|
| 47 |
+ VStack(spacing: 6) {
|
|
| 48 |
+ HStack(spacing: chartSectionSpacing) {
|
|
| 49 |
+ primaryAxisView( |
|
| 50 |
+ height: plotHeight, |
|
| 51 |
+ powerSeries: powerSeries, |
|
| 52 |
+ voltageSeries: voltageSeries, |
|
| 53 |
+ currentSeries: currentSeries |
|
| 54 |
+ ) |
|
| 55 |
+ .frame(width: axisColumnWidth, height: plotHeight) |
|
| 56 |
+ |
|
| 57 |
+ ZStack {
|
|
| 58 |
+ RoundedRectangle(cornerRadius: 18, style: .continuous) |
|
| 59 |
+ .fill(Color.primary.opacity(0.05)) |
|
| 60 |
+ |
|
| 61 |
+ RoundedRectangle(cornerRadius: 18, style: .continuous) |
|
| 62 |
+ .stroke(Color.secondary.opacity(0.16), lineWidth: 1) |
|
| 63 |
+ |
|
| 64 |
+ horizontalGuides(context: primarySeries.context) |
|
| 65 |
+ verticalGuides(context: primarySeries.context) |
|
| 66 |
+ renderedChart( |
|
| 67 |
+ powerSeries: powerSeries, |
|
| 68 |
+ voltageSeries: voltageSeries, |
|
| 69 |
+ currentSeries: currentSeries |
|
| 70 |
+ ) |
|
| 104 | 71 |
} |
| 72 |
+ .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) |
|
| 73 |
+ .frame(maxWidth: .infinity) |
|
| 74 |
+ .frame(height: plotHeight) |
|
| 75 |
+ |
|
| 76 |
+ secondaryAxisView( |
|
| 77 |
+ height: plotHeight, |
|
| 78 |
+ powerSeries: powerSeries, |
|
| 79 |
+ voltageSeries: voltageSeries, |
|
| 80 |
+ currentSeries: currentSeries |
|
| 81 |
+ ) |
|
| 82 |
+ .frame(width: axisColumnWidth, height: plotHeight) |
|
| 105 | 83 |
} |
| 84 |
+ |
|
| 85 |
+ xAxisLabelsView(context: primarySeries.context) |
|
| 86 |
+ .frame(height: xAxisHeight) |
|
| 106 | 87 |
} |
| 107 |
- xAxisLabelsView(context: primarySeries.context) |
|
| 108 |
- .padding(.horizontal, 10) |
|
| 109 |
- |
|
| 88 |
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) |
|
| 110 | 89 |
} |
| 111 | 90 |
} |
| 112 |
- else {
|
|
| 91 |
+ } else {
|
|
| 92 |
+ VStack(alignment: .leading, spacing: 12) {
|
|
| 93 |
+ chartToggleBar |
|
| 113 | 94 |
Text("Nothing to show!")
|
| 95 |
+ .foregroundColor(.secondary) |
|
| 114 | 96 |
} |
| 115 |
- |
|
| 116 | 97 |
} |
| 117 |
- .padding(10) |
|
| 118 |
- .font(.footnote) |
|
| 119 |
- .frame(maxWidth: .greatestFiniteMagnitude) |
|
| 120 |
- .withRoundedRectangleBackground( cornerRadius: 15, foregroundColor: .primary, opacity: 0.03 ) |
|
| 121 |
- .padding() |
|
| 98 |
+ } |
|
| 99 |
+ .font(.footnote) |
|
| 100 |
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) |
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ private var chartToggleBar: some View {
|
|
| 104 |
+ HStack(spacing: 8) {
|
|
| 105 |
+ Button(action: {
|
|
| 106 |
+ self.displayVoltage.toggle() |
|
| 107 |
+ if self.displayVoltage {
|
|
| 108 |
+ self.displayPower = false |
|
| 109 |
+ } |
|
| 110 |
+ }) { Text("Voltage") }
|
|
| 111 |
+ .asEnableFeatureButton(state: displayVoltage) |
|
| 112 |
+ |
|
| 113 |
+ Button(action: {
|
|
| 114 |
+ self.displayCurrent.toggle() |
|
| 115 |
+ if self.displayCurrent {
|
|
| 116 |
+ self.displayPower = false |
|
| 117 |
+ } |
|
| 118 |
+ }) { Text("Current") }
|
|
| 119 |
+ .asEnableFeatureButton(state: displayCurrent) |
|
| 120 |
+ |
|
| 121 |
+ Button(action: {
|
|
| 122 |
+ self.displayPower.toggle() |
|
| 123 |
+ if self.displayPower {
|
|
| 124 |
+ self.displayCurrent = false |
|
| 125 |
+ self.displayVoltage = false |
|
| 126 |
+ } |
|
| 127 |
+ }) { Text("Power") }
|
|
| 128 |
+ .asEnableFeatureButton(state: displayPower) |
|
| 129 |
+ } |
|
| 130 |
+ .frame(maxWidth: .infinity, alignment: .center) |
|
| 131 |
+ } |
|
| 132 |
+ |
|
| 133 |
+ @ViewBuilder |
|
| 134 |
+ private func primaryAxisView( |
|
| 135 |
+ height: CGFloat, |
|
| 136 |
+ powerSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 137 |
+ voltageSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 138 |
+ currentSeries: (points: [Measurements.Measurement.Point], context: ChartContext) |
|
| 139 |
+ ) -> some View {
|
|
| 140 |
+ if displayPower {
|
|
| 141 |
+ yAxisLabelsView( |
|
| 142 |
+ height: height, |
|
| 143 |
+ context: powerSeries.context, |
|
| 144 |
+ measurementUnit: "W", |
|
| 145 |
+ tint: .red |
|
| 146 |
+ ) |
|
| 147 |
+ } else if displayVoltage {
|
|
| 148 |
+ yAxisLabelsView( |
|
| 149 |
+ height: height, |
|
| 150 |
+ context: voltageSeries.context, |
|
| 151 |
+ measurementUnit: "V", |
|
| 152 |
+ tint: .green |
|
| 153 |
+ ) |
|
| 154 |
+ } else if displayCurrent {
|
|
| 155 |
+ yAxisLabelsView( |
|
| 156 |
+ height: height, |
|
| 157 |
+ context: currentSeries.context, |
|
| 158 |
+ measurementUnit: "A", |
|
| 159 |
+ tint: .blue |
|
| 160 |
+ ) |
|
| 161 |
+ } |
|
| 162 |
+ } |
|
| 163 |
+ |
|
| 164 |
+ @ViewBuilder |
|
| 165 |
+ private func renderedChart( |
|
| 166 |
+ powerSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 167 |
+ voltageSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 168 |
+ currentSeries: (points: [Measurements.Measurement.Point], context: ChartContext) |
|
| 169 |
+ ) -> some View {
|
|
| 170 |
+ if self.displayPower {
|
|
| 171 |
+ Chart(points: powerSeries.points, context: powerSeries.context, strokeColor: .red) |
|
| 172 |
+ .opacity(0.72) |
|
| 173 |
+ } else {
|
|
| 174 |
+ if self.displayVoltage {
|
|
| 175 |
+ Chart(points: voltageSeries.points, context: voltageSeries.context, strokeColor: .green) |
|
| 176 |
+ .opacity(0.78) |
|
| 177 |
+ } |
|
| 178 |
+ if self.displayCurrent {
|
|
| 179 |
+ Chart(points: currentSeries.points, context: currentSeries.context, strokeColor: .blue) |
|
| 180 |
+ .opacity(0.78) |
|
| 181 |
+ } |
|
| 182 |
+ } |
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+ @ViewBuilder |
|
| 186 |
+ private func secondaryAxisView( |
|
| 187 |
+ height: CGFloat, |
|
| 188 |
+ powerSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 189 |
+ voltageSeries: (points: [Measurements.Measurement.Point], context: ChartContext), |
|
| 190 |
+ currentSeries: (points: [Measurements.Measurement.Point], context: ChartContext) |
|
| 191 |
+ ) -> some View {
|
|
| 192 |
+ if displayVoltage && displayCurrent {
|
|
| 193 |
+ yAxisLabelsView( |
|
| 194 |
+ height: height, |
|
| 195 |
+ context: currentSeries.context, |
|
| 196 |
+ measurementUnit: "A", |
|
| 197 |
+ tint: .blue |
|
| 198 |
+ ) |
|
| 199 |
+ } else {
|
|
| 200 |
+ primaryAxisView( |
|
| 201 |
+ height: height, |
|
| 202 |
+ powerSeries: powerSeries, |
|
| 203 |
+ voltageSeries: voltageSeries, |
|
| 204 |
+ currentSeries: currentSeries |
|
| 205 |
+ ) |
|
| 122 | 206 |
} |
| 123 | 207 |
} |
| 124 | 208 |
|
@@ -159,77 +243,174 @@ struct MeasurementChartView: View {
|
||
| 159 | 243 |
} |
| 160 | 244 |
return (points, context) |
| 161 | 245 |
} |
| 246 |
+ |
|
| 247 |
+ private func yGuidePosition( |
|
| 248 |
+ for labelIndex: Int, |
|
| 249 |
+ context: ChartContext, |
|
| 250 |
+ height: CGFloat |
|
| 251 |
+ ) -> CGFloat {
|
|
| 252 |
+ let value = context.yAxisLabel(for: labelIndex, of: yLabels) |
|
| 253 |
+ let anchorPoint = CGPoint(x: context.origin.x, y: CGFloat(value)) |
|
| 254 |
+ return context.placeInRect(point: anchorPoint).y * height |
|
| 255 |
+ } |
|
| 256 |
+ |
|
| 257 |
+ private func xGuidePosition( |
|
| 258 |
+ for labelIndex: Int, |
|
| 259 |
+ context: ChartContext, |
|
| 260 |
+ width: CGFloat |
|
| 261 |
+ ) -> CGFloat {
|
|
| 262 |
+ let value = context.xAxisLabel(for: labelIndex, of: xLabels) |
|
| 263 |
+ let anchorPoint = CGPoint(x: CGFloat(value), y: context.origin.y) |
|
| 264 |
+ return context.placeInRect(point: anchorPoint).x * width |
|
| 265 |
+ } |
|
| 162 | 266 |
|
| 163 | 267 |
// MARK: Cu iteratie nu functioneaza deoarece view-ul din bucla nu este reimprospatat la modificare obiectului observat |
| 164 |
- fileprivate func xAxisLabelsView(context: ChartContext) -> some View {
|
|
| 268 |
+ fileprivate func xAxisLabelsView( |
|
| 269 |
+ context: ChartContext |
|
| 270 |
+ ) -> some View {
|
|
| 165 | 271 |
var timeFormat: String? |
| 166 | 272 |
switch context.size.width {
|
| 167 | 273 |
case 0..<3600: timeFormat = "HH:mm:ss" |
| 168 | 274 |
case 3600...86400: timeFormat = "HH:mm" |
| 169 |
- default: timeFormat = "E:HH:MM" |
|
| 275 |
+ default: timeFormat = "E HH:mm" |
|
| 276 |
+ } |
|
| 277 |
+ let labels = (1...xLabels).map {
|
|
| 278 |
+ Date(timeIntervalSince1970: context.xAxisLabel(for: $0, of: self.xLabels)).format(as: timeFormat!) |
|
| 170 | 279 |
} |
| 171 |
- return HStack {
|
|
| 172 |
- ForEach (1...xLabels, id: \.self) { i in
|
|
| 173 |
- Group {
|
|
| 174 |
- Text( "\(Date(timeIntervalSince1970: context.xAxisLabel(for: i, of: self.yLabels)).format(as: timeFormat!))" ) |
|
| 175 |
- .fontWeight(.semibold) |
|
| 176 |
- if i < self.xLabels {
|
|
| 177 |
- Spacer() |
|
| 280 |
+ |
|
| 281 |
+ return HStack(spacing: chartSectionSpacing) {
|
|
| 282 |
+ Color.clear |
|
| 283 |
+ .frame(width: axisColumnWidth) |
|
| 284 |
+ |
|
| 285 |
+ GeometryReader { geometry in
|
|
| 286 |
+ let labelWidth = max( |
|
| 287 |
+ geometry.size.width / CGFloat(max(xLabels - 1, 1)), |
|
| 288 |
+ 1 |
|
| 289 |
+ ) |
|
| 290 |
+ |
|
| 291 |
+ ZStack(alignment: .topLeading) {
|
|
| 292 |
+ Path { path in
|
|
| 293 |
+ for labelIndex in 1...self.xLabels {
|
|
| 294 |
+ let x = xGuidePosition( |
|
| 295 |
+ for: labelIndex, |
|
| 296 |
+ context: context, |
|
| 297 |
+ width: geometry.size.width |
|
| 298 |
+ ) |
|
| 299 |
+ path.move(to: CGPoint(x: x, y: 0)) |
|
| 300 |
+ path.addLine(to: CGPoint(x: x, y: 6)) |
|
| 301 |
+ } |
|
| 302 |
+ } |
|
| 303 |
+ .stroke(Color.secondary.opacity(0.26), lineWidth: 0.75) |
|
| 304 |
+ |
|
| 305 |
+ ForEach(Array(labels.enumerated()), id: \.offset) { item in
|
|
| 306 |
+ let labelIndex = item.offset + 1 |
|
| 307 |
+ let centerX = xGuidePosition( |
|
| 308 |
+ for: labelIndex, |
|
| 309 |
+ context: context, |
|
| 310 |
+ width: geometry.size.width |
|
| 311 |
+ ) |
|
| 312 |
+ |
|
| 313 |
+ Text(item.element) |
|
| 314 |
+ .font(.caption.weight(.semibold)) |
|
| 315 |
+ .monospacedDigit() |
|
| 316 |
+ .lineLimit(1) |
|
| 317 |
+ .minimumScaleFactor(0.68) |
|
| 318 |
+ .frame(width: labelWidth) |
|
| 319 |
+ .position( |
|
| 320 |
+ x: centerX, |
|
| 321 |
+ y: geometry.size.height * 0.7 |
|
| 322 |
+ ) |
|
| 178 | 323 |
} |
| 179 | 324 |
} |
| 325 |
+ .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) |
|
| 180 | 326 |
} |
| 327 |
+ |
|
| 328 |
+ Color.clear |
|
| 329 |
+ .frame(width: axisColumnWidth) |
|
| 181 | 330 |
} |
| 182 | 331 |
} |
| 183 | 332 |
|
| 184 |
- fileprivate func yAxisLabelsView(geometry: GeometryProxy, context: ChartContext, measurementUnit: String) -> some View {
|
|
| 185 |
- return ZStack {
|
|
| 186 |
- VStack {
|
|
| 187 |
- Text("\(context.yAxisLabel(for: 4, of: 4).format(fractionDigits: 2))")
|
|
| 188 |
- .fontWeight(.semibold) |
|
| 189 |
- .padding(.top, geometry.size.height*Constants.chartUnderscan/2 ) |
|
| 190 |
- Spacer() |
|
| 191 |
- ForEach (1..<yLabels-1, id: \.self) { i in
|
|
| 192 |
- Group {
|
|
| 193 |
- Text("\(context.yAxisLabel(for: self.yLabels-i, of: self.yLabels).format(fractionDigits: 2))")
|
|
| 194 |
- .fontWeight(.semibold) |
|
| 195 |
- Spacer() |
|
| 196 |
- } |
|
| 333 |
+ fileprivate func yAxisLabelsView( |
|
| 334 |
+ height: CGFloat, |
|
| 335 |
+ context: ChartContext, |
|
| 336 |
+ measurementUnit: String, |
|
| 337 |
+ tint: Color |
|
| 338 |
+ ) -> some View {
|
|
| 339 |
+ GeometryReader { geometry in
|
|
| 340 |
+ ZStack(alignment: .top) {
|
|
| 341 |
+ ForEach(0..<yLabels, id: \.self) { row in
|
|
| 342 |
+ let labelIndex = yLabels - row |
|
| 343 |
+ |
|
| 344 |
+ Text("\(context.yAxisLabel(for: labelIndex, of: yLabels).format(fractionDigits: 1))")
|
|
| 345 |
+ .font(.caption2.weight(.semibold)) |
|
| 346 |
+ .monospacedDigit() |
|
| 347 |
+ .lineLimit(1) |
|
| 348 |
+ .minimumScaleFactor(0.72) |
|
| 349 |
+ .frame(width: max(geometry.size.width - 6, 0)) |
|
| 350 |
+ .position( |
|
| 351 |
+ x: geometry.size.width / 2, |
|
| 352 |
+ y: yGuidePosition( |
|
| 353 |
+ for: labelIndex, |
|
| 354 |
+ context: context, |
|
| 355 |
+ height: geometry.size.height |
|
| 356 |
+ ) |
|
| 357 |
+ ) |
|
| 197 | 358 |
} |
| 198 |
- Text("\(context.yAxisLabel(for: 1, of: yLabels).format(fractionDigits: 2))")
|
|
| 199 |
- .fontWeight(.semibold) |
|
| 200 |
- .padding(.bottom, geometry.size.height*Constants.chartUnderscan/2 ) |
|
| 201 |
- } |
|
| 202 |
- VStack {
|
|
| 359 |
+ |
|
| 203 | 360 |
Text(measurementUnit) |
| 204 |
- .fontWeight(.bold) |
|
| 205 |
- .padding(.top, 5) |
|
| 206 |
- Spacer() |
|
| 361 |
+ .font(.caption2.weight(.bold)) |
|
| 362 |
+ .foregroundColor(tint) |
|
| 363 |
+ .padding(.horizontal, 6) |
|
| 364 |
+ .padding(.vertical, 4) |
|
| 365 |
+ .background( |
|
| 366 |
+ Capsule(style: .continuous) |
|
| 367 |
+ .fill(tint.opacity(0.14)) |
|
| 368 |
+ ) |
|
| 369 |
+ .padding(.top, 6) |
|
| 207 | 370 |
} |
| 208 | 371 |
} |
| 372 |
+ .frame(height: height) |
|
| 373 |
+ .background( |
|
| 374 |
+ RoundedRectangle(cornerRadius: 16, style: .continuous) |
|
| 375 |
+ .fill(tint.opacity(0.12)) |
|
| 376 |
+ ) |
|
| 377 |
+ .overlay( |
|
| 378 |
+ RoundedRectangle(cornerRadius: 16, style: .continuous) |
|
| 379 |
+ .stroke(tint.opacity(0.20), lineWidth: 1) |
|
| 380 |
+ ) |
|
| 209 | 381 |
} |
| 210 | 382 |
|
| 211 |
- fileprivate func horizontalGuides() -> some View {
|
|
| 383 |
+ fileprivate func horizontalGuides(context: ChartContext) -> some View {
|
|
| 212 | 384 |
GeometryReader { geometry in
|
| 213 | 385 |
Path { path in
|
| 214 |
- let pading = geometry.size.height*Constants.chartUnderscan |
|
| 215 |
- let height = geometry.size.height - pading |
|
| 216 |
- let border = pading/2 |
|
| 217 |
- for i: CGFloat in stride(from: 0, through: CGFloat(self.yLabels-1), by: 1) {
|
|
| 218 |
- path.addLine(from: CGPoint(x: 0, y: border + height*i/CGFloat(self.yLabels-1 )), to: CGPoint(x: geometry.size.width, y: border + height*i/CGFloat(self.yLabels-1))) |
|
| 386 |
+ for labelIndex in 1...self.yLabels {
|
|
| 387 |
+ let y = yGuidePosition( |
|
| 388 |
+ for: labelIndex, |
|
| 389 |
+ context: context, |
|
| 390 |
+ height: geometry.size.height |
|
| 391 |
+ ) |
|
| 392 |
+ path.addLine(from: CGPoint(x: 0, y: y), to: CGPoint(x: geometry.size.width, y: y)) |
|
| 219 | 393 |
} |
| 220 |
- }.stroke(lineWidth: 0.25) |
|
| 394 |
+ } |
|
| 395 |
+ .stroke(Color.secondary.opacity(0.38), lineWidth: 0.85) |
|
| 221 | 396 |
} |
| 222 | 397 |
} |
| 223 | 398 |
|
| 224 |
- fileprivate func verticalGuides() -> some View {
|
|
| 399 |
+ fileprivate func verticalGuides(context: ChartContext) -> some View {
|
|
| 225 | 400 |
GeometryReader { geometry in
|
| 226 | 401 |
Path { path in
|
| 227 | 402 |
|
| 228 |
- for i: CGFloat in stride(from: 1, through: CGFloat(self.xLabels-1), by: 1) {
|
|
| 229 |
- path.move(to: CGPoint(x: geometry.size.width*i/CGFloat(self.xLabels-1), y: 0) ) |
|
| 403 |
+ for labelIndex in 2..<self.xLabels {
|
|
| 404 |
+ let x = xGuidePosition( |
|
| 405 |
+ for: labelIndex, |
|
| 406 |
+ context: context, |
|
| 407 |
+ width: geometry.size.width |
|
| 408 |
+ ) |
|
| 409 |
+ path.move(to: CGPoint(x: x, y: 0) ) |
|
| 230 | 410 |
path.addLine(to: CGPoint(x: path.currentPoint!.x, y: geometry.size.height) ) |
| 231 | 411 |
} |
| 232 |
- }.stroke(lineWidth: 0.25) |
|
| 412 |
+ } |
|
| 413 |
+ .stroke(Color.secondary.opacity(0.34), style: StrokeStyle(lineWidth: 0.8, dash: [4, 4])) |
|
| 233 | 414 |
} |
| 234 | 415 |
} |
| 235 | 416 |
|