diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6340679 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +*.o +*.so +*.gir +*.typelib +build*/ + +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +*.log +*.sqlite diff --git a/README.md b/README.md index f67c19c..e5e2874 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,59 @@ -# pocplot -Simple GTK3/GObject Graph Plotting +# PocPlot + +A simple GTK3/GObject graph plotting library. + +This project was born from the need for making some nice looking plots in +another project I was working on. Many years ago I had used the plotting +facilities from GtkExtra but this module was never ported to GTK3. Having +looked into porting the GtkExtra modules from GTK2 I decided it was too complex +a task, given my requirements, so I started looking around for something else. +I found Goat Plot but this had issues with recent GTK3. However it was almost +what I wanted. Ultimately after a false start at fixing the issues there I +decided to sketch out some ideas and write my own. + +PocPlot is the result of that effort. I've been using this with no problems +other than occasional bug-fixes for about ten months at the time of writing +(September 2020). + +## Installation + +PocPlot uses the +[Meson build system](https://mesonbuild.com/Getting-meson.html). +Refer to the Meson manual for standard configuration options. + +Meson supports multiple build system backends. To build with +[Ninja](https://ninja-build.org/) do the following: + +``` sh +$ meson [options] --buildtype=release builddir +$ ninja -C builddir install +``` + +The following build options are supported (defaults in bold text): +* -Ddocs=true/**false** - build and install the documentation. +* -Dintrospection=**true**/false - enable GObject introspection. +* -Dvapi=true/**false** - use `vapigen` to build Vala support. + +A catalogue file, `poc-catalog.xml` is installed for use with Glade. + +Note that the meson/ninja installer does not require an explicit `sudo`, +instead it will prompt for a password during install. + +## Dependencies + +PocPlot depends only on recent gtk3 and glib. + +## Documentation + +Documentation is built during installation and is accessible using Devhelp. +Usage should be fairly straightforward. + +## Why PocPlot? + +The name PocPlot is an hommage to GoatPlot (poc is Irish for a he-goat). +Being short, `poc` is also a convenient GObject namespace. + +## Licence + +PocPlot is licensed under the GNU Lesser General Public License version 2.1. +Please refer to LICENSE for full details. diff --git a/docs/meson.build b/docs/meson.build new file mode 100644 index 0000000..931ae31 --- /dev/null +++ b/docs/meson.build @@ -0,0 +1,27 @@ +docpath = join_paths(get_option('datadir'), 'gtk-doc', 'html') + +glib_prefix = dependency('glib-2.0').get_pkgconfig_variable('prefix') +glib_docpath = join_paths(glib_prefix, 'share', 'gtk-doc', 'html') + +poc_ignore = [ + 'pocbag.c', + 'pocbag.h', + 'mathextra.h', + 'mathextra.h.in', +] + +gnome.gtkdoc('poc', + dependencies : [declare_dependency(link_with : lib), gtkdep], + ignore_headers : poc_ignore, + gobject_typesfile : ['poc.types'], + main_xml : meson.project_name() + '-docs.xml', + src_dir : [meson.source_root(), meson.build_root()], + mkdb_args: [ + '--default-includes=poc.h', + ], + fixxref_args: [ + '--html-dir=@0@'.format(docpath), + '--extra-dir=@0@'.format(glib_docpath), + ], + install : true +) diff --git a/docs/poc-docs.xml b/docs/poc-docs.xml new file mode 100644 index 0000000..7248d71 --- /dev/null +++ b/docs/poc-docs.xml @@ -0,0 +1,92 @@ + + + + PocPlot Reference Manual + + This document is for the PocPlot library. + + + 2020 + Brian Stafford + + + + + PocPlot Overview + + + + + Object Hierarchy + + + + + + PocPlot Objects and Types + + + PocPlot Plot Objects + + + + + + + + PocPlot Additional Objects + + + + + + PocPlot Types + + + + + PocPlot Internals + + + + + + Symbols + + API Index + + + + Index of deprecated symbols + + + + + + License + + PocPlot is free software; you can redistribute it and/or modify it under + the terms of the GNU Lesser General Public License + as published by the Free Software Foundation; either version 2.1 of the + License, or (at your option) any later version. + + + + PocPlot is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General + Public License for more details. + + + + You should have received a copy of the GNU Lesser General + Public License along with PocPlot; if not, see https://www.gnu.org/licenses/. + + + + + diff --git a/docs/poc.types b/docs/poc.types new file mode 100644 index 0000000..d6bc507 --- /dev/null +++ b/docs/poc.types @@ -0,0 +1,11 @@ +poc_axis_get_type +poc_axis_mode_get_type +poc_dataset_get_type +poc_dataset_spline_get_type +poc_double_array_get_type +poc_legend_get_type +poc_line_style_get_type +poc_plot_get_type +poc_point_array_get_type +poc_point_get_type +poc_sample_get_type diff --git a/mathextra.h.in b/mathextra.h.in new file mode 100644 index 0000000..cc38bd4 --- /dev/null +++ b/mathextra.h.in @@ -0,0 +1,29 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _mathextra_h +#define _mathextra_h + +#mesondefine HAVE_EXP10 + +#ifndef HAVE_EXP10 +# define exp10(x) exp ((x) * M_LN10) +# define exp10f(x) expf ((x) * (float) M_LN10) +#endif + +#endif diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..3c68a33 --- /dev/null +++ b/meson.build @@ -0,0 +1,112 @@ +project('poc', 'c', version : '0.1.0', default_options: ['c_std=c11']) +pkg = import('pkgconfig') +gnome = import('gnome') + +pocsource = [ + 'poc.h', + 'pocaxis.c', + 'pocaxis.h', + 'pocbag.c', + 'pocbag.h', + 'pocdataset.c', + 'pocdataset.h', + 'pocdatasetspline.c', + 'pocdatasetspline.h', + 'poclegend.c', + 'poclegend.h', + 'pocplot.c', + 'pocplot.h', + 'pocsample.c', + 'pocsample.h', + 'pocspline.c', + 'pocspline.h', + 'poctypes.c', + 'poctypes.h', +] + +cflags = [ + '-D__poc_compile__=1', + '-DGSEAL_ENABLE', + '-DG_DISABLE_DEPRECATED', + '-DG_DISABLE_SINGLE_INCLUDES', + '-DGDK_DISABLE_DEPRECATED', + '-DGTK_DISABLE_DEPRECATED', + '-DGDK_DISABLE_SINGLE_INCLUDES', + '-DGTK_DISABLE_SINGLE_INCLUDES', + '-DGTK_MULTIDEVICE_SAFE=1', +] + +cflags_warnings = [ + '-Wstrict-prototypes', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Walloc-zero', + '-Wduplicated-branches', + '-Wpointer-arith', + '-Wcast-align', + '-Wwrite-strings', + '-Wdeclaration-after-statement', + '-Wshadow', + '-Wredundant-decls', + '-Wpacked', + '-Wbad-function-cast', +] + +cc = meson.get_compiler('c') +if get_option('warning_level') == '3' + cflags += cflags_warnings +endif +add_project_arguments(cc.get_supported_arguments(cflags), language: 'c') + +mdep = cc.find_library('m', required: false) +gtkdep = dependency('gtk+-3.0') + +mapfile = 'pocplot.map' +vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) + +lib = library('poc', pocsource, + link_args : vflag, + link_depends : mapfile, + dependencies : [gtkdep, mdep], + soversion : meson.project_version(), + install : true) + +host_os = host_machine.system() +os_win32 = host_os.contains('mingw') or host_os.contains('windows') + +if get_option('introspection') + gir = gnome.generate_gir(lib, + sources : pocsource, + nsversion : '0.1', + namespace : 'Poc', + identifier_prefix: 'Poc', + symbol_prefix: 'poc', + includes: ['GLib-2.0', 'GObject-2.0', 'Gtk-3.0'], + install: true + ) + + if get_option('vapi') + vapi = gnome.generate_vapi('poc-0.1', + sources: gir[0], + packages: [ 'gtk+-3.0' ], + install: true, + metadata_dirs: [ meson.current_source_dir() ], + ) + endif +endif + +if get_option('docs') + subdir ('docs') +endif + +mathextra = configuration_data() +mathextra.set('HAVE_EXP10', cc.has_function('exp10', prefix : '#include ')) +configure_file(input : 'mathextra.h.in', + output : 'mathextra.h', + configuration : mathextra) + +install_headers(['poc.h', 'pocplot.h', 'pocdataset.h', 'pocaxis.h', 'pocsample.h', + 'pocdatasetspline.h', 'poclegend.h', 'pocspline.h', 'poctypes.h']) +pkg.generate(lib) + +install_data(['poc-catalog.xml'], install_dir: 'share/glade/catalogs') diff --git a/meson_options.txt b/meson_options.txt new file mode 100644 index 0000000..d6908e9 --- /dev/null +++ b/meson_options.txt @@ -0,0 +1,3 @@ +option('docs', type: 'boolean', value: 'false') +option('introspection', type: 'boolean', value: 'true') +option('vapi', type: 'boolean', value: 'false') diff --git a/poc-catalog.xml b/poc-catalog.xml new file mode 100644 index 0000000..5a65e6d --- /dev/null +++ b/poc-catalog.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/poc.h b/poc.h new file mode 100644 index 0000000..d266a83 --- /dev/null +++ b/poc.h @@ -0,0 +1,35 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _poc_h +#define _poc_h + +#define __poc_h_inside__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#undef __poc_h_inside__ + +#endif diff --git a/pocaxis.c b/pocaxis.c new file mode 100644 index 0000000..7228a36 --- /dev/null +++ b/pocaxis.c @@ -0,0 +1,1545 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#define _GNU_SOURCE /* See feature_test_macros(7) */ +#include "pocaxis.h" +#include +#include "mathextra.h" + +/** + * SECTION: pocaxis + * @title: PocAxis + * @short_description: Axis gadget for #PocPlot + * @see_also: #PocPlot #PocDataset + * + * Each dataset added to a plot requires an X and Y axis. Several datasets may + * share axes. An axis controls the upper and lower bounds and the displayed + * portion of the plot data. If an axis is configured using a #GtkAdjustment + * it may display a portion of its full range under control of the adjustment, + * otherwise it shows the full range between its lower and upper bounds. + * + * Although axes contain drawing code, drawing always takes place under control + * of the #PocPlot and on its canvas. Axes are vertical or horizontally + * orientated and are drawn at the appropriate edge of the plot depending on + * the associated dataset and plot surface. It is possible for the same axis to + * be shared on more than one plot and may be orientated differently on each. + * Axes are also responsible from drawing grid lines in the main plot area. + */ + +typedef struct _PocAxisPrivate PocAxisPrivate; +struct _PocAxisPrivate + { + PocAxisMode axis_mode; + gdouble lower_bound; + gdouble upper_bound; + + gdouble major_interval; + gboolean auto_interval; + guint minor_divisions; + + GtkAdjustment *adjustment; + gulong adj_changed_id; + gulong adj_value_changed_id; + + gfloat tick_size; + gfloat label_size; + PocLineStyle major_grid; + PocLineStyle minor_grid; + + gchar *legend; + gfloat legend_size; + + /* Bounds adjusted for the current mode */ + gdouble lower_mode; + gdouble upper_mode; + gdouble minor_interval; + }; + +G_DEFINE_TYPE_WITH_PRIVATE (PocAxis, poc_axis, G_TYPE_OBJECT) + +#if !GLIB_CHECK_VERSION(2,62,0) +static inline void +g_clear_signal_handler (gulong *id, gpointer instance) +{ + if (instance != NULL && *id != 0) + g_signal_handler_disconnect (instance, *id); + *id = 0; +} +#endif + +/** + * poc_axis_new: + * + * Create a new #PocAxis + * + * Returns: (transfer full): New #PocAxis + */ +PocAxis * +poc_axis_new (void) +{ + return g_object_new (POC_TYPE_AXIS, NULL); +} + +/* GObject {{{1 */ + +enum + { + PROP_0, + PROP_AXIS_MODE, + PROP_LOWER_BOUND, + PROP_UPPER_BOUND, + PROP_MAJOR_INTERVAL, + PROP_AUTO_INTERVAL, + PROP_MINOR_DIVISIONS, + PROP_TICK_SIZE, + PROP_LABEL_SIZE, + PROP_MAJOR_GRID, + PROP_MINOR_GRID, + PROP_LEGEND, + PROP_LEGEND_SIZE, + PROP_ADJUSTMENT, + + N_PROPERTIES + }; +static GParamSpec *poc_axis_prop[N_PROPERTIES]; + +enum + { + UPDATE, + N_SIGNAL + }; +static guint poc_axis_signals[N_SIGNAL]; + + +static void poc_axis_dispose (GObject *object); +static void poc_axis_finalize (GObject *object); +static void poc_axis_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_axis_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static void poc_axis_update_bounds (PocAxis *self); + +static void +poc_axis_class_init (PocAxisClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->dispose = poc_axis_dispose; + gobject_class->finalize = poc_axis_finalize; + gobject_class->set_property = poc_axis_set_property; + gobject_class->get_property = poc_axis_get_property; + + poc_axis_prop[PROP_AXIS_MODE] = g_param_spec_enum ( + "axis-mode", + "Axis Mode", "Show axis as linear, octaves or decades", + POC_TYPE_AXIS_MODE, + POC_AXIS_LINEAR, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_LOWER_BOUND] = g_param_spec_double ( + "lower-bound", + "Lower Bound", "Lower bound of range to plot on this axis", + -1e6, 1e6, 0.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_UPPER_BOUND] = g_param_spec_double ( + "upper-bound", + "Upper Bound", "Upper bound of range to plot on this axis", + -1e6, 1e6, 1.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + poc_axis_prop[PROP_MAJOR_INTERVAL] = g_param_spec_double ( + "major-interval", + "Major Interval", "Major tick interval", + 0.0, 1e6, 10.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_AUTO_INTERVAL] = g_param_spec_boolean ( + "auto-interval", + "Auto-Interval", "Automatically set major tick interval", + TRUE, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_MINOR_DIVISIONS] = g_param_spec_uint ( + "minor-divisions", + "Minor Divisions", "Minor tick interval on this axis", + 1, 100, 5, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + poc_axis_prop[PROP_TICK_SIZE] = g_param_spec_float ( + "tick-size", + "Tick Size", "Tick size on this axis", + 0.0f, 100.0f, 10.0f, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_LABEL_SIZE] = g_param_spec_float ( + "label-size", + "Label Size", "Text size for tick label", + 2.0f, 100.0f, 10.0f, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_MAJOR_GRID] = g_param_spec_enum ( + "major-grid", "Major Grid", + "Line style for major grid lines", + POC_TYPE_LINE_STYLE, POC_LINE_STYLE_SOLID, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_MINOR_GRID] = g_param_spec_enum ( + "minor-grid", "Minor Grid", + "Line style for minor grid lines", + POC_TYPE_LINE_STYLE, POC_LINE_STYLE_DASH, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + + poc_axis_prop[PROP_LEGEND] = g_param_spec_string ( + "legend", + "Legend", "Text for the axis legend.", + NULL, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_axis_prop[PROP_LEGEND_SIZE] = g_param_spec_float ( + "legend-size", + "Legend Size", "Text size for axis legend", + 2.0f, 100.0f, 14.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + poc_axis_prop[PROP_ADJUSTMENT] = g_param_spec_object ( + "adjustment", + "Adjustment", "Adjustment to scroll between bounds on axis", + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_axis_prop); + + poc_axis_signals[UPDATE] = g_signal_new ( + "update", G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + + +static void +poc_axis_init (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + priv->axis_mode = POC_AXIS_LINEAR; + priv->lower_mode = priv->lower_bound = 0.0; + priv->upper_mode = priv->upper_bound = 1.0; + priv->major_interval = 10.0; + priv->auto_interval = TRUE; + priv->minor_divisions = 5; + priv->tick_size = 10.0f; + priv->label_size = 10.0f; + priv->major_grid = POC_LINE_STYLE_SOLID; + priv->minor_grid = POC_LINE_STYLE_DASH; + priv->legend_size = 14.0f; + poc_axis_update_bounds (self); +} + +static void +poc_axis_dispose (GObject *object) +{ + PocAxis *self = (PocAxis *) object; + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_clear_signal_handler (&priv->adj_changed_id, priv->adjustment); + g_clear_signal_handler (&priv->adj_value_changed_id, priv->adjustment); + g_clear_object (&priv->adjustment); + + G_OBJECT_CLASS (poc_axis_parent_class)->dispose (object); +} + +static void +poc_axis_finalize (GObject *object) +{ + PocAxis *self = (PocAxis *) object; + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_free (priv->legend); + G_OBJECT_CLASS (poc_axis_parent_class)->finalize (object); +} + +static void +poc_axis_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocAxis *self = POC_AXIS (object); + + switch (prop_id) + { + case PROP_AXIS_MODE: + poc_axis_set_axis_mode (self, g_value_get_enum (value)); + break; + case PROP_LOWER_BOUND: + poc_axis_set_lower_bound (self, g_value_get_double (value)); + break; + case PROP_UPPER_BOUND: + poc_axis_set_upper_bound (self, g_value_get_double (value)); + break; + + case PROP_MAJOR_INTERVAL: + poc_axis_set_major_interval (self, g_value_get_double (value)); + break; + case PROP_AUTO_INTERVAL: + poc_axis_set_auto_interval (self, g_value_get_boolean (value)); + break; + case PROP_MINOR_DIVISIONS: + poc_axis_set_minor_divisions (self, g_value_get_uint (value)); + break; + + case PROP_TICK_SIZE: + poc_axis_set_tick_size (self, g_value_get_float (value)); + break; + case PROP_LABEL_SIZE: + poc_axis_set_label_size (self, g_value_get_float (value)); + break; + case PROP_MAJOR_GRID: + poc_axis_set_major_grid (self, g_value_get_enum (value)); + break; + case PROP_MINOR_GRID: + poc_axis_set_minor_grid (self, g_value_get_enum (value)); + break; + + case PROP_LEGEND: + poc_axis_set_legend (self, g_value_get_string (value)); + break; + case PROP_LEGEND_SIZE: + poc_axis_set_legend_size (self, g_value_get_float (value)); + break; + + case PROP_ADJUSTMENT: + poc_axis_set_adjustment (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +poc_axis_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocAxis *self = POC_AXIS (object); + + switch (prop_id) + { + case PROP_AXIS_MODE: + g_value_set_enum (value, poc_axis_get_axis_mode (self)); + break; + case PROP_LOWER_BOUND: + g_value_set_double (value, poc_axis_get_lower_bound (self)); + break; + case PROP_UPPER_BOUND: + g_value_set_double (value, poc_axis_get_upper_bound (self)); + break; + + case PROP_MAJOR_INTERVAL: + g_value_set_double (value, poc_axis_get_major_interval (self)); + break; + case PROP_AUTO_INTERVAL: + g_value_set_boolean (value, poc_axis_get_auto_interval (self)); + break; + case PROP_MINOR_DIVISIONS: + g_value_set_uint (value, poc_axis_get_minor_divisions (self)); + break; + + case PROP_TICK_SIZE: + g_value_set_float (value, poc_axis_get_tick_size (self)); + break; + case PROP_LABEL_SIZE: + g_value_set_float (value, poc_axis_get_label_size (self)); + break; + case PROP_MAJOR_GRID: + g_value_set_enum (value, poc_axis_get_major_grid (self)); + break; + case PROP_MINOR_GRID: + g_value_set_enum (value, poc_axis_get_minor_grid (self)); + break; + + case PROP_LEGEND: + g_value_set_string (value, poc_axis_get_legend (self)); + break; + case PROP_LEGEND_SIZE: + g_value_set_float (value, poc_axis_get_legend_size (self)); + break; + + case PROP_ADJUSTMENT: + g_value_set_object (value, poc_axis_get_adjustment (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* Properties {{{1 */ + +/* legend position {{{2 */ + +/** + * poc_axis_set_axis_mode: + * @self: A #PocAxis + * @axis_mode: A #PocAxisMode + * + * Show axis as linear, octaves or decades. + */ +void +poc_axis_set_axis_mode (PocAxis *self, PocAxisMode axis_mode) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->axis_mode = axis_mode; + poc_axis_update_bounds (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_AXIS_MODE]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_axis_mode: + * @self: A #PocAxis + * + * Return the current axis mode. + * + * Returns: a #PocAxisMode + */ +PocAxisMode +poc_axis_get_axis_mode (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), POC_AXIS_LINEAR); + + return priv->axis_mode; +} + +/* lower bound {{{2 */ + +/** + * poc_axis_set_lower_bound: + * @self: A #PocAxis + * @bound: lower bound + * + * Set lower bound of range to plot on this axis. + */ +void +poc_axis_set_lower_bound (PocAxis *self, gdouble bound) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->lower_bound = bound; + poc_axis_update_bounds (self); + if (priv->adjustment != NULL) + gtk_adjustment_set_lower (priv->adjustment, priv->lower_mode); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LOWER_BOUND]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_lower_bound: + * @self: A #PocAxis + * + * Get the lower bound of the axis range. + * + * Returns: lower bound + */ +gdouble +poc_axis_get_lower_bound (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->lower_bound; +} + +/* upper bound {{{2 */ + +/** + * poc_axis_set_upper_bound: + * @self: A #PocAxis + * @bound: upper bound + * + * Set upper bound of range to plot on this axis. + */ +void +poc_axis_set_upper_bound (PocAxis *self, gdouble bound) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->upper_bound = bound; + poc_axis_update_bounds (self); + if (priv->adjustment != NULL) + gtk_adjustment_set_upper (priv->adjustment, priv->upper_mode); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_UPPER_BOUND]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_upper_bound: + * @self: A #PocAxis + * + * Get the upper bound of the axis range. + * + * Returns: upper bound + */ +gdouble +poc_axis_get_upper_bound (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->upper_bound; +} + +/* adjustment {{{2 */ +static void +poc_axis_adj_value_changed (GtkAdjustment *adjustment, PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble value, page_size; + + value = gtk_adjustment_get_value (adjustment); + page_size = gtk_adjustment_get_page_size (adjustment); + priv->lower_mode = value; + priv->upper_mode = value + page_size; + poc_axis_notify_update (self); +} + +static void +poc_axis_adj_changed (GtkAdjustment *adjustment, PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble lower, upper; + + lower = gtk_adjustment_get_lower (adjustment); + upper = gtk_adjustment_get_upper (adjustment); + switch (priv->axis_mode) + { + case POC_AXIS_LOG_OCTAVE: + lower = exp2 (lower); + upper = exp2 (upper); + break; + case POC_AXIS_LOG_DECADE: + lower = exp10 (lower); + upper = exp10 (upper); + break; + default: + break; + } + priv->lower_bound = lower; + priv->upper_bound = upper; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LOWER_BOUND]); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_UPPER_BOUND]); + poc_axis_adj_value_changed (adjustment, self); +} + +/** + * poc_axis_set_adjustment: + * @self: A #PocAxis + * @adjustment: A #GtkAdjustment + * + * Set an adjustment to scroll between bounds on axis. + */ +void +poc_axis_set_adjustment (PocAxis *self, GtkAdjustment *adjustment) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble page_size; + + g_return_if_fail (POC_IS_AXIS (self)); + g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment)); + + g_clear_signal_handler (&priv->adj_changed_id, priv->adjustment); + g_clear_signal_handler (&priv->adj_value_changed_id, priv->adjustment); + g_set_object (&priv->adjustment, adjustment); + if (priv->adjustment != NULL) + { + /* call poc_axis_update_bounds() to ensure _mode variables are correct */ + poc_axis_update_bounds (self); + page_size = priv->upper_mode - priv->lower_mode; + gtk_adjustment_configure (priv->adjustment, + priv->lower_mode, + priv->lower_mode, priv->upper_mode, + page_size / 10.0, page_size / 2.0, page_size); + priv->adj_changed_id = g_signal_connect_object (priv->adjustment, + "changed", + G_CALLBACK (poc_axis_adj_changed), + self, 0); + priv->adj_value_changed_id = g_signal_connect_object (priv->adjustment, + "value-changed", + G_CALLBACK (poc_axis_adj_value_changed), + self, 0); + } + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_ADJUSTMENT]); +} + +/** + * poc_axis_get_adjustment: + * @self: A #PocAxis + * + * Get the axis adjustment. + * + * Returns: (transfer none): A #GtkAdjustment + */ +GtkAdjustment * +poc_axis_get_adjustment (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), NULL); + + return priv->adjustment; +} + +/* major interval {{{2 */ + +/** + * poc_axis_set_major_interval: + * @self: A #PocAxis + * @interval: tick interval + * + * Set the major tick interval. + */ +void +poc_axis_set_major_interval (PocAxis *self, gdouble interval) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->major_interval = interval; + priv->auto_interval = FALSE; + poc_axis_update_bounds (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_MAJOR_INTERVAL]); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_AUTO_INTERVAL]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_major_interval: + * @self: A #PocAxis + * + * Get the major tick interval. + * + * Returns: interval + */ +gdouble +poc_axis_get_major_interval (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->major_interval; +} + +/* auto interval {{{2 */ + +/** + * poc_axis_set_auto_interval: + * @self: A #PocAxis + * @enabled: enable auto calculation + * + * Automatically set major tick interval if @enabled is %TRUE. + */ +void +poc_axis_set_auto_interval (PocAxis *self, gboolean enabled) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->auto_interval = enabled; + poc_axis_update_bounds (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_AUTO_INTERVAL]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_auto_interval: + * @self: A #PocAxis + * + * Get whether auto calculation of the major interval is enabled. + * + * Returns: %TRUE if enabled + */ +gboolean +poc_axis_get_auto_interval (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->auto_interval; +} + +/* minor divisions {{{2 */ + +/** + * poc_axis_set_minor_divisions: + * @self: A #PocAxis + * @divisions: tick interval + * + * Minor tick interval on this axis. + */ +void +poc_axis_set_minor_divisions (PocAxis *self, guint divisions) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + priv->minor_divisions = divisions; + poc_axis_update_bounds (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_MAJOR_INTERVAL]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_minor_divisions: + * @self: A #PocAxis + * + * Get the minor tick interval. + * + * Returns: interval + */ +guint +poc_axis_get_minor_divisions (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->minor_divisions; +} + +/* tick size {{{1 */ + +/** + * poc_axis_set_tick_size: + * @self: A #PocAxis + * @size: tick size + * + * Set the tick size on this axis. + */ +void +poc_axis_set_tick_size (PocAxis *self, gfloat size) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + priv->tick_size = size; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_TICK_SIZE]); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_tick_size: + * @self: A #PocAxis + * + * Get the tick size. + * + * Returns: tick size + */ +gfloat +poc_axis_get_tick_size (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->tick_size; +} + +/* label size {{{1 */ + +/** + * poc_axis_set_label_size: + * @self: A #PocAxis + * @size: text size + * + * Set text size for tick label. + */ +void +poc_axis_set_label_size (PocAxis *self, gfloat size) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + priv->label_size = size; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LABEL_SIZE]); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_label_size: + * @self: A #PocAxis + * + * Get text size for the tick label. + * + * Returns: size + */ +gfloat +poc_axis_get_label_size (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->label_size; +} + +/* legend {{{2 */ + +static gchar * +maybe_null (const gchar *str) +{ + return (str != NULL && *str != '\0') ? g_strdup (str) : NULL; +} + +/** + * poc_axis_set_legend: + * @self: A #PocAxis + * @legend: legend text + * + * Set the legend text for the axis. + */ +void +poc_axis_set_legend (PocAxis *self, const gchar *legend) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_free (priv->legend); + priv->legend = maybe_null (legend); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LEGEND]); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_legend: + * @self: A #PocAxis + * + * Get the legend text for the axis. + * + * Returns: (transfer none): legend text + */ +const gchar * +poc_axis_get_legend (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), NULL); + + return priv->legend; +} + +/* legend size {{{1 */ + +/** + * poc_axis_set_legend_size: + * @self: A #PocAxis + * @size: Size for legend text + * + * Set text size for the axis legend. + */ +void +poc_axis_set_legend_size (PocAxis *self, gfloat size) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + priv->legend_size = size; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LEGEND_SIZE]); + poc_axis_notify_update (self); +} + +/** + * poc_axis_get_legend_size: + * @self: A #PocAxis + * + * Get text size for the axis legend. + * + * Returns: text size + */ +gfloat +poc_axis_get_legend_size (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + return priv->legend_size; +} + +/* "major_grid" {{{2 */ + +/** + * poc_axis_set_major_grid: + * @self: A #PocAxis + * @major_grid: Requested line style + * + * Set the line style for major grid lines. + */ +void +poc_axis_set_major_grid (PocAxis *self, PocLineStyle major_grid) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + if (priv->major_grid != major_grid) + { + priv->major_grid = major_grid; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_MAJOR_GRID]); + poc_axis_notify_update (self); + } +} + +/** + * poc_axis_get_major_grid: + * @self: A #PocAxis + * + * Get the line style for major grid lines. + * + * Returns: a #PocLineStyle + */ +PocLineStyle +poc_axis_get_major_grid (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), POC_LINE_STYLE_SOLID); + + return priv->major_grid; +} + +/* "minor_grid" {{{2 */ + +/** + * poc_axis_set_minor_grid: + * @self: A #PocAxis + * @minor_grid: Requested line style + * + * Set the line style for minor grid lines. + */ +void +poc_axis_set_minor_grid (PocAxis *self, PocLineStyle minor_grid) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + if (priv->minor_grid != minor_grid) + { + priv->minor_grid = minor_grid; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_MINOR_GRID]); + poc_axis_notify_update (self); + } +} + +/** + * poc_axis_get_minor_grid: + * @self: A #PocAxis + * + * Get the line style for minor grid lines. + * + * Returns: a #PocLineStyle + */ +PocLineStyle +poc_axis_get_minor_grid (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_val_if_fail (POC_IS_AXIS (self), POC_LINE_STYLE_DASH); + + return priv->minor_grid; +} + +/* virtual/private methods {{{1 */ + +void +poc_axis_notify_update (PocAxis *self) +{ + g_return_if_fail (POC_IS_AXIS (self)); + g_signal_emit (self, poc_axis_signals[UPDATE], 0); +} + +/* range of axes {{{1 */ + +/* XXX + lower/upper_bound are fixed and configure the adjustment range and vice versa + lower/upper_display are set from the position and page size of the adjustment + lower/upper_mode are computed from lower/upper_display + */ + +static void +poc_axis_update_bounds (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble temp, interval; + + if (priv->upper_bound < priv->lower_bound) + { + g_warning ("Lower axis bound less than upper bound, swapping"); + temp = priv->lower_bound; + priv->lower_bound = priv->upper_bound; + priv->upper_bound = temp; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_UPPER_BOUND]); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LOWER_BOUND]); + } + + switch (priv->axis_mode) + { + case POC_AXIS_LINEAR: + priv->lower_mode = priv->lower_bound; + priv->upper_mode = priv->upper_bound; + break; + case POC_AXIS_LOG_OCTAVE: + priv->lower_mode = log2 (priv->lower_bound); + priv->upper_mode = log2 (priv->upper_bound); + break; + case POC_AXIS_LOG_DECADE: + priv->lower_mode = log10 (priv->lower_bound); + priv->upper_mode = log10 (priv->upper_bound); + break; + } + + if (priv->auto_interval) + { + interval = priv->upper_mode - priv->lower_mode; + if (interval >= 10000.0) + priv->major_interval = 10000.0; + else if (interval >= 1000.0) + priv->major_interval = 1000.0; + else if (interval >= 100.0) + priv->major_interval = 100.0; + else if (interval >= 10.0) + priv->major_interval = 10.0; + else + priv->major_interval = 1.0; + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_MAJOR_INTERVAL]); + } + if (priv->minor_divisions != 0) + priv->minor_interval = priv->major_interval / priv->minor_divisions; +} + +/* Always uses linear value */ + +/** + * poc_axis_configure: + * @self: A #PocAxis + * @axis_mode: A #PocAxisMode + * @lower_bound: axis lower bound + * @upper_bound: axis upper bound + * + * Set axis parameters in a single call. + */ +void +poc_axis_configure (PocAxis *self, PocAxisMode axis_mode, + gdouble lower_bound, gdouble upper_bound) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + g_object_freeze_notify (G_OBJECT (self)); + + priv->lower_bound = lower_bound; + priv->upper_bound = upper_bound; + priv->axis_mode = axis_mode; + poc_axis_update_bounds (self); + + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_UPPER_BOUND]); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_LOWER_BOUND]); + g_object_notify_by_pspec (G_OBJECT (self), poc_axis_prop[PROP_AXIS_MODE]); + g_object_thaw_notify (G_OBJECT (self)); + poc_axis_notify_update (self); +} + +/* Always uses linear value */ + +/** + * poc_axis_get_range: + * @self: A #PocAxis + * @lower_bound: axis lower bound + * @upper_bound: axis upper bound + * + * Get full axis range. The visible part of the axis may be less if scrolling. + */ +void +poc_axis_get_range (PocAxis *self, gdouble *lower_bound, gdouble *upper_bound) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_return_if_fail (POC_IS_AXIS (self)); + + *lower_bound = priv->lower_bound; + *upper_bound = priv->upper_bound; +} + +/* Always uses linear value - will need antilog of adjustment page position */ + +/** + * poc_axis_get_display_range: + * @self: A #PocAxis + * @lower_bound: axis lower bound + * @upper_bound: axis upper bound + * + * Get displayed axis range. This may be less than the full range if scrolling. + */ +void +poc_axis_get_display_range (PocAxis *self, gdouble *lower_bound, gdouble *upper_bound) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble value, page_size; + + g_return_if_fail (POC_IS_AXIS (self)); + + if (priv->adjustment != NULL) + { + value = gtk_adjustment_get_value (priv->adjustment); + page_size = gtk_adjustment_get_page_size (priv->adjustment); + switch (priv->axis_mode) + { + case POC_AXIS_LINEAR: + *lower_bound = value; + *upper_bound = value + page_size; + break; + case POC_AXIS_LOG_OCTAVE: + *lower_bound = exp2 (value); + *upper_bound = exp2 (value + page_size); + break; + case POC_AXIS_LOG_DECADE: + *lower_bound = exp10 (value); + *upper_bound = exp10 (value + page_size); + break; + } + } + else + poc_axis_get_range (self, lower_bound, upper_bound); +} + +/* drawing {{{1 */ + +/** + * poc_axis_project: + * @self: A #PocAxis + * @value: value to project + * @norm: normalisation value + * + * Project a value from the dataset to pixel based position. The value is + * interpreted according to the axis mode. This function is intended for use + * in drawing code in subclasses of #PocDataset + * + * Returns: projected value + */ +double +poc_axis_project (PocAxis *self, gdouble value, gint norm) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + + g_assert (POC_IS_AXIS (self)); + + switch (priv->axis_mode) + { + case POC_AXIS_LINEAR: + break; + case POC_AXIS_LOG_OCTAVE: + value = log2 (value); + break; + case POC_AXIS_LOG_DECADE: + value = log10 (value); + break; + } + return poc_axis_linear_project (self, value, norm); +} + +/** + * poc_axis_linear_project: + * @self: A #PocAxis + * @value: value to project + * @norm: normalisation value + * + * Project a value from the dataset to pixel based position. The value is + * projected using linear interpolation between the axis limits. This function + * is intended for use in drawing code in subclasses of #PocDataset + * + * Returns: projected value + */ +double +poc_axis_linear_project (PocAxis *self, gdouble value, gint norm) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + double scale = (norm < 0 ? -norm : norm) - 1.0; + + value = ((value - priv->lower_mode) / (priv->upper_mode - priv->lower_mode)); + if (norm < 0) + return (1.0 - value) * scale; + else + return value * scale; +} + +/* Draw grid */ +static inline void +poc_axis_draw_grid_line (PocAxis *self, cairo_t *cr, GtkOrientation orientation, + guint width, guint height, double xy) +{ + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + cairo_move_to (cr, floor (poc_axis_linear_project (self, xy, width)) + 0.5, 0.5); + cairo_rel_line_to (cr, 0, height - 0.5); + break; + case GTK_ORIENTATION_VERTICAL: + cairo_move_to (cr, 0.5, floor (poc_axis_linear_project (self, xy, -height)) + 0.5); + cairo_rel_line_to (cr, width - 0.5, 0); + break; + } +} + +/** + * poc_axis_draw_grid: + * @self: A #PocAxis + * @cr: A #cairo_t + * @orientation: A #GtkOrientation + * @width: Plot area width + * @height: Plot area height + * @style: A #GtkStyleContext + * + * Draw plot grid lines in main plot area. Used by #PocPlot. + */ +void +poc_axis_draw_grid (PocAxis *self, cairo_t *cr, GtkOrientation orientation, + guint width, guint height, GtkStyleContext *style) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble xy, mxy; + gdouble lower_floor; + const double *dashes; + int num_dashes; + const GdkRGBA *major_stroke, *minor_stroke; + GdkRGBA rgba; + GtkStateFlags state; + + state = gtk_style_context_get_state (style); + + gtk_style_context_save (style); + gtk_style_context_add_class (style, "grid"); + gtk_style_context_get_color (style, state, &rgba); + major_stroke = &rgba; + minor_stroke = &rgba; + gtk_style_context_restore (style); + + + lower_floor = floor (priv->lower_mode / priv->major_interval) * priv->major_interval; + + cairo_new_path (cr); + + /* Draw the major grid */ + for (xy = lower_floor; + xy <= ceil (priv->upper_mode); + xy += priv->major_interval) + poc_axis_draw_grid_line (self, cr, orientation, width, height, xy); + cairo_set_line_width (cr, 1.0); + dashes = poc_line_style_get_dashes (priv->major_grid, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, major_stroke); + cairo_stroke (cr); + + /* Draw the minor grid */ + if (priv->minor_divisions != 0) + { + for (xy = lower_floor; xy <= priv->upper_mode; xy += priv->major_interval) + if (priv->axis_mode == POC_AXIS_LOG_DECADE) + for (mxy = 2.0; mxy < 10.0; mxy += 1.0) + poc_axis_draw_grid_line (self, cr, orientation, width, height, + xy + log10 (mxy)); + else + for (mxy = priv->minor_interval; + mxy < priv->major_interval; + mxy += priv->minor_interval) + poc_axis_draw_grid_line (self, cr, orientation, width, height, + xy + mxy); + cairo_set_line_width (cr, 0.5); + dashes = poc_line_style_get_dashes (priv->minor_grid, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, minor_stroke); + cairo_stroke (cr); + } +} + +#define EXTRA 2.0 + +static inline void +poc_axis_draw_tick (PocAxis *self, cairo_t *cr, + GtkOrientation orientation, GtkPackType pack, + guint width, guint height, double xy, double size) +{ + double pos; + + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + pos = poc_axis_linear_project (self, xy, width); + if (pos > width) + return; + cairo_move_to (cr, floor (pos) + 0.5, pack == GTK_PACK_START ? 0.5 : height - 0.5); + cairo_rel_line_to (cr, 0, pack == GTK_PACK_START ? size : -size); + break; + case GTK_ORIENTATION_VERTICAL: + pos = poc_axis_linear_project (self, xy, -height); + if (pos > height) + return; + cairo_move_to (cr, pack == GTK_PACK_START ? width - 0.5 : 0.5, floor (pos) + 0.5); + cairo_rel_line_to (cr, pack == GTK_PACK_START ? -size : size, 0); + break; + } +} + +static inline void +poc_axis_label_tick (PocAxis *self, cairo_t *cr, + GtkOrientation orientation, GtkPackType pack, + guint width, guint height, + const gchar *label, double xy, double size) +{ + cairo_text_extents_t extents; + double x, y; + + cairo_text_extents (cr, label, &extents); + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + x = poc_axis_linear_project (self, xy, width); + if (x >= width) + return; + x -= extents.width / 2; + if (pack == GTK_PACK_START) + y = size - extents.y_bearing + EXTRA; + else + y = height - 1 - size - EXTRA; + if (x < 0) + x = 0; + else if (x >= width - extents.width) + x = width - 1 - extents.width; + break; + case GTK_ORIENTATION_VERTICAL: + y = poc_axis_linear_project (self, xy, -height); + if (y >= height) + return; + if (pack == GTK_PACK_START) + x = width - 1 - (size + extents.width) - EXTRA; + else + x = size + EXTRA; + y -= extents.y_bearing / 2; + if (y < -extents.y_bearing) + y = -extents.y_bearing; + else if (y >= height) + y = height - 1; + break; + default: + return; + } + cairo_move_to (cr, x, y); + cairo_show_text (cr, label); +} + +/** + * poc_axis_draw_axis: + * @self: A #PocAxis + * @cr: A #cairo_t + * @orientation: A #GtkOrientation + * @pack: A #GtkPackType + * @width: Plot area width + * @height: Plot area height + * @style: A #GtkStyleContext + * + * Draw plot grid lines in main plot area. Used by #PocPlot. + */ +void +poc_axis_draw_axis (PocAxis *self, cairo_t *cr, + GtkOrientation orientation, GtkPackType pack, + guint width, guint height, GtkStyleContext *style) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble xy, mxy, axy; + cairo_text_extents_t extents; + gchar buffer[128]; + gfloat minor, ends; + gdouble lower_floor; + const GdkRGBA *major_stroke, *minor_stroke, *text_fill; + GdkRGBA rgba; + GtkStateFlags state; + + state = gtk_style_context_get_state (style); + + gtk_style_context_save (style); + gtk_style_context_add_class (style, "grid"); + gtk_style_context_get_color (style, state, &rgba); + major_stroke = &rgba; + minor_stroke = &rgba; + gtk_style_context_restore (style); + + lower_floor = floor (priv->lower_mode / priv->major_interval) * priv->major_interval; + + cairo_new_path (cr); + + /* Axis always stroked with a solid line */ + cairo_set_dash (cr, NULL, 0, 0.0); + + ends = priv->label_size; + if (priv->tick_size > 0.0f) + { + ends += priv->tick_size + (float) EXTRA; + + /* frame and major ticks */ + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + cairo_move_to (cr, 0, pack == GTK_PACK_START ? 0.5 : height - 0.5); + cairo_rel_line_to (cr, width - 1, 0); + break; + case GTK_ORIENTATION_VERTICAL: + cairo_move_to (cr, pack == GTK_PACK_START ? width - 0.5 : 0.5, 0); + cairo_rel_line_to (cr, 0, height - 1); + break; + } + for (xy = lower_floor; + xy <= ceil (priv->upper_mode); + xy += priv->major_interval) + poc_axis_draw_tick (self, cr, orientation, pack, width, height, + xy, priv->tick_size); + cairo_set_line_width (cr, 1.0); + gdk_cairo_set_source_rgba (cr, major_stroke); + cairo_stroke (cr); + + /* minor ticks */ + if (priv->minor_divisions != 0) + { + minor = priv->tick_size * 0.6f; + for (xy = lower_floor; xy <= priv->upper_mode; xy += priv->major_interval) + if (priv->axis_mode == POC_AXIS_LOG_DECADE) + for (mxy = 2.0; mxy < 10.0; mxy += 1.0) + poc_axis_draw_tick (self, cr, orientation, pack, width, height, + xy + log10 (mxy), minor); + else + for (mxy = priv->minor_interval; + mxy < priv->major_interval; + mxy += priv->minor_interval) + poc_axis_draw_tick (self, cr, orientation, pack, width, height, + xy + mxy, minor); + cairo_set_line_width (cr, 0.5); + gdk_cairo_set_source_rgba (cr, minor_stroke); + cairo_stroke (cr); + } + } + + /* Annotate major ticks */ + cairo_set_font_size (cr, priv->label_size); + gdk_cairo_set_source_rgba (cr, major_stroke); + for (xy = lower_floor; xy <= priv->upper_mode; xy += priv->major_interval) + { + axy = (priv->axis_mode == POC_AXIS_LOG_DECADE) ? exp10 (xy) : xy; + g_snprintf (buffer, sizeof buffer, "%g", axy); + poc_axis_label_tick (self, cr, orientation, pack, width, height, + buffer, xy, priv->tick_size); + } + + /* Text */ + gtk_style_context_get_color (style, state, &rgba); + text_fill = &rgba; + gdk_cairo_set_source_rgba (cr, text_fill); + + /* Legend */ + if (priv->legend != NULL) + { + cairo_set_font_size (cr, priv->legend_size); + cairo_text_extents (cr, priv->legend, &extents); + cairo_save (cr); + switch (orientation) + { + case GTK_ORIENTATION_HORIZONTAL: + if (pack == GTK_PACK_START) + cairo_move_to (cr, (width - extents.width) / 2, + ends - extents.y_bearing); + else + cairo_move_to (cr, (width - extents.width) / 2, + -extents.y_bearing); + break; + case GTK_ORIENTATION_VERTICAL: + if (pack == GTK_PACK_START) + { + cairo_move_to (cr, width - (ends - extents.y_bearing), + (height - extents.width) / 2); + cairo_rotate (cr, G_PI / 2.0); + } + else + { + cairo_move_to (cr, ends - extents.y_bearing, + (height + extents.width) / 2); + cairo_rotate (cr, -G_PI / 2.0); + } + break; + } + cairo_show_text (cr, priv->legend); + cairo_restore (cr); + } +} + +/** + * poc_axis_size: + * @self: A #PocAxis + * + * Compute the width or height of the axis on the plot canvas. + * Used by #PocPlot. + */ +gdouble +poc_axis_size (PocAxis *self) +{ + PocAxisPrivate *priv = poc_axis_get_instance_private (self); + gdouble size; + + g_return_val_if_fail (POC_IS_AXIS (self), 0.0); + + size = priv->label_size; + if (priv->tick_size > 0.0f) + size += priv->tick_size + (float) EXTRA; + if (priv->legend != NULL) + size += priv->legend_size + (float) EXTRA; + return size; +} diff --git a/pocaxis.h b/pocaxis.h new file mode 100644 index 0000000..1a18303 --- /dev/null +++ b/pocaxis.h @@ -0,0 +1,104 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocaxis_h +#define _pocaxis_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include +#include "poctypes.h" + +G_BEGIN_DECLS + +#define POC_TYPE_AXIS poc_axis_get_type () +G_DECLARE_DERIVABLE_TYPE (PocAxis, poc_axis, POC, AXIS, GObject) + +struct _PocAxisClass +{ + /*< private >*/ + GObjectClass parent_class; + + gpointer dummy1; + gpointer dummy2; + gpointer dummy3; + gpointer dummy4; +}; + +PocAxis * poc_axis_new (void); + +void poc_axis_set_axis_mode (PocAxis *self, PocAxisMode axis_mode); +PocAxisMode poc_axis_get_axis_mode (PocAxis *self); +void poc_axis_set_lower_bound (PocAxis *self, gdouble bound); +gdouble poc_axis_get_lower_bound (PocAxis *self); +void poc_axis_set_upper_bound (PocAxis *self, gdouble bound); +gdouble poc_axis_get_upper_bound (PocAxis *self); +void poc_axis_set_adjustment (PocAxis *self, GtkAdjustment *adjustment); +GtkAdjustment * poc_axis_get_adjustment (PocAxis *self); +void poc_axis_set_major_interval (PocAxis *self, gdouble interval); +gdouble poc_axis_get_major_interval (PocAxis *self); +void poc_axis_set_auto_interval (PocAxis *self, gboolean enabled); +gboolean poc_axis_get_auto_interval (PocAxis *self); +void poc_axis_set_minor_divisions (PocAxis *self, guint divisions); +guint poc_axis_get_minor_divisions (PocAxis *self); +void poc_axis_set_tick_size (PocAxis *self, gfloat size); +gfloat poc_axis_get_tick_size (PocAxis *self); +void poc_axis_set_label_size (PocAxis *self, gfloat size); +gfloat poc_axis_get_label_size (PocAxis *self); +void poc_axis_set_major_grid (PocAxis *self, PocLineStyle major_grid); +PocLineStyle poc_axis_get_major_grid (PocAxis *self); +void poc_axis_set_minor_grid (PocAxis *self, PocLineStyle minor_grid); +PocLineStyle poc_axis_get_minor_grid (PocAxis *self); +void poc_axis_set_legend (PocAxis *self, const gchar *legend); +const gchar * poc_axis_get_legend (PocAxis *self); +void poc_axis_set_legend_size (PocAxis *self, gfloat size); +gfloat poc_axis_get_legend_size (PocAxis *self); + +void poc_axis_notify_update (PocAxis *self); +void poc_axis_draw_axis (PocAxis *self, cairo_t *cr, + GtkOrientation orientation, + GtkPackType pack, + guint width, guint height, + GtkStyleContext *style); +void poc_axis_draw_grid (PocAxis *self, cairo_t *cr, + GtkOrientation orientation, + guint width, guint height, + GtkStyleContext *style); +gdouble poc_axis_size (PocAxis *self); + +/* convenience access to bounds */ +void poc_axis_configure (PocAxis *self, + PocAxisMode axis_mode, + gdouble lower_bound, + gdouble upper_bound); + +void poc_axis_get_range (PocAxis *self, + gdouble *lower_bound, + gdouble *upper_bound); +void poc_axis_get_display_range (PocAxis *self, + gdouble *lower_bound, + gdouble *upper_bound); + +double poc_axis_linear_project (PocAxis *self, gdouble value, gint norm); +double poc_axis_project (PocAxis *self, gdouble value, gint norm); + +G_END_DECLS + +#endif diff --git a/pocbag.c b/pocbag.c new file mode 100644 index 0000000..066c841 --- /dev/null +++ b/pocbag.c @@ -0,0 +1,246 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include "pocbag.h" + +struct PocBagItem + { + GObject *object; + gint count; + gpointer data; + GDestroyNotify destroy; + }; + +#if 0 +static struct PocBagItem * +poc_bag_item_new (void) +{ + struct PocBagItem *item; + + item = g_slice_new0 (struct PocBagItem); + item->count = 1; + return item; +} +#endif + +/** + * poc_bag_item_destroy: (skip) + */ +static void +poc_bag_item_destroy (gpointer data) +{ + struct PocBagItem *item = data; + + g_object_unref (item->object); + if (item->destroy != NULL) + (*item->destroy) (item->data); +} + +/** + * poc_object_bag_new: (skip) + */ +PocObjectBag * +poc_object_bag_new (void) +{ + GArray *array; + + array = g_array_new (FALSE, TRUE, sizeof (struct PocBagItem)); + g_array_set_clear_func (array, poc_bag_item_destroy); + return (PocObjectBag *) array; +} + +/** + * poc_object_bag_ref: (skip) + */ +PocObjectBag * +poc_object_bag_ref (PocObjectBag *bag) +{ + return (PocObjectBag *) g_array_ref ((GArray *) bag); +} + +/** + * poc_object_bag_unref: (skip) + */ +void +poc_object_bag_unref (PocObjectBag *bag) +{ + g_array_unref ((GArray *) bag); +} + +static inline gboolean +poc_object_bag_find_object (PocObjectBag *bag, GObject *object, guint *ret) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + item = (struct PocBagItem *) array->data; + for (i = 0; i < array->len; i++) + if (item[i].object == object) + { + *ret = i; + return TRUE; + } + return FALSE; +} + +/* TRUE if object was already in the bag */ +/** + * poc_object_bag_add: (skip) + */ +gboolean +poc_object_bag_add (PocObjectBag *bag, GObject *object) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item, new_item; + guint i; + + if (poc_object_bag_find_object (bag, object, &i)) + { + item = &g_array_index (array, struct PocBagItem, i); + item->count += 1; + return TRUE; + } + + new_item.object = g_object_ref (object); + new_item.count = 1; + new_item.data = NULL; + new_item.destroy = NULL; + g_array_append_val (array, new_item); + return FALSE; +} + +/** + * poc_object_bag_empty: (skip) + */ +void +poc_object_bag_empty (PocObjectBag *bag) +{ + GArray *array = (GArray *) bag; + + g_array_set_size (array, 0); +} + +/* TRUE if object was removed from the bag */ +/** + * poc_object_bag_remove: (skip) + */ +gboolean +poc_object_bag_remove (PocObjectBag *bag, GObject *object) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + if (poc_object_bag_find_object (bag, object, &i)) + { + item = &g_array_index (array, struct PocBagItem, i); + if ((item->count -= 1) <= 0) + { + g_array_remove_index (array, i); + return TRUE; + } + } + return FALSE; +} + +/** + * poc_object_bag_set_data_full: (skip) + */ +gboolean +poc_object_bag_set_data_full (PocObjectBag *bag, GObject *object, + gpointer data, GDestroyNotify destroy) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + if (poc_object_bag_find_object (bag, object, &i)) + { + item = &g_array_index (array, struct PocBagItem, i); + if (item->destroy != NULL) + (*item->destroy) (item->data); + item->data = data; + item->destroy = destroy; + return TRUE; + } + return FALSE; +} + +/** + * poc_object_bag_get_data: (skip) + */ +gpointer +poc_object_bag_get_data (PocObjectBag *bag, GObject *object) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + if (poc_object_bag_find_object (bag, object, &i)) + { + item = &g_array_index (array, struct PocBagItem, i); + return item->data; + } + return NULL; +} + +/* TRUE if object is in the bag */ +/** + * poc_object_bag_contains: (skip) + */ +gboolean +poc_object_bag_contains (PocObjectBag *bag, GObject *object) +{ + guint i; + + return poc_object_bag_find_object (bag, object, &i); +} + +/** + * poc_object_bag_find: (skip) + */ +GObject * +poc_object_bag_find (PocObjectBag *bag, GHRFunc predicate, gpointer user_data) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + item = (struct PocBagItem *) array->data; + for (i = 0; i < array->len; i++) + if ((*predicate) (item[i].object, item[i].data, user_data)) + return item[i].object; + return NULL; +} + +/** + * poc_object_bag_foreach: (skip) + */ +void +poc_object_bag_foreach (PocObjectBag *bag, GHFunc func, gpointer user_data) +{ + GArray *array = (GArray *) bag; + struct PocBagItem *item; + guint i; + + item = (struct PocBagItem *) array->data; + for (i = 0; i < array->len; i++) + (*func) (item[i].object, item[i].data, user_data); +} + diff --git a/pocbag.h b/pocbag.h new file mode 100644 index 0000000..3afcae4 --- /dev/null +++ b/pocbag.h @@ -0,0 +1,51 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocproto_h +#define _pocproto_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include + +G_BEGIN_DECLS + +typedef struct _PocObjectBag PocObjectBag; + +PocObjectBag * poc_object_bag_new (void); +PocObjectBag * poc_object_bag_ref (PocObjectBag *bag); +void poc_object_bag_unref (PocObjectBag *bag); + +gboolean poc_object_bag_add (PocObjectBag *bag, GObject *object); +gboolean poc_object_bag_remove (PocObjectBag *bag, GObject *object); +void poc_object_bag_empty (PocObjectBag *bag); +gboolean poc_object_bag_set_data_full + (PocObjectBag *bag, GObject *object, + gpointer data, GDestroyNotify destroy); +gpointer poc_object_bag_get_data (PocObjectBag *bag, GObject *object); +gboolean poc_object_bag_contains (PocObjectBag *bag, GObject *object); +GObject * poc_object_bag_find (PocObjectBag *bag, + GHRFunc predicate, gpointer user_data); +void poc_object_bag_foreach (PocObjectBag *bag, + GHFunc func, gpointer user_data); + +G_END_DECLS + +#endif diff --git a/pocdataset.c b/pocdataset.c new file mode 100644 index 0000000..56d8f33 --- /dev/null +++ b/pocdataset.c @@ -0,0 +1,682 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include "pocdataset.h" +#include "pocplot.h" + +/** + * SECTION: pocdataset + * @title: PocDataset + * @short_description: Dataset for #PocPlot + * @see_also: #PocPlot #PocAxis #PocDatasetSpline + * + * A #PocDataset represents data to be plotted on a #PocPlot canvas. Any + * number of datasets may be added to a plot. Each dataset has X and Y axes + * which controls the range of data plotted and allow optionally allow the plot + * to be scrolled. + * + * Although the dataset contains drawing code, drawing always takes place under + * control of the #PocPlot and on its canvas. + * + * The #PocDataset base class draws a plot using straight line segments between + * each of the control points, however it may be subclassed for alternative + * plotting algorithms. + */ + +typedef struct _PocDatasetPrivate PocDatasetPrivate; +struct _PocDatasetPrivate + { + PocPointArray *points; + gchar *nickname; + gchar *legend; + GdkRGBA line_stroke; + PocLineStyle line_style; + PocAxis *x_axis; + PocAxis *y_axis; + }; + +G_DEFINE_TYPE_WITH_PRIVATE (PocDataset, poc_dataset, G_TYPE_OBJECT) + +/** + * poc_dataset_new: + * + * Create a new #PocDataset + * + * Returns: (transfer full): New #PocDataset + */ +PocDataset * +poc_dataset_new (void) +{ + return g_object_new (POC_TYPE_DATASET, NULL); +} + +/* GObject {{{1 */ + +enum + { + PROP_0, + PROP_POINTS, + + PROP_NICKNAME, + PROP_LEGEND, + + PROP_LINE_STROKE, + PROP_LINE_STYLE, + + PROP_X_AXIS, + PROP_Y_AXIS, + + N_PROPERTIES + }; +static GParamSpec *poc_dataset_prop[N_PROPERTIES]; + +enum + { + UPDATE, + N_SIGNAL + }; +static guint poc_dataset_signals[N_SIGNAL]; + +static void poc_dataset_dispose (GObject *object); +static void poc_dataset_finalize (GObject *object); +static void poc_dataset_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_dataset_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static void poc_dataset_draw_real (PocDataset *self, cairo_t *cr, + guint width, guint height); +static void poc_dataset_invalidate_real (PocDataset *self); + +static void +poc_dataset_class_init (PocDatasetClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + + gobject_class->dispose = poc_dataset_dispose; + gobject_class->finalize = poc_dataset_finalize; + gobject_class->set_property = poc_dataset_set_property; + gobject_class->get_property = poc_dataset_get_property; + + class->draw = poc_dataset_draw_real; + class->invalidate = poc_dataset_invalidate_real; + + poc_dataset_prop[PROP_POINTS] = g_param_spec_boxed ( + "points", "Points", "Array of (X,Y) coordinates", + POC_TYPE_POINT_ARRAY, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + poc_dataset_prop[PROP_NICKNAME] = g_param_spec_string ( + "nickname", + "Nickname", + "Nickname for the dataset (\"nickname\" since Glade doesn't like \"name\")", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + poc_dataset_prop[PROP_LINE_STROKE] = g_param_spec_boxed ( + "line-stroke", + "Line Stroke Colour", "Colour for stroking lines", + GDK_TYPE_RGBA, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_dataset_prop[PROP_LINE_STYLE] = g_param_spec_enum ( + "line-style", "Line Style", + "Line style for plot line", + POC_TYPE_LINE_STYLE, POC_LINE_STYLE_SOLID, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + poc_dataset_prop[PROP_LEGEND] = g_param_spec_string ( + "legend", + "Legend", "Legend for the item", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + poc_dataset_prop[PROP_X_AXIS] = g_param_spec_object ( + "x-axis", + "X-Axis", "X axis for dataset", + POC_TYPE_AXIS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_dataset_prop[PROP_Y_AXIS] = g_param_spec_object ( + "y-axis", + "Y-Axis", "Y axis for dataset", + POC_TYPE_AXIS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_dataset_prop); + + poc_dataset_signals[UPDATE] = g_signal_new ( + "update", G_TYPE_FROM_CLASS (class), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + + +static void +poc_dataset_init (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 }; + + priv->line_stroke = white; + priv->line_style = POC_LINE_STYLE_SOLID; +} + +static void +poc_dataset_dispose (GObject *object) +{ + PocDataset *self = (PocDataset *) object; + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_clear_object (&priv->x_axis); + g_clear_object (&priv->y_axis); + + G_OBJECT_CLASS (poc_dataset_parent_class)->dispose (object); +} + +static void +poc_dataset_finalize (GObject *object) +{ + PocDataset *self = (PocDataset *) object; + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + if (priv->points != NULL) + poc_point_array_unref (priv->points); + g_free (priv->nickname); + g_free (priv->legend); + G_OBJECT_CLASS (poc_dataset_parent_class)->finalize (object); +} + +static void +poc_dataset_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocDataset *self = POC_DATASET (object); + + switch (prop_id) + { + case PROP_LINE_STYLE: + poc_dataset_set_line_style (self, g_value_get_enum (value)); + break; + case PROP_POINTS: + poc_dataset_set_points (self, g_value_get_boxed (value)); + break; + case PROP_NICKNAME: + poc_dataset_set_nickname (self, g_value_get_string (value)); + break; + case PROP_LEGEND: + poc_dataset_set_legend (self, g_value_get_string (value)); + break; + + case PROP_LINE_STROKE: + poc_dataset_set_line_stroke (self, g_value_get_boxed (value)); + break; + + case PROP_X_AXIS: + poc_dataset_set_x_axis (self, g_value_get_object (value)); + break; + case PROP_Y_AXIS: + poc_dataset_set_y_axis (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +poc_dataset_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocDataset *self = POC_DATASET (object); + GdkRGBA rgba; + + switch (prop_id) + { + case PROP_LINE_STYLE: + g_value_set_enum (value, poc_dataset_get_line_style (self)); + break; + case PROP_POINTS: + g_value_set_boxed (value, poc_dataset_get_points (self)); + break; + case PROP_NICKNAME: + g_value_set_string (value, poc_dataset_get_nickname (self)); + break; + case PROP_LEGEND: + g_value_set_string (value, poc_dataset_get_legend (self)); + break; + + case PROP_LINE_STROKE: + poc_dataset_get_line_stroke (self, &rgba); + g_value_set_boxed (value, &rgba); + break; + + case PROP_X_AXIS: + g_value_set_object (value, poc_dataset_get_x_axis (self)); + break; + case PROP_Y_AXIS: + g_value_set_object (value, poc_dataset_get_y_axis (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* Properties {{{1 */ + +/* nickname {{{2 */ + +/** + * poc_dataset_set_nickname: + * @self: A #PocDataset + * @nickname: dataset name + * + * Set the name for the dataset. + */ +void +poc_dataset_set_nickname (PocDataset *self, const gchar *nickname) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + + g_free (priv->nickname); + priv->nickname = g_strdup (nickname); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_NICKNAME]); +} + +/** + * poc_dataset_get_nickname: + * @self: A #PocDataset + * + * Get the name for the dataset. + * + * Returns: (transfer none): the nickname + */ +const gchar * +poc_dataset_get_nickname (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), NULL); + return priv->nickname; +} + +/* legend {{{2 */ + +/** + * poc_dataset_set_legend: + * @self: A #PocDataset + * @legend: legend text + * + * Set the legend for the dataset item. + */ +void +poc_dataset_set_legend (PocDataset *self, const gchar *legend) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + + g_free (priv->legend); + priv->legend = g_strdup (legend); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_LEGEND]); +} + +/** + * poc_dataset_get_legend: + * @self: A #PocDataset + * + * Get the legend for the dataset item. + * + * Returns: (transfer none): legend text + */ +const gchar * +poc_dataset_get_legend (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), NULL); + + return priv->legend; +} + +/* line stroke {{{2 */ + +/** + * poc_dataset_get_line_stroke: + * @self: A #PocDataset + * @rgba: A #GdkRGBA to receive the line stroke colour + * + * Get the colour for stroking plot lines. + */ +void +poc_dataset_get_line_stroke (PocDataset *self, GdkRGBA *rgba) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + g_return_if_fail (rgba != NULL); + + *rgba = priv->line_stroke; +} + +/** + * poc_dataset_set_line_stroke: + * @self: A #PocDataset + * @rgba: A #GdkRGBA + * + * Set the colour for stroking plot lines. + */ +void +poc_dataset_set_line_stroke (PocDataset *self, const GdkRGBA *rgba) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + g_return_if_fail (rgba != NULL); + + priv->line_stroke = *rgba; + poc_dataset_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_LINE_STROKE]); +} + +/* "line-style" {{{2 */ + +/** + * poc_dataset_set_line_style: + * @self: A #PocDataset + * @line_style: Requested line style + * + * Set the line style for stroking dataset plot lines. + */ +void +poc_dataset_set_line_style (PocDataset *self, PocLineStyle line_style) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + + if (priv->line_style != line_style) + { + priv->line_style = line_style; + poc_dataset_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_LINE_STYLE]); + } +} + +/** + * poc_dataset_get_line_style: + * @self: A #PocDataset + * + * Get the dataset's line style + * + * Returns: a #PocLineStyle + */ +PocLineStyle +poc_dataset_get_line_style (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), POC_LINE_STYLE_SOLID); + + return priv->line_style; +} + +/* X Axis {{{2 */ + +/** + * poc_dataset_set_x_axis: + * @self: A #PocDataset + * @axis: a #PocAxis + * + * Set the X axis associated with this dataset. + */ +void +poc_dataset_set_x_axis (PocDataset *self, PocAxis *axis) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + g_return_if_fail (POC_IS_AXIS (axis)); + + g_set_object (&priv->x_axis, axis); + poc_dataset_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_X_AXIS]); +} + +/** + * poc_dataset_get_x_axis: + * @self: A #PocDataset + * + * Get the X axis associated with this dataset. + * + * Returns: (transfer none): a #PocAxis + */ +PocAxis * +poc_dataset_get_x_axis (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), NULL); + + return priv->x_axis; +} + +/* Y Axis {{{2 */ + +/** + * poc_dataset_set_y_axis: + * @self: A #PocDataset + * @axis: a #PocAxis + * + * Set the Y axis associated with this dataset. + */ +void +poc_dataset_set_y_axis (PocDataset *self, PocAxis *axis) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_if_fail (POC_IS_DATASET (self)); + g_return_if_fail (POC_IS_AXIS (axis)); + + g_set_object (&priv->y_axis, axis); + poc_dataset_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_Y_AXIS]); +} + +/** + * poc_dataset_get_y_axis: + * @self: A #PocDataset + * + * Get the X axis associated with this dataset. + * + * Returns: (transfer none): a #PocAxis + */ +PocAxis * +poc_dataset_get_y_axis (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), NULL); + + return priv->y_axis; +} + +/* points {{{2 */ + +/** + * poc_dataset_set_points: + * @self: A #PocDataset + * @points: A #PocPointArray with the control points + * + * Set the array of control points for the dataset. + */ +void +poc_dataset_set_points (PocDataset *self, PocPointArray *points) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + PocPointArray *old; + + g_return_if_fail (POC_IS_DATASET (self)); + + old = priv->points; + priv->points = points != NULL ? poc_point_array_ref (points) : NULL; + if (old != NULL) + poc_point_array_unref (old); + poc_dataset_invalidate (self); + poc_dataset_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_prop[PROP_POINTS]); +} + +/** + * poc_dataset_get_points: + * @self: A #PocDataset + * + * Get the array of control points for the dataset. + * + * Returns: (transfer none): A #PocPointArray + */ +PocPointArray * +poc_dataset_get_points (PocDataset *self) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + + g_return_val_if_fail (POC_IS_DATASET (self), NULL); + + return priv->points; +} + +/** + * poc_dataset_set_points_array: + * @self: A #PocDataset + * @x: (array length=points): Array of x coordinates + * @y: (array length=points): Array of y coordinates + * @points: number of points in coordinate arrays + * + * Set the array of control points for the dataset. X and Y coordinates + * for each control point are specified in two arrays. + */ +void +poc_dataset_set_points_array (PocDataset *self, + const gdouble *x, const gdouble *y, guint points) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + PocPoint p; + guint i; + + g_return_if_fail (POC_IS_DATASET (self)); + + priv->points = poc_point_array_sized_new (points); + for (i = 0; i < points; i++) + { + p.x = x[i]; + p.y = y[i]; + poc_point_array_append_val (priv->points, p); + } +} + +/* virtual/private methods {{{1 */ + +void +poc_dataset_notify_update (PocDataset *self) +{ + g_return_if_fail (POC_IS_DATASET (self)); + g_signal_emit (self, poc_dataset_signals[UPDATE], 0); +} + +/** + * poc_dataset_invalidate: + * @self: A #PocDataset + * + * Notify subclasses cached data is invalid. + */ +void +poc_dataset_invalidate (PocDataset *self) +{ + PocDatasetClass *class; + + g_return_if_fail (POC_IS_DATASET (self)); + + class = POC_DATASET_GET_CLASS (self); + g_return_if_fail (class->invalidate != NULL); + (*class->invalidate) (self); +} + +static void +poc_dataset_invalidate_real (G_GNUC_UNUSED PocDataset *self) +{ +} + +/** + * poc_dataset_draw: + * @self: A #PocDataset + * @cr: A #cairo_t + * @width: Plot area width + * @height: Plot area height + * + * Draw the dataset in the main plot area. Used by #PocPlot. + */ +void +poc_dataset_draw (PocDataset *self, cairo_t *cr, guint width, guint height) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + PocDatasetClass *class; + + g_return_if_fail (POC_IS_DATASET (self)); + g_return_if_fail (POC_IS_AXIS (priv->x_axis)); + g_return_if_fail (POC_IS_AXIS (priv->y_axis)); + + class = POC_DATASET_GET_CLASS (self); + g_return_if_fail (class->draw != NULL); + (*class->draw) (self, cr, width, height); +} + +static void +poc_dataset_draw_real (PocDataset *self, cairo_t *cr, + guint width, guint height) +{ + PocDatasetPrivate *priv = poc_dataset_get_instance_private (self); + PocPoint p; + const double *dashes; + int num_dashes; + guint i; + + if (priv->points == NULL) + return; + + /* Draw the plot line */ + cairo_new_path (cr); + p = poc_point_array_index (priv->points, 0); + cairo_move_to (cr, poc_axis_project (priv->x_axis, p.x, width), + poc_axis_project (priv->y_axis, p.y, -height)); + for (i = 1; i < poc_point_array_len (priv->points); i++) + { + p = poc_point_array_index (priv->points, i); + cairo_line_to (cr, poc_axis_project (priv->x_axis, p.x, width), + poc_axis_project (priv->y_axis, p.y, -height)); + } + + /* Stroke the line */ + cairo_set_line_width (cr, 1.0); + dashes = poc_line_style_get_dashes (priv->line_style, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, &priv->line_stroke); + cairo_stroke (cr); +} + diff --git a/pocdataset.h b/pocdataset.h new file mode 100644 index 0000000..2612a97 --- /dev/null +++ b/pocdataset.h @@ -0,0 +1,91 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocdataset_h +#define _pocdataset_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include +#include "pocaxis.h" +#include "poctypes.h" + +G_BEGIN_DECLS + +#define POC_TYPE_DATASET poc_dataset_get_type () +G_DECLARE_DERIVABLE_TYPE (PocDataset, poc_dataset, POC, DATASET, GObject) + +/* vvvv private */ + +/** + * PocDatasetClass: + * @draw: Method called by #PocPlot to draw visible area of plot. + * @invalidate: Notify subclasses to invalidate cached data. + * + * The class structure for #PocDatasetClass. + */ +struct _PocDatasetClass +{ + /*< private >*/ + GObjectClass parent_class; + + /*< public >*/ + void (*draw) (PocDataset *self, cairo_t *cr, + guint width, guint height); + void (*invalidate) (PocDataset *self); + + /*< private >*/ + void (*dummy3) (PocDataset *self); + void (*dummy4) (PocDataset *self); +}; + +/* ^^^^ private */ + +PocDataset * poc_dataset_new (void); + +void poc_dataset_set_nickname (PocDataset *self, const gchar *nickname); +const gchar * poc_dataset_get_nickname (PocDataset *self); +void poc_dataset_set_legend (PocDataset *self, const gchar *legend); +const gchar * poc_dataset_get_legend (PocDataset *self); +void poc_dataset_get_line_stroke (PocDataset *self, GdkRGBA *rgba); +void poc_dataset_set_line_stroke (PocDataset *self, const GdkRGBA *rgba); +void poc_dataset_set_line_style (PocDataset *self, PocLineStyle line_style); +PocLineStyle poc_dataset_get_line_style (PocDataset *self); +void poc_dataset_set_x_axis (PocDataset *self, PocAxis *axis); +PocAxis * poc_dataset_get_x_axis (PocDataset *self); +void poc_dataset_set_y_axis (PocDataset *self, PocAxis *axis); +PocAxis * poc_dataset_get_y_axis (PocDataset *self); +void poc_dataset_set_points (PocDataset *self, + PocPointArray *points); +PocPointArray * poc_dataset_get_points (PocDataset *self); + +void poc_dataset_notify_update (PocDataset *self); +void poc_dataset_invalidate (PocDataset *self); +void poc_dataset_draw (PocDataset *self, cairo_t *cr, + guint width, guint height); + +void poc_dataset_set_points_array (PocDataset *self, + const gdouble *x, + const gdouble *y, + guint points); + +G_END_DECLS + +#endif diff --git a/pocdatasetspline.c b/pocdatasetspline.c new file mode 100644 index 0000000..74309f2 --- /dev/null +++ b/pocdatasetspline.c @@ -0,0 +1,373 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include +#include "pocdatasetspline.h" +#include "pocspline.h" + +/** + * SECTION: pocdatasetspline + * @title: PocDatasetSpline + * @short_description: Dataset for #PocPlot + * @see_also: #PocPlot #PocAxis #PocDataset + * + * A #PocDataset subclass which interpolates a curve from the control points + * based on the algorithm from Numerical Recipies 2nd Edition. + * Control points may be highlighted by drawing markers at their locations. + */ + +struct _PocDatasetSpline + { + PocDataset parent_instance; + + GdkRGBA marker_stroke; + GdkRGBA marker_fill; + gboolean show_markers; + + PocPointArray *points; + guint cache_width; + }; + +G_DEFINE_TYPE (PocDatasetSpline, poc_dataset_spline, POC_TYPE_DATASET) + +/** + * poc_dataset_spline_new: + * + * Create a new #PocDatasetSpline + * + * Returns: (transfer full): New #PocDatasetSpline + */ +PocDatasetSpline * +poc_dataset_spline_new (void) +{ + return g_object_new (POC_TYPE_DATASET_SPLINE, NULL); +} + +enum + { + PROP_0, + PROP_MARKER_FILL, + PROP_MARKER_STROKE, + PROP_SHOW_MARKERS, + N_PROPERTIES + }; +static GParamSpec *poc_dataset_spline_prop[N_PROPERTIES]; + +static void poc_dataset_spline_finalize (GObject *object); +static void poc_dataset_spline_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_dataset_spline_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static void poc_dataset_spline_draw (PocDataset *dataset, cairo_t *cr, + guint width, guint height); +static void poc_dataset_spline_invalidate (PocDataset *dataset); + +static void +poc_dataset_spline_class_init (PocDatasetSplineClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + PocDatasetClass *dataset_class = POC_DATASET_CLASS (class); + + gobject_class->finalize = poc_dataset_spline_finalize; + gobject_class->set_property = poc_dataset_spline_set_property; + gobject_class->get_property = poc_dataset_spline_get_property; + + dataset_class->draw = poc_dataset_spline_draw; + dataset_class->invalidate = poc_dataset_spline_invalidate; + + poc_dataset_spline_prop[PROP_MARKER_STROKE] = g_param_spec_boxed ( + "marker-stroke", + "Marker Stroke Colour", "Colour for stroking markers", + GDK_TYPE_RGBA, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_dataset_spline_prop[PROP_MARKER_FILL] = g_param_spec_boxed ( + "marker-fill", + "Marker Fill Colour", "Colour for filling markers", + GDK_TYPE_RGBA, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_dataset_spline_prop[PROP_SHOW_MARKERS] = g_param_spec_boolean ( + "show-markers", "Show Markers", "Show markers on graph lines", + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_dataset_spline_prop); +} + +static void +poc_dataset_spline_init (PocDatasetSpline *self) +{ + GdkRGBA clear = { 0.0, 0.0, 0.0, 0.0 }; + GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 }; + + self->marker_stroke = white; + self->marker_fill = clear; + self->show_markers = FALSE; +} + +static void +poc_dataset_spline_finalize (GObject *object) +{ + PocDatasetSpline *self = (PocDatasetSpline *) object; + + if (self->points != NULL) + poc_point_array_unref (self->points); + G_OBJECT_CLASS (poc_dataset_spline_parent_class)->finalize (object); +} + +static void +poc_dataset_spline_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocDatasetSpline *self = POC_DATASET_SPLINE (object); + + switch (prop_id) + { + case PROP_MARKER_STROKE: + poc_dataset_spline_set_marker_stroke (self, g_value_get_boxed (value)); + break; + case PROP_MARKER_FILL: + poc_dataset_spline_set_marker_fill (self, g_value_get_boxed (value)); + break; + case PROP_SHOW_MARKERS: + poc_dataset_spline_set_show_markers (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +poc_dataset_spline_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocDatasetSpline *self = POC_DATASET_SPLINE (object); + GdkRGBA rgba; + + switch (prop_id) + { + case PROP_MARKER_STROKE: + poc_dataset_spline_get_marker_stroke (self, &rgba); + g_value_set_boxed (value, &rgba); + break; + case PROP_MARKER_FILL: + poc_dataset_spline_get_marker_fill (self, &rgba); + g_value_set_boxed (value, &rgba); + break; + case PROP_SHOW_MARKERS: + g_value_set_boolean (value, poc_dataset_spline_get_show_markers (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* properties {{{1 */ + +/* marker stroke {{{2 */ + +/** + * poc_dataset_spline_get_marker_stroke: + * @self: A #PocDatasetSpline + * @rgba: A #GdkRGBA to receive the marker stroke colour + * + * Get the colour for stroking markers. + */ +void +poc_dataset_spline_get_marker_stroke (PocDatasetSpline *self, GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_DATASET_SPLINE (self)); + g_return_if_fail (rgba != NULL); + + *rgba = self->marker_stroke; +} + +/** + * poc_dataset_spline_set_marker_stroke: + * @self: A #PocDatasetSpline + * @rgba: A #GdkRGBA + * + * Set the colour for stroking markers. + */ +void +poc_dataset_spline_set_marker_stroke (PocDatasetSpline *self, const GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_DATASET_SPLINE (self)); + g_return_if_fail (rgba != NULL); + + self->marker_stroke = *rgba; + poc_dataset_notify_update (POC_DATASET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_spline_prop[PROP_MARKER_STROKE]); +} + +/* marker fill {{{2 */ + +/** + * poc_dataset_spline_get_marker_fill: + * @self: A #PocDatasetSpline + * @rgba: A #GdkRGBA + * + * Set the colour for stroking markers. + */ +void +poc_dataset_spline_get_marker_fill (PocDatasetSpline *self, GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_DATASET_SPLINE (self)); + g_return_if_fail (rgba != NULL); + + *rgba = self->marker_fill; +} + +/** + * poc_dataset_spline_set_marker_fill: + * @self: A #PocDatasetSpline + * @rgba: A #GdkRGBA to receive the marker stroke colour + * + * Get the colour for stroking markers. + */ +void +poc_dataset_spline_set_marker_fill (PocDatasetSpline *self, const GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_DATASET_SPLINE (self)); + g_return_if_fail (rgba != NULL); + + self->marker_fill = *rgba; + poc_dataset_notify_update (POC_DATASET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_spline_prop[PROP_MARKER_FILL]); +} + +/* show markers {{{2 */ + +/** + * poc_dataset_spline_set_show_markers: + * @self: A #PocDatasetSpline + * @value: Show markers if %TRUE + * + * Set whether to show markers on control points. + */ +void +poc_dataset_spline_set_show_markers (PocDatasetSpline *self, gboolean value) +{ + g_return_if_fail (POC_IS_DATASET_SPLINE (self)); + self->show_markers = !!value; + poc_dataset_notify_update (POC_DATASET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_dataset_spline_prop[PROP_SHOW_MARKERS]); +} + +/** + * poc_dataset_spline_get_show_markers: + * @self: A #PocDatasetSpline + * + * Get whether to show markers on control points. + * + * Returns: %TRUE if markers are shown. + */ +gboolean +poc_dataset_spline_get_show_markers (PocDatasetSpline *self) +{ + g_return_val_if_fail (POC_IS_DATASET_SPLINE (self), FALSE); + return self->show_markers; +} + +/* override class methods {{{1 */ + +static void +poc_dataset_spline_invalidate (PocDataset *dataset) +{ + PocDatasetSpline *self = POC_DATASET_SPLINE (dataset); + + POC_DATASET_CLASS (poc_dataset_spline_parent_class)->invalidate (dataset); + if (self->points != NULL) + { + poc_point_array_unref (self->points); + self->points = NULL; + } +} + +static void +poc_dataset_spline_draw (PocDataset *dataset, cairo_t *cr, + guint width, guint height) +{ + PocDatasetSpline *self = POC_DATASET_SPLINE (dataset); + PocPointArray *spline; + gdouble min_x, max_x; + PocPoint p; + guint i; + PocAxis *x_axis, *y_axis; + GdkRGBA line_stroke; + PocLineStyle line_style; + const double *dashes; + int num_dashes; + + spline = poc_dataset_get_points (POC_DATASET (self)); + if (spline == NULL) + return; + + x_axis = poc_dataset_get_x_axis (dataset); + y_axis = poc_dataset_get_y_axis (dataset); + poc_dataset_get_line_stroke (dataset, &line_stroke); + line_style = poc_dataset_get_line_style (dataset); + + if (self->points == NULL || self->cache_width != width) + { + self->cache_width = width; + poc_axis_get_display_range (x_axis, &min_x, &max_x); + if (self->points != NULL) + poc_point_array_unref (self->points); + self->points = poc_spline_get_points (spline, min_x, max_x, + width / 4 + 1); + } + + /* Draw the plot line */ + cairo_new_path (cr); + p = poc_point_array_index (self->points, 0); + cairo_move_to (cr, poc_axis_project (x_axis, p.x, width), + poc_axis_project (y_axis, p.y, -height)); + for (i = 1; i < poc_point_array_len (self->points); i++) + { + p = poc_point_array_index (self->points, i); + cairo_line_to (cr, poc_axis_project (x_axis, p.x, width), + poc_axis_project (y_axis, p.y, -height)); + } + + /* Stroke the line */ + //TODO Line width + cairo_set_line_width (cr, 1.0); + dashes = poc_line_style_get_dashes (line_style, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, &line_stroke); + cairo_stroke (cr); + + if (self->show_markers) + { + cairo_new_path (cr); + for (i = 0; i < poc_point_array_len (spline); i++) + { + cairo_new_sub_path (cr); + //TODO Marker size and style properties + p = poc_point_array_index (spline, i); + cairo_arc (cr, poc_axis_project (x_axis, p.x, width), + poc_axis_project (y_axis, p.y, -height), + 3, 0.0, 2.0 * G_PI); + } + gdk_cairo_set_source_rgba (cr, &self->marker_fill); + cairo_fill_preserve (cr); + gdk_cairo_set_source_rgba (cr, &self->marker_stroke); + cairo_stroke (cr); + } +} diff --git a/pocdatasetspline.h b/pocdatasetspline.h new file mode 100644 index 0000000..c7c5b10 --- /dev/null +++ b/pocdatasetspline.h @@ -0,0 +1,45 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocdatasetspline_h +#define _pocdatasetspline_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include "pocdataset.h" + +G_BEGIN_DECLS + +#define POC_TYPE_DATASET_SPLINE poc_dataset_spline_get_type () +G_DECLARE_FINAL_TYPE (PocDatasetSpline, poc_dataset_spline, + POC, DATASET_SPLINE, PocDataset) + +PocDatasetSpline *poc_dataset_spline_new (void); + +void poc_dataset_spline_get_marker_stroke (PocDatasetSpline *self, GdkRGBA *rgba); +void poc_dataset_spline_set_marker_stroke (PocDatasetSpline *self, const GdkRGBA *rgba); +void poc_dataset_spline_get_marker_fill (PocDatasetSpline *self, GdkRGBA *rgba); +void poc_dataset_spline_set_marker_fill (PocDatasetSpline *self, const GdkRGBA *rgba); +gboolean poc_dataset_spline_get_show_markers (PocDatasetSpline *self); +void poc_dataset_spline_set_show_markers (PocDatasetSpline *self, gboolean value); + +G_END_DECLS + +#endif diff --git a/poclegend.c b/poclegend.c new file mode 100644 index 0000000..ebe7598 --- /dev/null +++ b/poclegend.c @@ -0,0 +1,602 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +/* + * PocLegend + */ + +#include "poclegend.h" +#include + +/** + * SECTION: poclegend + * @title: PocLegend + * @short_description: Widget to display a legend for a #PocPlot. + * @see_also: #PocPlot + * + * #PocLegend displays a legend for the specified #PocPlot widget. + * Plot and dataset titles are used for legend text and dataset + * line styles and colours are used for the line samples. + * + * Please note that although operational, PocLegend is more of a + * proof-of-concept at present and needs some work on its aesthetics. + */ + +typedef struct _PocLegend PocLegend; +struct _PocLegend + { + GtkDrawingArea parent_instance; + + PocPlot *plot; + gdouble title_text_size; + gdouble legend_text_size; + gdouble line_sample_size; + gdouble line_spacing; + + gdouble width; + gboolean relayout; + }; + +G_DEFINE_TYPE (PocLegend, poc_legend, GTK_TYPE_DRAWING_AREA) + +#if !GLIB_CHECK_VERSION(2,62,0) +static inline void +g_clear_signal_handler (gulong *id, gpointer instance) +{ + if (instance != NULL && *id != 0) + g_signal_handler_disconnect (instance, *id); + *id = 0; +} +#endif + +/** + * poc_legend_new: + * + * Create a new #PocLegend + * + * Returns: (transfer full): New #PocLegend + */ +PocLegend * +poc_legend_new (void) +{ + return g_object_new (POC_TYPE_LEGEND, NULL); +} + +enum + { + PROP_0, + PROP_PLOT, + PROP_TITLE_TEXT_SIZE, + PROP_LEGEND_TEXT_SIZE, + PROP_LINE_SAMPLE_SIZE, + PROP_LINE_SPACING, + N_PROPERTIES + }; +static GParamSpec *poc_legend_prop[N_PROPERTIES]; + +static void poc_legend_dispose (GObject *object); +static void poc_legend_finalize (GObject *object); +static void poc_legend_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_legend_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static gboolean poc_legend_draw (GtkWidget *widget, cairo_t *cr); + +/* GObject {{{1 */ + +static void +poc_legend_class_init (PocLegendClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gobject_class->dispose = poc_legend_dispose; + gobject_class->finalize = poc_legend_finalize; + gobject_class->set_property = poc_legend_set_property; + gobject_class->get_property = poc_legend_get_property; + + gtk_widget_class_set_css_name (widget_class, "legend"); + widget_class->draw = poc_legend_draw; + + poc_legend_prop[PROP_PLOT] = g_param_spec_object ( + "plot", "Plot", + "Generate legend for the specified plot", + POC_TYPE_PLOT, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_legend_prop[PROP_TITLE_TEXT_SIZE] = g_param_spec_double ( + "title-text-size", "Title Text Size", + "Size for title text", + 1.0, G_MAXDOUBLE, 12.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_legend_prop[PROP_LEGEND_TEXT_SIZE] = g_param_spec_double ( + "legend-text-size", "Legend Text Size", + "Size for legend text", + 1.0, G_MAXDOUBLE, 10.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_legend_prop[PROP_LINE_SAMPLE_SIZE] = g_param_spec_double ( + "line-sample-size", "Line Sample Size", + "Length of sample line", + 1.0, G_MAXDOUBLE, 50.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_legend_prop[PROP_LINE_SPACING] = g_param_spec_double ( + "line-spacing", "Line Spacing", + "Factor to scale line spacing", + 0.8, 5.0, 1.0, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_legend_prop); +} + +static void +poc_legend_init (PocLegend *self) +{ + self->plot = NULL; + self->title_text_size = 12.0; + self->legend_text_size = 10.0; + self->line_sample_size = 50.0; + self->line_spacing = 1.0; +} + +static void +poc_legend_dispose (GObject *object) +{ + PocLegend *self = (PocLegend *) object; + + g_clear_object (&self->plot); + G_OBJECT_CLASS (poc_legend_parent_class)->dispose (object); +} + +static void +poc_legend_finalize (GObject *object) +{ + //PocLegend *self = (PocLegend *) object; + + G_OBJECT_CLASS (poc_legend_parent_class)->finalize (object); +} + +/* Properties {{{1 */ + +static void +poc_legend_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocLegend *self = POC_LEGEND (object); + + switch (prop_id) + { + case PROP_PLOT: + poc_legend_set_plot (self, g_value_get_object (value)); + break; + case PROP_TITLE_TEXT_SIZE: + poc_legend_set_title_text_size (self, g_value_get_double (value)); + break; + case PROP_LEGEND_TEXT_SIZE: + poc_legend_set_legend_text_size (self, g_value_get_double (value)); + break; + case PROP_LINE_SAMPLE_SIZE: + poc_legend_set_line_sample_size (self, g_value_get_double (value)); + break; + case PROP_LINE_SPACING: + poc_legend_set_line_spacing (self, g_value_get_double (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + + +static void +poc_legend_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocLegend *self = POC_LEGEND (object); + + switch (prop_id) + { + case PROP_PLOT: + g_value_set_object (value, poc_legend_get_plot (self)); + break; + case PROP_TITLE_TEXT_SIZE: + g_value_set_double (value, poc_legend_get_title_text_size (self)); + break; + case PROP_LEGEND_TEXT_SIZE: + g_value_set_double (value, poc_legend_get_legend_text_size (self)); + break; + case PROP_LINE_SAMPLE_SIZE: + g_value_set_double (value, poc_legend_get_line_sample_size (self)); + break; + case PROP_LINE_SPACING: + g_value_set_double (value, poc_legend_get_line_spacing (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* "plot" {{{2 */ + +/** + * poc_legend_set_plot: + * @self: A #PocLegend + * @plot: A #PocPlot + * + * Display a legend for the associated plot widget. + */ +void +poc_legend_set_plot (PocLegend *self, PocPlot *plot) +{ + g_return_if_fail (POC_IS_LEGEND (self)); + + if (g_set_object (&self->plot, plot)) + { + self->relayout = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_legend_prop[PROP_PLOT]); + } +} + +/** + * poc_legend_get_plot: + * @self: A #PocLegend + * + * Return the associated plot. + * + * Returns: (transfer none): a #PocPlot + */ +PocPlot * +poc_legend_get_plot (PocLegend *self) +{ + g_return_val_if_fail (POC_IS_LEGEND (self), NULL); + + return self->plot; +} + +/* "title-text-size" {{{2 */ + +/** + * poc_legend_set_title_text_size: + * @self: A #PocLegend + * @title_text_size: text size + * + * Set title text size. + */ +void +poc_legend_set_title_text_size (PocLegend *self, gdouble title_text_size) +{ + g_return_if_fail (POC_IS_LEGEND (self)); + + if (self->title_text_size != title_text_size) + { + self->title_text_size = title_text_size; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_legend_prop[PROP_TITLE_TEXT_SIZE]); + } +} + +/** + * poc_legend_get_title_text_size: + * @self: A #PocLegend + * + * Get title text size. + * + * Returns: size + */ +gdouble +poc_legend_get_title_text_size (PocLegend *self) +{ + g_return_val_if_fail (POC_IS_LEGEND (self), 0); + + return self->title_text_size; +} + +/* "legend-text-size" {{{2 */ + +/** + * poc_legend_set_legend_text_size: + * @self: A #PocLegend + * @legend_text_size: text size + * + * Set legend text size. + */ +void +poc_legend_set_legend_text_size (PocLegend *self, gdouble legend_text_size) +{ + g_return_if_fail (POC_IS_LEGEND (self)); + + if (self->legend_text_size != legend_text_size) + { + self->legend_text_size = legend_text_size; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_legend_prop[PROP_LEGEND_TEXT_SIZE]); + } +} + +/** + * poc_legend_get_legend_text_size: + * @self: A #PocLegend + * + * Get legend text size. + * + * Returns: size + */ +gdouble +poc_legend_get_legend_text_size (PocLegend *self) +{ + g_return_val_if_fail (POC_IS_LEGEND (self), 0); + + return self->legend_text_size; +} + +/* "line-sample-size" {{{2 */ + +/** + * poc_legend_set_line_sample_size: + * @self: A #PocLegend + * @line_sample_size: text size + * + * Set line sample size. + */ +void +poc_legend_set_line_sample_size (PocLegend *self, gdouble line_sample_size) +{ + g_return_if_fail (POC_IS_LEGEND (self)); + + if (self->line_sample_size != line_sample_size) + { + self->line_sample_size = line_sample_size; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_legend_prop[PROP_LINE_SAMPLE_SIZE]); + } +} + +/** + * poc_legend_get_line_sample_size: + * @self: A #PocLegend + * + * Get line sample size. + * + * Returns: size + */ +gdouble +poc_legend_get_line_sample_size (PocLegend *self) +{ + g_return_val_if_fail (POC_IS_LEGEND (self), 0); + + return self->line_sample_size; +} + +/* "line-spacing" {{{2 */ + +/** + * poc_legend_set_line_spacing: + * @self: A #PocLegend + * @line_spacing: text size + * + * Set line spacing as a multiple of the text height where 1.0 equals text + * height. + */ +void +poc_legend_set_line_spacing (PocLegend *self, gdouble line_spacing) +{ + g_return_if_fail (POC_IS_LEGEND (self)); + + if (self->line_spacing != line_spacing) + { + self->line_spacing = line_spacing; + g_object_notify_by_pspec (G_OBJECT (self), poc_legend_prop[PROP_LINE_SPACING]); + } +} + +/** + * poc_legend_get_line_spacing: + * @self: A #PocLegend + * + * Get line spacing. + * + * Returns: size + */ +gdouble +poc_legend_get_line_spacing (PocLegend *self) +{ + g_return_val_if_fail (POC_IS_LEGEND (self), 0); + + return self->line_spacing; +} + +/* layout {{{1 */ + +struct closure + { + PocLegend *self; + cairo_t *cr; + cairo_font_extents_t font_extents; + GdkRGBA foreground; + gint count; + gdouble y; + gdouble width; + gdouble height; + }; + +#define INTERNAL_BORDER 6.0 + +#if X +static gboolean +poc_legend_dimensions (G_GNUC_UNUSED PocPlot *plot, PocDataset *dataset, + gpointer user_data) +{ + struct closure *closure = user_data; + PocLegend *self = closure->self; + cairo_t *cr = closure->cr; + cairo_text_extents_t text_extents; + const gchar *legend; + gdouble width; + + width = self->line_sample_size; + legend = poc_dataset_get_legend (dataset); + if (legend != NULL) + { + cairo_text_extents (cr, legend, &text_extents); + width += text_extents.width + INTERNAL_BORDER; + } + if (width > closure->width) + closure->width = width; + closure->count += 1; + return FALSE; +} + +static void +poc_legend_layout (PocLegend *self, cairo_t *cr) +{ + struct closure closure; + cairo_text_extents_t text_extents; + const gchar *title; + double line_height; + + closure.self = self; + closure.cr = cr; + closure.count = 0; + + title = poc_plot_get_title (self->plot); + if (title != NULL) + { + cairo_set_font_size (cr, self->title_text_size); + cairo_font_extents (cr, &closure.font_extents); + cairo_text_extents (cr, title, &text_extents); + closure.width = text_extents.width; + closure.height = closure.font_extents.height; + } + else + closure.height = closure.width = 0.0; + + cairo_set_font_size (cr, self->legend_text_size); + cairo_font_extents (cr, &closure.font_extents); + poc_plot_dataset_foreach (self->plot, poc_legend_dimensions, &closure); + + line_height = closure.font_extents.height * self->line_spacing; + closure.height += line_height * closure.count; + + gtk_widget_set_size_request (GTK_WIDGET (self), + (gint) closure.width, (gint) closure.height); +} +#endif + +/* draw {{{1 */ + +static gboolean +poc_legend_sample (G_GNUC_UNUSED PocPlot *plot, PocDataset *dataset, + gpointer user_data) +{ + struct closure *closure = user_data; + PocLegend *self = closure->self; + cairo_t *cr = closure->cr; + cairo_text_extents_t text_extents; + GdkRGBA rgba; + const double *dashes; + int num_dashes; + PocLineStyle line_style; + const gchar *legend; + double line_height, line_ascent, x, y; + + line_height = closure->font_extents.height * self->line_spacing; + line_ascent = closure->font_extents.ascent * self->line_spacing; + + legend = poc_dataset_get_legend (dataset); + if (legend != NULL) + { + gdk_cairo_set_source_rgba (cr, &closure->foreground); + cairo_text_extents (cr, legend, &text_extents); + x = round ((closure->width * 1.5 - text_extents.width) / 2.0); + y = round (closure->y + line_ascent); + cairo_move_to (cr, x, y); + cairo_show_text (cr, legend); + } + + /* fetch sample parameters from dataset */ + poc_dataset_get_line_stroke (dataset, &rgba); + line_style = poc_dataset_get_line_style (dataset); + dashes = poc_line_style_get_dashes (line_style, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, &rgba); + + /* stroke the sample line */ + x = round ((closure->width / 2.0 - self->line_sample_size) / 2.0); + y = round (closure->y + line_height / 2.0); + cairo_move_to (cr, x + 0.5, y + 0.5); + cairo_rel_line_to (cr, self->line_sample_size, 0.0); + cairo_stroke (cr); + + closure->y += line_height; + return FALSE; +} + +static gboolean +poc_legend_draw (GtkWidget *widget, cairo_t *cr) +{ + PocLegend *self = (PocLegend *) widget; + cairo_text_extents_t text_extents; + struct closure closure; + const gchar *title; + GtkStyleContext *style; + GtkStateFlags state; + + if (self->plot == NULL) + return FALSE; + +#if X + if (self->relayout) + { + poc_legend_layout (self, cr); + self->relayout = FALSE; + } +#endif + + style = gtk_widget_get_style_context (widget); + state = gtk_style_context_get_state (style); + gtk_style_context_get_color (style, state, &closure.foreground); + + closure.self = self; + closure.cr = cr; + closure.width = gtk_widget_get_allocated_width (widget); + //closure.height = gtk_widget_get_allocated_height (widget); + + title = poc_plot_get_title (self->plot); + if (title != NULL) + { + cairo_set_font_size (cr, self->title_text_size); + cairo_font_extents (cr, &closure.font_extents); + cairo_text_extents (cr, title, &text_extents); + + cairo_move_to (cr, round ((closure.width - text_extents.width) / 2.0), + round (closure.font_extents.ascent)); + gdk_cairo_set_source_rgba (cr, &closure.foreground); + cairo_show_text (cr, title); + + closure.y = closure.font_extents.height; + } + else + closure.y = 0; + + cairo_set_font_size (cr, self->legend_text_size); + cairo_font_extents (cr, &closure.font_extents); + cairo_set_line_width (cr, 1.0); + poc_plot_dataset_foreach (self->plot, poc_legend_sample, &closure); + + return FALSE; +} diff --git a/poclegend.h b/poclegend.h new file mode 100644 index 0000000..0c8c004 --- /dev/null +++ b/poclegend.h @@ -0,0 +1,48 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _poclegend_h +#define _poclegend_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include "pocplot.h" + +G_BEGIN_DECLS + +#define POC_TYPE_LEGEND poc_legend_get_type () +G_DECLARE_FINAL_TYPE (PocLegend, poc_legend, POC, LEGEND, GtkDrawingArea) + +PocLegend * poc_legend_new (void); + +void poc_legend_set_plot (PocLegend *self, PocPlot *plot); +PocPlot *poc_legend_get_plot (PocLegend *self); +void poc_legend_set_title_text_size (PocLegend *self, gdouble title_text_size); +gdouble poc_legend_get_title_text_size (PocLegend *self); +void poc_legend_set_legend_text_size (PocLegend *self, gdouble legend_text_size); +gdouble poc_legend_get_legend_text_size (PocLegend *self); +void poc_legend_set_line_sample_size (PocLegend *self, gdouble line_sample_size); +gdouble poc_legend_get_line_sample_size (PocLegend *self); +void poc_legend_set_line_spacing (PocLegend *self, gdouble line_spacing); +gdouble poc_legend_get_line_spacing (PocLegend *self); + +G_END_DECLS + +#endif diff --git a/pocplot.c b/pocplot.c new file mode 100644 index 0000000..281ba71 --- /dev/null +++ b/pocplot.c @@ -0,0 +1,1358 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include "pocplot.h" +#include "pocdataset.h" +#include "pocaxis.h" +#include "pocbag.h" +#include "poctypes.h" + +#include + +/** + * SECTION: pocplot + * @title: PocPlot + * @short_description: #PocPlot canvas widget + * @see_also: #PocAxis #PocDataset #PocLegend #PocSample + * + * #PocPlot is a 2D graph plotting canvas. To create a plot, add #PocAxis and + * #PocDataset gadgets to this widget, see poc_plot_add_dataset() and + * poc_plot_add_axis(). + * + * # PocPlot as GtkBuildable + * + * The PocPlot implementation supports a custom `` element which supports + * any number of `` and `` elements. + * + * An example of a UI definition fragment for a PocPlot. + * |[ + * + * + * + * + * + * + * ... + * + * + * ... + * + * ]| + * + * The `` element supports the following attributes: + * + * - source: Xml id of a PocDataset element. + * - x-pack: an optional #GtkPackType, how to pack the dataset's X axis. + * - y-pack: an optional #GtkPackType, how to pack the dataset's Y axis. + * + * The `` elements support the following attribute: + * + * - source: Xml id of a PocAxis element. + * - pack: an optional #GtkPackType, how to pack the axis. + * - orientation: a #GtkOrientation, set the Axis orientation + * - hidden: an optional #gboolean, whether the axis should be displayed or hidden. + */ + +struct _PocPlot +{ + GtkDrawingArea parent_instance; + + GdkRGBA plot_fill; + gfloat border; + + gchar *title; + + PocObjectBag *axes; + PocObjectBag *datasets; + PocAxis *x_axis; + PocAxis *y_axis; + + GdkRectangle area; + gint width, height; + + gint solo; + guint enable_plot_fill : 1; + guint relayout : 1; +}; + +typedef struct _PocPlotAxis PocPlotAxis; +struct _PocPlotAxis + { + GtkPackType pack; + GtkOrientation orientation; + gboolean hidden; + GdkRectangle area; + }; + +typedef struct _PocPlotDataset PocPlotDataset; +struct _PocPlotDataset + { + gboolean solo; + }; + +static void poc_plot_buildable_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (PocPlot, poc_plot, GTK_TYPE_DRAWING_AREA, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, poc_plot_buildable_init)) + +/** + * poc_plot_new: + * + * Create a new #PocPlot + * + * Returns: (transfer full): New #PocPlot + */ +PocPlot * +poc_plot_new (void) +{ + return g_object_new (POC_TYPE_PLOT, NULL); +} + +/* GObject {{{1 */ + +enum + { + PROP_0, + + PROP_ENABLE_PLOT_FILL, + PROP_PLOT_FILL, + PROP_BORDER, + + PROP_TITLE, + + PROP_X_AXIS, + PROP_Y_AXIS, + + N_PROPERTIES + }; +static GParamSpec *poc_plot_prop[N_PROPERTIES]; + +static void poc_plot_dispose (GObject *object); +static void poc_plot_finalize (GObject *object); +static void poc_plot_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_plot_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static gboolean poc_plot_draw (GtkWidget *widget, cairo_t *cr); + +static void +poc_plot_class_init (PocPlotClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gobject_class->dispose = poc_plot_dispose; + gobject_class->finalize = poc_plot_finalize; + gobject_class->set_property = poc_plot_set_property; + gobject_class->get_property = poc_plot_get_property; + + gtk_widget_class_set_css_name (widget_class, "plot"); + widget_class->draw = poc_plot_draw; + + /*FIXME - the following should be specified by CSS */ + poc_plot_prop[PROP_ENABLE_PLOT_FILL] = g_param_spec_boolean ( + "enable-plot-fill", + "Enable plot Fill", "Enable plot fill colour", + FALSE, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_plot_prop[PROP_PLOT_FILL] = g_param_spec_boxed ( + "plot-fill", + "Plot Fill Colour", "Plot fill colour for graph area.", + GDK_TYPE_RGBA, + G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + poc_plot_prop[PROP_BORDER] = g_param_spec_float ( + "border", + "Internal Border", "Internal border width between plot items", + 0.0f, 100.0f, 6.0f, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + poc_plot_prop[PROP_TITLE] = g_param_spec_string ( + "title", + "Title", "Plot title.", + NULL, + G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + poc_plot_prop[PROP_X_AXIS] = g_param_spec_object ( + "x-axis", "X Axis", + "Current X axis - draw X coordinate grid for this axis", + POC_TYPE_AXIS, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + poc_plot_prop[PROP_Y_AXIS] = g_param_spec_object ( + "y-axis", "Y Axis", + "Current X axis - draw Y coordinate grid for this axis", + POC_TYPE_AXIS, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_plot_prop); +} + +static void +poc_plot_init (PocPlot *self) +{ + GdkRGBA white = { 1.0, 1.0, 1.0, 1.0 }; + + self->plot_fill = white; + + self->axes = poc_object_bag_new (); + self->datasets = poc_object_bag_new (); +} + +static void +poc_plot_dispose (GObject *object) +{ + PocPlot *self = (PocPlot *) object; + + g_clear_object (&self->x_axis); + g_clear_object (&self->y_axis); + if (self->datasets != NULL) + poc_object_bag_unref (self->datasets); + self->datasets = NULL; + if (self->axes != NULL) + poc_object_bag_unref (self->axes); + self->axes = NULL; + G_OBJECT_CLASS (poc_plot_parent_class)->dispose (object); +} + +static void +poc_plot_finalize (GObject *object) +{ + PocPlot *self = (PocPlot *) object; + + g_free (self->title); + G_OBJECT_CLASS (poc_plot_parent_class)->finalize (object); +} + +static void +poc_plot_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocPlot *self = POC_PLOT (object); + + switch (prop_id) + { + case PROP_ENABLE_PLOT_FILL: + poc_plot_set_enable_plot_fill (self, g_value_get_boolean (value)); + break; + case PROP_PLOT_FILL: + poc_plot_set_plot_fill (self, g_value_get_boxed (value)); + break; + case PROP_BORDER: + poc_plot_set_border (self, g_value_get_float (value)); + break; + + case PROP_TITLE: + poc_plot_set_title (self, g_value_get_string (value)); + break; + + case PROP_X_AXIS: + poc_plot_set_x_axis (self, g_value_get_object (value)); + break; + case PROP_Y_AXIS: + poc_plot_set_y_axis (self, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + +static void +poc_plot_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocPlot *self = POC_PLOT (object); + GdkRGBA rgba; + + switch (prop_id) + { + case PROP_ENABLE_PLOT_FILL: + g_value_set_boolean (value, poc_plot_get_enable_plot_fill (self)); + break; + case PROP_PLOT_FILL: + poc_plot_get_plot_fill (self, &rgba); + g_value_set_boxed (value, &rgba); + break; + case PROP_BORDER: + g_value_set_float (value, poc_plot_get_border (self)); + break; + + case PROP_TITLE: + g_value_set_string (value, poc_plot_get_title (self)); + break; + + case PROP_X_AXIS: + g_value_set_object (value, poc_plot_get_x_axis (self)); + break; + case PROP_Y_AXIS: + g_value_set_object (value, poc_plot_get_y_axis (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* Properties {{{1 */ + +/* enable plot fill {{{2 */ + +/** + * poc_plot_set_enable_plot_fill: + * @self: A #PocPlot + * @value: %TRUE to enable plot fill. + * + * Set whether to fill the plot area plot. + */ +void +poc_plot_set_enable_plot_fill (PocPlot *self, gboolean value) +{ + self->enable_plot_fill = !!value; + poc_plot_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_ENABLE_PLOT_FILL]); +} + +/** + * poc_plot_get_enable_plot_fill: + * @self: A #PocPlot + * + * Get whether to fill the plot area plot. + * + * Returns: %TRUE if plot fill is enabled. + */ +gboolean +poc_plot_get_enable_plot_fill (PocPlot *self) +{ + return self->enable_plot_fill; +} + +/* plot fill {{{2 */ + +/** + * poc_plot_get_plot_fill: + * @self: A #PocPlot + * @rgba: A #GdkRGBA with the plot background colour. + * + * Set the plot area background colour. + */ +void +poc_plot_get_plot_fill (PocPlot *self, GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (rgba != NULL); + + *rgba = self->plot_fill; +} + +/** + * poc_plot_set_plot_fill: + * @self: A #PocPlot + * @rgba: A #GdkRGBA to receive the plot background colour. + * + * Get the plot area plot colour. + */ +void +poc_plot_set_plot_fill (PocPlot *self, const GdkRGBA *rgba) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (rgba != NULL); + + self->plot_fill = *rgba; + poc_plot_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_PLOT_FILL]); +} + +/* border {{{1 */ + +/** + * poc_plot_set_border: + * @self: A #PocPlot + * @size: border width + * + * Set the internal border width. + */ +void +poc_plot_set_border (PocPlot *self, gfloat size) +{ + g_return_if_fail (POC_IS_PLOT (self)); + + self->border = size; + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_BORDER]); +} + +/** + * poc_plot_get_border: + * @self: A #PocPlot + * + * Get the internal border width. + * + * Returns: border width + */ +gfloat +poc_plot_get_border (PocPlot *self) +{ + g_return_val_if_fail (POC_IS_PLOT (self), 0.0); + + return self->border; +} + +/* title {{{2 */ + +/** + * poc_plot_set_title: + * @self: A #PocPlot + * @title: Title for the plot. + * + * Set the plot title. The title is not used directly by the #PocPlot widget, + * however it may be used by other gadgets added to the plot. + */ +void +poc_plot_set_title (PocPlot *self, const gchar *title) +{ + g_return_if_fail (POC_IS_PLOT (self)); + + g_free (self->title); + self->title = g_strdup (title); + poc_plot_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_TITLE]); +} + +/** + * poc_plot_get_title: + * @self: A #PocPlot + * + * Set the plot title. + * + * Returns: (transfer none): Plot title. + */ +const gchar * +poc_plot_get_title (PocPlot *self) +{ + g_return_val_if_fail (POC_IS_PLOT (self), NULL); + + return self->title; +} + +/* "x-axis" {{{2 */ + +/** + * poc_plot_set_x_axis: + * @self: A #PocPlot + * @x_axis: #PocAxis to set the current plot grid + * + * Set the current X axis. This is used to draw the X axis grid lines in the + * plot area. + */ +void +poc_plot_set_x_axis (PocPlot *self, PocAxis *x_axis) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_AXIS (x_axis)); + + if (poc_object_bag_contains (self->axes, G_OBJECT (x_axis)) + && g_set_object (&self->x_axis, x_axis)) + { + poc_plot_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_X_AXIS]); + } +} + +/** + * poc_plot_get_x_axis: + * @self: A #PocPlot + * + * Get the current X axis. + * + * Returns: (transfer none): current X-Axis + */ +PocAxis * +poc_plot_get_x_axis (PocPlot *self) +{ + g_return_val_if_fail (POC_IS_PLOT (self), NULL); + + return self->x_axis; +} + +/* "y-axis" {{{2 */ + +/** + * poc_plot_set_y_axis: + * @self: A #PocPlot + * @y_axis: #PocAxis to set the current plot grid + * + * Set the current Y axis. This is used to draw the Y axis grid lines in the + * plot area. + */ +void +poc_plot_set_y_axis (PocPlot *self, PocAxis *y_axis) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_AXIS (y_axis)); + + if (poc_object_bag_contains (self->axes, G_OBJECT (y_axis)) + && g_set_object (&self->y_axis, y_axis)) + { + poc_plot_notify_update (self); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_Y_AXIS]); + } +} + +/** + * poc_plot_get_y_axis: + * @self: A #PocPlot + * + * Get the current Y axis. + * + * Returns: (transfer none): current Y-Axis + */ +PocAxis * +poc_plot_get_y_axis (PocPlot *self) +{ + g_return_val_if_fail (POC_IS_PLOT (self), NULL); + + return self->y_axis; +} + +/** + * poc_plot_set_axis: + * @self: A #PocPlot + * @axis: #PocAxis to set the current plot grid + * + * Set the current axis. The #PocPlot determines whether @axis refers to an + * X or Y axis. + */ +void +poc_plot_set_axis (PocPlot *self, PocAxis *axis) +{ + PocPlotAxis *axis_data; + + g_return_if_fail (POC_IS_PLOT (self)); + + axis_data = poc_object_bag_get_data (self->axes, G_OBJECT (axis)); + g_return_if_fail (axis_data != NULL); + + if (axis_data->orientation == GTK_ORIENTATION_HORIZONTAL) + poc_plot_set_x_axis (self, axis); + else + poc_plot_set_y_axis (self, axis); +} + +/* draw {{{1 */ + +struct poc_plot_closure + { + PocPlot *self; + cairo_t *cr; + GtkStyleContext *style; + GdkRectangle area; + guint x_offset_start; + guint x_offset_end; + guint y_offset_start; + guint y_offset_end; + }; + +static void poc_plot_layout (PocPlot *self, struct poc_plot_closure *closure); + +static void +poc_plot_draw_dataset (gpointer object, gpointer object_data, + gpointer user_data) +{ + PocDataset *dataset = POC_DATASET (object); + PocPlotDataset *dataset_data = object_data; + struct poc_plot_closure *closure = user_data; + PocPlot *self = closure->self; + + if (self->solo == 0 || dataset_data->solo) + poc_dataset_draw (dataset, closure->cr, self->area.width, self->area.height); +} + +static void +poc_plot_draw_axis (gpointer object, gpointer object_data, gpointer user_data) +{ + PocAxis *axis = object; + PocPlotAxis *axis_data = object_data; + struct poc_plot_closure *closure = user_data; + + if (axis_data->hidden) + return; + + cairo_save (closure->cr); + gdk_cairo_rectangle (closure->cr, &axis_data->area); + cairo_clip (closure->cr); + cairo_translate (closure->cr, axis_data->area.x, axis_data->area.y); + poc_axis_draw_axis (axis, closure->cr, + axis_data->orientation, axis_data->pack, + axis_data->area.width, axis_data->area.height, + closure->style); + cairo_restore (closure->cr); +} + +static gboolean +poc_plot_draw (GtkWidget *widget, cairo_t *cr) +{ + PocPlot *self = (PocPlot *) widget; + struct poc_plot_closure closure = { NULL, NULL, NULL, { 0, 0, 0, 0 }, 0, 0, 0, 0 }; + GtkStyleContext *style; + GtkBorder border; + GtkStateFlags state; + + style = gtk_widget_get_style_context (widget); + state = gtk_style_context_get_state (style); + + closure.self = self; + closure.cr = cr; + closure.area.width = gtk_widget_get_allocated_width (widget); + closure.area.height = gtk_widget_get_allocated_height (widget); + closure.style = style; + + gtk_render_background (style, cr, closure.area.x, closure.area.y, + closure.area.width, closure.area.height); + gtk_render_frame (style, cr, closure.area.x, closure.area.y, + closure.area.width, closure.area.height); + + gtk_style_context_get_border (style, state, &border); + closure.area.x += border.left; + closure.area.y += border.top; + closure.area.width -= border.right + border.left; + closure.area.height -= border.bottom + border.top; + + gtk_style_context_get_padding (style, state, &border); + closure.area.x += border.left; + closure.area.y += border.top; + closure.area.width -= border.right + border.left; + closure.area.height -= border.bottom + border.top; + + if (self->relayout || closure.area.width != self->width || closure.area.height != self->height) + { + poc_plot_layout (self, &closure); + self->relayout = FALSE; + self->width = closure.area.width; + self->height = closure.area.height; + } + + /* Draw axes */ + poc_object_bag_foreach (self->axes, poc_plot_draw_axis, &closure); + + /**** draw the plot background ****/ + cairo_save (cr); + gdk_cairo_rectangle (cr, &self->area); + if (self->enable_plot_fill) + { + gdk_cairo_set_source_rgba (cr, &self->plot_fill); + cairo_fill_preserve (cr); + } + cairo_clip (cr); + cairo_translate (cr, self->area.x, self->area.y); + + /* Draw each dataset */ + poc_object_bag_foreach (self->datasets, poc_plot_draw_dataset, &closure); + + /* Draw the grid */ + if (self->x_axis != NULL) + poc_axis_draw_grid (self->x_axis, cr, GTK_ORIENTATION_HORIZONTAL, + self->area.width, self->area.height, style); + if (self->y_axis != NULL) + poc_axis_draw_grid (self->y_axis, cr, GTK_ORIENTATION_VERTICAL, + self->area.width, self->area.height, style); + + cairo_restore (cr); + + return FALSE; +} + +/* layout {{{1 */ + +static void +poc_plot_layout_axis1 (gpointer object, gpointer object_data, gpointer user_data) +{ + PocAxis *axis = object; + PocPlotAxis *axis_data = object_data; + struct poc_plot_closure *closure = user_data; + guint size; + + if (axis_data->hidden) + return; + + size = poc_axis_size (axis); + if (axis_data->orientation == GTK_ORIENTATION_VERTICAL) + { + if (axis_data->pack == GTK_PACK_START) + { + axis_data->area.x = closure->area.x + closure->x_offset_start; + axis_data->area.width = size; + closure->x_offset_start += size + closure->self->border; + } + else + { + axis_data->area.x = closure->area.x + closure->area.width - 1 + - (closure->x_offset_end + size); + axis_data->area.width = size; + closure->x_offset_end += size + closure->self->border; + } + } + else + { + if (axis_data->pack == GTK_PACK_START) + { + axis_data->area.y = closure->area.y + closure->area.height - 1 + - (closure->y_offset_start + size); + axis_data->area.height = size; + closure->y_offset_start += size + closure->self->border; + } + else + { + axis_data->area.y = closure->area.y + closure->y_offset_end; + axis_data->area.height = size; + closure->y_offset_end += size + closure->self->border; + } + } +} + +static void +poc_plot_layout_axis2 (G_GNUC_UNUSED gpointer object, gpointer object_data, + gpointer user_data) +{ + PocPlotAxis *axis_data = object_data; + struct poc_plot_closure *closure = user_data; + + if (axis_data->hidden) + return; + + if (axis_data->orientation == GTK_ORIENTATION_VERTICAL) + { + axis_data->area.y = closure->self->area.y; + axis_data->area.height = closure->self->area.height; + } + else + { + axis_data->area.x = closure->self->area.x; + axis_data->area.width = closure->self->area.width; + } +} + +static void +poc_plot_layout (PocPlot *self, struct poc_plot_closure *closure) +{ + closure->x_offset_start = closure->y_offset_start = 0; + closure->x_offset_end = closure->y_offset_end = 0; + + /* first pass set x coords for vertical axes, y for horizontal */ + poc_object_bag_foreach (self->axes, poc_plot_layout_axis1, closure); + /* set the plot area */ + self->area.x = closure->area.x + closure->x_offset_start; + self->area.width = closure->area.width - closure->x_offset_end + - closure->x_offset_start; + self->area.y = closure->area.y + closure->y_offset_end; + self->area.height = closure->area.height - closure->y_offset_start + - closure->y_offset_end; + //FIXME warn if insufficient area + /* second pass set y coords for vertical axes, x for horizontal */ + poc_object_bag_foreach (self->axes, poc_plot_layout_axis2, closure); +} + +/* methods {{{1 */ + +/** + * poc_plot_add_dataset: + * @self: A #PocPlot + * @dataset: #PocDataset to add. + * @x_pack: How to pack the X axis. + * @y_pack: How to pack the Y axis (start = bottom, end = top). + * + * Add a #PocDataset gadget to the plot. @x_pack and @y_pack determine which + * edge of the plot should show the axes. + */ +void +poc_plot_add_dataset (PocPlot *self, PocDataset *dataset, + GtkPackType x_pack, GtkPackType y_pack) +{ + PocAxis *axis; + PocPlotDataset *data; + + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_DATASET (dataset)); + + g_object_freeze_notify (G_OBJECT (self)); + + //XXX Dataset should probably be initiallyunowned and use ref_sink + if (!poc_object_bag_add (self->datasets, G_OBJECT (dataset))) + { + data = g_new0 (PocPlotDataset, 1); + poc_object_bag_set_data_full (self->datasets, G_OBJECT (dataset), data, g_free); + + g_signal_connect_object (dataset, "update", + G_CALLBACK (poc_plot_notify_update), self, + G_CONNECT_SWAPPED); + } + + if ((axis = poc_dataset_get_x_axis (dataset)) != NULL) + { + poc_plot_add_axis (self, axis, FALSE, x_pack, GTK_ORIENTATION_HORIZONTAL); + if (self->x_axis == NULL) + poc_plot_set_x_axis (self, axis); + } + if ((axis = poc_dataset_get_y_axis (dataset)) != NULL) + { + poc_plot_add_axis (self, axis, FALSE, y_pack, GTK_ORIENTATION_VERTICAL); + if (self->y_axis == NULL) + poc_plot_set_y_axis (self, axis); + } + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_thaw_notify (G_OBJECT (self)); +} + +static void +poc_plot_remove_dataset_internal (PocPlot *self, PocDataset *dataset) +{ + PocAxis *axis; + + g_signal_handlers_disconnect_by_func (dataset, + G_CALLBACK (poc_plot_notify_update), + self); + if ((axis = poc_dataset_get_x_axis (dataset)) != NULL) + poc_plot_remove_axis (self, axis); + if ((axis = poc_dataset_get_y_axis (dataset)) != NULL) + poc_plot_remove_axis (self, axis); +} + +/** + * poc_plot_remove_dataset: + * @self: A #PocPlot + * @dataset: #PocDataset to remove + * + * Remove a @dataset from the plot. The @dataset must already belong to the + * plot. Axes that are no longer referenced by another #PocDataset are also + * removed. + */ +void +poc_plot_remove_dataset (PocPlot *self, PocDataset *dataset) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_DATASET (dataset)); + + g_object_ref (dataset); + if (poc_object_bag_remove (self->datasets, G_OBJECT (dataset))) + { + poc_plot_remove_dataset_internal (self, dataset); + gtk_widget_queue_draw (GTK_WIDGET (self)); + } + g_object_unref (dataset); +} + +static void +poc_plot_clear_dataset_cb (gpointer object, G_GNUC_UNUSED gpointer object_data, + gpointer user_data) +{ + PocPlot *self = user_data; + PocDataset *dataset = object; + + poc_plot_remove_dataset_internal (self, dataset); +} + +/** + * poc_plot_clear_dataset: + * @self: A #PocPlot + * + * Remove all datasets from the plot. + */ +void +poc_plot_clear_dataset (PocPlot *self) +{ + poc_object_bag_foreach (self->datasets, poc_plot_clear_dataset_cb, self); + poc_object_bag_empty (self->datasets); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static gboolean +poc_plot_dataset_compare (gpointer object, G_GNUC_UNUSED gpointer object_data, + gpointer user_data) +{ + PocDataset *dataset = object; + const gchar *nickname = user_data; + const gchar *ds_name; + + ds_name = poc_dataset_get_nickname (dataset); + return g_strcmp0 (nickname, ds_name) == 0; +} + +/** + * poc_plot_find_dataset: + * @self: A #PocPlot + * @nickname: Nickname for a #PocDataset + * + * Find a dataset belonging to the plot with the requested nickname. + * + * returns: (transfer none): a #PocDataset or %NULL if not found. + */ +PocDataset * +poc_plot_find_dataset (PocPlot *self, const gchar *nickname) +{ + GObject *object; + + g_return_val_if_fail (POC_IS_PLOT (self), NULL); + object = poc_object_bag_find (self->datasets, poc_plot_dataset_compare, + (gpointer) nickname); + return object != NULL ? POC_DATASET (object) : NULL; +} + +/** + * poc_plot_solo_dataset: + * @self: A #PocPlot. + * @dataset: A #PocDataset belonging to the plot. + * @solo: %TRUE to show only this @dataset. + * + * Show only the grid lines and plot data for the specified @dataset. If + * multiple datasets have solo enabled only they are displayed, if no datasets + * are solo then all datasets are displayed. + */ +void +poc_plot_solo_dataset (PocPlot *self, PocDataset *dataset, gboolean solo) +{ + PocPlotDataset *data; + + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_DATASET (dataset)); + + data = poc_object_bag_get_data (self->datasets, G_OBJECT (dataset)); + g_return_if_fail (data != NULL); + + if (solo && !data->solo) + self->solo += 1; + else if (!solo && data->solo) + self->solo -= 1; + else + return; + data->solo = !!solo; + g_assert (self->solo >= 0); + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +/** + * poc_plot_add_axis: + * @self: A #PocPlot + * @axis: #PocAxis to add. + * @hidden: Do not draw @axis if %TRUE + * @pack: How to pack the axis. + * @orientation: Orientation for the axis. + * + * Add an axis to the plot. @pack and @orientation specify how the axis should + * be displayed. Normally this is not required as axes belonging to datasets + * are added automatically. + */ +void +poc_plot_add_axis (PocPlot *self, PocAxis *axis, gboolean hidden, + GtkPackType pack, GtkOrientation orientation) +{ + PocPlotAxis *data; + + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_AXIS (axis)); + + //XXX Axis should probably be initiallyunowned and use ref_sink + if (!poc_object_bag_add (self->axes, G_OBJECT (axis))) + { + data = g_new0 (PocPlotAxis, 1); + data->hidden = hidden; + data->pack = pack; + data->orientation = orientation; + poc_object_bag_set_data_full (self->axes, G_OBJECT (axis), data, g_free); + + g_signal_connect_object (axis, "update", + G_CALLBACK (poc_plot_notify_update), self, + G_CONNECT_SWAPPED); + + self->relayout = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +/** + * poc_plot_remove_axis: + * @self: A #PocPlot + * @axis: #PocAxis to remove. + * + * Remove the @axis from the plot. + */ +void +poc_plot_remove_axis (PocPlot *self, PocAxis *axis) +{ + g_return_if_fail (POC_IS_PLOT (self)); + g_return_if_fail (POC_IS_AXIS (axis)); + + if (poc_object_bag_remove (self->axes, G_OBJECT (axis))) + { + g_signal_handlers_disconnect_by_func (axis, + G_CALLBACK (poc_plot_notify_update), + self); + if (axis == self->x_axis) + { + g_clear_object (&self->x_axis); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_X_AXIS]); + } + if (axis == self->y_axis) + { + g_clear_object (&self->y_axis); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_Y_AXIS]); + } + self->relayout = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +/** + * poc_plot_clear_axes: + * @self: A #PocPlot + * + * Remove all axes from the plot. + */ +void +poc_plot_clear_axes (PocPlot *self) +{ + g_clear_object (&self->x_axis); + g_clear_object (&self->y_axis); + poc_object_bag_empty (self->axes); + + self->relayout = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (self)); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_X_AXIS]); + g_object_notify_by_pspec (G_OBJECT (self), poc_plot_prop[PROP_Y_AXIS]); +} + +struct point + { + gint x; + gint y; + }; + +static gboolean +poc_plot_is_axis_at_point (G_GNUC_UNUSED gpointer object, gpointer obj_data, + gpointer user_data) +{ + PocPlotAxis *axis_data = obj_data; + struct point *point = user_data; + + return point->x >= axis_data->area.x + && point->x < axis_data->area.x + axis_data->area.width + && point->y >= axis_data->area.y + && point->y < axis_data->area.y + axis_data->area.height; +} + +/** + * poc_plot_axis_at_point: + * @self: A #PocPlot + * @x: x-coordinate in pixels + * @y: y-coordinate in pixels + * + * Find the #PocAxis under the specifiec @x, @y coordinate. + * + * Returns: (transfer none): the #PocAxis. + */ +PocAxis * +poc_plot_axis_at_point (PocPlot *self, gdouble x, gdouble y) +{ + struct point point; + GObject *obj; + + point.x = x; + point.y = y; + obj = poc_object_bag_find (self->axes, poc_plot_is_axis_at_point, &point); + return POC_AXIS (obj); +} + +/** + * poc_plot_notify_update: + * @self: A #PocPlot + * + * Notify PocPlot of updates in a dataset or axis. + */ +void +poc_plot_notify_update (PocPlot *self) +{ + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +/* data and axis iterators {{{1 */ + +struct foreach_closure + { + PocPlot *self; + GHRFunc predicate; + gpointer user_data; + }; + +static gboolean +foreach_wrapper (gpointer object, G_GNUC_UNUSED gpointer object_data, + gpointer user_data) +{ + struct foreach_closure *closure = user_data; + + return (*closure->predicate) (closure->self, object, closure->user_data); +} + +/** + * poc_plot_dataset_foreach: + * @self: A #PocPlot + * @predicate: (scope call): iterator predicate function. + * @user_data: user data passed to @predicate. + * + * Call @predicate for each dataset in the plot. If @predicate returns + * %TRUE return the dataset. If iteration completes %NULL is returned. + * + * Returns: (transfer none): the #PocDataset or %NULL. + */ +PocDataset * +poc_plot_dataset_foreach (PocPlot *self, PocPlotDatasetForEachFunc predicate, + gpointer user_data) +{ + struct foreach_closure closure = { self, (GHRFunc) predicate, user_data }; + + return (PocDataset *) poc_object_bag_find (self->datasets, foreach_wrapper, &closure); +} + +/** + * poc_plot_axis_foreach: + * @self: A #PocPlot + * @predicate: (scope call): iterator predicate function. + * @user_data: user data passed to @predicate. + * + * Call @predicate for each axis in the plot. If @predicate returns + * %TRUE return the axis. If iteration completes %NULL is returned. + * + * Returns: (transfer none): the #PocAxis or %NULL. + */ +PocAxis * +poc_plot_axis_foreach (PocPlot *self, PocPlotAxisForEachFunc predicate, + gpointer user_data) +{ + struct foreach_closure closure = { self, (GHRFunc) predicate, user_data }; + + return (PocAxis *) poc_object_bag_find (self->axes, foreach_wrapper, &closure); +} + +/* buildable {{{1 */ +/* implements + + + + + */ + +typedef struct _PocPlotParser PocPlotParser; +struct _PocPlotParser + { + GSList *dataset; + GSList *axes; + }; + +typedef struct _PocParserDataset PocParserDataset; +struct _PocParserDataset + { + gchar *source; + GtkPackType x_pack; + GtkPackType y_pack; + }; + +static void +free_dataset_data (gpointer item) +{ + PocParserDataset *axis = item; + + g_free (axis->source); + g_free (axis); +} + +typedef struct _PocParserAxis PocParserAxis; +struct _PocParserAxis + { + gchar *source; + gboolean hidden; + GtkPackType pack; + GtkOrientation orientation; + }; + +static void +free_axis_data (gpointer item) +{ + PocParserAxis *axis = item; + + g_free (axis->source); + g_free (axis); +} + +/* sub parse {{{2 */ + +static gint +enum_from_string (GType enum_type, const gchar *string) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_peek (enum_type); + g_return_val_if_fail (enum_class != NULL, 0); + enum_value = g_enum_get_value_by_name (enum_class, string); + if (enum_value == NULL) + enum_value = g_enum_get_value_by_nick (enum_class, string); + if (enum_value == NULL) + enum_value = g_enum_get_value (enum_class, strtol (string, NULL, 0)); + g_return_val_if_fail (enum_value != NULL, 0); + + return enum_value->value; +} + +static void +poc_plot_parser_start_element (G_GNUC_UNUSED GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, const gchar **values, + gpointer user_data, GError **error) +{ + PocPlotParser *data = (PocPlotParser *) user_data; + gchar *source; + const gchar *pack, *y_pack, *orientation; + gboolean hidden; + PocParserDataset *dataset; + PocParserAxis *axis; + + if (strcmp (element_name, "dataset") == 0) + { + if (g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRDUP, + "source", &source, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, + "x-pack", &pack, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, + "y-pack", &y_pack, + G_MARKUP_COLLECT_INVALID)) + { + dataset = g_new0 (PocParserDataset, 1); + dataset->source = source; + dataset->x_pack = pack != NULL ? enum_from_string (GTK_TYPE_PACK_TYPE, pack) : GTK_PACK_START; + dataset->y_pack = y_pack != NULL ? enum_from_string (GTK_TYPE_PACK_TYPE, y_pack) : GTK_PACK_START; + data->dataset = g_slist_prepend (data->dataset, dataset); + } + } + else if (strcmp (element_name, "axis") == 0) + { + if (g_markup_collect_attributes (element_name, names, values, error, + G_MARKUP_COLLECT_STRDUP, + "source", &source, + G_MARKUP_COLLECT_BOOLEAN | G_MARKUP_COLLECT_OPTIONAL, + "hidden", &hidden, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, + "pack", &pack, + G_MARKUP_COLLECT_STRING, + "orientation", &orientation, + G_MARKUP_COLLECT_INVALID)) + { + axis = g_new0 (PocParserAxis, 1); + axis->source = source; + axis->hidden = hidden; + axis->pack = pack != NULL ? enum_from_string (GTK_TYPE_PACK_TYPE, pack) : GTK_PACK_START; + axis->orientation = enum_from_string (GTK_TYPE_ORIENTATION, orientation); + data->axes = g_slist_prepend (data->axes, axis); + } + } +} + +static const GMarkupParser poc_plot_sub_parser = + { + poc_plot_parser_start_element, + NULL, + NULL, + NULL, + NULL, + }; + +/* custom tag {{{2 */ + +static gboolean +poc_plot_buildable_custom_tag_start (G_GNUC_UNUSED GtkBuildable *buildable, + G_GNUC_UNUSED GtkBuilder *builder, + G_GNUC_UNUSED GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *parser_data) +{ + PocPlotParser *data = NULL; + + if (strcmp (tagname, "plot") != 0) + return FALSE; + + data = g_slice_new0 (PocPlotParser); + *parser = poc_plot_sub_parser; + *parser_data = data; + return TRUE; +} + +static void +poc_plot_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + G_GNUC_UNUSED GObject *child, + const gchar *tagname, + gpointer user_data) +{ + PocPlot *self = POC_PLOT (buildable); + PocPlotParser *data = user_data; + GSList *item; + GObject *object; + PocParserDataset *dataset; + PocParserAxis *axis; + + if (strcmp (tagname, "plot") != 0) + return; + + data->axes = g_slist_reverse (data->axes); + for (item = data->axes; item != NULL; item = item->next) + { + axis = item->data; + object = gtk_builder_get_object (builder, axis->source); + + if (object != NULL && POC_IS_AXIS (object)) + poc_plot_add_axis (self, POC_AXIS (object), + axis->hidden, axis->pack, axis->orientation); + } + g_slist_free_full (data->axes, free_axis_data); + + data->dataset = g_slist_reverse (data->dataset); + for (item = data->dataset; item != NULL; item = item->next) + { + dataset = item->data; + object = gtk_builder_get_object (builder, dataset->source); + + if (object != NULL && POC_IS_DATASET (object)) + poc_plot_add_dataset (self, POC_DATASET (object), + dataset->x_pack, dataset->y_pack); + } + g_slist_free_full (data->dataset, free_dataset_data); + + g_slice_free (PocPlotParser, data); +} + +/* buildable {{{2 */ + +static void +poc_plot_buildable_init (GtkBuildableIface *iface) +{ + iface->custom_tag_start = poc_plot_buildable_custom_tag_start; + iface->custom_finished = poc_plot_buildable_custom_finished; +} diff --git a/pocplot.h b/pocplot.h new file mode 100644 index 0000000..1e7ccaa --- /dev/null +++ b/pocplot.h @@ -0,0 +1,84 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocplot_h +#define _pocplot_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include +#include "pocaxis.h" +#include "pocdataset.h" +#include "poctypes.h" + +G_BEGIN_DECLS + +#define POC_TYPE_PLOT poc_plot_get_type () +G_DECLARE_FINAL_TYPE (PocPlot, poc_plot, POC, PLOT, GtkDrawingArea) + + +PocPlot * poc_plot_new (void); + +void poc_plot_set_enable_plot_fill (PocPlot *self, gboolean value); +gboolean poc_plot_get_enable_plot_fill (PocPlot *self); +void poc_plot_get_plot_fill (PocPlot *self, GdkRGBA *rgba); +void poc_plot_set_plot_fill (PocPlot *self, const GdkRGBA *rgba); +gfloat poc_plot_get_border (PocPlot *self); +void poc_plot_set_border (PocPlot *self, gfloat size); +void poc_plot_set_title (PocPlot *self, const gchar *title); +const gchar * poc_plot_get_title (PocPlot *self); +void poc_plot_set_x_axis (PocPlot *self, PocAxis *x_axis); +PocAxis * poc_plot_get_x_axis (PocPlot *self); +void poc_plot_set_y_axis (PocPlot *self, PocAxis *y_axis); +PocAxis * poc_plot_get_y_axis (PocPlot *self); +void poc_plot_set_axis (PocPlot *self, PocAxis *axis); + +void poc_plot_add_dataset (PocPlot *self, PocDataset *dataset, + GtkPackType x_pack, GtkPackType y_pack); +void poc_plot_remove_dataset (PocPlot *self, PocDataset *dataset); +void poc_plot_clear_dataset (PocPlot *self); +PocDataset * poc_plot_find_dataset (PocPlot *self, const gchar *nickname); +void poc_plot_solo_dataset (PocPlot *self, PocDataset *dataset, gboolean solo); +PocAxis * poc_plot_axis_at_point (PocPlot *self, gdouble x, gdouble y); +void poc_plot_add_axis (PocPlot *self, PocAxis *axis, + gboolean hidden, GtkPackType pack, + GtkOrientation orientation); +void poc_plot_remove_axis (PocPlot *self, PocAxis *axis); +void poc_plot_clear_axes (PocPlot *self); + +void poc_plot_notify_update (PocPlot *self); + +typedef gboolean (*PocPlotDatasetForEachFunc) (PocPlot *self, + PocDataset *dataset, + gpointer user_data); +PocDataset * poc_plot_dataset_foreach (PocPlot *self, + PocPlotDatasetForEachFunc predicate, + gpointer user_data); + +typedef gboolean (*PocPlotAxisForEachFunc) (PocPlot *self, + PocAxis *axis, + gpointer user_data); +PocAxis * poc_plot_axis_foreach (PocPlot *self, + PocPlotAxisForEachFunc predicate, + gpointer user_data); + +G_END_DECLS + +#endif diff --git a/pocplot.map b/pocplot.map new file mode 100644 index 0000000..9fe6992 --- /dev/null +++ b/pocplot.map @@ -0,0 +1,135 @@ +{ + global: + poc_axis_configure; + poc_axis_draw_axis; + poc_axis_draw_grid; + poc_axis_get_adjustment; + poc_axis_get_auto_interval; + poc_axis_get_axis_mode; + poc_axis_get_display_range; + poc_axis_get_label_size; + poc_axis_get_legend; + poc_axis_get_legend_size; + poc_axis_get_lower_bound; + poc_axis_get_major_grid; + poc_axis_get_major_interval; + poc_axis_get_minor_divisions; + poc_axis_get_minor_grid; + poc_axis_get_range; + poc_axis_get_tick_size; + poc_axis_get_type; + poc_axis_get_upper_bound; + poc_axis_linear_project; + poc_axis_mode_get_type; + poc_axis_new; + poc_axis_project; + poc_axis_set_adjustment; + poc_axis_set_auto_interval; + poc_axis_set_axis_mode; + poc_axis_set_label_size; + poc_axis_set_legend; + poc_axis_set_legend_size; + poc_axis_set_lower_bound; + poc_axis_set_major_grid; + poc_axis_set_major_interval; + poc_axis_set_minor_divisions; + poc_axis_set_minor_grid; + poc_axis_set_tick_size; + poc_axis_set_upper_bound; + poc_axis_size; + poc_dataset_draw; + poc_dataset_get_legend; + poc_dataset_get_line_stroke; + poc_dataset_get_line_style; + poc_dataset_get_nickname; + poc_dataset_get_points; + poc_dataset_get_type; + poc_dataset_get_x_axis; + poc_dataset_get_y_axis; + poc_dataset_invalidate; + poc_dataset_new; + poc_dataset_notify_update; + poc_dataset_set_legend; + poc_dataset_set_line_stroke; + poc_dataset_set_line_style; + poc_dataset_set_nickname; + poc_dataset_set_points; + poc_dataset_set_points_array; + poc_dataset_set_x_axis; + poc_dataset_set_y_axis; + poc_dataset_spline_get_marker_fill; + poc_dataset_spline_get_marker_stroke; + poc_dataset_spline_get_show_markers; + poc_dataset_spline_get_type; + poc_dataset_spline_new; + poc_dataset_spline_set_marker_fill; + poc_dataset_spline_set_marker_stroke; + poc_dataset_spline_set_show_markers; + poc_double_array_get_type; + poc_double_array_new; + poc_double_array_ref; + poc_double_array_set_size; + poc_double_array_sized_new; + poc_double_array_unref; + poc_enum_from_string; + poc_enum_to_string; + poc_legend_get_legend_text_size; + poc_legend_get_line_sample_size; + poc_legend_get_line_spacing; + poc_legend_get_plot; + poc_legend_get_title_text_size; + poc_legend_get_type; + poc_legend_new; + poc_legend_set_legend_text_size; + poc_legend_set_line_sample_size; + poc_legend_set_line_spacing; + poc_legend_set_plot; + poc_legend_set_title_text_size; + poc_line_style_get_dashes; + poc_line_style_get_type; + poc_plot_add_axis; + poc_plot_add_dataset; + poc_plot_axis_at_point; + poc_plot_axis_foreach; + poc_plot_clear_axes; + poc_plot_clear_dataset; + poc_plot_dataset_foreach; + poc_plot_find_dataset; + poc_plot_get_border; + poc_plot_get_enable_plot_fill; + poc_plot_get_plot_fill; + poc_plot_get_plot_ink; + poc_plot_get_title; + poc_plot_get_type; + poc_plot_get_x_axis; + poc_plot_get_y_axis; + poc_plot_new; + poc_plot_notify_update; + poc_plot_remove_axis; + poc_plot_remove_dataset; + poc_plot_set_axis; + poc_plot_set_border; + poc_plot_set_enable_plot_fill; + poc_plot_set_plot_fill; + poc_plot_set_plot_ink; + poc_plot_set_title; + poc_plot_set_x_axis; + poc_plot_set_y_axis; + poc_plot_solo_dataset; + poc_point_array_append_vals; + poc_point_array_get_type; + poc_point_array_new; + poc_point_array_ref; + poc_point_array_set_size; + poc_point_array_sized_new; + poc_point_array_unref; + poc_point_get_type; + poc_sample_get_dataset; + poc_sample_get_type; + poc_sample_new; + poc_sample_set_dataset; + poc_spline_get_points; + poc_spline_get_vector; + local: + *; +}; diff --git a/pocsample.c b/pocsample.c new file mode 100644 index 0000000..2ab515b --- /dev/null +++ b/pocsample.c @@ -0,0 +1,231 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +/* + * PocSample + */ + +#include "pocsample.h" +#include + +typedef struct _PocSample PocSample; +struct _PocSample + { + GtkDrawingArea parent_instance; + + PocDataset * dataset; + }; + +G_DEFINE_TYPE (PocSample, poc_sample, GTK_TYPE_DRAWING_AREA) + +/** + * SECTION: pocsample + * @title: PocSample + * @short_description: Line sample widget for use with #PocDataset. + * @see_also: #PocDataset + * + * Generate a sample of the line used to plot data in the associated + * #PocDataset. This is useful for use as an "image" in buttons or labels + * elsewhere in the UI. + */ + +/** + * poc_sample_new: + * @dataset: a #PocDataset + * + * Create a new #PocSample showing a line sample for @dataset. + * + * Returns: (transfer full): New #PocSample + */ +PocSample * +poc_sample_new (PocDataset *dataset) +{ + return g_object_new (POC_TYPE_SAMPLE, "dataset", dataset, NULL); +} + +enum + { + PROP_0, + PROP_DATASET, + N_PROPERTIES + }; +static GParamSpec *poc_sample_prop[N_PROPERTIES]; + +static void poc_sample_dispose (GObject *object); +static void poc_sample_finalize (GObject *object); +static void poc_sample_get_property (GObject *object, guint param_id, + GValue *value, GParamSpec *pspec); +static void poc_sample_set_property (GObject *object, guint param_id, + const GValue *value, GParamSpec *pspec); +static gboolean poc_sample_draw (GtkWidget *widget, cairo_t *cr); + +/* GObject {{{1 */ + +static void +poc_sample_class_init (PocSampleClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gobject_class->dispose = poc_sample_dispose; + gobject_class->finalize = poc_sample_finalize; + gobject_class->set_property = poc_sample_set_property; + gobject_class->get_property = poc_sample_get_property; + + gtk_widget_class_set_css_name (widget_class, "sample"); + widget_class->draw = poc_sample_draw; + + poc_sample_prop[PROP_DATASET] = g_param_spec_object ( + "dataset", "Dataset", + "Generate a line sample for the referenced dataset.", + POC_TYPE_DATASET, + G_PARAM_CONSTRUCT | G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, poc_sample_prop); +} + +static void +poc_sample_init (PocSample *self) +{ + self->dataset = NULL; +} + +static void +poc_sample_dispose (GObject *object) +{ + PocSample *self = (PocSample *) object; + + g_clear_object (&self->dataset); + G_OBJECT_CLASS (poc_sample_parent_class)->dispose (object); +} + +static void +poc_sample_finalize (GObject *object) +{ + //PocSample *self = (PocSample *) object; + + G_OBJECT_CLASS (poc_sample_parent_class)->finalize (object); +} + +/* Properties {{{1 */ + +static void +poc_sample_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + PocSample *self = POC_SAMPLE (object); + + switch (prop_id) + { + case PROP_DATASET: + poc_sample_set_dataset (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + return; + } +} + + +static void +poc_sample_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + PocSample *self = POC_SAMPLE (object); + + switch (prop_id) + { + case PROP_DATASET: + g_value_set_object (value, poc_sample_get_dataset (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* "dataset" {{{2 */ + +/** + * poc_sample_set_dataset: + * @self: A #PocSample + * @dataset: A #PocDataset + * + * Display a line sample for the associated dataset widget. + */ +void +poc_sample_set_dataset (PocSample *self, PocDataset *dataset) +{ + g_return_if_fail (POC_IS_SAMPLE (self)); + + if (g_set_object (&self->dataset, dataset)) + g_object_notify_by_pspec (G_OBJECT (self), poc_sample_prop[PROP_DATASET]); +} + +/** + * poc_sample_get_dataset: + * @self: A #PocSample + * + * Return the associated dataset. + * + * Returns: (transfer none): a #PocDataset + */ +PocDataset * +poc_sample_get_dataset (PocSample *self) +{ + g_return_val_if_fail (POC_IS_SAMPLE (self), NULL); + + return self->dataset; +} + +/* done */ + +static gboolean +poc_sample_draw (GtkWidget *widget, cairo_t *cr) +{ + PocSample *self = (PocSample *) widget; + GdkRGBA rgba; + const double *dashes; + int num_dashes; + PocLineStyle line_style; + GtkAllocation allocation; + GtkStyleContext *style; + + if (self->dataset == NULL) + return FALSE; + + style = gtk_widget_get_style_context (GTK_WIDGET (self)); + + /* fetch sample parameters from dataset */ + poc_dataset_get_line_stroke (self->dataset, &rgba); + line_style = poc_dataset_get_line_style (self->dataset); + + gtk_widget_get_allocation (widget, &allocation); + gtk_render_background (style, cr, 0, 0, allocation.width, allocation.height); + + cairo_move_to (cr, allocation.width / 10.0, round (allocation.height / 2.0) + 0.5); + cairo_rel_line_to (cr, allocation.width - allocation.width / 5.0, 0.0); + + /* stroke the sample line */ + cairo_set_line_width (cr, 1.0); + dashes = poc_line_style_get_dashes (line_style, &num_dashes); + cairo_set_dash (cr, dashes, num_dashes, 0.0); + gdk_cairo_set_source_rgba (cr, &rgba); + cairo_stroke (cr); + return FALSE; +} diff --git a/pocsample.h b/pocsample.h new file mode 100644 index 0000000..418c64f --- /dev/null +++ b/pocsample.h @@ -0,0 +1,40 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocsample_h +#define _pocsample_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include +#include "pocplot.h" + +G_BEGIN_DECLS + +#define POC_TYPE_SAMPLE poc_sample_get_type () +G_DECLARE_FINAL_TYPE (PocSample, poc_sample, POC, SAMPLE, GtkDrawingArea) + +PocSample * poc_sample_new (PocDataset *dataset); +void poc_sample_set_dataset (PocSample *self, PocDataset *dataset); +PocDataset * poc_sample_get_dataset (PocSample *self); + +G_END_DECLS + +#endif diff --git a/pocspline.c b/pocspline.c new file mode 100644 index 0000000..b0f52b0 --- /dev/null +++ b/pocspline.c @@ -0,0 +1,168 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include "pocspline.h" + +/** + * SECTION: pocspline + * @title: Spline + * @short_description: Solve the tridiagonal equation. + * @see_also: #PocDatasetSpline + * + * Interpolate a curve based on its control points using an algorithm that + * solves the tridiagonal equation based on Numerical Recipies 2nd Edition + */ + +static void +spline_solve (guint n, PocPoint point[], gdouble y2[]) +{ + gdouble p, sig, *u; + guint i, k; + + u = g_malloc ((n - 1) * sizeof (u[0])); + + y2[0] = y2[n-1] = u[0] = 0.0; /* set lower boundary condition to "natural" */ + + for (i = 1; i < n - 1; ++i) + { + sig = (point[i].x - point[i-1].x) / (point[i+1].x - point[i-1].x); + p = sig * y2[i-1] + 2.0; + y2[i] = (sig - 1.0) / p; + u[i] = ((point[i+1].y - point[i].y) / (point[i+1].x - point[i].x) + - (point[i].y - point[i-1].y) / (point[i].x - point[i-1].x)); + u[i] = (6.0 * u[i] / (point[i+1].x - point[i-1].x) - sig * u[i-1]) / p; + } + + for (k = n - 2; k > 0; --k) + y2[k] = y2[k] * y2[k+1] + u[k]; + + g_free (u); +} + +static gdouble +spline_eval (int n, PocPoint point[], gdouble y2[], gdouble val) +{ + gint k_lo, k_hi, k; + gdouble h, b, a; + + /* do a binary search for the right interval */ + k_lo = 0; k_hi = n - 1; + while (k_hi - k_lo > 1) + { + k = (k_hi + k_lo) / 2; + if (point[k].x > val) + k_hi = k; + else + k_lo = k; + } + + h = point[k_hi].x - point[k_lo].x; + a = (point[k_hi].x - val) / h; + b = (val - point[k_lo].x) / h; + return a * point[k_lo].y + b * point[k_hi].y + + ((a*a*a - a) * y2[k_lo] + (b*b*b - b) * y2[k_hi]) * (h*h) / 6.0; +} + +/** + * poc_spline_get_vector: + * @points: A #PocPointArray of control points. + * @min_x: The lowest (leftmost) X coordinate value. + * @max_x: The highest (rightmost) X coordinate value. + * @veclen: The number or points to be calculated in the result vector. + * + * Compute a vector of @veclen Y coordinates spaced evenly between and + * including @min_x and @max_x. + * + * Returns: (transfer full): A #PocDoubleArray of Y coordinates. + */ +PocDoubleArray * +poc_spline_get_vector (PocPointArray *points, + gdouble min_x, gdouble max_x, guint veclen) +{ + PocDoubleArray *array; + gdouble rx, ry, dx, *y2v; + guint x; + guint n_points; + + n_points = poc_point_array_len (points); + g_return_val_if_fail (n_points >= 2, NULL); + + array = poc_double_array_sized_new (veclen); + g_return_val_if_fail (array != NULL && array->data != NULL, NULL); + + y2v = g_malloc (n_points * sizeof (gdouble)); + spline_solve (n_points, points->data, y2v); + + rx = min_x; + dx = (max_x - min_x) / (veclen - 1); + poc_double_array_set_size (array, veclen); + for (x = 0; x < veclen; ++x, rx += dx) + { + ry = spline_eval (n_points, points->data, y2v, rx); + array->data[x] = ry; + } + + g_free (y2v); + + return array; +} + +/** + * poc_spline_get_points: + * @points: A #PocPointArray of control points. + * @min_x: The lowest (leftmost) X coordinate value. + * @max_x: The highest (rightmost) X coordinate value. + * @veclen: The number or points to be calculated in the result vector. + * + * Compute a vector of @veclen points spaced evenly between and including + * @min_x and @max_x. + * + * Returns: (transfer full): A #PocPointArray of (X,Y) coordinates. + */ +PocPointArray * +poc_spline_get_points (PocPointArray *points, + gdouble min_x, gdouble max_x, guint veclen) +{ + PocPointArray *array; + PocPoint p; + gdouble rx, ry, dx, *y2v; + guint x; + guint n_points; + + n_points = poc_point_array_len (points); + g_return_val_if_fail (n_points >= 2, NULL); + + array = poc_point_array_sized_new (veclen); + g_return_val_if_fail (array != NULL && array->data != NULL, NULL); + + y2v = g_malloc (n_points * sizeof (gdouble)); + spline_solve (n_points, points->data, y2v); + + rx = min_x; + dx = (max_x - min_x) / (veclen - 1); + for (x = 0; x < veclen; ++x, rx += dx) + { + ry = spline_eval (n_points, points->data, y2v, rx); + p.x = rx; + p.y = ry; + poc_point_array_append_val (array, p); + } + g_free (y2v); + + return array; +} diff --git a/pocspline.h b/pocspline.h new file mode 100644 index 0000000..ce88e26 --- /dev/null +++ b/pocspline.h @@ -0,0 +1,39 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _pocspline_h +#define _pocspline_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include "poctypes.h" + +G_BEGIN_DECLS + +PocPointArray * poc_spline_get_points (PocPointArray *points, + gdouble min_x, gdouble max_x, + guint veclen); +PocDoubleArray *poc_spline_get_vector (PocPointArray *points, + gdouble min_x, gdouble max_x, + guint veclen); + +G_END_DECLS + +#endif diff --git a/poctypes.c b/poctypes.c new file mode 100644 index 0000000..d715b0f --- /dev/null +++ b/poctypes.c @@ -0,0 +1,381 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#include "poctypes.h" + +/* Point {{{1 */ + +/** + * SECTION: poctypes + * @title: Poc Types + * @short_description: Boxed types and enums used by PocPlot. + * + * Boxed types and enums used by PocPlot. + */ + +/** + * PocPoint: + * @x: an X coordinate + * @y: a Y coordinate + * + * A boxed type representing an (x,y) coordinate. + */ + +static PocPoint * +poc_point_copy (const PocPoint *pt) +{ + return g_slice_dup (PocPoint, pt); +} + +static void +poc_point_free (PocPoint *pt) +{ + g_slice_free (PocPoint, pt); +} + +G_DEFINE_BOXED_TYPE (PocPoint, poc_point, poc_point_copy, poc_point_free) + +/* Point array {{{1 */ + +/** + * PocPointArray: + * @data: pointer to #PocPoint array. The data may be moved as elements are + * added to the array. + * @len: number of values in the array. + * + * A wrapper for #GArray to create an array of #PocPoint values + */ + +/** + * poc_point_array_new: + * + * A wrapper for #GArray to create an array of #PocPointArray values. + * + * Returns: (transfer full): a #PocPointArray + */ +PocPointArray * +poc_point_array_new (void) +{ + return (PocPointArray *) g_array_new (FALSE, FALSE, sizeof (PocPoint)); +} + +/** + * poc_point_array_sized_new: + * @reserved_size: number of elements preallocated + * + * A wrapper for #GArray to create an array of #PocPointArray values with + * @reserved_size elements preallocated. This avoids frequent reallocation, if + * you are going to add many elements to the array. Note however that the size + * of the array is still zero. + * + * Returns: (transfer full): a #PocPointArray + */ +PocPointArray * +poc_point_array_sized_new (guint reserved_size) +{ + return (PocPointArray *) g_array_sized_new (FALSE, FALSE, sizeof (PocPoint), + reserved_size); +} + +/** + * poc_point_array_ref: + * @array: a #PocPointArray + * + * Increments the reference count of @array by one. This function is + * thread-safe and may be called from any thread. + * + * Returns: the #PocPointArray + */ +PocPointArray * +poc_point_array_ref (PocPointArray *array) +{ + return (PocPointArray *) g_array_ref ((GArray *) array); +} + +/** + * poc_point_array_unref: + * @array: a #PocPointArray + * + * Increments the reference count of @array by one. This function is + * thread-safe and may be called from any thread. + */ +void +poc_point_array_unref (PocPointArray *array) +{ + g_array_unref ((GArray *) array); +} + +PocPointArray * +poc_point_array_append_vals (PocPointArray *dst, const PocPoint *src, guint len) +{ + return (PocPointArray *) g_array_append_vals ((GArray *) dst, src, len); +} + +/** + * poc_point_array_set_size: + * @array: a #PocPointArray + * @size: the new size of the array + * + * Sets the size of the array, expanding it if necessary + * The new elements are set to 0. + * + * Returns: (transfer none): the #PocPointArray + */ +PocPointArray * +poc_point_array_set_size (PocPointArray *array, guint size) +{ + return (PocPointArray *) g_array_set_size ((GArray *) array, size); +} + +G_DEFINE_BOXED_TYPE (PocPointArray, poc_point_array, + poc_point_array_ref, poc_point_array_unref) + +/* Double array {{{1 */ + +/** + * PocDoubleArray: + * @data: pointer to a #gdouble array. + * @len: number of values in the array. + * + * A wrapper for #GArray to create an array of #gdouble values. #GArray + * functions may also be used with appropriate casts. + */ + +/** + * poc_double_array_new: + * + * A wrapper for #GArray to create an array of #gdouble values. + * + * Returns: (transfer full): a #PocDoubleArray + */ +PocDoubleArray * +poc_double_array_new (void) +{ + return (PocDoubleArray *) g_array_new (FALSE, TRUE, sizeof (gdouble)); +} + +/** + * poc_double_array_sized_new: + * @reserved_size: number of elements preallocated + * + * A wrapper for #GArray to create an array of #gdouble values with + * @reserved_size elements preallocated. This avoids frequent reallocation, if + * you are going to add many elements to the array. Note however that the size + * of the array is still zero. + * + * Returns: (transfer full): a #PocDoubleArray + */ +PocDoubleArray * +poc_double_array_sized_new (guint reserved_size) +{ + return (PocDoubleArray *) g_array_sized_new (FALSE, TRUE, sizeof (gdouble), + reserved_size); +} + +/** + * poc_double_array_set_size: + * @array: a #PocDoubleArray + * @size: the new size of the array + * + * Sets the size of the array, expanding it if necessary + * The new elements are set to 0. + * + * Returns: (transfer none): the #PocDoubleArray + */ +PocDoubleArray * +poc_double_array_set_size (PocDoubleArray *array, guint size) +{ + return (PocDoubleArray *) g_array_set_size ((GArray *) array, size); +} + +/** + * poc_double_array_ref: + * @array: a #PocDoubleArray + * + * Increments the reference count of @array by one. This function is + * thread-safe and may be called from any thread. + * + * Returns: the #PocDoubleArray + */ +PocDoubleArray * +poc_double_array_ref (PocDoubleArray *array) +{ + return (PocDoubleArray *) g_array_ref ((GArray *) array); +} + +/** + * poc_double_array_unref: + * @array: a #PocDoubleArray + * + * Increments the reference count of @array by one. This function is + * thread-safe and may be called from any thread. + */ +void +poc_double_array_unref (PocDoubleArray *array) +{ + g_array_unref ((GArray *) array); +} + +G_DEFINE_BOXED_TYPE (PocDoubleArray, poc_double_array, + poc_double_array_ref, poc_double_array_unref) + + +/* Enums {{{1 */ + +const gchar * +poc_enum_to_string (GType enum_type, gint value) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_peek (enum_type); + g_return_val_if_fail (enum_class != NULL, NULL); + enum_value = g_enum_get_value (enum_class, value); + return enum_value != NULL ? enum_value->value_nick : NULL; +} + +gint +poc_enum_from_string (GType enum_type, const gchar *string) +{ + GEnumClass *enum_class; + GEnumValue *enum_value; + + enum_class = g_type_class_peek (enum_type); + g_return_val_if_fail (enum_class != NULL, 0); + enum_value = g_enum_get_value_by_nick (enum_class, string); + g_return_val_if_fail (enum_value != NULL, 0); + return enum_value->value; +} + +static void +poc_enum_transform_to_string (const GValue *src, GValue *dest) +{ + const gchar *string; + + string = poc_enum_to_string (G_VALUE_TYPE (src), g_value_get_enum (src)); + g_value_set_static_string (dest, string); +} + +static void +poc_enum_transform_from_string (const GValue *src, GValue *dest) +{ + gint value; + + value = poc_enum_from_string (G_VALUE_TYPE (dest), g_value_get_string (src)); + g_value_set_enum (dest, value); +} + +/* axis mode {{{2 */ + +GType +poc_axis_mode_get_type (void) +{ + GType type; + static gsize poc_axis_mode_type; + static const GEnumValue values[] = + { + { POC_AXIS_LINEAR, "POC_AXIS_LINEAR", "linear" }, + { POC_AXIS_LOG_OCTAVE, "POC_AXIS_LOG_OCTAVE", "octaves" }, + { POC_AXIS_LOG_DECADE, "POC_AXIS_LOG_DECADE", "decades" }, + { 0, NULL, NULL } + }; + + if (g_once_init_enter (&poc_axis_mode_type)) + { + type = g_enum_register_static (g_intern_static_string ("PocAxisMode"), + values); + g_value_register_transform_func (type, G_TYPE_STRING, + poc_enum_transform_to_string); + g_value_register_transform_func (G_TYPE_STRING, type, + poc_enum_transform_from_string); + g_once_init_leave (&poc_axis_mode_type, type); + } + return poc_axis_mode_type; +} + +/* line style {{{2 */ + +GType +poc_line_style_get_type (void) +{ + GType type; + static gsize poc_line_style_type; + static const GEnumValue values[] = + { + { POC_LINE_STYLE_SOLID, "POC_LINE_STYLE_SOLID", "solid" }, + { POC_LINE_STYLE_DOTS, "POC_LINE_STYLE_DOTS", "dots" }, + { POC_LINE_STYLE_DASH, "POC_LINE_STYLE_DASH", "dash" }, + { POC_LINE_STYLE_LONG_DASH, "POC_LINE_STYLE_LONG_DASH", "long-dash" }, + { POC_LINE_STYLE_DOT_DASH, "POC_LINE_STYLE_DOT_DASH", "dot-dash" }, + { POC_LINE_STYLE_LONG_SHORT_DASH, "POC_LINE_STYLE_LONG_SHORT_DASH", "long-short-dash" }, + { POC_LINE_STYLE_DOT_DOT_DASH, "POC_LINE_STYLE_DOT_DOT_DASH", "dot-dot-dash" }, + { 0, NULL, NULL } + }; + + if (g_once_init_enter (&poc_line_style_type)) + { + type = g_enum_register_static (g_intern_static_string ("PocLineStyle"), + values); + g_value_register_transform_func (type, G_TYPE_STRING, + poc_enum_transform_to_string); + g_value_register_transform_func (G_TYPE_STRING, type, + poc_enum_transform_from_string); + g_once_init_leave (&poc_line_style_type, type); + } + return poc_line_style_type; +} + +const double * +poc_line_style_get_dashes (PocLineStyle line_style, int *num_dashes) +{ + static const double dots[] = { 1.0 }; + static const double dash[] = { 2.0, 3.0 }; + static const double long_dash[] = { 4.0, 3.0 }; + static const double dot_dash[] = { 1.0, 1.0, 1.0, 1.0, 4.0 }; + static const double long_short_dash[] = { 4.0, 3.0, 2.0, 3.0 }; + static const double dot_dot_dash[] = { 1.0, 3.0, 1.0, 3.0, 4.0 }; + + switch (line_style) + { + case POC_LINE_STYLE_SOLID: + *num_dashes = 0; + return dots; /* just to avoid returning NULL */ + case POC_LINE_STYLE_DOTS: + *num_dashes = G_N_ELEMENTS (dots); + return dots; + case POC_LINE_STYLE_DASH: + *num_dashes = G_N_ELEMENTS (dash); + return dash; + case POC_LINE_STYLE_LONG_DASH: + *num_dashes = G_N_ELEMENTS (long_dash); + return long_dash; + case POC_LINE_STYLE_DOT_DASH: + *num_dashes = G_N_ELEMENTS (dot_dash); + return dot_dash; + case POC_LINE_STYLE_LONG_SHORT_DASH: + *num_dashes = G_N_ELEMENTS (long_short_dash); + return long_short_dash; + case POC_LINE_STYLE_DOT_DOT_DASH: + *num_dashes = G_N_ELEMENTS (dot_dot_dash); + return dot_dot_dash; + default: + //FIXME - complain + *num_dashes = 0; + return NULL; + } +} diff --git a/poctypes.h b/poctypes.h new file mode 100644 index 0000000..961b6f9 --- /dev/null +++ b/poctypes.h @@ -0,0 +1,142 @@ +/* This file is part of PocPlot. + * + * PocPlot is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * PocPlot is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with PocPlot; if not, see + * . + * + * Copyright 2020 Brian Stafford + */ +#ifndef _poctypes_h +#define _poctypes_h + +#if !defined(__poc_h_inside__) && !defined(__poc_compile__) +# error "only can be included directly" +#endif + +#include + +G_BEGIN_DECLS + +/* point */ + +typedef struct _PocPoint PocPoint; +struct _PocPoint +{ + gdouble x; + gdouble y; +}; +GType poc_point_get_type (void) G_GNUC_CONST; +#define POC_TYPE_POINT (poc_point_get_type ()) + +/* point array */ + +typedef struct _PocPointArray PocPointArray; +struct _PocPointArray +{ + PocPoint *data; + guint len; +}; +#define poc_point_array_index(a,i) ((a)->data[(i)]) +#define poc_point_array_len(a) ((a)->len) +#define poc_point_array_append_val(a,v) poc_point_array_append_vals (a, &(v), 1) +PocPointArray *poc_point_array_new (void); +PocPointArray *poc_point_array_sized_new (guint reserved_size); +PocPointArray *poc_point_array_set_size (PocPointArray *array, guint size); +PocPointArray *poc_point_array_append_vals (PocPointArray *dst, const PocPoint *src, guint len); +PocPointArray *poc_point_array_ref (PocPointArray *array); +void poc_point_array_unref (PocPointArray *array); +GType poc_point_array_get_type (void) G_GNUC_CONST; +#define POC_TYPE_POINT_ARRAY (poc_point_array_get_type ()) + +/* double array */ + +typedef struct _PocDoubleArray PocDoubleArray; +struct _PocDoubleArray +{ + gdouble *data; + guint len; +}; +#define poc_double_array_index(a,i) ((a)->data[(i)]) +#define poc_double_array_len(a) ((a)->len) +#define poc_double_array_append_vals(a,v,n) \ + ((PocDoubleArray *) g_array_append_vals ((GArray *) (a), (v), (n))) +#define poc_double_array_append_val(a,v) \ + poc_double_array_append_vals ((a), (v), 1) +PocDoubleArray *poc_double_array_new (void); +PocDoubleArray *poc_double_array_sized_new (guint reserved_size); +PocDoubleArray *poc_double_array_set_size (PocDoubleArray *array, guint size); +PocDoubleArray *poc_double_array_ref (PocDoubleArray *array); +void poc_double_array_unref (PocDoubleArray *array); +GType poc_double_array_get_type (void) G_GNUC_CONST; +#define POC_TYPE_DOUBLE_ARRAY (poc_double_array_get_type ()) + +/* Enums */ + +const gchar *poc_enum_to_string (GType enum_type, gint value); +gint poc_enum_from_string (GType enum_type, const gchar *string); + +/* axis mode */ + +/** + * PocAxisMode: + * @POC_AXIS_LINEAR: Show a linear axis + * @POC_AXIS_LOG_OCTAVE: Show a logarithmic axis in octaves + * @POC_AXIS_LOG_DECADE: Show a logarithmic axis in decades + * + * An enumerated type specifying the axis mode. + */ +typedef enum + { + POC_AXIS_LINEAR, + POC_AXIS_LOG_OCTAVE, + POC_AXIS_LOG_DECADE + } +PocAxisMode; + +#define POC_TYPE_AXIS_MODE poc_axis_mode_get_type () +GType poc_axis_mode_get_type (void) G_GNUC_CONST; + +/* line styles */ + +/** + * PocLineStyle: + * @POC_LINE_STYLE_SOLID: solid line + * @POC_LINE_STYLE_DOTS: dotted line + * @POC_LINE_STYLE_DASH: dashed line + * @POC_LINE_STYLE_LONG_DASH: long dashes + * @POC_LINE_STYLE_DOT_DASH: dot-dash line + * @POC_LINE_STYLE_LONG_SHORT_DASH: long-short dashes + * @POC_LINE_STYLE_DOT_DOT_DASH: dot-dot-dash line + * + * An enumerated type specifying the plot line style. These should be self + * explanatory. + */ +typedef enum + { + POC_LINE_STYLE_SOLID, + POC_LINE_STYLE_DOTS, + POC_LINE_STYLE_DASH, + POC_LINE_STYLE_LONG_DASH, + POC_LINE_STYLE_DOT_DASH, + POC_LINE_STYLE_LONG_SHORT_DASH, + POC_LINE_STYLE_DOT_DOT_DASH + } +PocLineStyle; + +#define POC_TYPE_LINE_STYLE poc_line_style_get_type () +GType poc_line_style_get_type (void) G_GNUC_CONST; +const double * poc_line_style_get_dashes (PocLineStyle line_style, + int *num_dashes); +G_END_DECLS + +#endif