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