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