flightplotting 0.2.12__tar.gz → 0.2.14__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flightplotting-0.2.12 → flightplotting-0.2.14}/PKG-INFO +4 -3
- {flightplotting-0.2.12 → flightplotting-0.2.14}/pyproject.toml +2 -2
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/__init__.py +1 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/plots.py +186 -43
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/templates.py +6 -14
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/traces.py +22 -12
- flightplotting-0.2.14/uv.lock +345 -0
- flightplotting-0.2.12/uv.lock +0 -476
- {flightplotting-0.2.12 → flightplotting-0.2.14}/.github/workflows/publish_pypi.yml +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/.gitignore +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/.vscode/settings.json +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/COPYING +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/MANIFEST.in +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/README.md +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/data/ColdDraftF3APlane.obj +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/data/__init__.py +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/model.py +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/py.typed +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/src/plotting/titlerenderer.py +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/tests/__init__.py +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/tests/data/__init__.py +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/tests/data/p23_flight.json +0 -0
- {flightplotting-0.2.12 → flightplotting-0.2.14}/tests/test_plots.py +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: flightplotting
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.14
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author-email: Thomas David <thomasdavid0@gmail.com>
|
|
6
|
+
License-File: COPYING
|
|
6
7
|
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist:
|
|
8
|
+
Requires-Dist: flightdata>=0.3.5
|
|
8
9
|
Requires-Dist: numpy>=2.1.3
|
|
9
10
|
Requires-Dist: pandas>=2.2.3
|
|
10
11
|
Requires-Dist: plotly>=5.24.1
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flightplotting"
|
|
3
|
-
version="v0.2.
|
|
3
|
+
version="v0.2.14"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Thomas David", email = "thomasdavid0@gmail.com" }]
|
|
7
7
|
requires-python = ">=3.12"
|
|
8
8
|
dependencies = [
|
|
9
|
-
"flightanalysis>=0.3.14",
|
|
10
9
|
"numpy>=2.1.3",
|
|
11
10
|
"pandas>=2.2.3",
|
|
12
11
|
"plotly>=5.24.1",
|
|
12
|
+
"flightdata>=0.3.5",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[build-system]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import plotly.graph_objects as go
|
|
2
3
|
from plotly.subplots import make_subplots
|
|
3
4
|
import plotly.express as px
|
|
@@ -15,16 +16,18 @@ from plotting.traces import (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
from flightdata import State
|
|
18
|
-
|
|
19
|
-
from geometry import Coord
|
|
19
|
+
import geometry as g
|
|
20
20
|
from plotting.model import obj
|
|
21
21
|
import numpy.typing as npt
|
|
22
22
|
import numpy as np
|
|
23
23
|
import pandas as pd
|
|
24
|
-
from typing import List, Union
|
|
25
|
-
from flightanalysis.scoring.box import Box
|
|
24
|
+
from typing import List, Union, Callable, Literal
|
|
26
25
|
|
|
27
26
|
|
|
27
|
+
|
|
28
|
+
def get_colour(i):
|
|
29
|
+
return px.colors.qualitative.Plotly[i % len(px.colors.qualitative.Plotly)]
|
|
30
|
+
|
|
28
31
|
def plotsec(
|
|
29
32
|
secs: State | list[State] | dict[str, State],
|
|
30
33
|
scale=5,
|
|
@@ -37,7 +40,10 @@ def plotsec(
|
|
|
37
40
|
show_axes=False,
|
|
38
41
|
ribb: bool = False,
|
|
39
42
|
tips: bool = True,
|
|
43
|
+
ribbonhover="t",
|
|
40
44
|
origin=False,
|
|
45
|
+
line=None,
|
|
46
|
+
modelscale=1,
|
|
41
47
|
):
|
|
42
48
|
traces = []
|
|
43
49
|
keys = None
|
|
@@ -51,22 +57,32 @@ def plotsec(
|
|
|
51
57
|
else:
|
|
52
58
|
keys = list(range(len(secs)))
|
|
53
59
|
showkeys = False
|
|
60
|
+
|
|
61
|
+
def _get_colour(i):
|
|
62
|
+
if isinstance(color, list):
|
|
63
|
+
return color[i % len(color)]
|
|
64
|
+
elif isinstance(color, str):
|
|
65
|
+
return color
|
|
66
|
+
else:
|
|
67
|
+
return get_colour(i)
|
|
54
68
|
|
|
55
69
|
for i, sec in enumerate(secs):
|
|
56
|
-
text = sec.data.t
|
|
57
|
-
|
|
70
|
+
text = sec.data.t # - sec.data.t.iloc[0]
|
|
71
|
+
|
|
58
72
|
if ribb:
|
|
59
|
-
traces += ribbon(sec, scale * 1.85,
|
|
73
|
+
traces += ribbon(sec, 0.5 * scale * 1.85, _get_colour(i), name=keys[i], opacity=0.5, hover=ribbonhover)
|
|
60
74
|
if tips:
|
|
61
|
-
traces += tiptrace(sec, scale * 1.85, text=text, name=keys[i])
|
|
75
|
+
traces += tiptrace(sec, scale * 1.85, text=text, name=keys[i], line=({} if line is None else line))
|
|
62
76
|
if nmodels > 0:
|
|
63
|
-
traces += meshes(nmodels, sec,
|
|
77
|
+
traces += meshes(nmodels, sec, _get_colour(i), scale * modelscale)
|
|
64
78
|
if cg:
|
|
65
|
-
traces.append(
|
|
79
|
+
traces.append(
|
|
80
|
+
cgtrace(sec, line=dict(color=_get_colour(i), width=2) | ({} if line is None else line), name=keys[i], text=text)
|
|
81
|
+
)
|
|
66
82
|
|
|
67
83
|
if origin:
|
|
68
|
-
traces += axestrace(Coord.zero(), 50)
|
|
69
|
-
|
|
84
|
+
traces += axestrace(g.Coord.zero(), 50)
|
|
85
|
+
|
|
70
86
|
if showkeys:
|
|
71
87
|
for i, key in enumerate(keys):
|
|
72
88
|
traces.append(
|
|
@@ -75,7 +91,7 @@ def plotsec(
|
|
|
75
91
|
y=[],
|
|
76
92
|
z=[],
|
|
77
93
|
mode="markers",
|
|
78
|
-
marker=dict(size=5, color=
|
|
94
|
+
marker=dict(size=5, color=_get_colour(i)),
|
|
79
95
|
name=key,
|
|
80
96
|
showlegend=True,
|
|
81
97
|
)
|
|
@@ -84,7 +100,7 @@ def plotsec(
|
|
|
84
100
|
if fig is None:
|
|
85
101
|
fig = go.Figure(
|
|
86
102
|
data=traces,
|
|
87
|
-
layout=go.Layout(template="flight3d
|
|
103
|
+
layout=go.Layout(template="flight3d", uirevision="foo"),
|
|
88
104
|
)
|
|
89
105
|
if show_axes:
|
|
90
106
|
fig.update_layout(
|
|
@@ -136,47 +152,33 @@ def plotdtw(sec: State, manoeuvres: List[str], span=3, fig=None):
|
|
|
136
152
|
|
|
137
153
|
|
|
138
154
|
def plot_regions(
|
|
139
|
-
st: State,
|
|
155
|
+
st: State,
|
|
156
|
+
label_group_name: str,
|
|
157
|
+
span=3,
|
|
158
|
+
colours=None,
|
|
159
|
+
fig=None,
|
|
160
|
+
ribbonhover="t",
|
|
161
|
+
**kwargs,
|
|
140
162
|
):
|
|
141
163
|
colours = px.colors.qualitative.Plotly if colours is None else colours
|
|
142
|
-
lab_cols = [lab_cols] if isinstance(lab_cols, str) else lab_cols
|
|
143
|
-
|
|
144
|
-
st = st.label(clabs=st.cumulative_labels(*lab_cols))
|
|
145
|
-
|
|
146
|
-
colmap = {}
|
|
147
164
|
|
|
148
165
|
traces = []
|
|
149
|
-
for i,
|
|
166
|
+
for i, k in enumerate(st.labels[label_group_name].keys()):
|
|
167
|
+
seg = getattr(st, label_group_name)[k]
|
|
150
168
|
if len(seg) < 3:
|
|
151
169
|
continue
|
|
152
|
-
blab, id = get_appended_id(k)
|
|
153
|
-
if blab not in colmap:
|
|
154
|
-
colmap[blab] = colours[len(colmap) % len(colours)]
|
|
155
170
|
traces += ribbon(
|
|
156
171
|
seg,
|
|
157
172
|
span,
|
|
158
|
-
|
|
159
|
-
name=
|
|
160
|
-
|
|
161
|
-
**kwargs[blab] if blab in kwargs else {},
|
|
162
|
-
)
|
|
163
|
-
traces.append(
|
|
164
|
-
go.Scatter3d(
|
|
165
|
-
x=seg.pos.x,
|
|
166
|
-
y=seg.pos.y,
|
|
167
|
-
z=seg.pos.z,
|
|
168
|
-
mode="lines",
|
|
169
|
-
line=dict(width=0, color=colmap[blab]),
|
|
170
|
-
name=k,
|
|
171
|
-
showlegend=False,
|
|
172
|
-
)
|
|
173
|
+
colours[i%len(colours)],
|
|
174
|
+
name=k,
|
|
175
|
+
hover=ribbonhover
|
|
173
176
|
)
|
|
174
177
|
|
|
178
|
+
|
|
175
179
|
if fig is None:
|
|
176
180
|
fig = go.Figure(layout=go.Layout(template="flight3d+judge_view"))
|
|
177
181
|
fig.add_traces(traces)
|
|
178
|
-
if box:
|
|
179
|
-
fig.add_traces(box.plot())
|
|
180
182
|
return fig
|
|
181
183
|
|
|
182
184
|
|
|
@@ -316,7 +318,7 @@ def multi_y_subplots(data: dict[str, pd.DataFrame], x: npt.NDArray = None):
|
|
|
316
318
|
cols=1,
|
|
317
319
|
shared_xaxes=True,
|
|
318
320
|
vertical_spacing=0.01,
|
|
319
|
-
#subplot_titles=list(data.keys()),
|
|
321
|
+
# subplot_titles=list(data.keys()),
|
|
320
322
|
)
|
|
321
323
|
|
|
322
324
|
for row, (k, v) in enumerate(data.items(), 1):
|
|
@@ -352,7 +354,7 @@ def multi_y_subplots(data: dict[str, pd.DataFrame], x: npt.NDArray = None):
|
|
|
352
354
|
yanchor="top",
|
|
353
355
|
),
|
|
354
356
|
f"yaxis{i}": dict(
|
|
355
|
-
title=list(data.keys())[i-1],
|
|
357
|
+
title=list(data.keys())[i - 1],
|
|
356
358
|
showline=True,
|
|
357
359
|
),
|
|
358
360
|
}
|
|
@@ -372,3 +374,144 @@ axis = dict(
|
|
|
372
374
|
zerolinecolor="lightgrey",
|
|
373
375
|
showline=True,
|
|
374
376
|
)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def create_ortho_state(st: State, axis: Literal['x', 'z'], width: g.Point, gap: g.Point) -> State:
|
|
380
|
+
"""rotate by 90 degrees about the given axis,
|
|
381
|
+
then move to where it should be in an orthographic projection.
|
|
382
|
+
assumes front view is along the Y axis (x right, z up).
|
|
383
|
+
also assumes state is centered at the origin.
|
|
384
|
+
"""
|
|
385
|
+
st = st.move(g.Transformation(g.Euler(
|
|
386
|
+
np.pi/2 if axis=="x" else 0,
|
|
387
|
+
0,
|
|
388
|
+
np.pi/2 if axis=="z" else 0
|
|
389
|
+
)))
|
|
390
|
+
|
|
391
|
+
if axis == 'x':
|
|
392
|
+
shift = g.PZ((width.y + width.z) / 2 + gap)
|
|
393
|
+
elif axis == 'z':
|
|
394
|
+
shift = g.PX(-(width.x + width.y) / 2 - gap)
|
|
395
|
+
|
|
396
|
+
st = st.move(g.Transformation( shift) ) # move back to center and offset by shift
|
|
397
|
+
|
|
398
|
+
return st
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def applysts(st: State | list[State] | dict[str, State], fun: Callable[[State], State]) -> Union[State, List[State], dict[str, State]]:
|
|
402
|
+
"""apply a transformation to all states in a list or dict of states"""
|
|
403
|
+
if isinstance(st, State):
|
|
404
|
+
return fun(st)
|
|
405
|
+
elif isinstance(st, list):
|
|
406
|
+
return [fun(s) for s in st]
|
|
407
|
+
elif isinstance(st, dict):
|
|
408
|
+
return {k: fun(v) for k, v in st.items()}
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def get_points(fig: go.Figure) -> g.Point:
|
|
412
|
+
"""extract all points from a figure"""
|
|
413
|
+
ps = []
|
|
414
|
+
for d in fig.data:
|
|
415
|
+
try:
|
|
416
|
+
ps.append(g.Point(d.x, d.y, d.z))
|
|
417
|
+
except Exception:
|
|
418
|
+
pass
|
|
419
|
+
return g.Point.concatenate(ps)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def plot_3view(st: State | list[State] | dict[str, State], plotfun: Callable, gap: float, legend_vstep=10):
|
|
423
|
+
|
|
424
|
+
allsts = State.stack(st, "grp") if not isinstance(st, State) else st
|
|
425
|
+
|
|
426
|
+
width = allsts.pos.max() - allsts.pos.min()
|
|
427
|
+
center = allsts.pos.min() + width / 2
|
|
428
|
+
|
|
429
|
+
st0 = applysts(st, lambda s: s.move(g.Transformation(-center)))
|
|
430
|
+
st1 = applysts(st0, lambda s: create_ortho_state(s, 'x', width, gap))
|
|
431
|
+
st2 = applysts(st0, lambda s: create_ortho_state(s, 'z', width, gap))
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
fig = go.Figure(data=
|
|
435
|
+
plotfun(st0) + plotfun(st1) + plotfun(st2)
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
anprops = dict(
|
|
439
|
+
showarrow=False,
|
|
440
|
+
font=dict(size=16, family="Rockwell"),
|
|
441
|
+
xanchor="center",
|
|
442
|
+
yanchor="middle",
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
fig = fig.update_layout(
|
|
447
|
+
template="plotly_white",
|
|
448
|
+
scene=dict(
|
|
449
|
+
camera=dict(
|
|
450
|
+
eye=dict(x=0, y=-1, z=0),
|
|
451
|
+
center=dict(x=0, y=0, z=0),
|
|
452
|
+
projection=dict(type="orthographic"),
|
|
453
|
+
),
|
|
454
|
+
xaxis=dict(visible=False),
|
|
455
|
+
yaxis=dict(visible=False),
|
|
456
|
+
zaxis=dict(visible=False),
|
|
457
|
+
annotations=[
|
|
458
|
+
dict(
|
|
459
|
+
x=0, y=0, z=-width.z[0] / 2 - gap/2,
|
|
460
|
+
text="Front View",
|
|
461
|
+
|
|
462
|
+
) | anprops,
|
|
463
|
+
dict(
|
|
464
|
+
x=0, y=0, z=width.z[0] / 2 + width.y[0] / 2 + gap/2,
|
|
465
|
+
text="Top View",
|
|
466
|
+
) | anprops
|
|
467
|
+
,
|
|
468
|
+
dict(
|
|
469
|
+
x=-width.x[0] / 2 - width.y[0] / 2 - gap, y=0, z=-width.z[0] / 2 - gap/2,
|
|
470
|
+
text="Left View",
|
|
471
|
+
) | anprops
|
|
472
|
+
] + ([] if not isinstance(st, dict) else [dict(
|
|
473
|
+
x=-width.x[0] / 2 - width.y[0] / 2 - gap,
|
|
474
|
+
y=0,
|
|
475
|
+
z=width.z[0] / 2 + width.y[0] / 2 + gap/2 + i * legend_vstep,
|
|
476
|
+
text=k,
|
|
477
|
+
font=dict(size=16, family="Rockwell", color=px.colors.qualitative.Plotly[i]),
|
|
478
|
+
showarrow=False,
|
|
479
|
+
) for i, k in enumerate(st.keys())] )
|
|
480
|
+
),
|
|
481
|
+
margin=dict(l=0, r=0, b=0, t=0),
|
|
482
|
+
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
return resize_3d_fig(fig, 600, False)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def resize_3d_fig(fig: go.Figure, width: int | None, width_is_height: bool=False, scale: float=1):
|
|
489
|
+
"""Resize a figure to the given width, height and zoom level.
|
|
490
|
+
preserves the aspect ratio of the scene.
|
|
491
|
+
Assumes view is in the positive Y direction
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
all_points = get_points(fig)
|
|
495
|
+
|
|
496
|
+
btm_left = all_points.min()
|
|
497
|
+
top_right = all_points.max()
|
|
498
|
+
|
|
499
|
+
bb = (top_right - btm_left)
|
|
500
|
+
width = width or (fig.layout.height if width_is_height else fig.layout.width) or 600
|
|
501
|
+
zoom = (0.008 * scale*width/(bb.z[0] if width_is_height else bb.x[0]))
|
|
502
|
+
ar = bb * zoom
|
|
503
|
+
height = ar.x[0] * width / ar.z[0] if width_is_height else ar.z[0] * width / ar.x[0]
|
|
504
|
+
fig.update_layout(
|
|
505
|
+
width=height if width_is_height else width,
|
|
506
|
+
height=width if width_is_height else height,
|
|
507
|
+
scene=dict(
|
|
508
|
+
aspectratio=dict(x=ar.x[0], y=ar.y[0], z=ar.z[0]),
|
|
509
|
+
camera=dict(
|
|
510
|
+
eye=dict(x=0, y=-1, z=0),
|
|
511
|
+
center=dict(x=0, y=0, z=0),
|
|
512
|
+
projection=dict(type="orthographic"),
|
|
513
|
+
)
|
|
514
|
+
),
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
return fig
|
|
@@ -2,21 +2,14 @@
|
|
|
2
2
|
import plotly.graph_objects as go
|
|
3
3
|
import plotly.io as pio
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
generic3d_template = go.layout.Template(layout=go.Layout(
|
|
6
7
|
margin=dict(l=0, r=0, t=0, b=0),
|
|
7
|
-
scene=dict(
|
|
8
|
-
aspectmode='data',
|
|
9
|
-
),
|
|
10
|
-
legend=dict(
|
|
11
|
-
font=dict(size=20),
|
|
12
|
-
yanchor="top",
|
|
13
|
-
y=0.99,
|
|
14
|
-
xanchor="left",
|
|
15
|
-
x=0.01
|
|
16
|
-
)
|
|
8
|
+
scene=dict(aspectmode='data')
|
|
17
9
|
))
|
|
18
10
|
|
|
19
|
-
pio.templates["
|
|
11
|
+
pio.templates["generic3d"] = generic3d_template
|
|
12
|
+
pio.templates["flight3d"] = generic3d_template
|
|
20
13
|
|
|
21
14
|
judges_view_template = go.layout.Template(layout=go.Layout(scene_camera=dict(
|
|
22
15
|
up=dict(x=0, y=0, z=1),
|
|
@@ -32,10 +25,9 @@ pio.templates["judge_view"] = judges_view_template
|
|
|
32
25
|
clean_paper_template = go.layout.Template(layout=go.Layout(
|
|
33
26
|
margin=dict(l=0, r=0, t=0, b=0),
|
|
34
27
|
scene=dict(
|
|
35
|
-
aspectmode='data',
|
|
36
28
|
xaxis = dict(visible=False),
|
|
37
29
|
yaxis = dict(visible=False),
|
|
38
|
-
zaxis =dict(visible=False)
|
|
30
|
+
zaxis = dict(visible=False)
|
|
39
31
|
),
|
|
40
32
|
legend=dict(
|
|
41
33
|
font=dict(size=20),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from typing import Literal
|
|
1
2
|
import plotly.graph_objects as go
|
|
2
3
|
import plotting.templates
|
|
3
4
|
from geometry import Point, Coord, Transformation
|
|
@@ -23,7 +24,7 @@ def boxtrace():
|
|
|
23
24
|
)]
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def meshes(npoints, seq: State, colour, scale=1, _obj: OBJ=None):
|
|
27
|
+
def meshes(npoints, seq: State | Transformation, colour: str=None, scale=1, _obj: OBJ=None):
|
|
27
28
|
_obj = obj if _obj is None else _obj
|
|
28
29
|
if scale != 1:
|
|
29
30
|
_obj = _obj.scale(scale)
|
|
@@ -36,10 +37,10 @@ def meshes(npoints, seq: State, colour, scale=1, _obj: OBJ=None):
|
|
|
36
37
|
locs = locs + list(np.cumsum(np.full(npoints-2, len(seq) / (npoints-1))).astype(int))
|
|
37
38
|
|
|
38
39
|
ms = []
|
|
39
|
-
for loc in locs:
|
|
40
|
+
for i, loc in enumerate(locs):
|
|
40
41
|
ms.append(_obj.transform(
|
|
41
|
-
|
|
42
|
-
).create_mesh(colour,f"{seq.time.t[loc]:.1f}"))
|
|
42
|
+
seq.iloc[loc].transform if isinstance(seq, State) else seq[loc]
|
|
43
|
+
).create_mesh(colour or "grey",f"{(seq.time.t[loc] if isinstance(seq, State) else i):.1f}"))
|
|
43
44
|
return ms
|
|
44
45
|
|
|
45
46
|
def vector(origin, direction, **kwargs):
|
|
@@ -106,12 +107,12 @@ def elementtraces(manoeuvre, sec: State):
|
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
|
|
109
|
-
def tiptrace(seq, span, **kwargs):
|
|
110
|
+
def tiptrace(seq, span, line=None, **kwargs):
|
|
110
111
|
|
|
111
112
|
def make_offset_trace(pos, colour):
|
|
112
113
|
tr = trace3d(
|
|
113
114
|
*seq.body_to_world(pos).data.T,
|
|
114
|
-
**dict(
|
|
115
|
+
**dict(line=dict(color=colour, width=1) | ({} if line is None else line), **kwargs)
|
|
115
116
|
)
|
|
116
117
|
tr['showlegend'] = False
|
|
117
118
|
return tr
|
|
@@ -201,24 +202,25 @@ def aoa_trace(sec, dash="dash", colours = px.colors.qualitative.Plotly):
|
|
|
201
202
|
#sec = sec.append_columns(sec.aoa())
|
|
202
203
|
return sec_col_trace(sec, ["alpha", "beta"], dash, colours, np.degrees)
|
|
203
204
|
|
|
204
|
-
def axestrace(cid: Coord, length:float=20.0):
|
|
205
|
+
def axestrace(cid: Coord | Transformation, length:float=20.0, **kwargs):
|
|
205
206
|
ntraces = []
|
|
206
207
|
colours = {"x":"red", "y":"blue", "z":"green"}
|
|
207
208
|
for i, ci in enumerate(cid):
|
|
209
|
+
if isinstance(ci, Transformation):
|
|
210
|
+
ci = ci.apply(Coord.zero())
|
|
208
211
|
for ax, col in zip([ci.x_axis, ci.y_axis, ci.z_axis], list("xyz")):
|
|
209
212
|
axis = Point.concatenate([ci.origin, ci.origin + ax * length])
|
|
210
213
|
ntraces.append(go.Scatter3d(
|
|
211
214
|
x=axis.x, y=axis.y, z=axis.z, mode="lines",
|
|
212
215
|
line=dict(color=colours[col]),
|
|
213
|
-
name=col,
|
|
214
|
-
|
|
216
|
+
name=f"{i}_{col}",
|
|
217
|
+
**kwargs
|
|
215
218
|
))
|
|
216
219
|
|
|
217
220
|
return ntraces
|
|
218
221
|
|
|
219
222
|
|
|
220
223
|
|
|
221
|
-
|
|
222
224
|
def _npinterzip(a, b):
|
|
223
225
|
"""
|
|
224
226
|
takes two numpy arrays and zips them.
|
|
@@ -243,7 +245,7 @@ def _npinterzip(a, b):
|
|
|
243
245
|
return c
|
|
244
246
|
|
|
245
247
|
|
|
246
|
-
def ribbon(sec: State, span: float, color, **kwargs):
|
|
248
|
+
def ribbon(sec: State, span: float, color, hover: Literal["i", "t"]='i', **kwargs):
|
|
247
249
|
"""TODO make the colouring more generic
|
|
248
250
|
"""
|
|
249
251
|
|
|
@@ -252,7 +254,13 @@ def ribbon(sec: State, span: float, color, **kwargs):
|
|
|
252
254
|
|
|
253
255
|
points = Point(_npinterzip(left.data, right.data))
|
|
254
256
|
|
|
255
|
-
|
|
257
|
+
match hover:
|
|
258
|
+
case "i":
|
|
259
|
+
text=[f"{i}" for i in np.arange(len(sec)*2)]
|
|
260
|
+
case _:
|
|
261
|
+
text=[f"{t:.1f}" for t in _npinterzip(sec.t, sec.t)]
|
|
262
|
+
|
|
263
|
+
|
|
256
264
|
_i = np.array(range(len(points) - 2)) # 1 2 3 4 5
|
|
257
265
|
|
|
258
266
|
_js = np.array(range(1, len(points), 2))
|
|
@@ -265,5 +273,7 @@ def ribbon(sec: State, span: float, color, **kwargs):
|
|
|
265
273
|
x=points.x, y=points.y, z=points.z, i=_i, j=_j, k=_k,
|
|
266
274
|
intensitymode="cell",
|
|
267
275
|
facecolor=np.full(len(_i), color),
|
|
276
|
+
text=text,
|
|
277
|
+
hovertemplate='i:%{text}<br>',
|
|
268
278
|
**kwargs
|
|
269
279
|
)]
|