Skip to content
Draft
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
59 changes: 47 additions & 12 deletions examples/demo_live.exs
Original file line number Diff line number Diff line change
Expand Up @@ -69,22 +69,57 @@ defmodule DemoLive do
<h2>Example graph</h2>

<.graph dimensions={@dimensions}>
<.x_axis_labels :let={date} axis={@x_axis}>
<%!-- X-axis labels --%>
<text
:for={date <- scale_values(@x_axis, ticks: 5)}
x={@x_axis[date]}
y={below_graph(@dimensions)}
dominant-baseline="hanging"
text-anchor="middle"
>
{Calendar.strftime(date, "%-m/%-d")}
</.x_axis_labels>

<%!-- this wraps text... why does it take in `axis`?? if we want to follow the SVG, we need to pass in `x` --%>
<.x_axis_label axis={@x_axis} value={~D[2023-08-02]} position={:top} color="red">
</text>

<%!-- Add label for a specific date above the graph --%>
<text
x={@x_axis[~D[2023-08-02]]}
y={above_graph(@dimensions)}
dominant-baseline="text-bottom"
text-anchor="middle"
>
{"Important Day"}
</.x_axis_label>

<.x_axis_grid_lines axis={@x_axis} stroke="#D3D3D3" />
</text>

<%!-- X-axis grid lines --%>
<line
:for={date <- scale_values(@x_axis, ticks: 5)}
x1={@x_axis[date]}
y1={graph_top(@dimensions)}
x2={@x_axis[date]}
y2={graph_bottom(@dimensions)}
stroke="#D3D3D3"
/>

<.y_axis_labels :let={value} axis={@y_axis} ticks={5}>
<%!-- Y-axis labels --%>
<text
:for={value <- scale_values(@y_axis, ticks: 5)}
x={left_of_graph(@dimensions)}
y={@y_axis[value]}
dominant-baseline="middle"
text-anchor="end"
>
{value}
</.y_axis_labels>

<.y_axis_grid_lines axis={@y_axis} ticks={5} stroke="#D3D3D3" />
</text>

<%!-- Y-axis grid lines --%>
<line
:for={value <- scale_values(@y_axis, ticks: 5)}
x1={graph_left(@dimensions)}
y1={@y_axis[value]}
x2={graph_right(@dimensions)}
y2={@y_axis[value]}
stroke="#D3D3D3"
/>

<.polyline points={points(@dataset[:x], @dataset[:y])} stroke="orange" stroke-width={2} />

Expand Down
98 changes: 80 additions & 18 deletions lib/plox.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ defmodule Plox do

use Phoenix.Component

alias Plox.Constants
alias Plox.Dimensions
alias Plox.Scale
alias Plox.XAxis
alias Plox.YAxis

# copied from SVG spec: https://svgwg.org/svg2-draft/styling.html#TermPresentationAttribute
@svg_presentation_globals ~w(alignment-baseline baseline-shift clip-path clip-rule color color-interpolation color-interpolation-filters cursor direction display dominant-baseline fill-opacity fill-rule filter flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-orientation-horizontal glyph-orientation-vertical image-rendering letter-spacing lighting-color marker-end marker-mid marker-start mask mask-type opacity overflow paint-order pointer-events shape-rendering stop-color stop-opacity stroke stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-overflow text-rendering transform-origin unicode-bidi vector-effect visibility white-space word-spacing writing-mode)

@doc """
Entrypoint component for rendering graphs and plots.
"""
Expand Down Expand Up @@ -49,7 +47,7 @@ defmodule Plox do
attr :ticks, :any
attr :step, :any
attr :start, :any
attr :rest, :global, include: ~w(gap rotation position) ++ @svg_presentation_globals
attr :rest, :global, include: ~w(gap rotation position) ++ Constants.svg_presentation_attrs()

slot :inner_block, required: true

Expand Down Expand Up @@ -77,8 +75,8 @@ defmodule Plox do
attr :gap, :integer, default: 16
attr :rotation, :integer, default: nil
attr :"dominant-baseline", :any, default: nil
attr :"text-anchor", :any, default: nil
attr :rest, :global, include: @svg_presentation_globals
attr :"text-anchor", :any, default: "middle"
attr :rest, :global, include: Constants.svg_presentation_attrs()

slot :inner_block, required: true

Expand All @@ -88,7 +86,7 @@ defmodule Plox do
x={x = @axis[@value]}
y={@axis.dimensions.height - @axis.dimensions.margin.bottom + @gap}
dominant-baseline={assigns[:"dominant-baseline"] || "hanging"}
text-anchor={assigns[:"text-anchor"] || "middle"}
text-anchor={assigns[:"text-anchor"]}
transform={
if @rotation,
do:
Expand All @@ -107,7 +105,7 @@ defmodule Plox do
x={x = @axis[@value]}
y={@axis.dimensions.margin.bottom - @gap}
dominant-baseline={assigns[:"dominant-baseline"] || "text-bottom"}
text-anchor={assigns[:"text-anchor"] || "middle"}
text-anchor={assigns[:"text-anchor"]}
transform={
if @rotation,
do: "rotate(#{@rotation}, #{x}, #{@axis.dimensions.margin.bottom - @gap})"
Expand All @@ -130,7 +128,7 @@ defmodule Plox do
attr :ticks, :any
attr :step, :any
attr :start, :any
attr :rest, :global, include: ~w(gap rotation position) ++ @svg_presentation_globals
attr :rest, :global, include: ~w(gap rotation position) ++ Constants.svg_presentation_attrs()

slot :inner_block, required: true

Expand Down Expand Up @@ -159,7 +157,7 @@ defmodule Plox do
attr :rotation, :integer, default: nil
attr :"dominant-baseline", :any, default: "middle"
attr :"text-anchor", :any, default: nil
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

slot :inner_block, required: true

Expand Down Expand Up @@ -209,7 +207,7 @@ defmodule Plox do
attr :ticks, :any
attr :step, :any
attr :start, :any
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def x_axis_grid_lines(assigns) do
~H"""
Expand All @@ -231,7 +229,7 @@ defmodule Plox do
attr :value, :any, required: true
attr :top_overdraw, :integer, default: 0
attr :bottom_overdraw, :integer, default: 0
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def x_axis_grid_line(assigns) do
~H"""
Expand All @@ -254,7 +252,7 @@ defmodule Plox do
attr :ticks, :any
attr :step, :any
attr :start, :any
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def y_axis_grid_lines(assigns) do
~H"""
Expand All @@ -274,7 +272,7 @@ defmodule Plox do

attr :axis, YAxis, required: true
attr :value, :any, required: true
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def y_axis_grid_line(assigns) do
~H"""
Expand All @@ -295,7 +293,7 @@ defmodule Plox do

attr :points, :any, required: true, doc: "String of coordinates (x1,y1 x2,y2...) or list of {x, y} tuples"
attr :fill, :any, default: "none"
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def polyline(%{points: points} = assigns) when is_binary(points), do: do_polyline(assigns)

Expand Down Expand Up @@ -323,7 +321,7 @@ defmodule Plox do

attr :points, :any, required: true, doc: "String of coordinates (x1,y1 x2,y2...) or list of {x, y} tuples"
attr :fill, :any, default: "none"
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def step_polyline(%{points: points} = assigns) when is_binary(points) do
points =
Expand Down Expand Up @@ -368,14 +366,13 @@ defmodule Plox do
"""
@doc type: :component

# TODO: I wonder if we can more dynamically determine all "dynamic"-possible attributes
attr :cx, :any, required: true
attr :cy, :any, required: true
attr :r, :any, required: true
attr :fill, :any, default: nil
attr :stroke, :any, default: nil
attr :"stroke-width", :any, default: nil
attr :rest, :global, include: @svg_presentation_globals
attr :rest, :global, include: Constants.svg_presentation_attrs()

def circle(assigns) do
~H"""
Expand Down Expand Up @@ -465,6 +462,71 @@ defmodule Plox do
end
end

@doc """
Returns scale values for rendering labels and grid lines.

## Example

iex> scale_values(x_axis, ticks: 5)
[~D[2023-08-01], ~D[2023-08-02], ...]
"""
def scale_values(%{scale: scale}, opts \\ []) do
opts = Map.new(opts)
Scale.values(scale, opts)
end

@doc """
Returns the y-coordinate for positioning elements below the graph (e.g., x-axis labels at bottom).
Default gap is 16px from the graph boundary.
"""
def below_graph(dimensions, gap \\ Constants.default_label_gap()) do
dimensions.height - dimensions.margin.bottom + gap
end

@doc """
Returns the y-coordinate for positioning elements above the graph (e.g., x-axis labels at top).
Default gap is 16px from the graph boundary.
"""
def above_graph(dimensions, gap \\ Constants.default_label_gap()) do
dimensions.margin.top - gap
end

@doc """
Returns the x-coordinate for positioning elements to the left of the graph (e.g., y-axis labels).
Default gap is 16px from the graph boundary.
"""
def left_of_graph(dimensions, gap \\ Constants.default_label_gap()) do
dimensions.margin.left - gap
end

@doc """
Returns the x-coordinate for positioning elements to the right of the graph (e.g., y-axis labels).
Default gap is 16px from the graph boundary.
"""
def right_of_graph(dimensions, gap \\ Constants.default_label_gap()) do
dimensions.width - dimensions.margin.right + gap
end

@doc """
Returns the top boundary of the graph area (for grid lines and other elements).
"""
def graph_top(dimensions), do: dimensions.margin.top

@doc """
Returns the bottom boundary of the graph area (for grid lines and other elements).
"""
def graph_bottom(dimensions), do: dimensions.height - dimensions.margin.bottom

@doc """
Returns the left boundary of the graph area (for grid lines and other elements).
"""
def graph_left(dimensions), do: dimensions.margin.left

@doc """
Returns the right boundary of the graph area (for grid lines and other elements).
"""
def graph_right(dimensions), do: dimensions.width - dimensions.margin.right

# @doc """
# Bar plot.
# """
Expand Down
81 changes: 81 additions & 0 deletions lib/plox/constants.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule Plox.Constants do
@moduledoc """
Constants used throughout Plox components.
"""

@doc """
SVG presentation attributes that can be passed via @rest in components.

See: https://svgwg.org/svg2-draft/styling.html#TermPresentationAttribute
"""
def svg_presentation_attrs do
~w(
alignment-baseline
baseline-shift
clip-path
clip-rule
color
color-interpolation
color-interpolation-filters
cursor
direction
display
dominant-baseline
fill
fill-opacity
fill-rule
filter
flood-color
flood-opacity
font-family
font-size
font-size-adjust
font-stretch
font-style
font-variant
font-weight
glyph-orientation-horizontal
glyph-orientation-vertical
image-rendering
letter-spacing
lighting-color
marker-end
marker-mid
marker-start
mask
mask-type
opacity
overflow
paint-order
pointer-events
shape-rendering
stop-color
stop-opacity
stroke
stroke-dasharray
stroke-dashoffset
stroke-linecap
stroke-linejoin
stroke-miterlimit
stroke-opacity
stroke-width
text-anchor
text-decoration
text-overflow
text-rendering
transform
transform-origin
unicode-bidi
vector-effect
visibility
white-space
word-spacing
writing-mode
)
end

@doc """
Default gap between graph boundary and labels (in pixels).
"""
def default_label_gap, do: 16
end