Skip to content

Commit

Permalink
Add chart to Ads activity performance
Browse files Browse the repository at this point in the history
  • Loading branch information
banghuazhao committed Nov 9, 2024
1 parent 096371f commit 1572b26
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ extension AdMobReportEntity {

// Function to handle comparison between two periods
private func calculateComparisonMetrics(lastPeriod: [DailyAdPerformance], previousPeriod: [DailyAdPerformance]) -> [AdMetricData] {
let shouldCreateChart = lastPeriod.count > 1 || previousPeriod.count > 1

let lastTotalRequests = lastPeriod.reduce(0) { $0 + $1.adRequests }
let lastTotalImpressions = lastPeriod.reduce(0) { $0 + $1.impressions }
let lastTotalEarnings = lastPeriod.reduce(Decimal(0)) { $0 + $1.estimatedEarnings }
Expand All @@ -239,59 +241,107 @@ extension AdMobReportEntity {
// Calculate percentage and value changes
let requestsValueChange = lastTotalRequests - previousTotalRequests
let requestsPercentageChange = percentageChange(current: lastTotalRequests, previous: previousTotalRequests)
let previousRequestData = previousPeriod.map { (date: $0.date, value: Decimal($0.adRequests)) }
let currentRequestData = lastPeriod.map { (date: $0.date, value: Decimal($0.adRequests)) }

let impressionsValueChange = lastTotalImpressions - previousTotalImpressions
let impressionsPercentageChange = percentageChange(current: lastTotalImpressions, previous: previousTotalImpressions)
let previousImpressionsData = previousPeriod.map { (date: $0.date, value: Decimal($0.impressions)) }
let currentImpressionsData = lastPeriod.map { (date: $0.date, value: Decimal($0.impressions)) }

let earningsValueChange = lastTotalEarnings - previousTotalEarnings
let earningsPercentageChange = percentageChange(current: lastTotalEarnings, previous: previousTotalEarnings)
let previousEarningsData = previousPeriod.map { (date: $0.date, value: $0.estimatedEarnings) }
let currentEarningsData = lastPeriod.map { (date: $0.date, value: $0.estimatedEarnings) }

let matchRateValueChange = lastTotalMatchRate - previousTotalMatchRate
let matchRatePercentageChange = percentageChange(current: lastTotalMatchRate, previous: previousTotalMatchRate)
let previousMatchRateData = previousPeriod.map { (date: $0.date, value: $0.matchRate) }
let currentMatchRateData = lastPeriod.map { (date: $0.date, value: $0.matchRate) }

let eCPMValueChange = lastTotalECPM - previousTotalECPM
let eCPMPercentageChange = percentageChange(current: lastTotalECPM, previous: previousTotalECPM)
let previousECPMData = previousPeriod.map { (date: $0.date, value: $0.eCPM) }
let currentECPMData = lastPeriod.map { (date: $0.date, value: $0.eCPM) }

let clicksValueChange = lastTotalClicks - previousTotalClicks
let clicksPercentageChange = percentageChange(current: lastTotalClicks, previous: previousTotalClicks)
let previousClicksData = previousPeriod.map { (date: $0.date, value: Decimal($0.clicks)) }
let currentClicksData = lastPeriod.map { (date: $0.date, value: Decimal($0.clicks)) }

// Format value changes and percentage changes together
return [
AdMetricData(
title: "Estimated earnings",
value: formatter.string(from: lastTotalEarnings as NSDecimalNumber) ?? "$0.00",
change: "\(formatter.string(from: earningsValueChange as NSDecimalNumber) ?? "$0.00") (\(formatChange(value: earningsPercentageChange)))",
isPositive: earningsPercentageChange >= 0
isPositive: earningsPercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousEarningsData),
LineChartData(dateCategory: .current, data: currentEarningsData),
]
: nil
),
AdMetricData(
title: "Requests",
value: lastTotalRequests.formatWithUnits,
change: "\(requestsValueChange.formatWithUnits) (\(formatChange(value: requestsPercentageChange)))",
isPositive: requestsPercentageChange >= 0
isPositive: requestsPercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousRequestData),
LineChartData(dateCategory: .current, data: currentRequestData),
]
: nil
),
AdMetricData(
title: "Impressions",
value: lastTotalImpressions.formatWithUnits,
change: "\(impressionsValueChange.formatWithUnits) (\(formatChange(value: impressionsPercentageChange)))",
isPositive: impressionsPercentageChange >= 0
isPositive: impressionsPercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousImpressionsData),
LineChartData(dateCategory: .current, data: currentImpressionsData),
]
: nil
),
AdMetricData(
title: "Match rate",
value: String(format: "%.2f%%", NSDecimalNumber(decimal: lastTotalMatchRate).doubleValue * 100),
change: "\(String(format: "%.2f%%", NSDecimalNumber(decimal: matchRateValueChange).doubleValue * 100)) (\(formatChange(value: matchRatePercentageChange)))",
isPositive: matchRatePercentageChange >= 0
isPositive: matchRatePercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousMatchRateData),
LineChartData(dateCategory: .current, data: currentMatchRateData),
]
: nil
),
AdMetricData(
title: "eCPM",
value: formatter.string(from: lastTotalECPM as NSDecimalNumber) ?? "$0.00",
change: "\(formatter.string(from: eCPMValueChange as NSDecimalNumber) ?? "$0.00") (\(formatChange(value: eCPMPercentageChange)))",
isPositive: eCPMPercentageChange >= 0
isPositive: eCPMPercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousECPMData),
LineChartData(dateCategory: .current, data: currentECPMData),
]
: nil
),
AdMetricData(
title: "Clicks",
value: lastTotalClicks.formatWithUnits,
change: "\(clicksValueChange.formatWithUnits) (\(formatChange(value: clicksPercentageChange)))",
isPositive: clicksPercentageChange >= 0
isPositive: clicksPercentageChange >= 0,
multiLineData: shouldCreateChart
? [
LineChartData(dateCategory: .previous, data: previousClicksData),
LineChartData(dateCategory: .current, data: currentClicksData),
]
: nil
),
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Copyright Apps Bay Limited. All rights reserved.
//

import Charts
import SwiftUI

struct AdsActivityPerformanceView: View {
Expand All @@ -11,17 +12,32 @@ struct AdsActivityPerformanceView: View {
let metrics: [AdMetricData] // Placeholder for your metric data

var columns: [GridItem] {
horizontalSizeClass == .compact
? [
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20)
]
: [
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
]
if horizontalSizeClass == .compact {
if metrics.first?.multiLineData != nil {
[
GridItem(.flexible(), spacing: 20),
]
} else {
[
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
]
}
} else {
if metrics.first?.multiLineData != nil {
[
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
]
} else {
[
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
GridItem(.flexible(), spacing: 20),
]
}
}
}

var body: some View {
Expand All @@ -30,9 +46,10 @@ struct AdsActivityPerformanceView: View {
.font(.headline)
.bold()

LazyVGrid(columns: columns, spacing: 20) {
LazyVGrid(columns: columns) {
ForEach(metrics) { metric in
MetricView(metric: metric)
.padding(2)
}
}
}
Expand All @@ -50,33 +67,124 @@ struct MetricView: View {
let metric: AdMetricData

var body: some View {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(metric.title)
.font(.caption)

Text(metric.value)
.font(.headline)
.bold()
Grid(alignment: .leading, horizontalSpacing: 0) {
GridRow {
HStack {
VStack(alignment: .leading, spacing: 5) {
Text(metric.title)
.font(.caption)

Text(metric.value)
.font(.headline)
.bold()

Text(metric.change)
.font(.caption)
.foregroundColor(metric.isPositive ? .green : .red)
.bold()
}
Spacer()
}
.frame(maxWidth: .infinity)

Text(metric.change)
.font(.caption)
.foregroundColor(metric.isPositive ? .green : .red)
.bold()
if let multiLineData = metric.multiLineData {
// Line Chart for the metric
Chart {
ForEach(multiLineData) { lineData in
ForEach(lineData.data.indices, id: \.self) { index in
LineMark(
x: .value("Date", index),
y: .value(metric.title, lineData.data[index].value)
)
.foregroundStyle(by: .value("Category", lineData.dateCategory.rawValue))
}
}
}
.chartXAxis(.hidden)
.chartYAxis(.hidden)
.chartLegend(.hidden)
.chartForegroundStyleScale([
DateCategory.previous.rawValue: .blue.opacity(0.2),
DateCategory.current.rawValue: .blue.opacity(1.0),
])
.chartYScale(domain: getMinForData ... getMaxForData)
.frame(maxWidth: .infinity)
.padding()
.frame(maxHeight: 80)
}
}
Spacer()
}
}

var getMinForData: Decimal {
guard let multiLineData = metric.multiLineData else { return 0 }
let min = multiLineData.map { $0.data }.flatMap { $0 }.map { $0.value }.min() ?? 0
return min
}

var getMaxForData: Decimal {
guard let multiLineData = metric.multiLineData else { return 0 }
let max = multiLineData.map { $0.data }.flatMap { $0 }.map { $0.value }.max() ?? 1
return max
}
}

extension Date {
var formattedShortDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .none
return formatter.string(from: self)
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
AdsActivityPerformanceView(metrics: [
AdMetricData(title: "Estimated earnings", value: "$140", change: "+$5.40 (+4.01%)", isPositive: true),
AdMetricData(title: "Requests", value: "220K", change: "+5.71K (+2.67%)", isPositive: true),
AdMetricData(title: "Impression", value: "115K", change: "+3.89K (+3.51%)", isPositive: true),
AdMetricData(title: "Match rate", value: "86.41%", change: "-1.33% (-1.51%)", isPositive: false),
AdMetricData(title: "eCPM", value: "$1.22", change: "+$0.01 (+0.48%)", isPositive: true),
AdMetricData(
title: "Estimated earnings",
value: "$140",
change: "+$5.40 (+4.01%)",
isPositive: true,
multiLineData: [
LineChartData(dateCategory: .previous, data: [
(date: Date().addingTimeInterval(-86400 * 4), value: 1.0),
(date: Date().addingTimeInterval(-86400 * 3), value: 2.0),
(date: Date().addingTimeInterval(-86400 * 2), value: 3.0),
(date: Date().addingTimeInterval(-86400 * 1), value: 2.5),
(date: Date(), value: 3.5),
]),
LineChartData(dateCategory: .current, data: [
(date: Date().addingTimeInterval(-86400 * 4), value: 2.0),
(date: Date().addingTimeInterval(-86400 * 3), value: 2.5),
(date: Date().addingTimeInterval(-86400 * 2), value: 3.0),
(date: Date().addingTimeInterval(-86400 * 1), value: 3.5),
// (date: Date(), value: 4.0),
]),
]
),
AdMetricData(
title: "Requests",
value: "220K",
change: "+5.71K (+2.67%)",
isPositive: true,
multiLineData: [
LineChartData(dateCategory: .previous, data: [
(date: Date().addingTimeInterval(-86400 * 4), value: 1.0),
(date: Date().addingTimeInterval(-86400 * 3), value: 2.0),
(date: Date().addingTimeInterval(-86400 * 2), value: 3.0),
(date: Date().addingTimeInterval(-86400 * 1), value: 2.5),
(date: Date(), value: 3.5),
]),
LineChartData(dateCategory: .current, data: [
(date: Date().addingTimeInterval(-86400 * 4), value: 2.0),
(date: Date().addingTimeInterval(-86400 * 3), value: 2.5),
(date: Date().addingTimeInterval(-86400 * 2), value: 3.0),
(date: Date().addingTimeInterval(-86400 * 1), value: 3.5),
(date: Date(), value: 4.0),
]),
]
),
])
}
}
25 changes: 11 additions & 14 deletions AdRevenueWatch/Presentation/Features/Report/TotalEarningsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,8 @@ struct TotalEarningsGridView: View {
// 1x4 layout for iPad (regular size)
GridRow {
todayEarnings
}
GridRow {
yesterdayEarnings
}
GridRow {
thisMonthEarnings
}
GridRow {
lastMonthEarnings
}
}
Expand All @@ -81,14 +75,17 @@ struct TotalEarningsColumnView: View {
var amount: String

var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(label)
.font(.footnote)
.foregroundColor(.white)
Text(amount)
.font(.headline)
.bold()
.foregroundColor(.white)
HStack {
VStack(alignment: .leading, spacing: 8) {
Text(label)
.font(.footnote)
.foregroundColor(.white)
Text(amount)
.font(.headline)
.bold()
.foregroundColor(.white)
}
Spacer()
}
.frame(maxWidth: .infinity)
}
Expand Down
15 changes: 14 additions & 1 deletion AdRevenueWatch/Presentation/Models/AdMetricData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Created by Banghua Zhao on 01/10/2024
// Copyright Apps Bay Limited. All rights reserved.
//


import Foundation

Expand All @@ -12,4 +11,18 @@ struct AdMetricData: Identifiable {
let value: String
let change: String
let isPositive: Bool

let multiLineData: [LineChartData]?
}

// Define a data model
struct LineChartData: Identifiable {
let id = UUID()
let dateCategory: DateCategory
let data: [(date: Date, value: Decimal)]
}

enum DateCategory: String {
case previous
case current
}

0 comments on commit 1572b26

Please sign in to comment.