Skip to content

Commit

Permalink
Feat/turnips chart display values on touch (Dimillian#131)
Browse files Browse the repository at this point in the history
* feat(Turnips): display values while tapping on AM/PM buttons in chart

* feat(Turnips): add bold modifier to chart displayed values

* refactor(Turnips): reorganize the code to use Anchor Preference

* feat(Turnips): add a background under values to get them more visible

* feat(Turnips): display values only when an AM/PM has been touched

* feat(Turnips): display values on Chart when interacting with vertical lines
  • Loading branch information
renaudjenny authored May 13, 2020
1 parent 79322a3 commit b1d1aa4
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 24 deletions.
4 changes: 4 additions & 0 deletions ACHNBrowserUI/ACHNBrowserUI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
4C7F2F782461F10300930928 /* TurnipsChartVerticalLegend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7F2F772461F10300930928 /* TurnipsChartVerticalLegend.swift */; };
4C7FE78324574FB50011E8AB /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FE78224574FB50011E8AB /* ActivityIndicator.swift */; };
4C7FE789245B57A10011E8AB /* AdaptsToSoftwareKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FE788245B57A10011E8AB /* AdaptsToSoftwareKeyboard.swift */; };
4CF14B75246B3E9E00F740BF /* TurnipsChartValuesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF14B74246B3E9E00F740BF /* TurnipsChartValuesView.swift */; };
4CF8049B24635E54005C6C63 /* Path.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF8049A24635E54005C6C63 /* Path.swift */; };
68345579244C994C0072FC0C /* LikeButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68345578244C994C0072FC0C /* LikeButtonView.swift */; };
68345589244E4DEF0072FC0C /* ACNHEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 68345588244E4DEF0072FC0C /* ACNHEvents */; };
Expand Down Expand Up @@ -358,6 +359,7 @@
4C7F2F772461F10300930928 /* TurnipsChartVerticalLegend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnipsChartVerticalLegend.swift; sourceTree = "<group>"; };
4C7FE78224574FB50011E8AB /* ActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = "<group>"; };
4C7FE788245B57A10011E8AB /* AdaptsToSoftwareKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdaptsToSoftwareKeyboard.swift; sourceTree = "<group>"; };
4CF14B74246B3E9E00F740BF /* TurnipsChartValuesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnipsChartValuesView.swift; sourceTree = "<group>"; };
4CF8049A24635E54005C6C63 /* Path.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Path.swift; sourceTree = "<group>"; };
68345578244C994C0072FC0C /* LikeButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeButtonView.swift; sourceTree = "<group>"; };
687B5767244A030700C1E86F /* TurnipsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TurnipsView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -629,6 +631,7 @@
4C2D6777245F4FE8005831C4 /* TurnipsChartMinBuyPriceCurve.swift */,
4C2D6775245F4F55005831C4 /* TurnipsChartMinMaxCurves.swift */,
69EC82ED2461E79D00CE9064 /* TurnipsChartTopLegendView.swift */,
4CF14B74246B3E9E00F740BF /* TurnipsChartValuesView.swift */,
4C7F2F772461F10300930928 /* TurnipsChartVerticalLegend.swift */,
4C2D676A245DA41E005831C4 /* TurnipsChartView.swift */,
);
Expand Down Expand Up @@ -1317,6 +1320,7 @@
693E72A7245EBB1200CD26F4 /* SafariView.swift in Sources */,
4C382EE8244E418800F446BA /* DismissingKeyboardOnSwipe.swift in Sources */,
69B13CA6245598E80089BD7B /* ItemDetailSeasonSectionView.swift in Sources */,
4CF14B75246B3E9E00F740BF /* TurnipsChartValuesView.swift in Sources */,
697E63F92449DADE008FB710 /* VillagerRowView.swift in Sources */,
4C7FE789245B57A10011E8AB /* AdaptsToSoftwareKeyboard.swift in Sources */,
6903CD11246C7EB800150FA1 /* CollectionProgressRow.swift in Sources */,
Expand Down
13 changes: 13 additions & 0 deletions ACHNBrowserUI/ACHNBrowserUI/extensions/View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ extension View {
func onWidthPreferenceChange<K: PreferenceKey>(_ key: K.Type = K.self, storeValueIn storage: Binding<CGFloat?>) -> some View where K.Value == CGFloat? {
onPreferenceChange(key, perform: { storage.wrappedValue = $0 })
}

func propagateSize<K: PreferenceKey>(_ key: K.Type = K.self, storeValueIn storage: Binding<CGSize?>) -> some View where K.Value == CGSize? {
overlay(
GeometryReader { proxy in
Color.clear
.anchorPreference(key: key, value: .bounds, transform: { proxy[$0].size })
}
)
}

func onSizePreferenceChange<K: PreferenceKey>(_ key: K.Type = K.self, storeValueIn storage: Binding<CGSize?>) -> some View where K.Value == CGSize? {
onPreferenceChange(key, perform: { storage.wrappedValue = $0 })
}
}

extension UIView {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct TurnipsChartBottomLegendView: View {
}

let predictions: TurnipPredictions
let positionPress: (Int) -> Void
private let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]

@State private var textsHeight: CGFloat?
Expand All @@ -27,7 +28,6 @@ struct TurnipsChartBottomLegendView: View {
}

func computeTexts(for geometry: GeometryProxy) -> some View {

let rect = geometry.frame(in: .local)
let (_, _, _, ratioX) = predictions.minMax?.roundedMinMaxAndRatios(rect: rect) ?? (0, 0, 0, 0)
let count = predictions.minMax?.count ?? 0
Expand All @@ -48,17 +48,18 @@ struct TurnipsChartBottomLegendView: View {
}
}
}

}

func meridiem(offset: Int, ratioX: CGFloat) -> some View {
Text(offset.isAM ? "AM" : "PM")
.font(.footnote)
.fontWeight(.semibold)
.foregroundColor(.acText)
.alignmentGuide(.leading, computeValue: { d in
-CGFloat(offset) * ratioX
})
Button(action: { self.positionPress(offset) }) {
Text(offset.isAM ? "AM" : "PM")
.font(.footnote)
.fontWeight(.semibold)
.foregroundColor(.acText)
.alignmentGuide(.leading, computeValue: { d in
-CGFloat(offset) * ratioX
})
}
}

func weekdays(offset: Int, weekday: String, ratioX: CGFloat) -> some View {
Expand All @@ -78,6 +79,9 @@ private extension Int {

struct TurnipsChartBottomLegendView_Previews: PreviewProvider {
static var previews: some View {
TurnipsChartBottomLegendView(predictions: TurnipsChartView_Previews.predictions)
TurnipsChartBottomLegendView(
predictions: TurnipsChartView_Previews.predictions,
positionPress: { _ in }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,28 @@ struct TurnipsChartGrid: Shape {
extension TurnipsChartGrid {
enum Display { case vertical, horizontal }
}

struct TurnipsChartGridInteractiveVerticalLines: View {
let predictions: TurnipPredictions
let positionPress: (Int) -> Void
let touchWidth: CGFloat = 20

var body: some View {
GeometryReader(content: lines)
}

func lines(geometry: GeometryProxy) -> some View {
let (_, _, _, ratioX) = predictions.minMax?.roundedMinMaxAndRatios(rect: geometry.frame(in: .local)) ?? (0, 0, 0, 0)
let count = predictions.minMax?.count ?? 0

return ZStack(alignment: .leading) {
ForEach(0..<count) { offset in
Button(action: { self.positionPress(offset) }) {
Color.clear
.frame(width: self.touchWidth)
.alignmentGuide(.leading, computeValue: { d in -CGFloat(offset) * ratioX })
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// TurnipsChartValuesView.swift
// ACHNBrowserUI
//
// Created by Renaud JENNY on 12/05/2020.
// Copyright © 2020 Thomas Ricouard. All rights reserved.
//

import SwiftUI
import Backend

struct TurnipsChartValuesView: View {
let predictions: TurnipPredictions
let position: Int
let padding: CGFloat = 16

var body: some View {
GeometryReader(content: values)
}

func values(geometry: GeometryProxy) -> some View {
ZStack {
Text("\(min)")
.bold()
.foregroundColor(.graphMinMax)
.modifier(ValueModifier(backgroundColor: Color.acBackground))
.position(positions(geometry: geometry).min)
Text("\(average)")
.bold()
.foregroundColor(.graphAverage)
.saturation(5)
.modifier(ValueModifier(backgroundColor: Color.acHeaderBackground))
.position(positions(geometry: geometry).average)
Text("\(max)")
.bold()
.foregroundColor(.graphMinMax)
.modifier(ValueModifier(backgroundColor: Color.acBackground))
.position(positions(geometry: geometry).max)
}
}

var min: Int {
predictions.minMax?.compactMap { $0.first }[position] ?? 0
}

var max: Int {
predictions.minMax?.compactMap { $0.second }[position] ?? 0
}

var average: Int { predictions.averagePrices?[position] ?? 0 }

typealias Positions = (min: CGPoint, max: CGPoint, average: CGPoint)
func positions(geometry: GeometryProxy) -> Positions {
let (_, maxY, ratioY, ratioX) = predictions.minMax?.roundedMinMaxAndRatios(rect: geometry.frame(in: .local)) ?? (0, 0, 0, 0)
let x = CGFloat(position) * ratioX
return (
min: CGPoint(x: x, y: ratioY * (maxY - CGFloat(min)) + padding),
max: CGPoint(x: x, y: ratioY * (maxY - CGFloat(max)) - padding),
average: CGPoint(x: x, y: ratioY * (maxY - CGFloat(average)) - padding)
)
}
}

extension TurnipsChartValuesView {
struct ValueModifier: ViewModifier {
let backgroundColor: Color

func body(content: Content) -> some View {
content
.padding(.horizontal, 8)
.background(Capsule().fill(backgroundColor.opacity(0.8)))
}
}
}

struct TurnipsChartValuesView_Previews: PreviewProvider {
static var previews: some View {
Preview()
}

private struct Preview: View {
@State private var position = 5.0
var body: some View {
VStack {
TurnipsChartValuesView(
predictions: TurnipsChartView_Previews.predictions,
position: Int(position)
)
Slider(value: $position, in: 0...11)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import Backend


struct TurnipsChartView: View {
private struct ChartHeightPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat?
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
private struct ChartSizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize?
static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
if let newValue = nextValue() { value = newValue }
}
}
Expand All @@ -23,43 +23,44 @@ struct TurnipsChartView: View {
var predictions: TurnipPredictions
@Binding var animateCurves: Bool
@Environment(\.presentationMode) var presentation

@State private var chartHeight: CGFloat?
@State private var chartSize: CGSize?
@State private var verticalLegendWidth: CGFloat?
@State private var bottomLegendHeight: CGFloat?

@State private var positionPressed: Int?

var body: some View {
VStack {
TurnipsChartTopLegendView()
HStack(alignment: .top) {
TurnipsChartVerticalLegend(predictions: predictions)
.frame(width: verticalLegendWidth, height: chartHeight)
.frame(width: verticalLegendWidth, height: chartSize?.height)
.padding(.top)
ScrollView(.horizontal, showsIndicators: false) {
chart.frame(width: 600, height: 500)
}
}
}
.onHeightPreferenceChange(ChartHeightPreferenceKey.self, storeValueIn: $chartHeight)
.onSizePreferenceChange(ChartSizePreferenceKey.self, storeValueIn: $chartSize)
.onHeightPreferenceChange(TurnipsChartBottomLegendView.HeightPreferenceKey.self, storeValueIn: $bottomLegendHeight)
.onWidthPreferenceChange(TurnipsChartVerticalLegend.WidthPreferenceKey.self, storeValueIn: $verticalLegendWidth)
}

private var chart: some View {
VStack(spacing: 10) {
curves
.propagateHeight(ChartHeightPreferenceKey.self)
TurnipsChartBottomLegendView(predictions: predictions)
TurnipsChartBottomLegendView(predictions: predictions, positionPress: positionPress)
.frame(height: bottomLegendHeight)
}
.padding()
}

private var curves: some View {
ZStack(alignment: .leading) {
TurnipsChartGridInteractiveVerticalLines(predictions: predictions, positionPress: positionPress)
TurnipsChartGrid(predictions: predictions)
.stroke()
.opacity(0.5)
.propagateSize(ChartSizePreferenceKey.self, storeValueIn: $chartSize)
TurnipsChartMinBuyPriceCurve(predictions: predictions)
.stroke(style: StrokeStyle(dash: [Self.verticalLinesCount]))
.foregroundColor(PredictionCurve.minBuyPrice.color)
Expand All @@ -74,8 +75,15 @@ struct TurnipsChartView: View {
.foregroundColor(PredictionCurve.average.color)
.saturation(5)
.blendMode(.screen)
positionPressed.map {
TurnipsChartValuesView(predictions: predictions, position: $0)
}
}.animation(.spring())
}

private func positionPress(_ position: Int) {
positionPressed = position
}
}

struct TurnipsChartView_Previews: PreviewProvider {
Expand All @@ -85,17 +93,17 @@ struct TurnipsChartView_Previews: PreviewProvider {
animateCurves: .constant(true)
)
}

static let predictions = TurnipPredictions(
minBuyPrice: 83,
averagePrices: averagePrices,
minMax: minMax,
averageProfits: averageProfits
)

static let averagePrices = [89, 85, 88, 104, 110, 111, 111, 111, 106, 98, 82, 77]

static let minMax = [[38, 142], [33, 142], [29, 202], [24, 602], [19, 602], [14, 602], [9, 602], [29, 602], [24, 602], [19, 602], [14, 202], [9, 201]]

static let averageProfits = [89, 85, 88, 104, 110, 111, 111, 111, 106, 98, 82, 77]
}

0 comments on commit b1d1aa4

Please sign in to comment.