├── .gitignore ├── LICENSE ├── README.md ├── demo ├── gtkchart-demo.c └── meson.build ├── images ├── gauge-angular.png ├── gauge-line.png ├── line.png ├── number.png └── scatter.png ├── meson.build ├── meson_options.txt └── src ├── gtkchart.c ├── gtkchart.h └── meson.build /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | libgtkchart is available for use under the following license, commonly known as 2 | the 3-clause (or "modified") BSD license: 3 | 4 | ----------------------------------------------------------------------- 5 | Copyright (c) 2022 Martin Lund 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions 10 | are met: 11 | 12 | 1. Redistributions of source code must retain the above copyright 13 | notice, this list of conditions and the following disclaimer. 14 | 2. Redistributions in binary form must reproduce the above copyright 15 | notice, this list of conditions and the following disclaimer in the 16 | documentation and/or other materials provided with the distribution. 17 | 3. Neither the name of the copyright holders nor contributors may be 18 | used to endorse or promote products derived from this software 19 | without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | ----------------------------------------------------------------------- 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GtkChart - a chart widget for GTK4 2 | 3 | [Screencast from 2022-09-30 16-06-43 - gtkchart-fixed.webm](https://user-images.githubusercontent.com/353407/193289610-34b59005-1bc7-4f60-ba94-ecf34d601a4b.webm) 4 | 5 | ## Introduction 6 | 7 | The beginnings of a small chart widget library for GTK4. 8 | 9 | This is a spinoff from the [lxi-tools](https://lxi-tools.github.io) project. 10 | 11 | Much can be improved but it is better than nothing. 12 | 13 | ### Looking for maintainers or contributors to help improve this library. Please reach out if interested! 14 | 15 | ## Motivation 16 | 17 | Couldn't find a chart widget library for GTK4 so created one. 18 | 19 | ## Features 20 | 21 | * Various chart types 22 | * Line 23 | * Scatter 24 | * Linear gauge 25 | * Angular gauge 26 | * Number 27 | * Dimensionally scalable 28 | * Plot and render data live 29 | * Save rendered chart to PNG 30 | * Save plotted data to CSV 31 | * Demo application 32 | 33 | ## Todo 34 | 35 | * Optimize Cairo/snapshot code 36 | * Make charts handle negative axis ranges 37 | * Introduce gtk\_chart\_set\_x\_min() 38 | * Introduce gtk\_chart\_set\_y\_min() 39 | * Make charts zoomable 40 | * Make chart axes autoscale depending on plot value 41 | * Etc. 42 | 43 | ## Usage 44 | 45 | ``` 46 | #include 47 | #include 48 | ... 49 | // Required for GtkChart to be recognized by builder 50 | gtk_chart_get_type(); 51 | ... 52 | GtkChart *chart = GTK_CHART(gtk_chart_new()); 53 | gtk_chart_set_type(chart, GTK_CHART_TYPE_LINE); 54 | gtk_chart_set_title(chart, "Title"); 55 | gtk_chart_set_label(chart, "Label"); 56 | gtk_chart_set_x_label(chart, "X label [ ]"); 57 | gtk_chart_set_y_label(chart, "Y label [ ]"); 58 | gtk_chart_set_x_max(chart, 100); 59 | gtk_chart_set_y_max(chart, 10); 60 | gtk_chart_set_width(chart, 800); 61 | ... 62 | gtk_chart_plot_point(chart, 0.0, 0.0); 63 | gtk_chart_plot_point(chart, 1.0, 1.0); 64 | gtk_chart_plot_point(chart, 2.0, 2.0); 65 | gtk_chart_plot_point(chart, 3.0, 3.0); 66 | ... 67 | gtk_chart_save_csv(chart, "chart0.csv"); 68 | gtk_chart_save_png(chart, "chart0.png"); 69 | ``` 70 | 71 | See the [demo application](demo/gtkchart-demo.c) for more details. 72 | 73 | ## Chart Types 74 | 75 |

76 | 77 | 78 | 79 | 80 | 81 |

82 | 83 | -------------------------------------------------------------------------------- /demo/gtkchart-demo.c: -------------------------------------------------------------------------------- 1 | /* 2 | * GtkChart - demo application 3 | * 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static GtkChart *chart; 13 | static GMutex producer_mutex; 14 | 15 | struct point_t 16 | { 17 | double x; 18 | double y; 19 | }; 20 | 21 | static gboolean gui_chart_plot_thread(gpointer user_data) 22 | { 23 | struct point_t *point = user_data; 24 | 25 | gtk_chart_plot_point(chart, point->x, point->y); 26 | 27 | return G_SOURCE_REMOVE; 28 | } 29 | 30 | static void activate_cb(GApplication *app, gpointer user_data) 31 | { 32 | GtkWidget *window; 33 | (void)user_data; 34 | 35 | window = gtk_application_window_new(GTK_APPLICATION(app)); 36 | gtk_window_set_title(GTK_WINDOW(window), "gtkchart-demo"); 37 | gtk_window_set_default_size(GTK_WINDOW(window), 1000, 700); 38 | 39 | chart = GTK_CHART(gtk_chart_new()); 40 | gtk_chart_set_type(chart, GTK_CHART_TYPE_LINE); 41 | gtk_chart_set_title(chart, "Sine signal, f(x) = 5 + 3sin(x)"); 42 | gtk_chart_set_label(chart, "Label"); 43 | gtk_chart_set_x_label(chart, "X label [unit]"); 44 | gtk_chart_set_y_label(chart, "Y label [unit]"); 45 | gtk_chart_set_x_max(chart, 100); 46 | gtk_chart_set_y_max(chart, 10); 47 | gtk_chart_set_width(chart, 800); 48 | 49 | // gtk_chart_set_color(chart, "text_color", "red"); 50 | // gtk_chart_set_color(chart, "line_color", "blue"); 51 | // gtk_chart_set_color(chart, "grid_color", "green"); 52 | // gtk_chart_set_color(chart, "axis_color", "yellow"); 53 | // gtk_chart_set_font(chart, "Arial"); 54 | 55 | gtk_window_set_child(GTK_WINDOW(window), GTK_WIDGET(chart)); 56 | 57 | gtk_widget_show(window); 58 | 59 | g_mutex_unlock(&producer_mutex); 60 | } 61 | 62 | static gpointer producer_function(gpointer user_data) 63 | { 64 | (void)user_data; 65 | struct point_t point; 66 | double x = 0; 67 | double y; 68 | 69 | g_mutex_lock(&producer_mutex); 70 | 71 | // Plot demo sine signal 72 | while (x < 100) 73 | { 74 | y = 5.0 + 3.0*sin(x); 75 | point.x = x; 76 | point.y = y; 77 | g_idle_add(gui_chart_plot_thread, &point); 78 | usleep(20*1000); 79 | x += 0.1; 80 | } 81 | 82 | gtk_chart_save_csv(chart, "chart0.csv"); 83 | gtk_chart_save_png(chart, "chart0.png"); 84 | 85 | g_mutex_unlock(&producer_mutex); 86 | 87 | return NULL; 88 | } 89 | 90 | int main(int argc, char **argv) 91 | { 92 | int stat; 93 | g_autoptr (AdwApplication) app = NULL; 94 | 95 | // Spawn data generating thread 96 | g_mutex_lock(&producer_mutex); 97 | GThread *producer_thread = g_thread_new("producer thread", producer_function, NULL); 98 | 99 | // Start GTK/Adwaita application 100 | app = adw_application_new("com.github.lundmar.gtkchart", G_APPLICATION_DEFAULT_FLAGS); 101 | g_signal_connect(app, "activate", G_CALLBACK (activate_cb), NULL); 102 | stat = g_application_run(G_APPLICATION (app), argc, argv); 103 | 104 | // Clean up 105 | g_object_unref(app); 106 | g_thread_join(producer_thread); 107 | 108 | return stat; 109 | } 110 | -------------------------------------------------------------------------------- /demo/meson.build: -------------------------------------------------------------------------------- 1 | cc = meson.get_compiler('c') 2 | libm_dep = cc.find_library('m', required : false) 3 | 4 | libgtk_dep = dependency('gtk4', version: '>= 4.5.0', required: true, 5 | fallback : ['gtk', 'libgtk_dep'], 6 | default_options: ['introspection=disabled', 7 | 'demos=false', 8 | 'build-examples=false', 9 | 'build-tests=false']) 10 | 11 | libadwaita_dep = dependency('libadwaita-1', version: '>= 1.0.1', required: true, 12 | fallback : ['libadwaita', 'libadwaita_dep'], 13 | default_options: ['introspection=disabled', 14 | 'examples=false', 15 | 'tests=false']) 16 | 17 | demo_deps = [libm_dep, libgtk_dep, libadwaita_dep] 18 | 19 | executable('gtkchart-demo', 20 | 'gtkchart-demo.c', 21 | dependencies: demo_deps, 22 | include_directories: include_directories('../src'), 23 | link_with: libgtkchart, 24 | install: false, 25 | ) 26 | -------------------------------------------------------------------------------- /images/gauge-angular.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lundmar/gtkchart/c33e42ecd4595e8ead553dc62fd07560b5d7485a/images/gauge-angular.png -------------------------------------------------------------------------------- /images/gauge-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lundmar/gtkchart/c33e42ecd4595e8ead553dc62fd07560b5d7485a/images/gauge-line.png -------------------------------------------------------------------------------- /images/line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lundmar/gtkchart/c33e42ecd4595e8ead553dc62fd07560b5d7485a/images/line.png -------------------------------------------------------------------------------- /images/number.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lundmar/gtkchart/c33e42ecd4595e8ead553dc62fd07560b5d7485a/images/number.png -------------------------------------------------------------------------------- /images/scatter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lundmar/gtkchart/c33e42ecd4595e8ead553dc62fd07560b5d7485a/images/scatter.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('libgtkchart', 'c', 2 | version: '0.2', 3 | license: 'BSD-3-Clause', 4 | meson_version: '>= 0.53.2', 5 | default_options: [ 'warning_level=2', 'buildtype=release', 'c_std=gnu11', 'optimization=2', 'b_lundef=false' ] 6 | ) 7 | 8 | subdir('src') 9 | 10 | pkg_mod = import('pkgconfig') 11 | pkg_mod.generate(libraries: libgtkchart, 12 | name: 'libgtkchart', 13 | filebase: 'libgtkchart', 14 | description: 'A small chart widget library for GTK4' 15 | ) 16 | 17 | build_demo = get_option('build_demo') 18 | if build_demo 19 | subdir('demo') 20 | endif 21 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('build_demo', 2 | type : 'boolean', value: false, 3 | description : 'Build demo application') 4 | -------------------------------------------------------------------------------- /src/gtkchart.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Martin Lund 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the copyright holders nor contributors may be 15 | * used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include "gtkchart.h" 33 | 34 | #define UNUSED(expr) do { (void)(expr); } while (0) 35 | 36 | struct chart_point_t 37 | { 38 | double x; 39 | double y; 40 | }; 41 | 42 | struct _GtkChart 43 | { 44 | GtkWidget parent_instance; 45 | int type; 46 | char *title; 47 | char *label; 48 | char *x_label; 49 | char *y_label; 50 | double x_max; 51 | double y_max; 52 | double value; 53 | double value_min; 54 | double value_max; 55 | int width; 56 | void *user_data; 57 | GSList *point_list; 58 | GtkSnapshot *snapshot; 59 | GdkRGBA text_color; 60 | GdkRGBA line_color; 61 | GdkRGBA grid_color; 62 | GdkRGBA axis_color; 63 | gchar *font_name; 64 | }; 65 | 66 | struct _GtkChartClass 67 | { 68 | GtkWidgetClass parent_class; 69 | }; 70 | 71 | G_DEFINE_TYPE (GtkChart, gtk_chart, GTK_TYPE_WIDGET) 72 | 73 | static void gtk_chart_init(GtkChart *self) 74 | { 75 | // Defaults 76 | self->type = GTK_CHART_TYPE_UNKNOWN; 77 | self->title = NULL; 78 | self->label = NULL; 79 | self->x_label = NULL; 80 | self->y_label = NULL; 81 | self->x_max = 100; 82 | self->y_max = 100; 83 | self->value_min = 0; 84 | self->value_max = 100; 85 | self->width = 500; 86 | self->snapshot = NULL; 87 | self->text_color.alpha = -1.0; 88 | self->line_color.alpha = -1.0; 89 | self->grid_color.alpha = -1.0; 90 | self->axis_color.alpha = -1.0; 91 | self->font_name = NULL; 92 | 93 | // Automatically use GTK font 94 | GtkSettings *widget_settings = gtk_widget_get_settings(&self->parent_instance); 95 | GValue font_name_value = G_VALUE_INIT; 96 | g_object_get_property(G_OBJECT (widget_settings), "gtk-font-name", &font_name_value); 97 | gchar *font_string = g_strdup_value_contents(&font_name_value); 98 | 99 | // Extract name of font from font string (" ") 100 | gchar *font_name = &font_string[1]; // Skip " 101 | for (unsigned int i=0; ifont_name = g_strdup(font_name); 110 | g_free(font_string); 111 | 112 | //gtk_widget_init_template (GTK_WIDGET (self)); 113 | } 114 | 115 | static void gtk_chart_finalize (GObject *object) 116 | { 117 | GtkChart *self = GTK_CHART (object); 118 | 119 | G_OBJECT_CLASS (gtk_chart_parent_class)->finalize (G_OBJECT (self)); 120 | } 121 | 122 | static void gtk_chart_dispose (GObject *object) 123 | { 124 | GtkChart *self = GTK_CHART (object); 125 | GtkWidget *child; 126 | 127 | while ((child = gtk_widget_get_first_child (GTK_WIDGET (object)))) 128 | { 129 | gtk_widget_unparent (child); 130 | } 131 | 132 | // Cleanup 133 | g_free(self->title); 134 | g_free(self->label); 135 | g_free(self->x_label); 136 | g_free(self->y_label); 137 | 138 | gdk_display_sync(gdk_display_get_default()); 139 | 140 | g_slist_free_full(g_steal_pointer(&self->point_list), g_free); 141 | g_slist_free(self->point_list); 142 | 143 | G_OBJECT_CLASS (gtk_chart_parent_class)->dispose (object); 144 | } 145 | 146 | static void chart_draw_line_or_scatter(GtkChart *self, 147 | GtkSnapshot *snapshot, 148 | float h, 149 | float w) 150 | { 151 | cairo_text_extents_t extents; 152 | char value[20]; 153 | 154 | // Assume aspect ratio w:h = 2:1 155 | 156 | // Set up Cairo region 157 | cairo_t * cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, w, h)); 158 | cairo_set_antialias (cr, CAIRO_ANTIALIAS_FAST); 159 | cairo_set_tolerance (cr, 1.5); 160 | gdk_cairo_set_source_rgba (cr, &self->text_color); 161 | cairo_select_font_face (cr, self->font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 162 | 163 | // Move coordinate system to bottom left 164 | cairo_translate(cr, 0, h); 165 | 166 | // Invert y-axis 167 | cairo_scale(cr, 1, -1); 168 | 169 | // Draw title 170 | cairo_set_font_size (cr, 15.0 * (w/650)); 171 | cairo_text_extents(cr, self->title, &extents); 172 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.9 * h - extents.height/2); 173 | cairo_save(cr); 174 | cairo_scale(cr, 1, -1); 175 | cairo_show_text (cr, self->title); 176 | cairo_restore(cr); 177 | 178 | // Draw x-axis label 179 | cairo_set_font_size (cr, 11.0 * (w/650)); 180 | cairo_text_extents(cr, self->x_label, &extents); 181 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.075 * h); 182 | cairo_save(cr); 183 | cairo_scale(cr, 1, -1); 184 | cairo_show_text (cr, self->x_label); 185 | cairo_restore(cr); 186 | 187 | // Draw y-axis label 188 | cairo_text_extents(cr, self->y_label, &extents); 189 | cairo_move_to (cr, 0.035 * w, 0.5 * h - extents.width/2); 190 | cairo_save(cr); 191 | cairo_rotate(cr, M_PI/2); 192 | cairo_scale(cr, 1, -1); 193 | cairo_show_text (cr, self->y_label); 194 | cairo_restore(cr); 195 | 196 | // Draw x-axis 197 | gdk_cairo_set_source_rgba (cr, &self->axis_color); 198 | cairo_set_line_width (cr, 1); 199 | cairo_move_to (cr, 0.1 * w, 0.2 * h); 200 | cairo_line_to (cr, 0.9 * w, 0.2 * h); 201 | cairo_stroke (cr); 202 | 203 | // Draw y-axis 204 | cairo_set_line_width (cr, 1); 205 | cairo_move_to (cr, 0.1 * w, 0.8 * h); 206 | cairo_line_to (cr, 0.1 * w, 0.2 * h); 207 | cairo_stroke (cr); 208 | 209 | // Draw x-axis value at 100% mark 210 | gdk_cairo_set_source_rgba (cr, &self->text_color); 211 | g_snprintf(value, sizeof(value), "%.1f", self->x_max); 212 | cairo_set_font_size (cr, 8.0 * (w/650)); 213 | cairo_text_extents(cr, value, &extents); 214 | cairo_move_to (cr, 0.9 * w - extents.width/2, 0.16 * h); 215 | cairo_save(cr); 216 | cairo_scale(cr, 1, -1); 217 | cairo_show_text (cr, value); 218 | cairo_restore(cr); 219 | 220 | // Draw x-axis value at 75% mark 221 | g_snprintf(value, sizeof(value), "%.1f", (self->x_max/4) * 3); 222 | cairo_set_font_size (cr, 8.0 * (w/650)); 223 | cairo_text_extents(cr, value, &extents); 224 | cairo_move_to (cr, 0.7 * w - extents.width/2, 0.16 * h); 225 | cairo_save(cr); 226 | cairo_scale(cr, 1, -1); 227 | cairo_show_text (cr, value); 228 | cairo_restore(cr); 229 | 230 | // Draw x-axis value at 50% mark 231 | g_snprintf(value, sizeof(value), "%.1f", self->x_max/2); 232 | cairo_set_font_size (cr, 8.0 * (w/650)); 233 | cairo_text_extents(cr, value, &extents); 234 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.16 * h); 235 | cairo_save(cr); 236 | cairo_scale(cr, 1, -1); 237 | cairo_show_text (cr, value); 238 | cairo_restore(cr); 239 | 240 | // Draw x-axis value at 25% mark 241 | g_snprintf(value, sizeof(value), "%.1f", self->x_max/4); 242 | cairo_set_font_size (cr, 8.0 * (w/650)); 243 | cairo_text_extents(cr, value, &extents); 244 | cairo_move_to (cr, 0.3 * w - extents.width/2, 0.16 * h); 245 | cairo_save(cr); 246 | cairo_scale(cr, 1, -1); 247 | cairo_show_text (cr, value); 248 | cairo_restore(cr); 249 | 250 | // Draw x-axis value at 0% mark 251 | cairo_set_font_size (cr, 8.0 * (w/650)); 252 | cairo_text_extents(cr, "0", &extents); 253 | cairo_move_to (cr, 0.1 * w - extents.width/2, 0.16 * h); 254 | cairo_save(cr); 255 | cairo_scale(cr, 1, -1); 256 | cairo_show_text (cr, "0"); 257 | cairo_restore(cr); 258 | 259 | // Draw y-axis value at 0% mark 260 | cairo_set_font_size (cr, 8.0 * (w/650)); 261 | cairo_text_extents(cr, "0", &extents); 262 | cairo_move_to (cr, 0.091 * w - extents.width, 0.191 * h); 263 | cairo_save(cr); 264 | cairo_scale(cr, 1, -1); 265 | cairo_show_text (cr, "0"); 266 | cairo_restore(cr); 267 | 268 | // Draw y-axis value at 25% mark 269 | g_snprintf(value, sizeof(value), "%.1f", self->y_max/4); 270 | cairo_set_font_size (cr, 8.0 * (w/650)); 271 | cairo_text_extents(cr, value, &extents); 272 | cairo_move_to (cr, 0.091 * w - extents.width, 0.34 * h); 273 | cairo_save(cr); 274 | cairo_scale(cr, 1, -1); 275 | cairo_show_text (cr, value); 276 | cairo_restore(cr); 277 | 278 | // Draw y-axis value at 50% mark 279 | g_snprintf(value, sizeof(value), "%.1f", self->y_max/2); 280 | cairo_set_font_size (cr, 8.0 * (w/650)); 281 | cairo_text_extents(cr, value, &extents); 282 | cairo_move_to (cr, 0.091 * w - extents.width, 0.49 * h); 283 | cairo_save(cr); 284 | cairo_scale(cr, 1, -1); 285 | cairo_show_text (cr, value); 286 | cairo_restore(cr); 287 | 288 | // Draw y-axis value at 75% mark 289 | g_snprintf(value, sizeof(value), "%.1f", (self->y_max/4) * 3); 290 | cairo_set_font_size (cr, 8.0 * (w/650)); 291 | cairo_text_extents(cr, value, &extents); 292 | cairo_move_to (cr, 0.091 * w - extents.width, 0.64 * h); 293 | cairo_save(cr); 294 | cairo_scale(cr, 1, -1); 295 | cairo_show_text (cr, value); 296 | cairo_restore(cr); 297 | 298 | // Draw y-axis value at 100% mark 299 | g_snprintf(value, sizeof(value), "%.1f", self->y_max); 300 | cairo_set_font_size (cr, 8.0 * (w/650)); 301 | cairo_text_extents(cr, value, &extents); 302 | cairo_move_to (cr, 0.091 * w - extents.width, 0.79 * h); 303 | cairo_save(cr); 304 | cairo_scale(cr, 1, -1); 305 | cairo_show_text (cr, value); 306 | cairo_restore(cr); 307 | 308 | // Draw grid x-line 25% 309 | gdk_cairo_set_source_rgba (cr, &self->grid_color); 310 | cairo_set_line_width (cr, 1); 311 | cairo_move_to (cr, 0.1 * w, 0.35 * h); 312 | cairo_line_to (cr, 0.9 * w, 0.35 * h); 313 | cairo_stroke (cr); 314 | 315 | // Draw grid x-line 50% 316 | cairo_set_line_width (cr, 1); 317 | cairo_move_to (cr, 0.1 * w, 0.5 * h); 318 | cairo_line_to (cr, 0.9 * w, 0.5 * h); 319 | cairo_stroke (cr); 320 | 321 | // Draw grid x-line 75% 322 | cairo_set_line_width (cr, 1); 323 | cairo_move_to (cr, 0.1 * w, 0.65 * h); 324 | cairo_line_to (cr, 0.9 * w, 0.65 * h); 325 | cairo_stroke (cr); 326 | 327 | // Draw grid x-line 100% 328 | cairo_set_line_width (cr, 1); 329 | cairo_move_to (cr, 0.1 * w, 0.8 * h); 330 | cairo_line_to (cr, 0.9 * w, 0.8 * h); 331 | cairo_stroke (cr); 332 | 333 | // Draw grid y-line 25% 334 | cairo_set_line_width (cr, 1); 335 | cairo_move_to (cr, 0.3 * w, 0.8 * h); 336 | cairo_line_to (cr, 0.3 * w, 0.2 * h); 337 | cairo_stroke (cr); 338 | 339 | // Draw grid y-line 50% 340 | cairo_set_line_width (cr, 1); 341 | cairo_move_to (cr, 0.5 * w, 0.8 * h); 342 | cairo_line_to (cr, 0.5 * w, 0.2 * h); 343 | cairo_stroke (cr); 344 | 345 | // Draw grid y-line 75% 346 | cairo_set_line_width (cr, 1); 347 | cairo_move_to (cr, 0.7 * w, 0.8 * h); 348 | cairo_line_to (cr, 0.7 * w, 0.2 * h); 349 | cairo_stroke (cr); 350 | 351 | // Draw grid y-line 100% 352 | cairo_set_line_width (cr, 1); 353 | cairo_move_to (cr, 0.9 * w, 0.8 * h); 354 | cairo_line_to (cr, 0.9 * w, 0.2 * h); 355 | cairo_stroke (cr); 356 | 357 | // Move coordinate system to (0,0) of drawn coordinate system 358 | cairo_translate(cr, 0.1 * w, 0.2 * h); 359 | gdk_cairo_set_source_rgba (cr, &self->line_color); 360 | cairo_set_line_width (cr, 2.0); 361 | 362 | // Calc scales 363 | float x_scale = (w - 2 * 0.1 * w) / self->x_max; 364 | float y_scale = (h - 2 * 0.2 * h) / self->y_max; 365 | 366 | // Draw data points from list 367 | GSList *l; 368 | for (l = self->point_list; l != NULL; l = l->next) 369 | { 370 | struct chart_point_t *point = l->data; 371 | 372 | switch (self->type) 373 | { 374 | case GTK_CHART_TYPE_LINE: 375 | if (l == self->point_list) 376 | { 377 | // Move to first point 378 | cairo_move_to(cr, point->x * x_scale, point->y * y_scale); 379 | } 380 | else 381 | { 382 | // Draw line to next point 383 | cairo_line_to(cr, point->x * x_scale, point->y * y_scale); 384 | cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); 385 | cairo_stroke(cr); 386 | cairo_move_to(cr, point->x * x_scale, point->y * y_scale); 387 | } 388 | break; 389 | 390 | case GTK_CHART_TYPE_SCATTER: 391 | // Draw square 392 | //cairo_rectangle (cr, point->x * x_scale, point->y * y_scale, 4, 4); 393 | //cairo_fill(cr); 394 | 395 | // Draw point 396 | cairo_set_line_width(cr, 3); 397 | cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); 398 | cairo_move_to(cr, point->x * x_scale, point->y * y_scale); 399 | cairo_close_path (cr); 400 | cairo_stroke (cr); 401 | break; 402 | } 403 | } 404 | 405 | cairo_destroy (cr); 406 | } 407 | 408 | static void chart_draw_number(GtkChart *self, 409 | GtkSnapshot *snapshot, 410 | float h, 411 | float w) 412 | { 413 | cairo_text_extents_t extents; 414 | char value[20]; 415 | 416 | // Assume aspect ratio w:h = 1:1 417 | 418 | // Set up Cairo region 419 | cairo_t * cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, w, h)); 420 | cairo_set_antialias (cr, CAIRO_ANTIALIAS_FAST); 421 | cairo_set_tolerance (cr, 1.5); 422 | gdk_cairo_set_source_rgba (cr, &self->text_color); 423 | cairo_select_font_face (cr, self->font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 424 | 425 | // Move coordinate system to bottom left 426 | cairo_translate(cr, 0, h); 427 | 428 | // Invert y-axis 429 | cairo_scale(cr, 1, -1); 430 | 431 | // Draw title 432 | cairo_set_font_size (cr, 15.0 * (w/650)); 433 | cairo_text_extents(cr, self->title, &extents); 434 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.9 * h - extents.height/2); 435 | cairo_save(cr); 436 | cairo_scale(cr, 1, -1); 437 | cairo_show_text (cr, self->title); 438 | cairo_restore(cr); 439 | 440 | // Draw label 441 | cairo_set_font_size (cr, 25.0 * (w/650)); 442 | cairo_text_extents(cr, self->label, &extents); 443 | cairo_move_to(cr, 0.5 * w - extents.width/2, 0.2 * h - extents.height/2); 444 | cairo_save(cr); 445 | cairo_scale(cr, 1, -1); 446 | cairo_show_text(cr, self->label); 447 | cairo_restore(cr); 448 | 449 | // Draw number 450 | g_snprintf(value, sizeof(value), "%.1f", self->value); 451 | cairo_set_font_size (cr, 140.0 * (w/650)); 452 | cairo_text_extents(cr, value, &extents); 453 | cairo_move_to(cr, 0.5 * w - extents.width/2, 0.5 * h - extents.height/2); 454 | cairo_save(cr); 455 | cairo_scale(cr, 1, -1); 456 | cairo_show_text(cr, value); 457 | cairo_restore(cr); 458 | 459 | cairo_destroy (cr); 460 | } 461 | 462 | static void chart_draw_gauge_linear(GtkChart *self, 463 | GtkSnapshot *snapshot, 464 | float h, 465 | float w) 466 | { 467 | cairo_text_extents_t extents; 468 | char value[20]; 469 | 470 | // Assume aspect ratio w:h = 1:2 471 | 472 | // Set up Cairo region 473 | cairo_t * cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, w, h)); 474 | cairo_set_antialias (cr, CAIRO_ANTIALIAS_FAST); 475 | cairo_set_tolerance (cr, 1.5); 476 | gdk_cairo_set_source_rgba (cr, &self->text_color); 477 | cairo_select_font_face (cr, self->font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 478 | 479 | // Move coordinate system to bottom left 480 | cairo_translate(cr, 0, h); 481 | 482 | // Invert y-axis 483 | cairo_scale(cr, 1, -1); 484 | 485 | // Draw title 486 | cairo_set_font_size (cr, 15.0 * (2*w/650)); 487 | cairo_text_extents(cr, self->title, &extents); 488 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.95 * h - extents.height/2); 489 | cairo_save(cr); 490 | cairo_scale(cr, 1, -1); 491 | cairo_show_text (cr, self->title); 492 | cairo_restore(cr); 493 | 494 | // Draw label 495 | cairo_set_font_size (cr, 25.0 * (w/650)); 496 | cairo_text_extents(cr, self->label, &extents); 497 | cairo_move_to(cr, 0.5 * w - extents.width/2, 0.05 * h - extents.height/2); 498 | cairo_save(cr); 499 | cairo_scale(cr, 1, -1); 500 | cairo_show_text(cr, self->label); 501 | cairo_restore(cr); 502 | 503 | // Draw minimum value 504 | g_snprintf(value, sizeof(value), "%.0f", self->value_min); 505 | cairo_set_font_size (cr, 25.0 * (w/650)); 506 | cairo_text_extents(cr, value, &extents); 507 | cairo_move_to(cr, 0.7 * w, 0.1 * h - extents.height/2); 508 | cairo_save(cr); 509 | cairo_scale(cr, 1, -1); 510 | cairo_show_text(cr, value); 511 | cairo_restore(cr); 512 | 513 | // Draw maximum value 514 | g_snprintf(value, sizeof(value), "%.0f", self->value_max); 515 | cairo_set_font_size (cr, 25.0 * (w/650)); 516 | cairo_text_extents(cr, value, &extents); 517 | cairo_move_to(cr, 0.7 * w, 0.9 * h - extents.height/2); 518 | cairo_save(cr); 519 | cairo_scale(cr, 1, -1); 520 | cairo_show_text(cr, value); 521 | cairo_restore(cr); 522 | 523 | // Draw minimum line 524 | gdk_cairo_set_source_rgba (cr, &self->axis_color); 525 | cairo_move_to(cr, 0.375 * w, 0.1 * h); 526 | cairo_line_to(cr, 0.625 * w, 0.1 * h); 527 | cairo_set_line_width (cr, 1); 528 | cairo_stroke (cr); 529 | 530 | // Draw maximum line 531 | cairo_move_to(cr, 0.375 * w, 0.9 * h); 532 | cairo_line_to(cr, 0.625 * w, 0.9 * h); 533 | cairo_set_line_width (cr, 1); 534 | cairo_stroke (cr); 535 | 536 | // Move coordinate system to (0,0) of gauge line start 537 | cairo_translate(cr, 0.5 * w, 0.1 * h); 538 | 539 | // Draw gauge line 540 | gdk_cairo_set_source_rgba (cr, &self->line_color); 541 | cairo_move_to(cr, 0, 0); 542 | float y_scale = (h - 2 * 0.1 * h) / self->value_max; 543 | cairo_set_line_width (cr, 0.2 * w); 544 | cairo_line_to(cr, 0, self->value * y_scale); 545 | cairo_stroke (cr); 546 | 547 | cairo_destroy (cr); 548 | } 549 | 550 | static void chart_draw_gauge_angular(GtkChart *self, 551 | GtkSnapshot *snapshot, 552 | float h, 553 | float w) 554 | { 555 | cairo_text_extents_t extents; 556 | char value[20]; 557 | 558 | // Assume aspect ratio w:h = 1:1 559 | 560 | // Set up Cairo region 561 | cairo_t * cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, w, h)); 562 | cairo_set_antialias (cr, CAIRO_ANTIALIAS_FAST); 563 | // cairo_set_tolerance (cr, 1.5); 564 | gdk_cairo_set_source_rgba (cr, &self->text_color); 565 | cairo_select_font_face (cr, self->font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 566 | 567 | // Move coordinate system to bottom left 568 | cairo_translate(cr, 0, h); 569 | 570 | // Invert y-axis 571 | cairo_scale(cr, 1, -1); 572 | 573 | // Draw title 574 | cairo_set_font_size (cr, 15.0 * (2*w/650)); 575 | cairo_text_extents(cr, self->title, &extents); 576 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.9 * h - extents.height/2); 577 | cairo_save(cr); 578 | cairo_scale(cr, 1, -1); 579 | cairo_show_text (cr, self->title); 580 | cairo_restore(cr); 581 | 582 | // Draw label 583 | cairo_set_font_size (cr, 25.0 * (w/650)); 584 | cairo_text_extents(cr, self->label, &extents); 585 | cairo_move_to(cr, 0.5 * w - extents.width/2, 0.1 * h - extents.height/2); 586 | cairo_save(cr); 587 | cairo_scale(cr, 1, -1); 588 | cairo_show_text(cr, self->label); 589 | cairo_restore(cr); 590 | 591 | // Draw minimum value 592 | g_snprintf(value, sizeof(value), "%.0f", self->value_min); 593 | cairo_set_font_size (cr, 25.0 * (w/650)); 594 | cairo_text_extents(cr, value, &extents); 595 | cairo_move_to(cr, 0.225 * w, 0.25 * h - extents.height/2); 596 | cairo_save(cr); 597 | cairo_scale(cr, 1, -1); 598 | cairo_show_text(cr, value); 599 | cairo_restore(cr); 600 | 601 | // Draw maximum value 602 | g_snprintf(value, sizeof(value), "%.0f", self->value_max); 603 | cairo_set_font_size (cr, 25.0 * (w/650)); 604 | cairo_text_extents(cr, value, &extents); 605 | cairo_move_to(cr, 0.77 * w - extents.width, 0.25 * h - extents.height/2); 606 | cairo_save(cr); 607 | cairo_scale(cr, 1, -1); 608 | cairo_show_text(cr, value); 609 | cairo_restore(cr); 610 | 611 | // Draw minimum line 612 | gdk_cairo_set_source_rgba (cr, &self->axis_color); 613 | cairo_move_to(cr, 0.08 * w, 0.25 * h); 614 | cairo_line_to(cr, 0.22 * w, 0.25 * h); 615 | cairo_set_line_width (cr, 1); 616 | cairo_stroke (cr); 617 | 618 | // Draw maximum line 619 | cairo_move_to(cr, 0.78 * w, 0.25 * h); 620 | cairo_line_to(cr, 0.92 * w, 0.25 * h); 621 | cairo_set_line_width (cr, 1); 622 | cairo_stroke (cr); 623 | 624 | // Re-invert y-axis 625 | cairo_scale(cr, 1, -1); 626 | 627 | // Draw arc 628 | gdk_cairo_set_source_rgba (cr, &self->line_color); 629 | double xc = 0.5 * w; 630 | double yc = -0.25 * h; 631 | double radius = 0.35 * w; 632 | double angle1 = 180 * (M_PI/180.0); 633 | double angle = self->value * (180 / (self->value_max)); 634 | double angle2 = 180 * (M_PI/180.0) + angle * (M_PI/180.0); 635 | cairo_set_line_width (cr, 0.1 * w); 636 | cairo_arc (cr, xc, yc, radius, angle1, angle2); 637 | cairo_stroke (cr); 638 | 639 | cairo_destroy (cr); 640 | } 641 | 642 | static void chart_draw_unknown_type(GtkChart *self, 643 | GtkSnapshot *snapshot, 644 | float h, 645 | float w) 646 | { 647 | UNUSED(self); 648 | 649 | cairo_text_extents_t extents; 650 | const char *warning = "Unknown chart type"; 651 | 652 | // Set up Cairo region 653 | cairo_t * cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT(0, 0, w, h)); 654 | gdk_cairo_set_source_rgba (cr, &self->text_color); 655 | cairo_select_font_face (cr, self->font_name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 656 | 657 | // Move coordinate system to bottom left 658 | cairo_translate(cr, 0, h); 659 | 660 | // Invert y-axis 661 | cairo_scale(cr, 1, -1); 662 | 663 | // Draw title 664 | cairo_set_font_size (cr, 30.0 * (w/650)); 665 | cairo_text_extents(cr, warning, &extents); 666 | cairo_move_to (cr, 0.5 * w - extents.width/2, 0.5 * h - extents.height/2); 667 | cairo_save(cr); 668 | cairo_scale(cr, 1, -1); 669 | cairo_show_text (cr, warning); 670 | cairo_restore(cr); 671 | 672 | cairo_destroy (cr); 673 | } 674 | 675 | 676 | static void gtk_chart_snapshot (GtkWidget *widget, 677 | GtkSnapshot *snapshot) 678 | { 679 | GtkChart *self = GTK_CHART(widget); 680 | 681 | float width = gtk_widget_get_width (widget); 682 | float height = gtk_widget_get_height (widget); 683 | 684 | // Automatically update colors if none set 685 | GtkStyleContext *context = gtk_widget_get_style_context(&self->parent_instance); 686 | if (self->text_color.alpha == -1.0) 687 | { 688 | gtk_style_context_get_color(context, &self->text_color); 689 | } 690 | if (self->line_color.alpha == -1.0) 691 | { 692 | gtk_style_context_lookup_color (context, "theme_selected_bg_color", &self->line_color); 693 | } 694 | if (self->grid_color.alpha == -1.0) 695 | { 696 | gtk_style_context_get_color(context, &self->grid_color); 697 | self->grid_color.alpha = 0.1; 698 | } 699 | if (self->axis_color.alpha == -1.0) 700 | { 701 | gtk_style_context_get_color(context, &self->axis_color); 702 | } 703 | 704 | // Draw various chart types 705 | switch (self->type) 706 | { 707 | case GTK_CHART_TYPE_LINE: 708 | case GTK_CHART_TYPE_SCATTER: 709 | chart_draw_line_or_scatter(self, snapshot, height, width); 710 | break; 711 | 712 | case GTK_CHART_TYPE_NUMBER: 713 | chart_draw_number(self, snapshot, height, width); 714 | break; 715 | 716 | case GTK_CHART_TYPE_GAUGE_LINEAR: 717 | chart_draw_gauge_linear(self, snapshot, height, width); 718 | break; 719 | 720 | case GTK_CHART_TYPE_GAUGE_ANGULAR: 721 | chart_draw_gauge_angular(self, snapshot, height, width); 722 | break; 723 | 724 | default: 725 | chart_draw_unknown_type(self, snapshot, height, width); 726 | break; 727 | } 728 | 729 | self->snapshot = snapshot; 730 | } 731 | 732 | static void gtk_chart_class_init (GtkChartClass *class) 733 | { 734 | GObjectClass *object_class = G_OBJECT_CLASS (class); 735 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); 736 | 737 | object_class->finalize = gtk_chart_finalize; 738 | object_class->dispose = gtk_chart_dispose; 739 | 740 | widget_class->snapshot = gtk_chart_snapshot; 741 | } 742 | 743 | EXPORT GtkWidget * gtk_chart_new (void) 744 | { 745 | 746 | return g_object_new (GTK_TYPE_CHART, NULL); 747 | } 748 | 749 | EXPORT void gtk_chart_set_user_data(GtkChart *chart, void *user_data) 750 | { 751 | chart->user_data = user_data; 752 | } 753 | 754 | EXPORT void * gtk_chart_get_user_data(GtkChart *chart) 755 | { 756 | return chart->user_data; 757 | } 758 | 759 | EXPORT void gtk_chart_set_type(GtkChart *chart, GtkChartType type) 760 | { 761 | chart->type = type; 762 | } 763 | 764 | EXPORT void gtk_chart_set_title(GtkChart *chart, const char *title) 765 | { 766 | 767 | g_assert_nonnull(chart); 768 | g_assert_nonnull(title); 769 | 770 | if (chart->title != NULL) 771 | { 772 | g_free(chart->title); 773 | } 774 | 775 | chart->title = g_strdup(title); 776 | } 777 | 778 | EXPORT void gtk_chart_set_label(GtkChart *chart, const char *label) 779 | { 780 | g_assert_nonnull(chart); 781 | g_assert_nonnull(label); 782 | 783 | if (chart->label != NULL) 784 | { 785 | g_free(chart->label); 786 | } 787 | 788 | chart->label = g_strdup(label); 789 | } 790 | 791 | EXPORT void gtk_chart_set_x_label(GtkChart *chart, const char *x_label) 792 | { 793 | g_assert_nonnull(chart); 794 | g_assert_nonnull(x_label); 795 | 796 | if (chart->x_label != NULL) 797 | { 798 | g_free(chart->x_label); 799 | } 800 | 801 | chart->x_label = g_strdup(x_label); 802 | } 803 | 804 | EXPORT void gtk_chart_set_y_label(GtkChart *chart, const char *y_label) 805 | { 806 | g_assert_nonnull(chart); 807 | g_assert_nonnull(y_label); 808 | 809 | if (chart->y_label != NULL) 810 | { 811 | g_free(chart->y_label); 812 | } 813 | 814 | chart->y_label = g_strdup(y_label); 815 | } 816 | 817 | EXPORT void gtk_chart_set_x_max(GtkChart *chart, double x_max) 818 | { 819 | chart->x_max = x_max; 820 | } 821 | 822 | EXPORT void gtk_chart_set_y_max(GtkChart *chart, double y_max) 823 | { 824 | chart->y_max = y_max; 825 | } 826 | 827 | EXPORT void gtk_chart_set_width(GtkChart *chart, int width) 828 | { 829 | chart->width = width; 830 | } 831 | 832 | EXPORT void gtk_chart_plot_point(GtkChart *chart, double x, double y) 833 | { 834 | // Allocate memory for new point 835 | struct chart_point_t *point = g_new0(struct chart_point_t, 1); 836 | point->x = x; 837 | point->y = y; 838 | 839 | // Add point to list to be drawn 840 | chart->point_list = g_slist_append(chart->point_list, point); 841 | 842 | // Queue draw of widget 843 | if (GTK_IS_WIDGET(chart)) 844 | { 845 | gtk_widget_queue_draw(GTK_WIDGET(chart)); 846 | } 847 | } 848 | 849 | EXPORT void gtk_chart_set_value(GtkChart *chart, double value) 850 | { 851 | chart->value = value; 852 | 853 | // Queue draw of widget 854 | if (GTK_IS_WIDGET(chart)) 855 | { 856 | gtk_widget_queue_draw(GTK_WIDGET(chart)); 857 | } 858 | } 859 | 860 | EXPORT void gtk_chart_set_value_min(GtkChart *chart, double value) 861 | { 862 | chart->value_min = value; 863 | } 864 | 865 | EXPORT void gtk_chart_set_value_max(GtkChart *chart, double value) 866 | { 867 | chart->value_max = value; 868 | } 869 | 870 | EXPORT bool gtk_chart_save_csv(GtkChart *chart, const char *filename) 871 | { 872 | struct chart_point_t *point; 873 | GSList *l; 874 | 875 | // Open file 876 | FILE *file = fopen(filename, "w"); // write only 877 | 878 | if (file == NULL) 879 | { 880 | g_print("Error: Could not open file\n"); 881 | return false; 882 | } 883 | 884 | // Write CSV data 885 | for (l = chart->point_list; l != NULL; l = l->next) 886 | { 887 | point = l->data; 888 | fprintf(file, "%f,%f\n", point->x, point->y); 889 | } 890 | 891 | // Close file 892 | fclose(file); 893 | 894 | return true; 895 | } 896 | 897 | EXPORT bool gtk_chart_save_png(GtkChart *chart, const char *filename) 898 | { 899 | int width = gtk_widget_get_width (GTK_WIDGET(chart)); 900 | int height = gtk_widget_get_height (GTK_WIDGET(chart)); 901 | 902 | // Get to the PNG image file from paintable 903 | GdkPaintable *paintable = gtk_widget_paintable_new (GTK_WIDGET(chart)); 904 | GtkSnapshot *snapshot = gtk_snapshot_new (); 905 | gdk_paintable_snapshot (paintable, snapshot, width, height); 906 | GskRenderNode *node = gtk_snapshot_free_to_node (snapshot); 907 | GskRenderer *renderer = gsk_cairo_renderer_new (); 908 | gsk_renderer_realize (renderer, NULL, NULL); 909 | GdkTexture *texture = gsk_renderer_render_texture (renderer, node, NULL); 910 | gdk_texture_save_to_png (texture, filename); 911 | 912 | // Cleanup 913 | g_object_unref(texture); 914 | gsk_renderer_unrealize(renderer); 915 | g_object_unref(renderer); 916 | gsk_render_node_unref(node); 917 | g_object_unref(paintable); 918 | 919 | return true; 920 | } 921 | 922 | EXPORT bool gtk_chart_set_color(GtkChart *chart, char *name, char *color) 923 | { 924 | g_assert_nonnull(chart); 925 | g_assert_nonnull(name); 926 | 927 | if (strcmp(name, "text_color") == 0) 928 | { 929 | return gdk_rgba_parse(&chart->text_color, color); 930 | } 931 | else if (strcmp(name, "line_color") == 0) 932 | { 933 | return gdk_rgba_parse(&chart->line_color, color); 934 | } 935 | else if (strcmp(name, "grid_color") == 0) 936 | { 937 | return gdk_rgba_parse(&chart->grid_color, color); 938 | } 939 | else if (strcmp(name, "axis_color") == 0) 940 | { 941 | return gdk_rgba_parse(&chart->axis_color, color); 942 | } 943 | 944 | return false; 945 | } 946 | 947 | EXPORT void gtk_chart_set_font(GtkChart *chart, const char *name) 948 | { 949 | g_assert_nonnull(chart); 950 | g_assert_nonnull(name); 951 | 952 | if (chart->font_name != NULL) 953 | { 954 | g_free(chart->font_name); 955 | } 956 | 957 | chart->font_name = g_strdup(name); 958 | } 959 | -------------------------------------------------------------------------------- /src/gtkchart.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Martin Lund 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 3. Neither the name of the copyright holders nor contributors may be 15 | * used to endorse or promote products derived from this software 16 | * without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #pragma once 32 | 33 | #include 34 | 35 | #if defined _WIN32 || defined __CYGWIN__ 36 | #define EXPORT __declspec(dllexport) 37 | #else 38 | #if defined __GNUC__ 39 | #define EXPORT __attribute__ ((visibility("default"))) 40 | #else 41 | #pragma message ("Compiler does not support symbol visibility.") 42 | #define EXPORT 43 | #endif 44 | #endif 45 | 46 | G_BEGIN_DECLS 47 | 48 | #define GTK_TYPE_CHART (gtk_chart_get_type ()) 49 | G_DECLARE_FINAL_TYPE (GtkChart, gtk_chart, GTK, CHART, GtkWidget) 50 | 51 | typedef enum 52 | { 53 | GTK_CHART_TYPE_UNKNOWN, 54 | GTK_CHART_TYPE_LINE, 55 | GTK_CHART_TYPE_SCATTER, 56 | GTK_CHART_TYPE_GAUGE_ANGULAR, 57 | GTK_CHART_TYPE_GAUGE_LINEAR, 58 | GTK_CHART_TYPE_NUMBER 59 | } GtkChartType; 60 | 61 | EXPORT GtkWidget * gtk_chart_new (void); 62 | EXPORT void gtk_chart_set_type(GtkChart *chart, GtkChartType type); 63 | EXPORT void gtk_chart_set_title(GtkChart *chart, const char *title); 64 | EXPORT void gtk_chart_set_label(GtkChart *chart, const char *label); 65 | EXPORT void gtk_chart_set_x_label(GtkChart *chart, const char *x_label); 66 | EXPORT void gtk_chart_set_y_label(GtkChart *chart, const char *y_label); 67 | EXPORT void gtk_chart_set_x_max(GtkChart *chart, double x_max); 68 | EXPORT void gtk_chart_set_y_max(GtkChart *chart, double y_max); 69 | EXPORT void gtk_chart_set_width(GtkChart *chart, int width); 70 | EXPORT void gtk_chart_plot_point(GtkChart *chart, double x, double y); 71 | EXPORT void gtk_chart_set_value(GtkChart *chart, double value); 72 | EXPORT void gtk_chart_set_value_min(GtkChart *chart, double value); 73 | EXPORT void gtk_chart_set_value_max(GtkChart *chart, double value); 74 | EXPORT bool gtk_chart_save_csv(GtkChart *chart, const char *filename); 75 | EXPORT bool gtk_chart_save_png(GtkChart *chart, const char *filename); 76 | EXPORT void gtk_chart_set_user_data(GtkChart *chart, void *user_data); 77 | EXPORT void * gtk_chart_get_user_data(GtkChart *chart); 78 | EXPORT bool gtk_chart_set_color(GtkChart *chart, char *name, char *color); 79 | EXPORT void gtk_chart_set_font(GtkChart *chart, const char *name); 80 | 81 | G_END_DECLS 82 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | libgtkchart_sources = ['gtkchart.c'] 2 | 3 | libglib_dep = dependency('glib-2.0', version: '>= 2.70', required: true, 4 | fallback : ['glib', 'libglib_dep'], 5 | default_options: ['tests=false']) 6 | 7 | libgtk_dep = dependency('gtk4', version: '>= 4.4.0', required: true, 8 | fallback : ['gtk', 'libgtk_dep'], 9 | default_options: ['introspection=disabled', 10 | 'demos=false', 11 | 'build-examples=false', 12 | 'build-tests=false']) 13 | 14 | libgtkchart_deps = [libglib_dep, libgtk_dep] 15 | 16 | libgtkchart = shared_library('gtkchart', 17 | libgtkchart_sources, 18 | dependencies: libgtkchart_deps, 19 | install: true 20 | ) 21 | 22 | install_headers('gtkchart.h') 23 | --------------------------------------------------------------------------------