1use std::time::Duration;
2
3use gpui::prelude::*;
4use gpui::{
5 AppContext, Application, AsyncWindowContext, Bounds, Timer, WindowBounds, WindowOptions, div,
6 px, size,
7};
8
9use gpui_liveplot::{
10 AxisConfig, LineStyle, MarkerShape, MarkerStyle, Plot, PlotLinkGroup, PlotLinkOptions,
11 PlotView, PlotViewConfig, Range, Rgba, Series, SeriesKind, Theme, View,
12};
13
14struct AdvancedDemo {
15 top: gpui::Entity<PlotView>,
16 bottom: gpui::Entity<PlotView>,
17}
18
19impl gpui::Render for AdvancedDemo {
20 fn render(
21 &mut self,
22 _window: &mut gpui::Window,
23 _cx: &mut gpui::Context<Self>,
24 ) -> impl gpui::IntoElement {
25 div()
26 .size_full()
27 .flex()
28 .flex_col()
29 .child(div().flex_1().child(self.top.clone()))
30 .child(div().flex_1().child(self.bottom.clone()))
31 }
32}
33
34fn build_views(
35 cx: &mut gpui::App,
36) -> (
37 gpui::Entity<PlotView>,
38 gpui::Entity<PlotView>,
39 Series,
40 Series,
41) {
42 let mut stream_a = Series::line("stream-A").with_kind(SeriesKind::Line(LineStyle {
43 color: Rgba {
44 r: 0.2,
45 g: 0.82,
46 b: 0.95,
47 a: 1.0,
48 },
49 width: 2.0,
50 }));
51 let mut stream_b = Series::line("stream-B").with_kind(SeriesKind::Line(LineStyle {
52 color: Rgba {
53 r: 0.95,
54 g: 0.64,
55 b: 0.28,
56 a: 1.0,
57 },
58 width: 2.0,
59 }));
60
61 for i in 0..1_000 {
62 let phase = i as f64 * 0.02;
63 let _ = stream_a.push_y((phase * 0.9).sin() + 0.2 * (phase * 0.13).cos());
64 let _ = stream_b.push_y((phase * 0.45).cos() * 1.15 + 0.15 * (phase * 0.09).sin());
65 }
66
67 let events = Series::from_iter_points(
68 "events(scatter)",
69 (0..200).map(|i| {
70 let x = i as f64 * 80.0 + 40.0;
71 let y = (x * 0.02).sin() * 0.9;
72 gpui_liveplot::Point::new(x, y)
73 }),
74 SeriesKind::Scatter(MarkerStyle {
75 color: Rgba {
76 r: 0.95,
77 g: 0.25,
78 b: 0.55,
79 a: 1.0,
80 },
81 size: 5.0,
82 shape: MarkerShape::Circle,
83 }),
84 );
85
86 let baseline = Series::from_explicit_callback(
87 "baseline(callback)",
88 |x| (x * 0.015).sin() * 0.4,
89 Range::new(0.0, 25_000.0),
90 5_000,
91 SeriesKind::Line(LineStyle {
92 color: Rgba {
93 r: 0.45,
94 g: 0.45,
95 b: 0.5,
96 a: 0.8,
97 },
98 width: 1.0,
99 }),
100 );
101
102 let mut top_plot = Plot::builder()
103 .theme(Theme::dark())
104 .x_axis(AxisConfig::builder().title("Sample").build())
105 .y_axis(AxisConfig::builder().title("Top: stream + events").build())
106 .view(View::FollowLastN { points: 2_000 })
107 .build();
108 top_plot.add_series(&stream_a);
109 top_plot.add_series(&events);
110
111 let mut bottom_plot = Plot::builder()
112 .theme(Theme::dark())
113 .x_axis(AxisConfig::builder().title("Sample").build())
114 .y_axis(
115 AxisConfig::builder()
116 .title("Bottom: stream + baseline")
117 .build(),
118 )
119 .view(View::FollowLastNXY { points: 2_000 })
120 .build();
121 bottom_plot.add_series(&stream_b);
122 bottom_plot.add_series(&baseline);
123
124 let config = PlotViewConfig {
125 show_legend: true,
126 show_hover: true,
127 ..Default::default()
128 };
129
130 let link_group = PlotLinkGroup::new();
131 let options = PlotLinkOptions {
132 link_x: true,
133 link_y: false,
134 link_cursor: true,
135 link_brush: true,
136 link_reset: true,
137 };
138
139 let top = cx.new(|_| {
140 PlotView::with_config(top_plot, config.clone()).with_link_group(link_group.clone(), options)
141 });
142 let bottom =
143 cx.new(|_| PlotView::with_config(bottom_plot, config).with_link_group(link_group, options));
144
145 (top, bottom, stream_a, stream_b)
146}
147
148fn spawn_updates(
149 window: &mut gpui::Window,
150 cx: &mut gpui::App,
151 top: gpui::Entity<PlotView>,
152 bottom: gpui::Entity<PlotView>,
153 mut stream_a: Series,
154 mut stream_b: Series,
155) {
156 window
157 .spawn(cx, move |cx: &mut AsyncWindowContext| {
158 let mut cx = cx.clone();
159 async move {
160 let mut phase = 0.0_f64;
161 loop {
162 Timer::after(Duration::from_millis(16)).await;
163 let _ = stream_a.extend_y((0..120).map(|_| {
164 let y = (phase * 0.9).sin() + 0.2 * (phase * 0.13).cos();
165 phase += 0.02;
166 y
167 }));
168 let _ = stream_b.extend_y((0..120).map(|_| {
169 let y = (phase * 0.45).cos() * 1.15 + 0.15 * (phase * 0.09).sin();
170 phase += 0.02;
171 y
172 }));
173
174 let _ = cx.update(|_, cx| {
175 top.update(cx, |_view, view_cx| view_cx.notify());
176 bottom.update(cx, |_view, view_cx| view_cx.notify());
177 });
178 }
179 }
180 })
181 .detach();
182}
183
184fn main() {
185 Application::new().run(|cx| {
186 let options = WindowOptions {
187 window_bounds: Some(WindowBounds::Windowed(Bounds::centered(
188 None,
189 size(px(1_000.0), px(740.0)),
190 cx,
191 ))),
192 ..Default::default()
193 };
194
195 cx.open_window(options, |window, cx| {
196 let (top, bottom, stream_a, stream_b) = build_views(cx);
197 spawn_updates(window, cx, top.clone(), bottom.clone(), stream_a, stream_b);
198 cx.new(|_| AdvancedDemo { top, bottom })
199 })
200 .unwrap();
201 });
202}