Skip to content

Commit

Permalink
Add options to configure the chart in the query
Browse files Browse the repository at this point in the history
  • Loading branch information
winsmith committed Jan 16, 2025
1 parent 46546a0 commit 86037e2
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

/// Configuration for aggregations in a chart.
///
/// Maps to "seriesConfiguration" internally in the charting library.
///
/// Subset of https://echarts.apache.org/en/option.html#series-line
public struct ChartAggregationConfiguration: Codable, Equatable {
public var startAngle: Int?
public var endAngle: Int?
public var radius: [String]?
public var center: [String]?
public var stack: String?

public init(
startAngle: Int? = nil,
endAngle: Int? = nil,
radius: [String]? = nil,
center: [String]? = nil,
stack: String? = nil
) {
self.startAngle = startAngle
self.endAngle = endAngle
self.radius = radius
self.center = center
self.stack = stack
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Foundation

/// Display configuration for charts. Overrides various default display options.
///
/// Not hashable, because we don't want to include these values in the cache, as cached calculation results won't change based on these values.
public struct ChartConfiguration: Codable, Equatable {
/// The display mode for the chart.
public var displayMode: ChartDisplayMode?

/// Enable dark mode for this chart
public var darkMode: Bool?

/// Global chart settings
public var options: ChartConfigurationOptions?

/// Applied to every single aggregation and post-aggregation in the chart
public var aggregationConfiguration: ChartAggregationConfiguration?

public init(
displayMode: ChartDisplayMode? = nil,
darkMode: Bool? = nil,
options: ChartConfigurationOptions? = nil,
aggregationConfiguration: ChartAggregationConfiguration? = nil
) {
self.displayMode = displayMode
self.darkMode = darkMode
self.options = options
self.aggregationConfiguration = aggregationConfiguration
}
}

public enum ChartDisplayMode: String, Codable {
case raw
case barChart
case lineChart
case pieChart
case funnelChart
case experimentChart
case matrix
case sankey
case lineChartRace
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import Foundation

/// Options for configuring a chart in our charting library
///
/// Subset of echart's options https://echarts.apache.org/en/option.html
public struct ChartConfigurationOptions: Codable, Equatable {
/// Whether to enable animation.
public var animation: Bool?

/// Duration of the animation in milliseconds.
public var animationDuration: Int?

/// Easing function of the animation.
public var animationEasing: EasingFunction?

/// Show a tooltip for this chart
public var tooltip: ToolTipConfiguration?

public var grid: GridConfiguration?

public var xAxis: AxisOptions?
public var yAxis: AxisOptions?

public init(
animation: Bool? = nil,
animationDuration: Int? = nil,
animationEasing: EasingFunction? = nil,
tooltip: ToolTipConfiguration? = nil,
grid: GridConfiguration? = nil,
xAxis: AxisOptions? = nil,
yAxis: AxisOptions? = nil
) {
self.animation = animation
self.animationDuration = animationDuration
self.animationEasing = animationEasing
self.tooltip = tooltip
self.grid = grid
self.xAxis = xAxis
self.yAxis = yAxis
}
}

public struct ToolTipConfiguration: Codable, Equatable {
public var show: Bool?

public init(show: Bool? = nil) {
self.show = show
}
}

public struct GridConfiguration: Codable, Equatable {
public var top: Int?
public var bottom: Int?
public var left: Int?
public var right: Int?
public var containLabel: Bool?

public init(
top: Int? = nil,
bottom: Int? = nil,
left: Int? = nil,
right: Int? = nil,
containLabel: Bool? = nil
) {
self.top = top
self.bottom = bottom
self.left = left
self.right = right
self.containLabel = containLabel
}
}

public enum EasingFunction: String, Codable {
case linear
case quadraticIn
case quadraticOut
case quadraticInOut
case cubicIn
case cubicOut
case cubicInOut
case quarticIn
case quarticOut
case quarticInOut
case quinticIn
case quinticOut
case quinticInOut
case sinusoidalIn
case sinusoidalOut
case sinusoidalInOut
case exponentialIn
case exponentialOut
case exponentialInOut
case circularIn
case circularOut
case circularInOut
case elasticIn
case elasticOut
case elasticInOut
case backIn
case backOut
case backInOut
case bounceIn
case bounceOut
case bounceInOut
}

public struct AxisOptions: Codable, Equatable {
/// Set this to false to prevent the axis from showing.
public var show: Bool?
public var position: Position?
public var type: AxisType?
public var name: String?
/// Set this to true to invert the axis.
public var inverse: Bool?

public init(
show: Bool? = nil,
position: AxisOptions.Position? = nil,
type: AxisOptions.AxisType? = nil,
name: String? = nil,
inverse: Bool? = nil
) {
self.show = show
self.position = position
self.type = type
self.name = name
self.inverse = inverse
}

public enum Position: String, Codable, Equatable {
case top
case bottom
}

public enum AxisType: String, Codable, Equatable {
/// Numerical axis, suitable for continuous data.
case value
/// Category axis, suitable for discrete category data.
case category
/// Time axis, suitable for continuous time series data. As compared to value axis, it has a better formatting for time and a different tick calculation method. For example, it decides to use month, week, day or hour for tick based on the range of span.

Check warning on line 140 in Sources/DataTransferObjects/Chart Configuration/ChartConfigurationOptions.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Line should be 200 characters or less: currently 261 characters (line_length)
case time
/// Log axis, suitable for log data. Stacked bar or line series with type: 'log' axes may lead to significant visual errors and may have unintended effects in certain circumstances. Their use should be avoided.

Check warning on line 142 in Sources/DataTransferObjects/Chart Configuration/ChartConfigurationOptions.swift

View workflow job for this annotation

GitHub Actions / Run Swiftlint

Line should be 200 characters or less: currently 218 characters (line_length)
case log
}
}
7 changes: 7 additions & 0 deletions Sources/DataTransferObjects/Query/CustomQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
postAggregations: [PostAggregator]? = nil,
limit: Int? = nil,
context: QueryContext? = nil,
chartConfiguration: ChartConfiguration? = nil,
valueFormatter: ValueFormatter? = nil,
threshold: Int? = nil,
metric: TopNMetricSpec? = nil,
Expand Down Expand Up @@ -58,6 +59,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
self.postAggregations = postAggregations
self.limit = limit
self.context = context
self.chartConfiguration = chartConfiguration
self.valueFormatter = valueFormatter
self.threshold = threshold
self.metric = metric
Expand Down Expand Up @@ -91,6 +93,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
postAggregations: [PostAggregator]? = nil,
limit: Int? = nil,
context: QueryContext? = nil,
chartConfiguration: ChartConfiguration? = nil,
valueFormatter: ValueFormatter? = nil,
threshold: Int? = nil,
metric: TopNMetricSpec? = nil,
Expand Down Expand Up @@ -122,6 +125,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
self.postAggregations = postAggregations
self.limit = limit
self.context = context
self.chartConfiguration = chartConfiguration
self.valueFormatter = valueFormatter
self.threshold = threshold
self.metric = metric
Expand Down Expand Up @@ -194,6 +198,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
public var postAggregations: [PostAggregator]?
public var limit: Int?
public var context: QueryContext?
public var chartConfiguration: ChartConfiguration?
public var valueFormatter: ValueFormatter?

/// Only for topN Queries: An integer defining the N in the topN (i.e. how many results you want in the top list)
Expand Down Expand Up @@ -257,6 +262,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
hasher.combine(postAggregations)
hasher.combine(limit)
hasher.combine(context)
// chartConfiguration deliberately not included in hash, because we don't want to invalidate caches based on chart configuration
hasher.combine(valueFormatter)
hasher.combine(threshold)
hasher.combine(metric)
Expand Down Expand Up @@ -294,6 +300,7 @@ public struct CustomQuery: Codable, Hashable, Equatable {
postAggregations = try container.decodeIfPresent([PostAggregator].self, forKey: CustomQuery.CodingKeys.postAggregations)
limit = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.limit)
context = try container.decodeIfPresent(QueryContext.self, forKey: CustomQuery.CodingKeys.context)
chartConfiguration = try container.decodeIfPresent(ChartConfiguration.self, forKey: CustomQuery.CodingKeys.chartConfiguration)
valueFormatter = try container.decodeIfPresent(ValueFormatter.self, forKey: CustomQuery.CodingKeys.valueFormatter)
threshold = try container.decodeIfPresent(Int.self, forKey: CustomQuery.CodingKeys.threshold)
dimension = try container.decodeIfPresent(DimensionSpec.self, forKey: CustomQuery.CodingKeys.dimension)
Expand Down
38 changes: 38 additions & 0 deletions Tests/QueryTests/ChartAggregtionConfigurationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// HavingTests.swift
// DataTransferObjects
//
// Created by Daniel Jilg on 09.01.25.
//

import DataTransferObjects
import XCTest

class ChartAggregtionConfigurationTests: XCTestCase {
func testChartAggregationConfiguration() throws {
let aggregationConfiguration = ChartAggregationConfiguration(
startAngle: 12,
endAngle: 13,
radius: ["12%", "50%"],
center: ["0%", "12%"],
stack: "hello"
)

let encodedAggregationConfiguration = """
{
"center": ["0%", "12%"],
"endAngle": 13,
"radius": ["12%", "50%"],
"stack": "hello",
"startAngle": 12
}
"""
.filter { !$0.isWhitespace }

let encoded = try JSONEncoder.telemetryEncoder.encode(aggregationConfiguration)
XCTAssertEqual(String(data: encoded, encoding: .utf8)!, encodedAggregationConfiguration)

let decoded = try JSONDecoder.telemetryDecoder.decode(ChartAggregationConfiguration.self, from: encoded)
XCTAssertEqual(aggregationConfiguration, decoded)
}
}
40 changes: 40 additions & 0 deletions Tests/QueryTests/ChartConfigurationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// HavingTests.swift
// DataTransferObjects
//
// Created by Daniel Jilg on 09.01.25.
//

import DataTransferObjects
import XCTest

class ChartConfigurationTests: XCTestCase {
func testChartConfiguration() throws {
let chartConfiguration = ChartConfiguration(
displayMode: .barChart,
darkMode: false,
options: .init(animation: false),
aggregationConfiguration: .init(stack: "hello")
)

let encodedChartConfiguration = """
{
"aggregationConfiguration": {
"stack": "hello"
},
"darkMode": false,
"displayMode": "barChart",
"options": {
"animation": false
}
}
"""
.filter { !$0.isWhitespace }

let encoded = try JSONEncoder.telemetryEncoder.encode(chartConfiguration)
XCTAssertEqual(String(data: encoded, encoding: .utf8)!, encodedChartConfiguration)

let decoded = try JSONDecoder.telemetryDecoder.decode(ChartConfiguration.self, from: encoded)
XCTAssertEqual(chartConfiguration, decoded)
}
}
Loading

0 comments on commit 86037e2

Please sign in to comment.