]> git.scottworley.com Git - batteryviewer/blame_incremental - chart.c
Draw axis if data crosses zero
[batteryviewer] / chart.c
... / ...
CommitLineData
1/*
2 * h/t https://ptomato.name/advanced-gtk-techniques/html/custom-container.html
3 * for examples of how to make GTK widgets
4 */
5
6#include "chart.h"
7#include <float.h>
8#include <gtk/gtk.h>
9
10#define BV_CHART_PRIVATE(obj) \
11 (G_TYPE_INSTANCE_GET_PRIVATE((obj), BV_CHART_TYPE, BVChartPrivate))
12
13typedef struct _BVChartPrivate BVChartPrivate;
14
15struct _BVChartPrivate {
16 GArray *points;
17 float minx, miny, maxx, maxy;
18};
19
20struct BVChartPoint {
21 float x, y;
22};
23
24struct ScreenPoint {
25 float x, y;
26};
27
28G_DEFINE_TYPE_WITH_CODE(BVChart, bv_chart, GTK_TYPE_DRAWING_AREA,
29 G_ADD_PRIVATE(BVChart))
30
31typedef void (*cairo_draw_func)(cairo_t *cr, double x, double y);
32
33static struct ScreenPoint to_screen(BVChartPrivate *priv,
34 GtkAllocation *allocation,
35 struct BVChartPoint *p) {
36 int margin = 3;
37 float xscale = (allocation->width - 2 * margin) / (priv->maxx - priv->minx);
38 float yscale = (allocation->height - 2 * margin) / (priv->miny - priv->maxy);
39 struct ScreenPoint screen_p = {
40 .x = xscale * (p->x - priv->minx) + margin,
41 .y = yscale * (p->y - priv->maxy) + margin,
42 };
43 return screen_p;
44}
45
46static void draw_data(BVChartPrivate *priv, cairo_t *cr,
47 GtkAllocation *allocation) {
48 cairo_set_source_rgb(cr, 0.0, 0.0, 1.0);
49 float gap_threshold = 3.0;
50 float prevx = 0.0;
51 for (guint i = 0; i < priv->points->len; i++) {
52 struct BVChartPoint *p =
53 &g_array_index(priv->points, struct BVChartPoint, i);
54 gboolean is_gap = p->x - prevx > gap_threshold;
55 cairo_draw_func f = is_gap ? cairo_move_to : cairo_line_to;
56 struct ScreenPoint screen_p = to_screen(priv, allocation, p);
57 f(cr, screen_p.x, screen_p.y);
58 prevx = p->x;
59 }
60 cairo_stroke(cr);
61}
62
63static void draw_axis(BVChartPrivate *priv, cairo_t *cr,
64 GtkAllocation *allocation) {
65 if (priv->miny < 0.0 && priv->maxy > 0.0) {
66 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
67 struct BVChartPoint p = {.x = priv->minx, .y = 0.0};
68 struct ScreenPoint screen_p = to_screen(priv, allocation, &p);
69 cairo_move_to(cr, screen_p.x, screen_p.y);
70 p.x = priv->maxx;
71 screen_p = to_screen(priv, allocation, &p);
72 cairo_line_to(cr, screen_p.x, screen_p.y);
73 cairo_stroke(cr);
74 }
75}
76
77static gboolean bv_chart_draw(GtkWidget *widget, cairo_t *cr) {
78 BVChart *chart = BV_CHART(widget);
79 BVChartPrivate *priv = bv_chart_get_instance_private(chart);
80
81 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
82 cairo_paint(cr);
83
84 if (priv->points->len < 2)
85 return TRUE;
86
87 GtkAllocation allocation;
88 gtk_widget_get_allocation(widget, &allocation);
89
90 draw_axis(priv, cr, &allocation);
91 draw_data(priv, cr, &allocation);
92
93 return TRUE;
94}
95
96static void bv_chart_class_init(BVChartClass *klass) {
97 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
98 widget_class->draw = bv_chart_draw;
99}
100
101static void bv_chart_init(BVChart *chart) {
102 gtk_widget_set_has_window(GTK_WIDGET(chart), FALSE);
103 BVChartPrivate *priv = bv_chart_get_instance_private(chart);
104 gboolean zero_terminated = FALSE;
105 gboolean clear_ = FALSE;
106 priv->points =
107 g_array_new(zero_terminated, clear_, sizeof(struct BVChartPoint));
108 priv->minx = FLT_MAX;
109 priv->miny = FLT_MAX;
110 priv->maxx = FLT_MIN;
111 priv->maxy = FLT_MIN;
112}
113
114GtkWidget *bv_chart_new() {
115 return GTK_WIDGET(g_object_new(bv_chart_get_type(), NULL));
116}
117
118void bv_chart_add_point(BVChart *chart, float x, float y) {
119 BVChartPrivate *priv = bv_chart_get_instance_private(chart);
120 if (x < priv->minx)
121 priv->minx = x;
122 if (y < priv->miny)
123 priv->miny = y;
124 if (x > priv->maxx)
125 priv->maxx = x;
126 if (y > priv->maxy)
127 priv->maxy = y;
128 struct BVChartPoint p = {.x = x, .y = y};
129 g_array_append_val(priv->points, p);
130}