Skip to content

Add position modifier #101

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions Sources/LiveViewNativeCharts/ChartContentBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct ChartContentBuilder: ContentBuilder {
enum ModifierType: String, Decodable {
case foregroundStyle = "foreground_style"
case offset
case position
case symbol
}

Expand Down Expand Up @@ -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)
}
Expand Down
67 changes: 67 additions & 0 deletions Sources/LiveViewNativeCharts/Modifiers/PositionModifier.swift
Original file line number Diff line number Diff line change
@@ -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
/// <BarMark modifiers={position(by: {"Type", item.type}, axis: :horizontal)} />
/// ```
///
/// ## 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<R: RootRegistry>(
to content: Builder.Content,
on element: ElementNode,
in context: Builder.Context<R>
) -> 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)
}
}
42 changes: 41 additions & 1 deletion Sources/LiveViewNativeCharts/Types/MarkDimension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand All @@ -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
}
}
20 changes: 20 additions & 0 deletions lib/live_view_native_swift_ui_charts/modifiers/position.ex
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions lib/live_view_native_swift_ui_charts/types/mark_dimension.ex
Original file line number Diff line number Diff line change
@@ -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