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