Skip to content

Commit 8771d24

Browse files
committed
Added graph plot's central api
1 parent 31e65eb commit 8771d24

3 files changed

Lines changed: 195 additions & 44 deletions

File tree

docs/Plot/plot.md

Lines changed: 52 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -303,21 +303,29 @@ auto cfg = AnimateConfig()
303303
.size_px(240, 140) // Size in pixels (auto-converts to chars)
304304
.labels("cyan") // Color for X/Y axis labels
305305
.ranges("magenta") // Color for min/max range values
306-
.set_title("My Animation");
306+
.set_title("My Animation")
307+
.loop(false); // Stop after duration (don't loop)
307308
```
308309

309310
**Available configuration methods:**
310311

311-
| Method | Description | Default |
312-
| -------------------- | ------------------------------------------ | --------- |
313-
| `.x_range(min, max)` | X axis range | -10 to 10 |
314-
| `.time(seconds)` | Animation duration | 10.0 |
315-
| `.framerate(fps)` | Frames per second | 30 |
316-
| `.size(w, h)` | Size in terminal characters | 80×24 |
317-
| `.size_px(w, h)` | Size in pixels (Braille converts to chars) | - |
318-
| `.labels(color)` | Color for X/Y axis labels | "cyan" |
319-
| `.ranges(color)` | Color for min/max values | "magenta" |
320-
| `.set_title(text)` | Plot title | "" |
312+
| Method | Description | Default |
313+
| -------------------- | ---------------------------------------------------- | --------- |
314+
| `.x_range(min, max)` | X axis range | -10 to 10 |
315+
| `.time(seconds)` | Animation duration | 10.0 |
316+
| `.framerate(fps)` | Frames per second | 30 |
317+
| `.size(w, h)` | Size in terminal characters | 80×24 |
318+
| `.size_px(w, h)` | Size in pixels (Braille converts to chars) | - |
319+
| `.labels(color)` | Color for X/Y axis labels | "cyan" |
320+
| `.ranges(color)` | Color for min/max values | "magenta" |
321+
| `.set_title(text)` | Plot title | "" |
322+
| `.loop(bool)` | Loop animation (true) or stop after duration (false) | true |
323+
324+
**Signal Handling:**
325+
326+
- Press **Ctrl+C** to stop the animation at any time
327+
- Terminal state (cursor visibility) is automatically restored via RAII
328+
- Signal handlers are properly restored after animation ends
321329

322330
---
323331

@@ -411,6 +419,39 @@ int main() {
411419

412420
---
413421

422+
### Non-Looping Animation
423+
424+
Use `.loop(false)` to make the animation stop after the specified duration:
425+
426+
```cpp
427+
#include <pythonic/pythonic.hpp>
428+
using namespace pythonic::plot;
429+
430+
int main() {
431+
auto cfg = AnimateConfig()
432+
.x_range(-PI, PI)
433+
.time(5.0) // Run for exactly 5 seconds
434+
.framerate(30)
435+
.size(80, 24)
436+
.loop(false); // Stop after duration (don't repeat)
437+
438+
animate(cfg,
439+
std::make_tuple(
440+
[](double t, double x) { return std::sin(x + t); },
441+
"green",
442+
"wave"
443+
)
444+
);
445+
446+
std::cout << "Animation completed after 5 seconds!\n";
447+
return 0;
448+
}
449+
```
450+
451+
> **Note:** When `.loop(true)` (default), the animation repeats until you press Ctrl+C.
452+
453+
---
454+
414455
### Complete Animation Example
415456
416457
Here's a comprehensive example demonstrating all features:

include/pythonic/pythonicPlot.hpp

Lines changed: 104 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
#include <thread>
6161
#include <chrono>
6262
#include <atomic>
63+
#include <csignal>
6364
#include <map>
6465
#include <set>
6566
#include <memory>
@@ -1527,6 +1528,9 @@ namespace pythonic
15271528

15281529
// ==================== Animation Support ====================
15291530

1531+
// Global flag for signal handling in animations
1532+
inline std::atomic<bool> g_animation_interrupted{false};
1533+
15301534
/**
15311535
* @brief Configuration for plot animations
15321536
*
@@ -1542,7 +1546,7 @@ namespace pythonic
15421546
* cfg.height = 40;
15431547
*
15441548
* // Or using the fluent builder pattern:
1545-
* auto cfg = AnimateConfig().x_range(-PI, PI).size(120, 40).fps(60);
1549+
* auto cfg = AnimateConfig().x_range(-PI, PI).size(120, 40).fps(60).loop(false);
15461550
*/
15471551
struct AnimateConfig
15481552
{
@@ -1553,6 +1557,7 @@ namespace pythonic
15531557
int width = 80; ///< Width in terminal characters
15541558
int height = 24; ///< Height in terminal characters
15551559
bool use_pixels = false; ///< If true, width/height are pixels (converted to chars)
1560+
bool loop_animation = true; ///< If true, loop animation; if false, stop after duration
15561561
std::string label_color = "cyan"; ///< Color for X/Y axis labels
15571562
std::string range_color = "magenta"; ///< Color for min/max range values
15581563
std::string title = ""; ///< Optional title
@@ -1603,6 +1608,11 @@ namespace pythonic
16031608
title = t;
16041609
return *this;
16051610
}
1611+
AnimateConfig &loop(bool l)
1612+
{
1613+
loop_animation = l;
1614+
return *this;
1615+
}
16061616

16071617
// Get actual character dimensions (converts pixels if needed)
16081618
int char_width() const { return use_pixels ? (width + 1) / 2 : width; }
@@ -1612,6 +1622,53 @@ namespace pythonic
16121622
// Internal implementation for multi-plot animation
16131623
namespace detail
16141624
{
1625+
// Signal handler for clean Ctrl+C exit
1626+
inline void animation_signal_handler(int /*sig*/)
1627+
{
1628+
g_animation_interrupted.store(true);
1629+
}
1630+
1631+
// RAII helper to manage terminal state and signal handlers
1632+
class TerminalStateGuard
1633+
{
1634+
public:
1635+
TerminalStateGuard()
1636+
{
1637+
// Reset interrupt flag
1638+
g_animation_interrupted.store(false);
1639+
1640+
// Save old signal handler and install ours
1641+
_old_handler = std::signal(SIGINT, animation_signal_handler);
1642+
1643+
// Hide cursor
1644+
std::cout << "\033[?25l" << std::flush;
1645+
}
1646+
1647+
~TerminalStateGuard()
1648+
{
1649+
// Restore cursor
1650+
std::cout << "\033[?25h" << std::flush;
1651+
1652+
// Clear screen back to normal position and add newline
1653+
std::cout << "\n"
1654+
<< std::flush;
1655+
1656+
// Restore old signal handler
1657+
if (_old_handler != SIG_ERR)
1658+
{
1659+
std::signal(SIGINT, _old_handler);
1660+
}
1661+
}
1662+
1663+
bool interrupted() const
1664+
{
1665+
return g_animation_interrupted.load();
1666+
}
1667+
1668+
private:
1669+
void (*_old_handler)(int) = SIG_DFL;
1670+
};
1671+
16151672
template <typename... PlotEntries>
16161673
inline void animate_impl(const AnimateConfig &cfg, PlotEntries &&...plots)
16171674
{
@@ -1648,57 +1705,71 @@ namespace pythonic
16481705
(sample_range(plots), ...);
16491706
fig.ylim(y_min - 0.1 * (y_max - y_min), y_max + 0.1 * (y_max - y_min));
16501707

1651-
// Hide cursor
1652-
std::cout << "\033[?25l" << std::flush;
1708+
// Use RAII guard for terminal state and signal handling
1709+
TerminalStateGuard terminal_guard;
16531710

16541711
auto frame_time = std::chrono::microseconds(static_cast<int>(1000000.0 / cfg.fps));
16551712
auto start_time = std::chrono::steady_clock::now();
16561713

1657-
try
1714+
while (!terminal_guard.interrupted())
16581715
{
1659-
while (true)
1660-
{
1661-
auto now = std::chrono::steady_clock::now();
1662-
double t = std::chrono::duration<double>(now - start_time).count();
1716+
auto now = std::chrono::steady_clock::now();
1717+
double t = std::chrono::duration<double>(now - start_time).count();
16631718

1664-
if (t > cfg.duration)
1719+
// Handle looping vs stopping
1720+
if (t > cfg.duration)
1721+
{
1722+
if (cfg.loop_animation)
16651723
{
16661724
t = std::fmod(t, cfg.duration);
1725+
start_time = now - std::chrono::duration_cast<std::chrono::steady_clock::duration>(
1726+
std::chrono::duration<double>(t));
16671727
}
1728+
else
1729+
{
1730+
// Non-looping: stop after duration
1731+
break;
1732+
}
1733+
}
16681734

1669-
fig.clear();
1670-
fig.set_time(t);
1735+
fig.clear();
1736+
fig.set_time(t);
1737+
1738+
auto plot_one = [&](auto &&plot_tuple)
1739+
{
1740+
auto &f = std::get<0>(plot_tuple);
1741+
const auto &color = std::get<1>(plot_tuple);
16711742

1672-
auto plot_one = [&](auto &&plot_tuple)
1743+
if constexpr (std::tuple_size_v<std::decay_t<decltype(plot_tuple)>> >= 3)
16731744
{
1674-
auto &f = std::get<0>(plot_tuple);
1675-
const auto &color = std::get<1>(plot_tuple);
1745+
const auto &label = std::get<2>(plot_tuple);
1746+
fig.plot_animated(f, cfg.x_min, cfg.x_max, color, label);
1747+
}
1748+
else
1749+
{
1750+
fig.plot_animated(f, cfg.x_min, cfg.x_max, color);
1751+
}
1752+
};
16761753

1677-
if constexpr (std::tuple_size_v<std::decay_t<decltype(plot_tuple)>> >= 3)
1678-
{
1679-
const auto &label = std::get<2>(plot_tuple);
1680-
fig.plot_animated(f, cfg.x_min, cfg.x_max, color, label);
1681-
}
1682-
else
1683-
{
1684-
fig.plot_animated(f, cfg.x_min, cfg.x_max, color);
1685-
}
1686-
};
1754+
(plot_one(plots), ...);
16871755

1688-
(plot_one(plots), ...);
1756+
std::cout << "\033[H" << fig.render_to_string();
16891757

1690-
std::cout << "\033[H" << fig.render_to_string();
1758+
// Show different message depending on loop setting
1759+
if (cfg.loop_animation)
1760+
{
16911761
std::cout << "\nt = " << std::fixed << std::setprecision(2) << t
16921762
<< "s (Press Ctrl+C to stop)" << std::flush;
1693-
1694-
std::this_thread::sleep_for(frame_time);
16951763
}
1696-
}
1697-
catch (...)
1698-
{
1699-
}
1764+
else
1765+
{
1766+
std::cout << "\nt = " << std::fixed << std::setprecision(2) << t
1767+
<< "s / " << cfg.duration << "s" << std::flush;
1768+
}
17001769

1701-
std::cout << "\033[?25h" << std::flush;
1770+
std::this_thread::sleep_for(frame_time);
1771+
}
1772+
// TerminalStateGuard destructor will restore terminal state
17021773
}
17031774
} // namespace detail
17041775

plan/next.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
🚀 Feature Ideas for Pythonic
2+
Based on your library's capabilities, here are some exciting feature ideas:
3+
4+
1. Interactive Mode
5+
Add keyboard controls during animation (pause/resume with spacebar, speed up/slow down with +/-)
6+
Mouse click to inspect values at specific points
7+
Real-time parameter adjustment
8+
2. Plot Types Expansion
9+
Bar charts - Animated bar graphs with Braille rendering
10+
Scatter plots - Point clouds with different markers
11+
Heatmaps - 2D intensity maps using Unicode blocks
12+
Histograms - Real-time updating histograms
13+
Polar plots - Circular coordinate system
14+
3. Multi-Figure Layout
15+
Subplots - Grid of multiple independent figures (2x2, 3x1, etc.)
16+
Split screen - Synchronized animations side by side
17+
Dashboard mode - Multiple plots updating independently
18+
4. Export Features
19+
GIF export - Export animations as animated GIFs directly
20+
SVG export - Vector graphics output for documentation
21+
ANSI to HTML - Convert terminal output to styled HTML
22+
5. Data Source Integration
23+
Live data plotting - Plot from stdin, files, or sockets in real-time
24+
CSV/JSON import - Load data from files easily
25+
Streaming mode - Continuous data feed plotting
26+
6. Visual Enhancements
27+
Grid lines - Optional background grid
28+
Annotations - Text labels at specific points
29+
Markers - Highlight specific data points
30+
Fill/area under curve - Shaded regions between curves
31+
Gradient colors - Color transitions along curves
32+
7. Math/Scientific Features
33+
FFT visualization - Show frequency spectrum
34+
Phase space plots - 2D parametric animations
35+
Vector fields - Arrow plots for derivatives/gradients
36+
3D surface plots - Isometric projection rendering
37+
8. Performance & Quality
38+
Double buffering - Smoother animations with less flicker
39+
Adaptive resolution - Auto-scale based on terminal size

0 commit comments

Comments
 (0)