mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
Channel statistics improvements
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// ChartVisibilityItem.swift
|
||||
// GraphCore
|
||||
//
|
||||
// Created by Mikhail Filimonov on 26.02.2020.
|
||||
// Copyright © 2020 Telegram. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
struct ChartVisibilityItem {
|
||||
var title: String
|
||||
var color: GColor
|
||||
|
||||
static func generateItemsFrames(for chartWidth: CGFloat, items: [ChartVisibilityItem]) -> [CGRect] {
|
||||
var previousPoint = CGPoint(x: ChatVisibilityItemConstants.insets.left, y: ChatVisibilityItemConstants.insets.top)
|
||||
var frames: [CGRect] = []
|
||||
|
||||
for item in items {
|
||||
let labelSize = textSize(with: item.title, font: ChatVisibilityItemConstants.textFont)
|
||||
let width = (labelSize.width + ChatVisibilityItemConstants.labelTextApproxInsets).rounded(.up)
|
||||
if previousPoint.x + width < (chartWidth - ChatVisibilityItemConstants.insets.left - ChatVisibilityItemConstants.insets.right) {
|
||||
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight)))
|
||||
} else if previousPoint.x <= ChatVisibilityItemConstants.insets.left {
|
||||
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight)))
|
||||
} else {
|
||||
previousPoint.y += ChatVisibilityItemConstants.itemHeight + ChatVisibilityItemConstants.itemSpacing
|
||||
previousPoint.x = ChatVisibilityItemConstants.insets.left
|
||||
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: ChatVisibilityItemConstants.itemHeight)))
|
||||
}
|
||||
previousPoint.x += width + ChatVisibilityItemConstants.itemSpacing
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
}
|
||||
enum ChatVisibilityItemConstants {
|
||||
static let itemHeight: CGFloat = 30
|
||||
static let itemSpacing: CGFloat = 8
|
||||
static let labelTextApproxInsets: CGFloat = 40
|
||||
static let insets = NSEdgeInsets(top: 0, left: 16, bottom: 16, right: 16)
|
||||
static let textFont = NSFont.systemFont(ofSize: 14, weight: .medium)
|
||||
}
|
||||
|
||||
public struct ChartDetailsViewModel {
|
||||
public struct Value {
|
||||
public let prefix: String?
|
||||
public let title: String
|
||||
public let value: String
|
||||
public let color: GColor
|
||||
public let visible: Bool
|
||||
public init(prefix: String?,
|
||||
title: String,
|
||||
value: String,
|
||||
color: GColor,
|
||||
visible: Bool) {
|
||||
self.prefix = prefix
|
||||
self.title = title
|
||||
self.value = value
|
||||
self.color = color
|
||||
self.visible = visible
|
||||
}
|
||||
}
|
||||
|
||||
public internal(set) var title: String
|
||||
public internal(set) var showArrow: Bool
|
||||
public internal(set) var showPrefixes: Bool
|
||||
public internal(set) var values: [Value]
|
||||
public internal(set) var totalValue: Value?
|
||||
public internal(set) var tapAction: (() -> Void)?
|
||||
|
||||
static let blank = ChartDetailsViewModel(title: "", showArrow: false, showPrefixes: false, values: [], totalValue: nil, tapAction: nil)
|
||||
public init(title: String,
|
||||
showArrow: Bool,
|
||||
showPrefixes: Bool,
|
||||
values: [Value],
|
||||
totalValue: Value?,
|
||||
tapAction: (() -> Void)?) {
|
||||
self.title = title
|
||||
self.showArrow = showArrow
|
||||
self.showPrefixes = showPrefixes
|
||||
self.values = values
|
||||
self.totalValue = totalValue
|
||||
self.tapAction = tapAction
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// ChardData.swift
|
||||
// GraphTest
|
||||
//
|
||||
// Created by Andrei Salavei on 3/11/19.
|
||||
// Copyright © 2019 Andrei Salavei. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public struct ChartsCollection {
|
||||
public struct Chart {
|
||||
public internal(set) var color: GColor
|
||||
public internal(set) var name: String
|
||||
public internal(set) var values: [Double]
|
||||
}
|
||||
|
||||
public internal(set) var axisValues: [Date]
|
||||
public internal(set) var chartValues: [Chart]
|
||||
|
||||
static let blank = ChartsCollection(axisValues: [], chartValues: [])
|
||||
var isBlank: Bool {
|
||||
return axisValues.isEmpty || chartValues.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
public extension ChartsCollection {
|
||||
public init(from decodedData: [String: Any]) throws {
|
||||
guard let columns = decodedData["columns"] as? [[Any]] else {
|
||||
throw ChartsError.generalConversion("Unable to get columns from: \(decodedData)")
|
||||
}
|
||||
guard let types = decodedData["types"] as? [String: String] else {
|
||||
throw ChartsError.generalConversion("Unable to get types from: \(decodedData)")
|
||||
}
|
||||
guard let names = decodedData["names"] as? [String: String] else {
|
||||
throw ChartsError.generalConversion("Unable to get names from: \(decodedData)")
|
||||
}
|
||||
guard let colors = decodedData["colors"] as? [String: String] else {
|
||||
throw ChartsError.generalConversion("Unable to get colors from: \(decodedData)")
|
||||
}
|
||||
|
||||
// chart.colors – Color for each variable in 6-hex-digit format (e.g. "#AAAAAA").
|
||||
// chart.names – Name for each variable.
|
||||
// chart.percentage – true for percentage based values.
|
||||
// chart.stacked – true for values stacking on top of each other.
|
||||
// chart.y_scaled – true for charts with 2 Y axes.
|
||||
|
||||
var axixValuesToSetup: [Date] = []
|
||||
var chartToSetup: [Chart] = []
|
||||
for column in columns {
|
||||
guard let columnId = column.first as? String else {
|
||||
throw ChartsError.generalConversion("Unable to get column name from: \(column)")
|
||||
}
|
||||
guard let typeString = types[columnId], let type = ColumnType(rawValue: typeString) else {
|
||||
throw ChartsError.generalConversion("Unable to get column type from: \(types) - \(columnId)")
|
||||
}
|
||||
switch type {
|
||||
case .axix:
|
||||
axixValuesToSetup = try column.dropFirst().map { Date(timeIntervalSince1970: try Convert.doubleFrom($0) / 1000) }
|
||||
case .chart, .bar, .area, .step:
|
||||
guard let colorString = colors[columnId],
|
||||
let color = GColor(hexString: colorString) else {
|
||||
throw ChartsError.generalConversion("Unable to get color name from: \(colors) - \(columnId)")
|
||||
}
|
||||
guard let name = names[columnId] else {
|
||||
throw ChartsError.generalConversion("Unable to get column name from: \(names) - \(columnId)")
|
||||
}
|
||||
let values = try column.dropFirst().map { try Convert.doubleFrom($0) }
|
||||
chartToSetup.append(Chart(color: color,
|
||||
name: name,
|
||||
values: values))
|
||||
}
|
||||
}
|
||||
|
||||
guard axixValuesToSetup.isEmpty == false,
|
||||
chartToSetup.isEmpty == false,
|
||||
chartToSetup.firstIndex(where: { $0.values.count != axixValuesToSetup.count }) == nil else {
|
||||
throw ChartsError.generalConversion("Saniazing: Invalid number of items: \(axixValuesToSetup), \(chartToSetup)")
|
||||
}
|
||||
self.axisValues = axixValuesToSetup
|
||||
self.chartValues = chartToSetup
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private enum ColumnType: String {
|
||||
case axix = "x"
|
||||
case chart = "line"
|
||||
case area = "area"
|
||||
case bar = "bar"
|
||||
case step = "step"
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
//
|
||||
// ChartsDataManager.swift
|
||||
// GraphTest
|
||||
//
|
||||
// Created by Andrei Salavei on 3/11/19.
|
||||
// Copyright © 2019 Andrei Salavei. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public class ChartsDataManager {
|
||||
public static func readChart(item: [String: Any], extraCopiesCount: Int = 0, sync: Bool, success: @escaping (ChartsCollection) -> Void, failure: @escaping (Error) -> Void) {
|
||||
let workItem: (() -> Void) = {
|
||||
do {
|
||||
var collection = try ChartsCollection(from: item)
|
||||
for _ in 0..<extraCopiesCount {
|
||||
for valueIndex in collection.chartValues.indices {
|
||||
collection.chartValues[valueIndex] .values += collection.chartValues[valueIndex].values
|
||||
}
|
||||
guard let firstValue = collection.axisValues.first,
|
||||
let lastValule = collection.axisValues.last else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
let startItem = lastValule.addingTimeInterval(.day)
|
||||
for valueIndex in collection.axisValues.indices {
|
||||
let intervalToAdd = collection.axisValues[valueIndex].timeIntervalSince(firstValue)
|
||||
let newDate = startItem.addingTimeInterval(intervalToAdd)
|
||||
collection.axisValues.append(newDate)
|
||||
}
|
||||
}
|
||||
if sync {
|
||||
success(collection)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
success(collection)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
assertionFailure("Error occure: \(error)")
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sync {
|
||||
workItem()
|
||||
} else {
|
||||
DispatchQueue.global().async(execute: workItem)
|
||||
}
|
||||
}
|
||||
|
||||
public static func readChart(data: Data, extraCopiesCount: Int = 0, sync: Bool, success: @escaping (ChartsCollection) -> Void, failure: @escaping (Error) -> Void) {
|
||||
let workItem: (() -> Void) = {
|
||||
do {
|
||||
let decoded = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let item = decoded as? [String: Any] else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
var collection = try ChartsCollection(from: item)
|
||||
for _ in 0..<extraCopiesCount {
|
||||
for valueIndex in collection.chartValues.indices {
|
||||
collection.chartValues[valueIndex] .values += collection.chartValues[valueIndex].values
|
||||
}
|
||||
guard let firstValue = collection.axisValues.first,
|
||||
let lastValule = collection.axisValues.last else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
let startItem = lastValule.addingTimeInterval(.day)
|
||||
for valueIndex in collection.axisValues.indices {
|
||||
let intervalToAdd = collection.axisValues[valueIndex].timeIntervalSince(firstValue)
|
||||
let newDate = startItem.addingTimeInterval(intervalToAdd)
|
||||
collection.axisValues.append(newDate)
|
||||
}
|
||||
}
|
||||
if sync {
|
||||
success(collection)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
success(collection)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
assertionFailure("Error occure: \(error)")
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sync {
|
||||
workItem()
|
||||
} else {
|
||||
DispatchQueue.global().async(execute: workItem)
|
||||
}
|
||||
}
|
||||
|
||||
public static func readChart(file: URL, extraCopiesCount: Int = 0, sync: Bool, success: @escaping (ChartsCollection) -> Void, failure: @escaping (Error) -> Void) {
|
||||
let workItem: (() -> Void) = {
|
||||
do {
|
||||
let data = try Data(contentsOf: file)
|
||||
let decoded = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let item = decoded as? [String: Any] else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
var collection = try ChartsCollection(from: item)
|
||||
for _ in 0..<extraCopiesCount {
|
||||
for valueIndex in collection.chartValues.indices {
|
||||
collection.chartValues[valueIndex] .values += collection.chartValues[valueIndex].values
|
||||
}
|
||||
guard let firstValue = collection.axisValues.first,
|
||||
let lastValule = collection.axisValues.last else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
let startItem = lastValule.addingTimeInterval(.day)
|
||||
for valueIndex in collection.axisValues.indices {
|
||||
let intervalToAdd = collection.axisValues[valueIndex].timeIntervalSince(firstValue)
|
||||
let newDate = startItem.addingTimeInterval(intervalToAdd)
|
||||
collection.axisValues.append(newDate)
|
||||
}
|
||||
}
|
||||
if sync {
|
||||
success(collection)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
success(collection)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
assertionFailure("Error occure: \(error)")
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if sync {
|
||||
workItem()
|
||||
} else {
|
||||
DispatchQueue.global().async(execute: workItem)
|
||||
}
|
||||
}
|
||||
|
||||
public static func readCharts(file: URL, extraCopiesCount: Int = 0, sync: Bool, success: @escaping ([ChartsCollection]) -> Void, failure: @escaping (Error) -> Void) {
|
||||
let workItem: (() -> Void) = {
|
||||
do {
|
||||
let data = try Data(contentsOf: file)
|
||||
let decoded = try JSONSerialization.jsonObject(with: data, options: [])
|
||||
guard let items = decoded as? [[String: Any]] else {
|
||||
throw ChartsError.invalidJson
|
||||
}
|
||||
var collections = try items.map { try ChartsCollection(from: $0) }
|
||||
for _ in 0..<extraCopiesCount {
|
||||
for collrctionIndex in collections.indices {
|
||||
for valueIndex in collections[collrctionIndex].chartValues.indices {
|
||||
collections[collrctionIndex].chartValues[valueIndex] .values += collections[collrctionIndex].chartValues[valueIndex].values
|
||||
}
|
||||
guard let firstValue = collections[collrctionIndex].axisValues.first,
|
||||
let lastValule = collections[collrctionIndex].axisValues.last else {
|
||||
return
|
||||
}
|
||||
let startItem = lastValule.addingTimeInterval(.day)
|
||||
for valueIndex in collections[collrctionIndex].axisValues.indices {
|
||||
let intervalToAdd = collections[collrctionIndex].axisValues[valueIndex].timeIntervalSince(firstValue)
|
||||
let newDate = startItem.addingTimeInterval(intervalToAdd)
|
||||
collections[collrctionIndex].axisValues.append(newDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
if sync {
|
||||
success(collections)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
success(collections)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
assertionFailure("Error occure: \(error)")
|
||||
failure(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
if sync {
|
||||
workItem()
|
||||
} else {
|
||||
DispatchQueue.global().async(execute: workItem)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
19
submodules/GraphCore/Sources/Charts Reader/ChartsError.swift
Normal file
19
submodules/GraphCore/Sources/Charts Reader/ChartsError.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// ChartsError.swift
|
||||
// GraphTest
|
||||
//
|
||||
// Created by Andrei Salavei on 3/11/19.
|
||||
// Copyright © 2019 Andrei Salavei. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
enum ChartsError: Error {
|
||||
case invalidJson
|
||||
case generalConversion(String)
|
||||
}
|
||||
47
submodules/GraphCore/Sources/Charts Reader/Convert.swift
Normal file
47
submodules/GraphCore/Sources/Charts Reader/Convert.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Convert.swift
|
||||
// GraphTest
|
||||
//
|
||||
// Created by Andrei Salavei on 3/11/19.
|
||||
// Copyright © 2019 Andrei Salavei. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import Cocoa
|
||||
#else
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public enum Convert {
|
||||
public static func doubleFrom(_ value: Any?) throws -> Double {
|
||||
guard let double = try doubleFrom(value, lenientCast: false) else {
|
||||
throw ChartsError.generalConversion("Unable to cast \(String(describing: value)) to \(Double.self)")
|
||||
}
|
||||
return double
|
||||
}
|
||||
|
||||
public static func doubleFrom(_ value: Any?, lenientCast: Bool = false) throws -> Double? {
|
||||
guard let value = value else {
|
||||
return nil
|
||||
}
|
||||
if let intValue = value as? Int {
|
||||
return Double(intValue)
|
||||
} else if let floatValue = value as? Float {
|
||||
return Double(floatValue)
|
||||
} else if let int64Value = value as? Int64 {
|
||||
return Double(int64Value)
|
||||
} else if let intValue = value as? Int {
|
||||
return Double(intValue)
|
||||
} else if let stringValue = value as? String {
|
||||
if let doubleValue = Double(stringValue) {
|
||||
return doubleValue
|
||||
}
|
||||
}
|
||||
if lenientCast {
|
||||
return nil
|
||||
} else {
|
||||
throw ChartsError.generalConversion("Unable to cast \(String(describing: value)) to \(Double.self)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user