Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticks protocol #22

Merged
merged 9 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/SwiftVizScale/AnyContinuousScale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ internal final class _ContinuousScale<WrappedContinuousScale: ContinuousScale>:
///
/// Encapsulates a scale that conforms to the``ContinuousScale`` protocol, identified by ``ContinuousScaleType``.
public struct AnyContinuousScale<InputType: ConvertibleWithDouble & NiceValue,
OutputType: ConvertibleWithDouble>: ContinuousScale, TickScale
OutputType: ConvertibleWithDouble>: ContinuousScale
{
private let _box: _AnyContinuousScale<InputType, OutputType>

Expand Down
30 changes: 28 additions & 2 deletions Sources/SwiftVizScale/BandScale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import Foundation
///
/// Band scales are useful for bar charts, calculating explicit bands with optional spacing to align with elements of a collection.
/// If you mapping discrete data into a scatter plot, consider using the ``PointScale`` instead.
public struct BandScale<CategoryType: Comparable, OutputType: ConvertibleWithDouble>: Scale {
public struct BandScale<CategoryType: Comparable, OutputType: ConvertibleWithDouble>: DiscreteScale {
/// The lower value of the range into which the discrete values map.
public let rangeLower: OutputType?
/// The upper value of the range into which the discrete values map.
Expand All @@ -36,6 +36,9 @@ public struct BandScale<CategoryType: Comparable, OutputType: ConvertibleWithDou
/// An array of the types the scale maps into.
public let domain: [CategoryType]

/// The type of discrete scale.
public let scaleType: DiscreteScaleType = .band

/// Creates a new band scale.
/// - Parameters:
/// - domain: An array of the types the scale maps into.
Expand Down Expand Up @@ -228,9 +231,32 @@ public struct BandScale<CategoryType: Comparable, OutputType: ConvertibleWithDou
}
}

extension BandScale: CustomStringConvertible {
public var description: String {
"\(scaleType)\(domain)->[\(String(describing: rangeLower)):\(String(describing: rangeHigher))]"
}
}

public extension BandScale {
func ticks(rangeLower lower: RangeType, rangeHigher higher: RangeType) -> [Tick<RangeType>] {
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
let updatedScale = range(lower: lower, higher: higher)
return domain.compactMap { tickValue in
guard let tickRangeValue = updatedScale.scale(tickValue) else {
return nil
}
return Tick(value: tickValue, location: tickRangeValue.middle)
}
}
}

/// A type used to indicate the start and stop positions for a band associated with the provided value.
public struct Band<EnclosedType, RangeType> {
public struct Band<EnclosedType, RangeType: ConvertibleWithDouble> {
public let lower: RangeType
public let higher: RangeType
public var middle: RangeType {
RangeType.fromDouble((higher.toDouble() - lower.toDouble()) / 2.0 + lower.toDouble())
}

public let value: EnclosedType
}
131 changes: 111 additions & 20 deletions Sources/SwiftVizScale/ContinuousScale.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Foundation
import Numerics

#if canImport(CoreGraphics)
import CoreGraphics
#endif
// =============================================================
// ContinuousScale.swift

Expand Down Expand Up @@ -220,27 +222,116 @@ public extension ContinuousScale {
func invert(_ rangeValue: OutputType, to upper: OutputType) -> InputType? {
invert(rangeValue, from: 0, to: upper)
}

/// Converts an array of values that matches the scale's input type to a list of ticks that are within the scale's domain.
///
/// Used for manually specifying a series of ticks that you want to have displayed.
///
/// Values presented for display that are *not* within the domain of the scale are dropped.
/// Values that scale outside of the range you provide are adjusted based on the setting of ``ContinuousScale/transformType``.
/// - Parameter inputValues: an array of values of the Scale's InputType
/// - Parameter lower: The lower value of the range the scale maps to.
/// - Parameter higher: The higher value of the range the scale maps to.
/// - Returns: A list of tick values validated against the domain, and range based on the setting of ``ContinuousScale/transformType``
func tickValues(_ inputValues: [InputType], from lower: OutputType, to higher: OutputType) -> [Tick<OutputType>] {
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
inputValues.compactMap { inputValue in
if domainContains(inputValue),
let rangeValue = scale(inputValue, from: lower, to: higher)
{
switch transformType {
case .none:
return Tick(value: inputValue, location: rangeValue)
case .drop:
if rangeValue > higher || rangeValue < lower {
return nil
}
return Tick(value: inputValue, location: rangeValue)
case .clamp:
if rangeValue > higher {
return Tick(value: inputValue, location: higher)
} else if rangeValue < lower {
return Tick(value: inputValue, location: lower)
}
return Tick(value: inputValue, location: rangeValue)
}
}
return nil
}
}
}

// NOTE(heckj): OTHER SCALES: make a PowScale (& maybe Sqrt, Log, Ln)

// Quantize scale: Quantize scales use a discrete range and a
// continuous domain. Range mapping is done by dividing the domain
// evenly by the number of elements in the range. Because the range
// is discrete, the values do not have to be numbers.

// Quantile scale: Quantile scales are similar to quantize scales,
// but instead of evenly dividing the domain, they determine threshold
// values based on the domain that are used as the cutoffs between
// values in the range. Quantile scales take an array of values for a
// domain (not just a lower and upper limit) and maps range to be an
// even distribution over the input domain

// Threshold Scale
// Power Scale
// Ordinal Scale
// Band Scale
// Point Scale
public extension ContinuousScale where InputType == Int {
/// Returns an array of the locations within the output range to locate ticks for the scale.
///
/// - Parameter range: a ClosedRange representing the representing the range we are mapping the values into with the scale
/// - Returns: an Array of the values within the ClosedRange of range
func ticks(rangeLower: OutputType, rangeHigher: OutputType) -> [Tick<OutputType>] {
let tickValues = InputType.rangeOfNiceValues(min: domainLower, max: domainHigher, ofSize: desiredTicks)
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
return tickValues.compactMap { tickValue in
if let tickRangeLocation = scale(tickValue, from: rangeLower, to: rangeHigher) {
return Tick(value: tickValue, location: tickRangeLocation)
}
return nil
}
}
}

public extension ContinuousScale where InputType == Float {
/// Returns an array of the locations within the output range to locate ticks for the scale.
///
/// - Parameter range: a ClosedRange representing the representing the range we are mapping the values into with the scale
/// - Returns: an Array of the values within the ClosedRange of range
func ticks(rangeLower: OutputType, rangeHigher: OutputType) -> [Tick<OutputType>] {
let tickValues = InputType.rangeOfNiceValues(min: domainLower, max: domainHigher, ofSize: desiredTicks)
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
return tickValues.compactMap { tickValue in
if let tickRangeLocation = scale(tickValue, from: rangeLower, to: rangeHigher) {
return Tick(value: tickValue, location: tickRangeLocation)
}
return nil
}
}
}

#if canImport(CoreGraphics)
public extension ContinuousScale where InputType == CGFloat {
/// Returns an array of the locations within the output range to locate ticks for the scale.
///
/// - Parameter range: a ClosedRange representing the representing the range we are mapping the values into with the scale
/// - Returns: an Array of the values within the ClosedRange of range
func ticks(rangeLower: OutputType, rangeHigher: OutputType) -> [Tick<OutputType>] {
let tickValues = InputType.rangeOfNiceValues(min: domainLower, max: domainHigher, ofSize: desiredTicks)
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
return tickValues.compactMap { tickValue in
if let tickRangeLocation = scale(tickValue, from: rangeLower, to: rangeHigher) {
return Tick(value: tickValue, location: tickRangeLocation)
}
return nil
}
}
}
#endif

public extension ContinuousScale where InputType == Double {
/// Returns an array of the locations within the output range to locate ticks for the scale.
///
/// - Parameter range: a ClosedRange representing the representing the range we are mapping the values into with the scale
/// - Returns: an Array of the values within the ClosedRange of range
func ticks(rangeLower: OutputType, rangeHigher: OutputType) -> [Tick<OutputType>] {
let tickValues = InputType.rangeOfNiceValues(min: domainLower, max: domainHigher, ofSize: desiredTicks)
// NOTE(heckj): perf: for a larger number of ticks, it may be more efficient to assign the range to a temp scale and then iterate on that...
return tickValues.compactMap { tickValue in
if let tickRangeLocation = scale(tickValue, from: rangeLower, to: rangeHigher),
tickRangeLocation <= rangeHigher
{
return Tick(value: tickValue, location: tickRangeLocation)
}
return nil
}
}
}

// MARK: - general functions used in various implementations of Scale

Expand Down
36 changes: 36 additions & 0 deletions Sources/SwiftVizScale/DiscreteScale.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// DiscreteScale.swift
//

import Foundation

/// The type of discrete scale.
public enum DiscreteScaleType: Equatable {
/// A discrete scale that returns a point for the scaled value.
case point
/// A discrete scale that returns a band for the scaled value.
case band

var description: String {
switch self {
case .point:
return "point"
case .band:
return "band"
}
}
}

/// A type that maps discrete values of an input _domain_ to an output _range_.
public protocol DiscreteScale: Scale, CustomStringConvertible {
var scaleType: DiscreteScaleType { get }
/// The lower value of the range into which the discrete values map.
var rangeLower: RangeType? { get }
/// The upper value of the range into which the discrete values map.
var rangeHigher: RangeType? { get }

/// An array of the types the scale maps into.
var domain: [InputType] { get }

func ticks(rangeLower: RangeType, rangeHigher: RangeType) -> [Tick<RangeType>]
}
14 changes: 8 additions & 6 deletions Sources/SwiftVizScale/Documentation.docc/AnyContinuousScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@
- ``SwiftVizScale/AnyContinuousScale/transformAgainstDomain(_:)``
- ``SwiftVizScale/AnyContinuousScale/domainContains(_:)``

<!--### Creating Ticks-->
<!---->
<!--- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-2i6ex``-->
<!--- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-3xaat``-->
<!--- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-9vifj``-->
<!--- ``SwiftVizScale/AnyContinuousScale/tickValues(_:from:to:)``-->
### Creating Ticks

- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-2vo55``
- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-5pjzg``
- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-81saj``
- ``SwiftVizScale/AnyContinuousScale/ticks(rangeLower:rangeHigher:)-fvz5``
- ``SwiftVizScale/AnyContinuousScale/tickValues(_:from:to:)``

1 change: 1 addition & 0 deletions Sources/SwiftVizScale/Documentation.docc/Band.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Inspecting a Band

- ``SwiftVizScale/Band/higher``
- ``SwiftVizScale/Band/middle``
- ``SwiftVizScale/Band/lower``
- ``SwiftVizScale/Band/value``

4 changes: 4 additions & 0 deletions Sources/SwiftVizScale/Documentation.docc/BandScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@
- ``SwiftVizScale/BandScale/invert(_:from:to:)``
- ``SwiftVizScale/BandScale/scale(_:)``
- ``SwiftVizScale/BandScale/scale(_:from:to:)``

### Creating Ticks

- ``SwiftVizScale/BandScale/ticks(rangeLower:rangeHigher:)``
15 changes: 15 additions & 0 deletions Sources/SwiftVizScale/Documentation.docc/DiscreteScale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ``SwiftVizScale/DiscreteScale``

## Topics

### Inspecting Scales

- ``SwiftVizScale/DiscreteScale/domain``
- ``SwiftVizScale/DiscreteScale/rangeHigher``
- ``SwiftVizScale/DiscreteScale/rangeLower``
- ``SwiftVizScale/DiscreteScale/scaleType``
- ``SwiftVizScale/DiscreteScaleType``

### Creating Ticks

- ``SwiftVizScale/DiscreteScale/ticks(rangeLower:rangeHigher:)``
3 changes: 2 additions & 1 deletion Sources/SwiftVizScale/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ Loosely based on the APIs and the visualization constructs created by Mike Bosto
- ``SwiftVizScale/BandScale``
- ``SwiftVizScale/Band``
- ``SwiftVizScale/PointScale``
- ``SwiftVizScale/DiscreteScale``
- ``SwiftVizScale/DiscreteScaleType``

### Ticks

- ``SwiftVizScale/Tick``
- ``SwiftVizScale/TickScale``

### Supporting Types

Expand Down
7 changes: 4 additions & 3 deletions Sources/SwiftVizScale/Documentation.docc/LinearScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@

### Creating Ticks

- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-27usp``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-8gkz5``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-94hgn``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-34j``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-38m9n``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-70q0x``
- ``SwiftVizScale/LinearScale/ticks(rangeLower:rangeHigher:)-8k218``
- ``SwiftVizScale/LinearScale/tickValues(_:from:to:)``
7 changes: 4 additions & 3 deletions Sources/SwiftVizScale/Documentation.docc/LogScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@

### Creating Ticks

- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-2y7om``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-8v9ne``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-96cqk``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-3jo2u``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-56os6``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-8n0cl``
- ``SwiftVizScale/LogScale/ticks(rangeLower:rangeHigher:)-8s65h``
- ``SwiftVizScale/LogScale/tickValues(_:from:to:)``
4 changes: 4 additions & 0 deletions Sources/SwiftVizScale/Documentation.docc/PointScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@
- ``SwiftVizScale/PointScale/invert(_:from:to:)``
- ``SwiftVizScale/PointScale/scale(_:)``
- ``SwiftVizScale/PointScale/scale(_:from:to:)``

### Creating Ticks

- ``SwiftVizScale/PointScale/ticks(rangeLower:rangeHigher:)``
7 changes: 4 additions & 3 deletions Sources/SwiftVizScale/Documentation.docc/PowerScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@

### Creating Ticks

- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-2rzfa``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-3ayzj``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-92fac``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-1gol``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-3gn53``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-54f6d``
- ``SwiftVizScale/PowerScale/ticks(rangeLower:rangeHigher:)-8avol``
- ``SwiftVizScale/PowerScale/tickValues(_:from:to:)``
8 changes: 4 additions & 4 deletions Sources/SwiftVizScale/Documentation.docc/RadialScale.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

### Creating Ticks

- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-1avr``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-2yzm0``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-8b7la``
- ``SwiftVizScale/RadialScale/tickValues(_:from:to:)``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-1q9bi``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-3htvb``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-4vgq2``
- ``SwiftVizScale/RadialScale/ticks(rangeLower:rangeHigher:)-8e854``
14 changes: 0 additions & 14 deletions Sources/SwiftVizScale/Documentation.docc/TickScale.md

This file was deleted.

2 changes: 1 addition & 1 deletion Sources/SwiftVizScale/LinearScale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation
import Numerics

/// A linear scale for transforming and mapping continuous input values within a domain to output values you provide.
public struct LinearScale<InputType: ConvertibleWithDouble & NiceValue, OutputType: ConvertibleWithDouble>: TickScale {
public struct LinearScale<InputType: ConvertibleWithDouble & NiceValue, OutputType: ConvertibleWithDouble>: ContinuousScale {
/// The lower bound of the input domain.
public let domainLower: InputType
/// The upper bound of the input domain.
Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftVizScale/LogScale.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Foundation
import Numerics

/// A logarithmic scale created with a continuous input domain that provides methods to convert values within that domain to an output range.
public struct LogScale<InputType: ConvertibleWithDouble & NiceValue, OutputType: ConvertibleWithDouble>: TickScale {
public struct LogScale<InputType: ConvertibleWithDouble & NiceValue, OutputType: ConvertibleWithDouble>: ContinuousScale {
/// The lower bound of the input domain.
public let domainLower: InputType
/// The upper bound of the input domain.
Expand Down
Loading