From d6d6ed00605c826d179ec50b9115930d53c3372d Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Wed, 9 Sep 2020 16:52:41 +0100 Subject: [PATCH] PocPlot initial github commit. --- .gitignore | 19 + README.md | 61 +- docs/meson.build | 27 + docs/poc-docs.xml | 92 +++ docs/poc.types | 11 + mathextra.h.in | 29 + meson.build | 112 ++++ meson_options.txt | 3 + poc-catalog.xml | 56 ++ poc.h | 35 + pocaxis.c | 1545 ++++++++++++++++++++++++++++++++++++++++++++ pocaxis.h | 104 +++ pocbag.c | 246 +++++++ pocbag.h | 51 ++ pocdataset.c | 682 +++++++++++++++++++ pocdataset.h | 91 +++ pocdatasetspline.c | 373 +++++++++++ pocdatasetspline.h | 45 ++ poclegend.c | 602 +++++++++++++++++ poclegend.h | 48 ++ pocplot.c | 1358 ++++++++++++++++++++++++++++++++++++++ pocplot.h | 84 +++ pocplot.map | 135 ++++ pocsample.c | 231 +++++++ pocsample.h | 40 ++ pocspline.c | 168 +++++ pocspline.h | 39 ++ poctypes.c | 381 +++++++++++ poctypes.h | 142 ++++ 29 files changed, 6808 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 docs/meson.build create mode 100644 docs/poc-docs.xml create mode 100644 docs/poc.types create mode 100644 mathextra.h.in create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 poc-catalog.xml create mode 100644 poc.h create mode 100644 pocaxis.c create mode 100644 pocaxis.h create mode 100644 pocbag.c create mode 100644 pocbag.h create mode 100644 pocdataset.c create mode 100644 pocdataset.h create mode 100644 pocdatasetspline.c create mode 100644 pocdatasetspline.h create mode 100644 poclegend.c create mode 100644 poclegend.h create mode 100644 pocplot.c create mode 100644 pocplot.h create mode 100644 pocplot.map create mode 100644 pocsample.c create mode 100644 pocsample.h create mode 100644 pocspline.c create mode 100644 pocspline.h create mode 100644 poctypes.c create mode 100644 poctypes.h 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