diff --git a/Sources/LiveViewNativeCharts/ChartContentBuilder.swift b/Sources/LiveViewNativeCharts/ChartContentBuilder.swift index a6df4a3..fee57b0 100644 --- a/Sources/LiveViewNativeCharts/ChartContentBuilder.swift +++ b/Sources/LiveViewNativeCharts/ChartContentBuilder.swift @@ -27,6 +27,7 @@ struct ChartContentBuilder: ContentBuilder { enum ModifierType: String, Decodable { case foregroundStyle = "foreground_style" case offset + case position case symbol } @@ -71,6 +72,8 @@ struct ChartContentBuilder: ContentBuilder { return try ForegroundStyleModifier(from: decoder) case .offset: return try OffsetModifier(from: decoder) + case .position: + return try PositionModifier(from: decoder) case .symbol: return try SymbolModifier(from: decoder) } diff --git a/Sources/LiveViewNativeCharts/Modifiers/PositionModifier.swift b/Sources/LiveViewNativeCharts/Modifiers/PositionModifier.swift new file mode 100644 index 0000000..1fc6e52 --- /dev/null +++ b/Sources/LiveViewNativeCharts/Modifiers/PositionModifier.swift @@ -0,0 +1,67 @@ +// +// PositionModifier.swift +// +// +// Created by Carson Katri on 7/5/23. +// + +import Charts +import SwiftUI +import LiveViewNative + +// this modifier is created by `liveview-client-swiftui`, and an implementation for Charts is provided here. +/// Position marks with a plottable value. +/// +/// Use this modifier on a mark to set its position based on a plottable value. +/// Optionally provide an ``axis`` and ``span`` to customize the positioning. +/// +/// ```html +/// +/// ``` +/// +/// ## Arguments +/// * ``value`` +/// * ``axis`` +/// * ``span`` +#if swift(>=5.8) +@_documentation(visibility: public) +#endif +struct PositionModifier: ContentModifier { + typealias Builder = ChartContentBuilder + + /// The plottable value used for positioning marks. + /// + /// See ``AnyPlottableValue`` for more details. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + let value: AnyPlottableValue + + /// The axis marks are positioned along. + /// + /// See ``LiveViewNativeCharts/LiveViewNative/SwiftUI/Axis`` for a list of possible values. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + let axis: Axis? + + /// The total space available for marks. + /// + /// See ``LiveViewNativeCharts/Charts/MarkDimension/init(from:)-2ggwd`` for more details. + #if swift(>=5.8) + @_documentation(visibility: public) + #endif + let span: MarkDimension? + + func apply( + to content: Builder.Content, + on element: ElementNode, + in context: Builder.Context + ) -> Builder.Content { + unbox(content: content, label: value.label, value.value) + } + + func unbox(content: Builder.Content, label: String, _ v: some Plottable) -> Builder.Content { + content.position(by: .value(label, v), axis: axis, span: span ?? .automatic) + } +} diff --git a/Sources/LiveViewNativeCharts/Types/MarkDimension.swift b/Sources/LiveViewNativeCharts/Types/MarkDimension.swift index 7831912..92f0413 100644 --- a/Sources/LiveViewNativeCharts/Types/MarkDimension.swift +++ b/Sources/LiveViewNativeCharts/Types/MarkDimension.swift @@ -16,7 +16,7 @@ import LiveViewNativeCore /// * A fixed number: `75` /// * A ratio with the `%` suffix: `50%` /// * An inset with the `-` prefix: `-10` -extension MarkDimension: AttributeDecodable { +extension MarkDimension: AttributeDecodable, Decodable { public init(from attribute: LiveViewNativeCore.Attribute?) throws { guard let value = attribute?.value else { throw AttributeDecodingError.missingAttribute(Self.self) } @@ -36,4 +36,44 @@ extension MarkDimension: AttributeDecodable { throw AttributeDecodingError.badValue(Self.self) } } + + /// A size to use for a mark. + /// + /// `:automatic` is the simplest way to create a mark dimension. + /// + /// In other cases, use a tuple where the first element is the type, and the second is the value. + /// + /// `fixed`, `inset`, and `ratio` types can be used. + /// + /// ```elixir + /// {:fixed, 50} + /// {:inset, 15} + /// {:ratio, 0.5} + /// ``` + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let value = try container.decode(Double.self, forKey: .value) + switch try container.decode(MarkDimensionType.self, forKey: .type) { + case .automatic: + self = .automatic + case .fixed: + self = .fixed(value) + case .inset: + self = .inset(value) + case .ratio: + self = .ratio(value) + } + } + + enum MarkDimensionType: String, Decodable { + case automatic + case ratio + case inset + case fixed + } + + enum CodingKeys: CodingKey { + case type + case value + } } diff --git a/lib/live_view_native_swift_ui_charts/modifiers/position.ex b/lib/live_view_native_swift_ui_charts/modifiers/position.ex new file mode 100644 index 0000000..ea2c28f --- /dev/null +++ b/lib/live_view_native_swift_ui_charts/modifiers/position.ex @@ -0,0 +1,20 @@ +defmodule LiveViewNativeSwiftUiCharts.Modifiers.Position do + use LiveViewNativePlatform.Modifier + + alias LiveViewNativeSwiftUiCharts.Types.{PlottableValue, MarkDimension} + + modifier_schema "position" do + field :value, PlottableValue + field :axis, Ecto.Enum, values: ~w(horizontal vertical all)a + field :span, MarkDimension + + field :x, :float, default: 0.0 + field :y, :float, default: 0.0 + end + + def params([by: value]), do: [value: value] + def params(params) when is_list(params) or is_map(params) do + params = Enum.into(params, %{}) + Map.put_new(params, :value, Map.get(params, :by)) + end +end diff --git a/lib/live_view_native_swift_ui_charts/types/mark_dimension.ex b/lib/live_view_native_swift_ui_charts/types/mark_dimension.ex new file mode 100644 index 0000000..7a02d37 --- /dev/null +++ b/lib/live_view_native_swift_ui_charts/types/mark_dimension.ex @@ -0,0 +1,11 @@ +defmodule LiveViewNativeSwiftUiCharts.Types.MarkDimension do + @derive Jason.Encoder + defstruct [:type, :value] + + use LiveViewNativePlatform.Modifier.Type + def type, do: :map + + def cast(:automatic = type), do: cast({type, 0}) + def cast({type, value}), do: {:ok, %__MODULE__{ type: type, value: value }} + def cast(_), do: :error +end