Visualize.Feed.Window (Visualize v0.8.0)

Copy Markdown View Source

Windows over a feed's window field (spec/08 §11.2, D-140, D-154).

A window is nil — whatever a feed holds — or [from, to], either bound nil for open. Its bounds are values of the feed's window field: numbers for a :number field, or instants — DateTime, NaiveDateTime or Date values — for a :time one, a window and a column of one kind (applies?/2). A window may also be a box (spec/08 §11.1, D-168), %{ranges: %{field => [from, to]}, latest: boolean}: ranges over any :time or :number field, and each identity's last row. box/2 gives any window as its box, range/2 reads one field's range, check/2 holds a box to a schema and latest_rows/3 keeps each identity's last row. These are the comparisons, the trims and the numeric position that Visualize.Feed.finish/3, Visualize.Feed.Monitor and Visualize.Feed.Combine share.

Examples

iex> Visualize.Feed.Window.within?(5, [0, 10])
true
iex> Visualize.Feed.Window.within?(~U[2026-10-11 12:00:00Z], [~U[2026-10-11 13:00:00Z], nil])
false
iex> Visualize.Feed.Window.trim([%{t: 1}, %{t: 5}, %{t: 9}], :t, [2, nil])
[%{t: 5}, %{t: 9}]

Summary

Types

A box (spec/08 §11.1, D-168): ranges over fields, and whether to keep each identity's last row.

t()

Functions

Whether a window applies to a window field of the type, :time or :number (spec/08 §11.2): its bounds instants for :time, numbers for :number. A nil window, and one with no bound, applies to either; a window that mixes kinds, or whose bounds are no times, applies to neither.

The rows whose field lies in [from, upper): from inclusive and open when nil, upper exclusive and open when nil. The seam of a combination (spec/08 §11.5).

The box a window asks of a schema (spec/08 §11.1, D-168): %{ranges: ranges, latest: boolean}, or nil for no window. A map's absent ranges is %{} and its absent latest false; the shorthand [from, to] is the window field's range where it applies to the window field (applies?/2), and nil — the feed's own span — where it does not or the schema has no window field. The box is not checked (check/2).

Whether a schema can answer a window (spec/08 §11.1): :ok, or {:error, faults}, every fault {path, reason} from the box to the value, ordered by path. nil and the shorthand [from, to] are :ok, as they always were: a shorthand that does not apply is the feed's own span. A box is held to the schema — each ranged field described, :time or :number, its range two ends of its type, not inverted — and latest: true to a window field and a declared identity.

:lt, :eq or :gt between two times of one kind: numbers by value, and DateTime, NaiveDateTime and Date each by their own compare/2.

The earlier of two times, a nil ignored.

Whether a value is an instant: a DateTime, a NaiveDateTime or a Date — what a :time window field holds (spec/08 §10.1, D-154). A number is not one.

The intersection of two windows, either nil for everything. Bounds take the later from and the earlier to.

The later of two times, a nil ignored.

Each identity's last row (spec/08 §11.1, D-168): of the rows, for each identity identify gives a row, the row whose time — a function of a row giving a number, as position/1 gives one — is greatest, the later row in order where two tie, kept in their order. A row whose identity or time is nil names nothing and is dropped. A feed's latest and a design's :window step's (spec/14 §5.4.1) are this function.

The rows whose field lies strictly after time, every row for nil. The seam between what a feed holds and a push (spec/08 §11.4).

A time as a number, for a reduction's x: a number itself, a DateTime its Unix microseconds, a NaiveDateTime its Gregorian seconds and a Date its Gregorian days. Anything else is nil, a gap.

A field's range of a window (spec/08 §11.1): a box's ranges entry for the field, read by either spelling of its name; the shorthand [from, to] itself, which is the window field's; nil when there is none. Any other window is returned as it is, so a provider that reads its window field's range this way still refuses what it cannot read.

Whether a value is one a window can be over: a number, or an instant (instant?/1).

The rows whose field lies within the window (within?/2), in their order; every row for a nil window. The field is read by either spelling (Visualize.Data.Table.get/2).

Whether a time lies within a window: both bounds inclusive, a nil bound open, a nil window everything. A nil time lies within no window but nil.

Types

box()

@type box() :: %{
  ranges: %{required(Visualize.Data.Schema.name()) => [time() | nil]},
  latest: boolean()
}

A box (spec/08 §11.1, D-168): ranges over fields, and whether to keep each identity's last row.

t()

@type t() :: [time() | nil] | nil

time()

@type time() :: number() | DateTime.t() | NaiveDateTime.t() | Date.t()

Functions

applies?(arg1, type)

@spec applies?(t(), :time | :number) :: boolean()

Whether a window applies to a window field of the type, :time or :number (spec/08 §11.2): its bounds instants for :time, numbers for :number. A nil window, and one with no bound, applies to either; a window that mixes kinds, or whose bounds are no times, applies to neither.

iex> Visualize.Feed.Window.applies?([~U[2026-10-12 00:00:00Z], nil], :time)
true
iex> Visualize.Feed.Window.applies?([~U[2026-10-12 00:00:00Z], nil], :number)
false
iex> Visualize.Feed.Window.applies?([0, 10], :number)
true

before(rows, field, from, upper)

@spec before([map()], atom() | String.t(), time() | nil, time() | nil) :: [map()]

The rows whose field lies in [from, upper): from inclusive and open when nil, upper exclusive and open when nil. The seam of a combination (spec/08 §11.5).

iex> Visualize.Feed.Window.before([%{t: 1}, %{t: 5}, %{t: 9}], :t, nil, 5)
[%{t: 1}]

box(window, schema)

The box a window asks of a schema (spec/08 §11.1, D-168): %{ranges: ranges, latest: boolean}, or nil for no window. A map's absent ranges is %{} and its absent latest false; the shorthand [from, to] is the window field's range where it applies to the window field (applies?/2), and nil — the feed's own span — where it does not or the schema has no window field. The box is not checked (check/2).

iex> alias Visualize.Data.Schema
iex> schema = Schema.new!(%{t: %{type: :number, window: true}, v: %{type: :number}})
iex> Visualize.Feed.Window.box([0, 10], schema)
%{ranges: %{t: [0, 10]}, latest: false}
iex> Visualize.Feed.Window.box([~U[2026-10-14 00:00:00Z], nil], schema)
nil
iex> Visualize.Feed.Window.box(%{ranges: %{v: [0, 1]}}, schema)
%{ranges: %{v: [0, 1]}, latest: false}

check(window, schema)

@spec check(term(), Visualize.Data.Schema.t()) ::
  :ok | {:error, [{list(), term()}, ...]}

Whether a schema can answer a window (spec/08 §11.1): :ok, or {:error, faults}, every fault {path, reason} from the box to the value, ordered by path. nil and the shorthand [from, to] are :ok, as they always were: a shorthand that does not apply is the feed's own span. A box is held to the schema — each ranged field described, :time or :number, its range two ends of its type, not inverted — and latest: true to a window field and a declared identity.

iex> alias Visualize.Data.Schema
iex> schema = Schema.new!(%{t: %{type: :number, window: true}, s: %{type: :category}})
iex> Visualize.Feed.Window.check(%{ranges: %{t: [9, 1], s: ["a", "b"]}, latest: true}, schema)
{:error,
 [
   {[:latest], {:window, :no_identity}},
   {[:ranges, :s], {:invalid_for, :category}},
   {[:ranges, :t], {:window, :inverted}}
 ]}

compare(a, b)

@spec compare(time(), time()) :: :lt | :eq | :gt

:lt, :eq or :gt between two times of one kind: numbers by value, and DateTime, NaiveDateTime and Date each by their own compare/2.

iex> Visualize.Feed.Window.compare(1, 1.0)
:eq

earliest(a, b)

@spec earliest(time() | nil, time() | nil) :: time() | nil

The earlier of two times, a nil ignored.

instant?(arg1)

@spec instant?(term()) :: boolean()

Whether a value is an instant: a DateTime, a NaiveDateTime or a Date — what a :time window field holds (spec/08 §10.1, D-154). A number is not one.

iex> Visualize.Feed.Window.instant?(~D[2026-10-12])
true
iex> Visualize.Feed.Window.instant?(12)
false

intersect(window, window)

@spec intersect(t(), t()) :: t()

The intersection of two windows, either nil for everything. Bounds take the later from and the earlier to.

iex> Visualize.Feed.Window.intersect([0, 10], [5, nil])
[5, 10]

latest(a, b)

@spec latest(time() | nil, time() | nil) :: time() | nil

The later of two times, a nil ignored.

iex> Visualize.Feed.Window.latest(nil, 3)
3

latest_rows(rows, time, identify)

@spec latest_rows([map()], (map() -> number() | nil), (map() -> term())) :: [map()]

Each identity's last row (spec/08 §11.1, D-168): of the rows, for each identity identify gives a row, the row whose time — a function of a row giving a number, as position/1 gives one — is greatest, the later row in order where two tie, kept in their order. A row whose identity or time is nil names nothing and is dropped. A feed's latest and a design's :window step's (spec/14 §5.4.1) are this function.

iex> rows = [%{id: "a", t: 1}, %{id: "b", t: 2}, %{id: "a", t: 3}, %{id: nil, t: 4}]
iex> Visualize.Feed.Window.latest_rows(rows, & &1.t, & &1.id)
[%{id: "b", t: 2}, %{id: "a", t: 3}]

newer(rows, field, time)

@spec newer([map()], atom() | String.t(), time() | nil) :: [map()]

The rows whose field lies strictly after time, every row for nil. The seam between what a feed holds and a push (spec/08 §11.4).

iex> Visualize.Feed.Window.newer([%{t: 1}, %{t: 5}, %{t: 9}], :t, 5)
[%{t: 9}]

position(value)

@spec position(term()) :: number() | nil

A time as a number, for a reduction's x: a number itself, a DateTime its Unix microseconds, a NaiveDateTime its Gregorian seconds and a Date its Gregorian days. Anything else is nil, a gap.

iex> Visualize.Feed.Window.position(~U[1970-01-01 00:00:01Z])
1000000

range(window, field)

@spec range(term(), atom() | String.t()) :: term()

A field's range of a window (spec/08 §11.1): a box's ranges entry for the field, read by either spelling of its name; the shorthand [from, to] itself, which is the window field's; nil when there is none. Any other window is returned as it is, so a provider that reads its window field's range this way still refuses what it cannot read.

iex> Visualize.Feed.Window.range(%{ranges: %{"t" => [1, 5], x: [0, 9]}}, :t)
[1, 5]
iex> Visualize.Feed.Window.range([1, 5], :t)
[1, 5]
iex> Visualize.Feed.Window.range(%{latest: true}, :t)
nil

time?(value)

@spec time?(term()) :: boolean()

Whether a value is one a window can be over: a number, or an instant (instant?/1).

trim(rows, field, window)

@spec trim([map()], atom() | String.t(), t()) :: [map()]

The rows whose field lies within the window (within?/2), in their order; every row for a nil window. The field is read by either spelling (Visualize.Data.Table.get/2).

within?(time, arg2)

@spec within?(time() | nil, t()) :: boolean()

Whether a time lies within a window: both bounds inclusive, a nil bound open, a nil window everything. A nil time lies within no window but nil.