Powered by AppSignal & Oban Pro

Starting Position and Segments

22_starting_position_and_segments.livemd

Starting Position and Segments

Mix.install([
  {:plotly_ex, "~> 0.3"},
  {:kino, "~> 0.18"},
  {:req, "~> 0.5"}
])

Section

starts: %{x:, y:, z:} seeds streamtubes from specific positions rather than auto-placing them. This controls where flow lines begin, making the visualization more targeted. maxdisplayed: limits the total number of tubes shown (useful for dense fields). Here we use Plotly's wind dataset — a real 3D atmospheric wind-field grid.

alias Plotly.{Figure, Streamtube}

csv =
  Req.get!(
    "https://raw.githubusercontent.com/plotly/datasets/master/streamtube-wind.csv"
  ).body

parse_num = fn s ->
  case Float.parse(String.trim(s)) do
    {f, _} -> f
    :error -> 0.0
  end
end

[header_row | data_rows] = String.split(csv, "\n", trim: true)
headers = header_row |> String.split(",") |> Enum.drop(1) |> Enum.map(&String.trim/1)

by_name =
  data_rows
  |> Enum.map(fn row -> row |> String.split(",") |> Enum.drop(1) |> Enum.map(parse_num) end)
  |> Enum.zip()
  |> Enum.map(&Tuple.to_list/1)
  |> then(&Enum.zip(headers, &1))
  |> Map.new()

[xs, ys, zs, us, vs, ws] = Enum.map(~w[x y z u v w], &by_name[&1])

Figure.new()
|> Figure.add_trace(
  Streamtube.new(
    x: xs, y: ys, z: zs,
    u: us, v: vs, w: ws,
    starts: %{
      x: List.duplicate(80, 16),
      y: List.flatten(List.duplicate([20, 30, 40, 50], 4)),
      z: [0, 0, 0, 0, 5, 5, 5, 5, 10, 10, 10, 10, 15, 15, 15, 15]
    },
    sizeref: 0.3,
    colorscale: "Portland",
    showscale: false,
    maxdisplayed: 3000
  )
)
|> Figure.update_layout(
  title: "Streamtube — Custom Starting Positions",
  scene: %{
    xaxis: %{title: "X"},
    yaxis: %{title: "Y"},
    zaxis: %{title: "Z"}
  }
)
|> Plotly.show()