[goffice] Add initial support for data labels.



commit 1577667c0c8fbe599f9687b7e303a5aa0da14213
Author: Jean Brefort <jean brefort normalesup org>
Date:   Tue Aug 2 19:19:45 2011 +0200

    Add initial support for data labels.

 ChangeLog                                |   17 +
 goffice/data/go-data-impl.h              |    4 +
 goffice/data/go-data.c                   |  107 +++++-
 goffice/data/go-data.h                   |    7 +
 goffice/goffice.c                        |    1 +
 goffice/graph/Makefile.am                |    7 +-
 goffice/graph/goffice-graph.h            |   16 +-
 goffice/graph/gog-series-impl.h          |    3 +
 goffice/graph/gog-series-labels-prefs.ui |  227 ++++++++++
 goffice/graph/gog-series-labels.c        |  720 ++++++++++++++++++++++++++++++
 goffice/graph/gog-series-labels.h        |   55 +++
 goffice/graph/gog-series.c               |   26 ++
 goffice/graph/gog-theme.c                |   20 +
 plugins/plot_barcol/gog-1.5d.c           |   31 +-
 plugins/plot_barcol/gog-barcol.c         |  157 ++++++-
 plugins/plot_barcol/gog-dropbar.c        |   12 +-
 plugins/plot_barcol/gog-line.c           |   15 +-
 plugins/plot_barcol/gog-minmax.c         |   12 +-
 18 files changed, 1389 insertions(+), 48 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index c5c40d3..fa52a0a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,20 @@
+2011-08-02  Jean Brefort  <jean brefort normalesup org>
+
+	* goffice/data/go-data-impl.h: add markup support.
+	* goffice/data/go-data.c: ditto.
+	* goffice/data/go-data.h: ditto.
+	* goffice/goffice.c: add initial support for data labels.
+	* goffice/graph/Makefile.am: ditto.
+	* goffice/graph/goffice-graph.h: ditto.
+	* goffice/graph/gog-series-impl.h: ditto.
+	* goffice/graph/gog-series-labels-prefs.ui: ditto.
+	* goffice/graph/gog-series-labels.c: ditto.
+	* goffice/graph/gog-series-labels.h: ditto.
+	* goffice/graph/gog-theme.c: ditto.
+	* plugins/plot_barcol/gog-1.5d.c: add support for d	ata labels in bar/col
+	plots.
+	* plugins/plot_barcol/gog-barcol.c: ditto.
+
 2011-08-02  Andreas J. Guelzow <aguelzow pyrshep ca>
 
 	* goffice/app/go-cmd-context-impl.h (GOCmdContextClass): move
diff --git a/goffice/data/go-data-impl.h b/goffice/data/go-data-impl.h
index c95568c..5ffe415 100644
--- a/goffice/data/go-data-impl.h
+++ b/goffice/data/go-data-impl.h
@@ -57,6 +57,7 @@ typedef struct {
 	void		(*get_bounds)		(GOData *data, double *minimum, double *maximum);
 	double		(*get_value)		(GOData *data, unsigned int *coordinates);
 	char *		(*get_string)		(GOData *data, unsigned int *coordinates);
+	PangoAttrList * (*get_markup)		(GOData *data, unsigned int *coordinates);
 
 	/* signals */
 	void (*changed)	(GOData *dat);
@@ -72,6 +73,7 @@ typedef struct {
 	GODataClass base;
 	double       (*get_value)  (GODataScalar *scalar);
 	char const  *(*get_str)	   (GODataScalar *scalar);
+	PangoAttrList *(*get_markup) (GODataScalar *data);
 } GODataScalarClass;
 
 #define GO_DATA_VECTOR_LEN_CACHED GO_DATA_SIZE_CACHED
@@ -90,6 +92,7 @@ typedef struct {
 	void	 (*load_values) (GODataVector *vec);
 	double	 (*get_value)   (GODataVector *vec, unsigned i);
 	char	*(*get_str)	(GODataVector *vec, unsigned i);
+	PangoAttrList *(*get_markup) (GODataVector *data, unsigned i);
 } GODataVectorClass;
 
 #define	GO_DATA_MATRIX_SIZE_CACHED GO_DATA_SIZE_CACHED
@@ -109,6 +112,7 @@ typedef struct {
 	void	 (*load_values) (GODataMatrix *vec);
 	double	 (*get_value)   (GODataMatrix *mat, unsigned i, unsigned j);
 	char	*(*get_str)	(GODataMatrix *mat, unsigned i, unsigned j);
+	PangoAttrList *(*get_markup) (GODataMatrix *mat, unsigned i, unsigned j);
 } GODataMatrixClass;
 
 G_END_DECLS
diff --git a/goffice/data/go-data.c b/goffice/data/go-data.c
index fc56d43..6505e62 100644
--- a/goffice/data/go-data.c
+++ b/goffice/data/go-data.c
@@ -510,6 +510,50 @@ go_data_get_matrix_string (GOData *data, unsigned int row, unsigned int column)
 	return go_data_get_string (data, 2, coordinates);
 }
 
+static PangoAttrList *
+go_data_get_markup (GOData *data, unsigned int n_coordinates, unsigned int *coordinates)
+{
+	GODataClass const *data_class;
+	unsigned int n_dimensions;
+
+	g_return_val_if_fail (GO_IS_DATA (data), NULL);
+
+	data_class = GO_DATA_GET_CLASS (data);
+
+	n_dimensions = data_class->get_n_dimensions (data);
+	if (n_dimensions != n_coordinates) {
+		g_warning ("[GOData::get_markup] Wrong number of coordinates (given %d - needed %d)",
+			   n_coordinates, n_dimensions);
+
+		return NULL;
+	}
+
+	return (data_class->get_markup)? data_class->get_markup (data, coordinates): NULL;
+}
+
+PangoAttrList *
+go_data_get_scalar_markup (GOData *data)
+{
+	return go_data_get_markup (data, 0, NULL);
+}
+
+PangoAttrList *
+go_data_get_vector_markup (GOData *data, unsigned int column)
+{
+	return go_data_get_markup (data, 1, &column);
+}
+
+PangoAttrList *
+go_data_get_matrix_markup (GOData *data, unsigned int row, unsigned int column)
+{
+	unsigned int coordinates[2];
+
+	coordinates[0] = column;
+	coordinates[1] = row;
+
+	return go_data_get_markup (data, 2, coordinates);
+}
+
 /*************************************************************************/
 
 #define GO_DATA_SCALAR_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST ((k), GO_TYPE_DATA_SCALAR, GODataScalarClass))
@@ -555,6 +599,12 @@ _data_scalar_get_string (GOData *data, unsigned int *coordinates)
 	return g_strdup (go_data_scalar_get_str ((GODataScalar *) data));
 }
 
+static PangoAttrList *
+_data_scalar_get_markup (GOData *data, unsigned int *coordinates)
+{
+	return pango_attr_list_copy (go_data_scalar_get_markup ((GODataScalar *) data));
+}
+
 static void
 go_data_scalar_class_init (GODataClass *data_class)
 {
@@ -563,6 +613,7 @@ go_data_scalar_class_init (GODataClass *data_class)
 	data_class->get_bounds =	_data_scalar_get_bounds;
 	data_class->get_value =		_data_scalar_get_value;
 	data_class->get_string =	_data_scalar_get_string;
+	data_class->get_markup =	_data_scalar_get_markup;
 }
 
 GSF_CLASS_ABSTRACT (GODataScalar, go_data_scalar,
@@ -583,10 +634,18 @@ char const *
 go_data_scalar_get_str (GODataScalar *scalar)
 {
 	GODataScalarClass const *klass = GO_DATA_SCALAR_GET_CLASS (scalar);
-	g_return_val_if_fail (klass != NULL, NULL);
+	g_return_val_if_fail (klass != NULL, "");
 	return (*klass->get_str) (scalar);
 }
 
+PangoAttrList *
+go_data_scalar_get_markup (GODataScalar *scalar)
+{
+	GODataScalarClass const *klass = GO_DATA_SCALAR_GET_CLASS (scalar);
+	g_return_val_if_fail (klass != NULL, NULL);
+	return (klass->get_markup)? (*klass->get_markup) (scalar): NULL;
+}
+
 /*************************************************************************/
 
 #define GO_DATA_VECTOR_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST ((k), GO_TYPE_DATA_VECTOR, GODataVectorClass))
@@ -637,6 +696,12 @@ _data_vector_get_string (GOData *data, unsigned int *coordinates)
 	return go_data_vector_get_str ((GODataVector *) data, coordinates[0]);
 }
 
+static PangoAttrList *
+_data_vector_get_markup (GOData *data, unsigned int *coordinates)
+{
+	return go_data_vector_get_markup ((GODataVector *) data, coordinates[0]);
+}
+
 static void
 go_data_vector_class_init (GODataClass *data_class)
 {
@@ -647,6 +712,7 @@ go_data_vector_class_init (GODataClass *data_class)
 	data_class->get_bounds =	_data_vector_get_bounds;
 	data_class->get_value =		_data_vector_get_value;
 	data_class->get_string =	_data_vector_get_string;
+	data_class->get_markup =	_data_vector_get_markup;
 }
 
 GSF_CLASS_ABSTRACT (GODataVector, go_data_vector,
@@ -719,6 +785,21 @@ go_data_vector_get_str (GODataVector *vec, unsigned i)
 	return res;
 }
 
+PangoAttrList *
+go_data_vector_get_markup (GODataVector *vec, unsigned i)
+{
+	GODataVectorClass const *klass = GO_DATA_VECTOR_GET_CLASS (vec);
+
+	g_return_val_if_fail (klass != NULL, NULL);
+	if (! (vec->base.flags & GO_DATA_VECTOR_LEN_CACHED)) {
+		(*klass->load_len) (vec);
+		g_return_val_if_fail (vec->base.flags & GO_DATA_VECTOR_LEN_CACHED, NULL);
+	}
+	g_return_val_if_fail ((int)i < vec->len, NULL);
+
+	return (klass->get_markup)? (*klass->get_markup) (vec, i): NULL;
+}
+
 void
 go_data_vector_get_minmax (GODataVector *vec, double *min, double *max)
 {
@@ -816,6 +897,12 @@ _data_matrix_get_string (GOData *data, unsigned int *coordinates)
 	return go_data_matrix_get_str ((GODataMatrix *) data, coordinates[1], coordinates[0]);
 }
 
+static PangoAttrList *
+_data_matrix_get_markup (GOData *data, unsigned int *coordinates)
+{
+	return go_data_matrix_get_markup ((GODataMatrix *) data, coordinates[1], coordinates[0]);
+}
+
 static void
 go_data_matrix_class_init (GODataClass *data_class)
 {
@@ -826,6 +913,7 @@ go_data_matrix_class_init (GODataClass *data_class)
 	data_class->get_bounds =	_data_matrix_get_bounds;
 	data_class->get_value =		_data_matrix_get_value;
 	data_class->get_string =	_data_matrix_get_string;
+	data_class->get_markup =	_data_matrix_get_markup;
 }
 
 GSF_CLASS_ABSTRACT (GODataMatrix, go_data_matrix,
@@ -886,7 +974,7 @@ go_data_matrix_get_str (GODataMatrix *mat, unsigned i, unsigned j)
 	GODataMatrixClass const *klass = GO_DATA_MATRIX_GET_CLASS (mat);
 	char *res;
 
-	g_return_val_if_fail (klass != NULL, NULL);
+	g_return_val_if_fail (klass != NULL, g_strdup (""));
 	if (! (mat->base.flags & GO_DATA_MATRIX_SIZE_CACHED)) {
 		(*klass->load_size) (mat);
 		g_return_val_if_fail (mat->base.flags & GO_DATA_MATRIX_SIZE_CACHED, g_strdup (""));
@@ -899,6 +987,21 @@ go_data_matrix_get_str (GODataMatrix *mat, unsigned i, unsigned j)
 	return res;
 }
 
+PangoAttrList *
+go_data_matrix_get_markup (GODataMatrix *mat, unsigned i, unsigned j)
+{
+	GODataMatrixClass const *klass = GO_DATA_MATRIX_GET_CLASS (mat);
+
+	g_return_val_if_fail (klass != NULL, NULL);
+	if (! (mat->base.flags & GO_DATA_MATRIX_SIZE_CACHED)) {
+		(*klass->load_size) (mat);
+		g_return_val_if_fail (mat->base.flags & GO_DATA_MATRIX_SIZE_CACHED, NULL);
+	}
+	g_return_val_if_fail (((int)i < mat->size.rows) && ((int)j < mat->size.columns), NULL);
+
+	return (klass->get_markup)? (*klass->get_markup) (mat, i, j): NULL;
+}
+
 void
 go_data_matrix_get_minmax (GODataMatrix *mat, double *min, double *max)
 {
diff --git a/goffice/data/go-data.h b/goffice/data/go-data.h
index 75b0481..ff28ee7 100644
--- a/goffice/data/go-data.h
+++ b/goffice/data/go-data.h
@@ -62,6 +62,10 @@ char *		go_data_get_scalar_string	(GOData *data);
 char *		go_data_get_vector_string	(GOData *data, unsigned int column);
 char *		go_data_get_matrix_string	(GOData *data, unsigned int row, unsigned int column);
 
+PangoAttrList *	go_data_get_scalar_markup	(GOData *data);
+PangoAttrList *	go_data_get_vector_markup	(GOData *data, unsigned int column);
+PangoAttrList *	go_data_get_matrix_markup	(GOData *data, unsigned int row, unsigned int column);
+
 /*************************************************************************/
 
 #define GO_TYPE_DATA_SCALAR	(go_data_scalar_get_type ())
@@ -72,6 +76,7 @@ GType go_data_scalar_get_type (void);
 
 double      go_data_scalar_get_value  (GODataScalar *val);
 char const *go_data_scalar_get_str    (GODataScalar *val);
+PangoAttrList *go_data_scalar_get_markup    (GODataScalar *val);
 
 /*************************************************************************/
 
@@ -85,6 +90,7 @@ int	 go_data_vector_get_len    (GODataVector *vec);
 double	*go_data_vector_get_values (GODataVector *vec);
 double	 go_data_vector_get_value  (GODataVector *vec, unsigned i);
 char	*go_data_vector_get_str    (GODataVector *vec, unsigned i);
+PangoAttrList *go_data_vector_get_markup (GODataVector *vec, unsigned i);
 void	 go_data_vector_get_minmax (GODataVector *vec, double *min, double *max);
 gboolean go_data_vector_increasing (GODataVector *vec);
 gboolean go_data_vector_decreasing (GODataVector *vec);
@@ -102,6 +108,7 @@ GODataMatrixSize	 go_data_matrix_get_size    (GODataMatrix *mat);
 double	*go_data_matrix_get_values (GODataMatrix *mat);
 double	 go_data_matrix_get_value  (GODataMatrix *mat, unsigned i, unsigned j);
 char	*go_data_matrix_get_str    (GODataMatrix *mat, unsigned i, unsigned j);
+PangoAttrList *go_data_matrix_get_markup (GODataMatrix *mat, unsigned i, unsigned j);
 void	 go_data_matrix_get_minmax (GODataMatrix *mat, double *min, double *max);
 
 G_END_DECLS
diff --git a/goffice/goffice.c b/goffice/goffice.c
index 163c446..351ba12 100644
--- a/goffice/goffice.c
+++ b/goffice/goffice.c
@@ -206,6 +206,7 @@ libgoffice_init (void)
 #endif
 	(void) GOG_TYPE_ERROR_BAR;
 	(void) GOG_TYPE_REG_EQN;
+	(void) GOG_TYPE_SERIES_LABELS;
 	(void) GOG_TYPE_SERIES_LINES;
 	(void) GO_TYPE_DATA_SCALAR_VAL;
 	(void) GO_TYPE_DATA_SCALAR_STR;
diff --git a/goffice/graph/Makefile.am b/goffice/graph/Makefile.am
index c7af691..3efdb3e 100644
--- a/goffice/graph/Makefile.am
+++ b/goffice/graph/Makefile.am
@@ -30,6 +30,7 @@ libgoffice_graph_la_SOURCES = \
 	gog-trend-line.c		\
 	gog-reg-curve.c			\
 	gog-smoothed-curve.c	\
+	gog-series-labels.c		\
 	gog-series-lines.c		\
 	gog-data-set.c			\
 	\
@@ -66,7 +67,8 @@ libgoffice_graph_la_HEADERS = \
 	gog-error-bar.h			\
 	gog-trend-line.h		\
 	gog-reg-curve.h			\
-	gog-smoothed-curve.h		\
+	gog-smoothed-curve.h	\
+	gog-series-labels.h		\
 	gog-series-lines.h		\
 	gog-data-set.h			\
 	gog-renderer.h
@@ -90,8 +92,9 @@ dist_ui_DATA = \
 	gog-axis-prefs.ui		\
 	gog-error-bar-prefs.ui	\
 	gog-reg-curve-prefs.ui	\
-	gog-reg-eqn-prefs.ui		\
+	gog-reg-eqn-prefs.ui	\
 	gog-series-prefs.ui		\
+	gog-series-labels-prefs.ui	\
 	gog-3d-box-prefs.ui
 
 if GOFFICE_WITH_LASEM
diff --git a/goffice/graph/goffice-graph.h b/goffice/graph/goffice-graph.h
index cb04021..2d3a10f 100644
--- a/goffice/graph/goffice-graph.h
+++ b/goffice/graph/goffice-graph.h
@@ -56,7 +56,8 @@ typedef struct _GogEquation	GogEquation;
 typedef struct _GogRegCurve	GogRegCurve;
 typedef struct _GogRegEqn	GogRegEqn;
 typedef struct _GogTrendLineType	GogTrendLineType;
-typedef struct _GogSeriesLines GogSeriesLines;
+typedef struct _GogSeriesLines  GogSeriesLines;
+typedef struct _GogSeriesLabels GogSeriesLabels;
 typedef struct _GogSmoothedCurve	GogSmoothedCurve;
 typedef struct _Gog3DBox	Gog3DBox;
 
@@ -199,6 +200,18 @@ typedef enum {
 	GOG_PLOT_RENDERING_BEFORE_GRID
 } GogPlotRenderingOrder;
 
+typedef enum {
+	GOG_SERIES_LABELS_DEFAULT_POS = 0,
+	GOG_SERIES_LABELS_CENTERED = 1,
+	GOG_SERIES_LABELS_TOP = 1 << 1,
+	GOG_SERIES_LABELS_BOTTOM = 1 << 2,
+	GOG_SERIES_LABELS_LEFT = 1 << 3,
+	GOG_SERIES_LABELS_RIGHT = 1 << 4,
+	GOG_SERIES_LABELS_OUTSIDE = 1 << 5,
+	GOG_SERIES_LABELS_INSIDE = 1 << 6,
+	GOG_SERIES_LABELS_NEAR_ORIGIN = 1 << 7,
+} GogSeriesLabelsPos;
+
 #define GOG_POSITION_IS_SPECIAL(pos) (((pos) & GOG_POSITION_SPECIAL)&&(!((pos) & GOG_POSITION_MANUAL)))
 #define GOG_POSITION_IS_PADDING(pos) (((pos) & GOG_POSITION_PADDING)&&(!((pos) & GOG_POSITION_MANUAL)))
 
@@ -255,6 +268,7 @@ G_END_DECLS
 #include <goffice/graph/gog-reg-curve.h>
 #include <goffice/graph/gog-renderer.h>
 #include <goffice/graph/gog-series-lines.h>
+#include <goffice/graph/gog-series-labels.h>
 #include <goffice/graph/gog-smoothed-curve.h>
 #include <goffice/graph/gog-theme.h>
 
diff --git a/goffice/graph/gog-series-impl.h b/goffice/graph/gog-series-impl.h
index 0b173bb..a7fb959 100644
--- a/goffice/graph/gog-series-impl.h
+++ b/goffice/graph/gog-series-impl.h
@@ -84,6 +84,9 @@ struct _GogSeries {
 
 	GOLineInterpolation	interpolation;
 	gboolean interpolation_skip_invalid;
+	/* data related to data labels */
+	GogSeriesLabelsPos default_pos;
+	unsigned allowed_pos; /* if 0, no data labels can be addded */
 };
 
 typedef struct {
diff --git a/goffice/graph/gog-series-labels-prefs.ui b/goffice/graph/gog-series-labels-prefs.ui
new file mode 100644
index 0000000..7025550
--- /dev/null
+++ b/goffice/graph/gog-series-labels-prefs.ui
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.0 -->
+  <object class="GtkListStore" id="available-list">
+    <columns>
+      <!-- column-name text -->
+      <column type="gchararray"/>
+      <!-- column-name dim -->
+      <column type="guint"/>
+    </columns>
+  </object>
+  <object class="GtkListStore" id="used-list">
+    <columns>
+      <!-- column-name text -->
+      <column type="gchararray"/>
+      <!-- column-name dim -->
+      <column type="guint"/>
+    </columns>
+  </object>
+  <object class="GtkAdjustment" id="offset-adj">
+    <property name="upper">10</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkGrid" id="series-labels-prefs">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">12</property>
+    <property name="row_spacing">12</property>
+    <property name="column_spacing">6</property>
+    <child>
+      <object class="GtkLabel" id="pos-label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">_Position:</property>
+        <property name="use_underline">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">0</property>
+        <property name="width">2</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="offset-label">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">_Offset:</property>
+        <property name="use_underline">True</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">1</property>
+        <property name="width">2</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkLabel" id="custom-lbl">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="xalign">0</property>
+        <property name="label" translatable="yes">Custom labels:</property>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">6</property>
+        <property name="width">2</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkSpinButton" id="offset-btn">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="halign">start</property>
+        <property name="invisible_char">â</property>
+        <property name="invisible_char_set">True</property>
+        <property name="adjustment">offset-adj</property>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">1</property>
+        <property name="width">3</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="raise">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_action_appearance">False</property>
+        <child>
+          <object class="GtkImage" id="image2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="stock">gtk-go-up</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">2</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="add">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_action_appearance">False</property>
+        <child>
+          <object class="GtkImage" id="image1">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="stock">gtk-go-forward</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">3</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="remove">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_action_appearance">False</property>
+        <child>
+          <object class="GtkImage" id="image3">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="stock">gtk-go-back</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">4</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkButton" id="lower">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">True</property>
+        <property name="use_action_appearance">False</property>
+        <child>
+          <object class="GtkImage" id="image4">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="stock">gtk-go-down</property>
+            <property name="icon-size">1</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">5</property>
+        <property name="width">1</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkTreeView" id="available-tree">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="model">available-list</property>
+        <property name="search_column">0</property>
+        <child internal-child="selection">
+          <object class="GtkTreeSelection" id="treeview-selection"/>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">0</property>
+        <property name="top_attach">2</property>
+        <property name="width">2</property>
+        <property name="height">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkTreeView" id="used-tree">
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="model">used-list</property>
+        <property name="search_column">0</property>
+        <child internal-child="selection">
+          <object class="GtkTreeSelection" id="treeview-selection1"/>
+        </child>
+      </object>
+      <packing>
+        <property name="left_attach">3</property>
+        <property name="top_attach">2</property>
+        <property name="width">2</property>
+        <property name="height">4</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkComboBox" id="position-box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="halign">start</property>
+      </object>
+      <packing>
+        <property name="left_attach">2</property>
+        <property name="top_attach">0</property>
+        <property name="width">3</property>
+        <property name="height">1</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/goffice/graph/gog-series-labels.c b/goffice/graph/gog-series-labels.c
new file mode 100644
index 0000000..935d9ec
--- /dev/null
+++ b/goffice/graph/gog-series-labels.c
@@ -0,0 +1,720 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gog-series-labels.c
+ *
+ * Copyright (C) 2011 Jean Brefort (jean brefort normalesup org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#include <goffice/goffice-config.h>
+#include <goffice/graph/gog-series-labels.h>
+
+#include <gsf/gsf-impl-utils.h>
+#include <glib/gi18n-lib.h>
+
+typedef GogOutlinedObjectClass GogSeriesLabelsClass;
+
+static GObjectClass *series_labels_parent_klass;
+
+enum {
+	SERIES_LABELS_PROP_0,
+	SERIES_LABELS_PROP_POSITION,
+	SERIES_LABELS_PROP_OFFSET,
+	SERIES_LABELS_PROP_FORMAT
+};
+
+struct {
+	char const *label;
+	GogSeriesLabelsPos pos;
+} positions [] = 
+{
+	{ N_("Centered"), GOG_SERIES_LABELS_CENTERED },
+	{ N_("Top"), GOG_SERIES_LABELS_TOP },
+	{ N_("Bottom"), GOG_SERIES_LABELS_BOTTOM },
+	{ N_("Left"), GOG_SERIES_LABELS_LEFT },
+	{ N_("Right"), GOG_SERIES_LABELS_RIGHT },
+	{ N_("Outside"), GOG_SERIES_LABELS_OUTSIDE },
+	{ N_("Inside"), GOG_SERIES_LABELS_INSIDE },
+	{ N_("Near origin"), GOG_SERIES_LABELS_NEAR_ORIGIN }
+};
+
+#ifdef GOFFICE_WITH_GTK
+
+struct SeriesLabelsState {
+	GtkWidget *offset_btn, *offset_lbl;
+	GogSeriesLabels *labels;
+	GtkListStore *avail_list, *used_list;
+	GtkTreeSelection *avail_sel, *used_sel;
+	GtkWidget *raise, *lower, *add, *remove;
+};
+
+static void
+used_selection_changed_cb (struct SeriesLabelsState *state)
+{
+	GtkTreeIter iter, last, first;
+	GtkTreePath *f, *l;
+	GtkTreeModel *model = GTK_TREE_MODEL (state->used_list);
+	int count = 0;  /* we count the unselected items to avoid empty labels */
+	g_free (state->labels->format);
+	state->labels->format = NULL;
+	if (gtk_tree_model_get_iter_first (model, &iter)) {
+		GtkTreeModel *model = GTK_TREE_MODEL (state->used_list);
+		/* if the first row is not selected and a second row exists, set the up button sensitive */
+		first = last = iter;
+		gtk_widget_set_sensitive (state->raise,
+		                          !gtk_tree_selection_iter_is_selected (state->used_sel, &iter)
+		                          && gtk_tree_selection_count_selected_rows (state->used_sel)
+		                          && gtk_tree_model_iter_next (model, &last));
+		/* now find the last iter */
+		do {
+			int dim;
+			char *new_format;
+			last = iter;
+			if (!gtk_tree_selection_iter_is_selected (state->used_sel, &last))
+				count++;
+			gtk_tree_model_get (model, &iter, 1, &dim, -1);
+			switch (dim) {
+			case -1:
+				new_format = (state->labels->format)?
+						g_strconcat (state->labels->format, " %c", NULL):
+						g_strdup ("%c");
+				break;
+			case -2:
+				new_format = (state->labels->format)?
+						g_strconcat (state->labels->format, " %l", NULL):
+						g_strdup ("%l");
+				break;
+			default:
+				new_format = (state->labels->format)?
+						g_strdup_printf ("%s %%%d", state->labels->format, dim):
+						g_strdup_printf ("%%%d", dim);
+			}
+			g_free (state->labels->format);
+			state->labels->format = new_format;
+		} while (gtk_tree_model_iter_next (model, &iter));
+		f = gtk_tree_model_get_path (model, &first);
+		l = gtk_tree_model_get_path (model, &last);
+		gtk_widget_set_sensitive (state->lower,
+		                          !gtk_tree_selection_iter_is_selected (state->used_sel, &last)
+		                          && gtk_tree_selection_count_selected_rows (state->used_sel)
+		                          && gtk_tree_path_compare (f, l));
+		gtk_tree_path_free (f);
+		gtk_tree_path_free (l);
+	} else {
+		gtk_widget_set_sensitive (state->raise, FALSE);
+		gtk_widget_set_sensitive (state->lower, FALSE);
+	}
+	gtk_widget_set_sensitive (state->remove,
+	                          count > 0 && gtk_tree_selection_count_selected_rows (state->used_sel));
+}
+
+static void
+add_cb (G_GNUC_UNUSED GtkButton *btn, struct SeriesLabelsState *state)
+{
+	GtkTreeIter iter, add_iter;
+	char *name;
+	int id;
+	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (state->avail_list), &iter);
+	/* we don't need to check if the iter is valid since otherwise,
+	 the button would be unsensitive */
+	while (1) {
+		if (gtk_tree_selection_iter_is_selected (state->avail_sel, &iter)) {
+			gtk_tree_model_get (GTK_TREE_MODEL (state->avail_list),
+			                    &iter, 0, &name, 1, &id, -1);
+			gtk_list_store_append (state->used_list, &add_iter);
+			gtk_list_store_set (state->used_list, &add_iter,
+			                    0, name, 1, id, -1);
+			g_free (name);
+			if (!gtk_list_store_remove (state->avail_list, &iter))
+				break;
+		} else if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (state->avail_list), &iter))
+			break;
+	}
+	used_selection_changed_cb (state);
+	gog_object_emit_changed (GOG_OBJECT (state->labels), TRUE);
+}
+
+static void
+remove_cb (G_GNUC_UNUSED GtkButton *btn, struct SeriesLabelsState *state)
+{
+	GtkTreeIter iter, add_iter;
+	char *name;
+	int id;
+	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (state->used_list), &iter);
+	/* we don't need to check if the iter is valid since otherwise,
+	 the button would be unsensitive */
+	while (1) {
+		if (gtk_tree_selection_iter_is_selected (state->used_sel, &iter)) {
+			gtk_tree_model_get (GTK_TREE_MODEL (state->used_list),
+			                    &iter, 0, &name, 1, &id, -1);
+			gtk_list_store_append (state->avail_list, &add_iter);
+			gtk_list_store_set (state->avail_list, &add_iter,
+			                    0, name, 1, id, -1);
+			g_free (name);
+			if (!gtk_list_store_remove (state->used_list, &iter))
+				break;
+		} else if (!gtk_tree_model_iter_next (GTK_TREE_MODEL (state->used_list), &iter))
+			break;
+	}
+	used_selection_changed_cb (state);
+	gog_object_emit_changed (GOG_OBJECT (state->labels), TRUE);
+}
+
+static void
+raise_cb (G_GNUC_UNUSED GtkButton *btn, struct SeriesLabelsState *state)
+{
+	GtkTreeModel *model = GTK_TREE_MODEL (state->used_list);
+	GtkTreeIter iter, prev;
+	gtk_tree_model_get_iter_first (model, &iter);
+	while (prev = iter, gtk_tree_model_iter_next (model, &iter))
+		if (gtk_tree_selection_iter_is_selected (state->used_sel, &iter))
+			gtk_list_store_move_before (state->used_list, &iter, &prev);
+	gtk_tree_model_get_iter_first (model, &iter);
+	used_selection_changed_cb (state);
+	gog_object_emit_changed (GOG_OBJECT (state->labels), TRUE);
+}
+
+static void
+lower_cb (G_GNUC_UNUSED GtkButton *btn, struct SeriesLabelsState *state)
+{
+	GtkTreeModel *model = GTK_TREE_MODEL (state->used_list);
+	GtkTreeIter iter, prev, last;
+	gboolean valid = TRUE;
+	gtk_tree_model_get_iter_first (model, &iter);
+	while (valid) {
+		while (!gtk_tree_selection_iter_is_selected (state->used_sel, &iter)) {
+			if (!gtk_tree_model_iter_next (model, &iter)) {
+				valid = FALSE;
+				break;
+			}
+		}
+		if (!valid)
+			break;
+		prev = last = iter; /* first selected row in the block */
+		while (gtk_tree_selection_iter_is_selected (state->used_sel, &iter)) {
+			if (!gtk_tree_model_iter_next (model, &iter)) {
+				valid = FALSE;
+				break;
+			}
+			last = iter;
+		}
+		if (!valid)
+			break;
+		valid = gtk_tree_model_iter_next (model, &iter);
+		/* at this point,last should be the unselectd row after the selected block */
+		gtk_list_store_move_before (state->used_list, &last, &prev);
+	}
+	used_selection_changed_cb (state);
+	gog_object_emit_changed (GOG_OBJECT (state->labels), TRUE);
+}
+
+static void
+position_changed_cb (GtkComboBox *box, struct SeriesLabelsState *state)
+{
+	GtkTreeModel *model = gtk_combo_box_get_model (box);
+	GtkTreeIter iter;
+	GogSeriesLabelsPos pos;
+
+	if (gtk_combo_box_get_active_iter (box, &iter)) {
+		gtk_tree_model_get (model, &iter, 1, &pos, -1);
+		gog_series_labels_set_position (state->labels, pos);
+		if (gog_series_labels_get_position (state->labels) ==  GOG_SERIES_LABELS_CENTERED) {
+			gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->offset_btn), 0);
+			gtk_widget_set_sensitive (state->offset_btn, FALSE);
+			gtk_widget_set_sensitive (state->offset_lbl, FALSE);
+		} else {
+			gtk_widget_set_sensitive (state->offset_btn, TRUE);
+			gtk_widget_set_sensitive (state->offset_lbl, TRUE);
+		}
+	}
+}
+
+static void
+offset_changed_cb (GtkSpinButton *btn, GogSeriesLabels *labels)
+{
+	labels->offset = gtk_spin_button_get_value_as_int (btn);
+	gog_object_emit_changed (gog_object_get_parent (GOG_OBJECT (labels)), TRUE);
+}
+
+static void
+avail_selection_changed_cb (struct SeriesLabelsState *state)
+{
+	gtk_widget_set_sensitive (state->add,
+	                          gtk_tree_selection_count_selected_rows (state->avail_sel));
+}
+
+static void
+gog_series_labels_populate_editor (GogObject *gobj,
+				   GOEditor *editor,
+				   GogDataAllocator *dalloc,
+				   GOCmdContext *cc)
+{
+	GtkBuilder *gui;
+	GtkWidget *labels_prefs, *w;
+	GtkComboBox *box;
+	GtkListStore *list;
+	GtkCellRenderer *cell;
+	GtkTreeIter iter;
+	GogSeriesLabels *labels = (GogSeriesLabels *) gobj;
+	GogPlot *plot = (GogPlot *) gog_object_get_parent_typed (gobj, GOG_TYPE_PLOT);
+	int i = 0, id = -1, def = 0;
+	unsigned j;
+	struct SeriesLabelsState *state = g_new (struct SeriesLabelsState, 1);
+
+	gui = go_gtk_builder_new ("gog-series-labels-prefs.ui", GETTEXT_PACKAGE, cc);
+	labels_prefs = go_gtk_builder_get_widget (gui, "series-labels-prefs");
+	state->labels = labels;
+
+	box = GTK_COMBO_BOX (go_gtk_builder_get_widget (gui, "position-box"));
+	list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_UINT);
+	gtk_combo_box_set_model (box, GTK_TREE_MODEL (list));
+	cell = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (box), cell, TRUE);
+	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (box), cell,
+					"text", 0, NULL);
+	for (j = 0; j < G_N_ELEMENTS (positions); j++)
+		if (labels->allowed_pos & positions[j].pos) {
+			gtk_list_store_append (list, &iter);
+			gtk_list_store_set (list, &iter, 0, _(positions[j].label), 1, positions[j].pos, -1);
+			if (labels->position == positions[j].pos)
+					id = i;
+			if (labels->default_pos == positions[j].pos)
+				def = i;
+			i++;
+		}
+	gtk_combo_box_set_active (box, (id >= 0)? id: def);
+	g_signal_connect (G_OBJECT (box), "changed", G_CALLBACK (position_changed_cb), state);
+
+	w = go_gtk_builder_get_widget (gui, "offset-btn");
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), labels->offset);
+	g_signal_connect (G_OBJECT (w), "value-changed", G_CALLBACK (offset_changed_cb), labels);
+	state->offset_btn = w;
+	state->offset_lbl = go_gtk_builder_get_widget (gui, "offset-label");
+
+	w = go_gtk_builder_get_widget (gui, "available-tree");
+	cell = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (w), 0, _("Available data"),
+	                                             cell, "text", 0, NULL);
+	state->avail_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (w));
+	gtk_tree_selection_set_mode (state->avail_sel, GTK_SELECTION_MULTIPLE);
+	g_signal_connect_swapped (G_OBJECT (state->avail_sel), "changed",
+	                  G_CALLBACK (avail_selection_changed_cb), state);
+	w = go_gtk_builder_get_widget (gui, "used-tree");
+	cell = gtk_cell_renderer_text_new ();
+	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (w), 0, _("Used data"),
+	                                             cell, "text", 0, NULL);
+	state->used_sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (w));
+	gtk_tree_selection_set_mode (state->used_sel, GTK_SELECTION_MULTIPLE);
+	g_signal_connect_swapped (G_OBJECT (state->used_sel), "changed",
+	                  G_CALLBACK (used_selection_changed_cb), state);
+
+	state->add = go_gtk_builder_get_widget (gui, "add");
+	gtk_widget_set_sensitive (state->add, FALSE);
+	g_signal_connect (G_OBJECT (state->add), "clicked", G_CALLBACK (add_cb), state);
+	state->remove = go_gtk_builder_get_widget (gui, "remove");
+	gtk_widget_set_sensitive (state->remove, FALSE);
+	g_signal_connect (G_OBJECT (state->remove), "clicked", G_CALLBACK (remove_cb), state);
+	state->raise = go_gtk_builder_get_widget (gui, "raise");
+	gtk_widget_set_sensitive (state->raise, FALSE);
+	g_signal_connect (G_OBJECT (state->raise), "clicked", G_CALLBACK (raise_cb), state);
+	state->lower = go_gtk_builder_get_widget (gui, "lower");
+	gtk_widget_set_sensitive (state->lower, FALSE);
+	g_signal_connect (G_OBJECT (state->lower), "clicked", G_CALLBACK (lower_cb), state);
+
+	if (plot != NULL) {
+		/* what should be done if there is no plot, btw is it possible that there is no plot? */
+		GSList *dims = NULL;
+		char *cur;
+		state->avail_list = GTK_LIST_STORE (gtk_builder_get_object (gui, "available-list"));
+		gtk_list_store_clear (state->avail_list); /* GtkBuilder seems to add an empty line */
+		state->used_list = GTK_LIST_STORE (gtk_builder_get_object (gui, "used-list"));
+		gtk_list_store_clear (state->used_list);
+		/* populate used list */
+		cur = labels->format;
+		while (*cur) {
+			/* go to next % */
+			while (*cur && *cur != '%')
+				cur = g_utf8_next_char (cur);
+			cur++; /* skip the % */
+			switch (*cur) {
+			case 0:
+				break; /* protect against a % terminated string */
+			case 'c':
+				gtk_list_store_append (state->used_list, &iter);
+				gtk_list_store_set (state->used_list, &iter, 0, _("Custom labels"), 1, -1, -1);
+				dims = g_slist_prepend (dims, GINT_TO_POINTER (-1));
+				break;
+			case 'l':
+				gtk_list_store_append (state->used_list, &iter);
+				gtk_list_store_set (state->used_list, &iter, 0, _("Legend entry"), 1, -2, -1);
+				dims = g_slist_prepend (dims, GINT_TO_POINTER (-2));
+				break;
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9': {
+				unsigned dim = strtoul (cur, &cur, 10);
+				if (dim < plot->desc.series.num_dim)
+				switch (plot->desc.series.dim[j].ms_type) {
+				case GOG_MS_DIM_ERR_plus1:
+				case GOG_MS_DIM_ERR_minus1:
+				case GOG_MS_DIM_ERR_plus2:
+				case GOG_MS_DIM_ERR_minus2:
+					/* we need to eliminate these */
+					break;
+				default:
+					gtk_list_store_append (state->used_list, &iter);
+					gtk_list_store_set (state->used_list, &iter, 0, _(plot->desc.series.dim[dim].name), 1, dim, -1);
+					dims = g_slist_prepend (dims, GINT_TO_POINTER (dim));
+					break;
+				}
+				break;
+			}
+			default:
+				cur = g_utf8_next_char (cur); /* skip unknown character */
+			}
+		}
+		for (j = 0; j < plot->desc.series.num_dim; j++) {
+			switch (plot->desc.series.dim[j].ms_type) {
+			case GOG_MS_DIM_ERR_plus1:
+			case GOG_MS_DIM_ERR_minus1:
+			case GOG_MS_DIM_ERR_plus2:
+			case GOG_MS_DIM_ERR_minus2:
+				/* we need to eliminate these */
+				break;
+			default:
+				if (!g_slist_find (dims, GUINT_TO_POINTER (j))) {
+					gtk_list_store_append (state->avail_list, &iter);
+					gtk_list_store_set (state->avail_list, &iter, 0, _(plot->desc.series.dim[j].name), 1, j, -1);
+				}
+				break;
+			}
+		}
+		if (!g_slist_find (dims, GINT_TO_POINTER (-1))) {
+			gtk_list_store_append (state->avail_list, &iter);
+			gtk_list_store_set (state->avail_list, &iter, 0, _("Custom labels"), 1, -1, -1);
+		}
+		if (!g_slist_find (dims, GINT_TO_POINTER (-2))) {
+			gtk_list_store_append (state->avail_list, &iter);
+			gtk_list_store_set (state->avail_list, &iter, 0, _("Legend entry"), 1, -2, -1);
+		}
+		gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (state->avail_list), 1, GTK_SORT_ASCENDING);
+	}
+	w = GTK_WIDGET (gog_data_allocator_editor (dalloc, GOG_DATASET (gobj), 0, GOG_DATA_VECTOR));
+	gtk_widget_show (w);
+	gtk_grid_attach (GTK_GRID (labels_prefs), w, 2, 6, 3, 1);
+
+	g_object_set_data_full (G_OBJECT (labels_prefs), "state", state, g_free);
+
+	go_editor_add_page (editor,labels_prefs, _("Details"));
+	gtk_widget_show_all (labels_prefs),
+	g_object_unref (gui);
+	(GOG_OBJECT_CLASS(series_labels_parent_klass)->populate_editor) (gobj, editor, dalloc, cc);
+}
+#endif
+
+static void
+gog_series_labels_init_style (GogStyledObject *gso, GOStyle *style)
+{
+	style->interesting_fields = GO_STYLE_OUTLINE | GO_STYLE_FILL |
+		GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT;
+	gog_theme_fillin_style (gog_object_get_theme (GOG_OBJECT (gso)),
+		style, GOG_OBJECT (gso), 0, GO_STYLE_OUTLINE | GO_STYLE_FILL |
+	        GO_STYLE_FONT | GO_STYLE_TEXT_LAYOUT);
+}
+
+static void
+gog_series_labels_set_property (GObject *obj, guint param_id,
+			 GValue const *value, GParamSpec *pspec)
+{
+	GogSeriesLabels *labels = GOG_SERIES_LABELS (obj);
+
+	switch (param_id) {
+	case SERIES_LABELS_PROP_POSITION: {
+		char const *name = g_value_get_string (value);
+		if (!strcmp (name, "centered"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_CENTERED);
+		else if (!strcmp (name, "top"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_TOP);
+		else if (!strcmp (name, "bottom"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_BOTTOM);
+		else if (!strcmp (name, "left"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_LEFT);
+		else if (!strcmp (name, "right"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_RIGHT);
+		else if (!strcmp (name, "outside"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_OUTSIDE);
+		else if (!strcmp (name, "inside"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_INSIDE);
+		else if (!strcmp (name, "near origin"))
+			gog_series_labels_set_position (labels, GOG_SERIES_LABELS_NEAR_ORIGIN);
+		return;
+	}
+	case SERIES_LABELS_PROP_OFFSET: {
+		unsigned offset = g_value_get_uint (value);
+		if (offset == labels->offset)
+			return;
+		labels->offset = offset;
+		break;
+	case SERIES_LABELS_PROP_FORMAT:
+		g_free (labels->format);
+		labels->format = g_strdup (g_value_get_string (value));
+		break;
+	}
+
+	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
+		 return; /* NOTE : RETURN */
+	}
+
+	gog_object_emit_changed (gog_object_get_parent (GOG_OBJECT (obj)), TRUE);
+}
+
+static void
+gog_series_labels_get_property (GObject *obj, guint param_id,
+			 GValue *value, GParamSpec *pspec)
+{
+	GogSeriesLabels *labels = GOG_SERIES_LABELS (obj);
+
+	switch (param_id) {
+	case SERIES_LABELS_PROP_POSITION: {
+		char const *posname;
+		switch (labels->position) {
+		default:
+		case GOG_SERIES_LABELS_DEFAULT_POS:
+			posname = "default";
+			break;
+		case GOG_SERIES_LABELS_CENTERED:
+			posname = "centered";
+			break;
+		case GOG_SERIES_LABELS_TOP:
+			posname = "top";
+			break;
+		case GOG_SERIES_LABELS_BOTTOM:
+			posname = "bottom";
+			break;
+		case GOG_SERIES_LABELS_LEFT:
+			posname = "left";
+			break;
+		case GOG_SERIES_LABELS_RIGHT:
+			posname = "right";
+			break;
+		case GOG_SERIES_LABELS_OUTSIDE:
+			posname = "outside";
+			break;
+		case GOG_SERIES_LABELS_INSIDE:
+			posname = "inside";
+			break;
+		case GOG_SERIES_LABELS_NEAR_ORIGIN:
+			posname = "near origin";
+			break;
+		}
+		g_value_set_string (value, posname);
+		break;
+	}
+	case SERIES_LABELS_PROP_OFFSET:
+		g_value_set_uint (value, labels->offset);
+		break;
+	case SERIES_LABELS_PROP_FORMAT:
+		g_value_set_string (value, labels->format);
+		break;
+
+	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
+		 break;
+	}
+}
+
+static void
+gog_series_labels_parent_changed (GogObject *obj, gboolean was_set)
+{
+	GogSeriesLabels *labels = GOG_SERIES_LABELS (obj);
+	GogPlot *plot;
+	unsigned j;
+
+	if (!was_set)
+		return;
+	plot = (GogPlot *) gog_object_get_parent_typed (obj, GOG_TYPE_PLOT);
+	g_free (labels->format);
+	labels->format = NULL;
+	for (j = 0; j < plot->desc.series.num_dim; j++) {
+		/* FIXME, this might depend upon the series type */
+		switch (plot->desc.series.dim[j].ms_type) {
+		case GOG_MS_DIM_VALUES:
+			labels->format = g_strdup_printf ("%%%u", j);
+				j = plot->desc.series.num_dim; /* ensure we exit the loop */
+			break;
+		default:
+			break;
+		}
+	}
+	
+}
+
+static void
+gog_series_labels_finalize (GObject *obj)
+{
+	GogSeriesLabels *labels = GOG_SERIES_LABELS (obj);
+	gog_dataset_finalize (GOG_DATASET (obj));
+	g_free (labels->format);
+	series_labels_parent_klass->finalize (obj);
+}
+
+static void 
+gog_series_labels_class_init (GObjectClass *obj_klass)
+{
+	GogObjectClass *gog_klass = (GogObjectClass *) obj_klass;
+	GogStyledObjectClass *style_klass = (GogStyledObjectClass *) obj_klass;
+	series_labels_parent_klass = g_type_class_peek_parent (obj_klass);
+
+	obj_klass->set_property	= gog_series_labels_set_property;
+	obj_klass->get_property	= gog_series_labels_get_property;
+	obj_klass->finalize	= gog_series_labels_finalize;
+        g_object_class_install_property (obj_klass, SERIES_LABELS_PROP_POSITION,
+		 g_param_spec_string ("position",
+			_("Position"),
+			_("Position of the label relative to the data graphic element"),
+			"default",
+			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
+        g_object_class_install_property (obj_klass, SERIES_LABELS_PROP_OFFSET,
+		 g_param_spec_uint ("offset",
+			_("Offset"),
+			_("Offset to add to the label position"),
+			0, 10, 0,
+			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
+        g_object_class_install_property (obj_klass, SERIES_LABELS_PROP_FORMAT,
+		 g_param_spec_string ("format",
+			_("Format"),
+			_("Label format"),
+			"",
+			GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
+
+#ifdef GOFFICE_WITH_GTK
+	gog_klass->populate_editor = gog_series_labels_populate_editor;
+#endif
+	gog_klass->parent_changed = gog_series_labels_parent_changed;
+	style_klass->init_style = gog_series_labels_init_style;
+}
+
+static void
+gog_series_labels_dataset_dims (GogDataset const *set, int *first, int *last)
+{
+	*first = 0;
+	*last = 0;
+}
+
+static GogDatasetElement *
+gog_series_labels_dataset_get_elem (GogDataset const *set, int dim_i)
+{
+	GogSeriesLabels const *sl = GOG_SERIES_LABELS (set);
+	g_return_val_if_fail (0 == dim_i, NULL);
+	return (GogDatasetElement *) &sl->custom_labels;
+}
+
+static void
+gog_series_labels_dataset_dim_changed (GogDataset *set, int dim_i)
+{
+	gog_object_request_update (GOG_OBJECT (set));
+}
+
+static void
+gog_series_labels_dataset_init (GogDatasetClass *iface)
+{
+	iface->get_elem	   = gog_series_labels_dataset_get_elem;
+	iface->dims	   = gog_series_labels_dataset_dims;
+	iface->dim_changed = gog_series_labels_dataset_dim_changed;
+}
+
+GSF_CLASS_FULL (GogSeriesLabels, gog_series_labels,
+		NULL, NULL, gog_series_labels_class_init, NULL,
+		NULL, GOG_TYPE_OUTLINED_OBJECT, 0,
+                GSF_INTERFACE (gog_series_labels_dataset_init, GOG_TYPE_DATASET))
+
+void
+gog_series_labels_set_allowed_position (GogSeriesLabels *lbls, unsigned allowed)
+{
+	lbls->allowed_pos = allowed;
+	if ((lbls->position & allowed) == 0 && lbls->position != GOG_SERIES_LABELS_DEFAULT_POS) {
+		lbls->position = GOG_SERIES_LABELS_DEFAULT_POS;
+		gog_object_emit_changed (gog_object_get_parent (GOG_OBJECT (lbls)), TRUE);
+	}
+}
+
+void
+gog_series_labels_set_position (GogSeriesLabels *lbls, GogSeriesLabelsPos pos)
+{
+	switch (pos) {
+	case GOG_SERIES_LABELS_DEFAULT_POS:
+	case GOG_SERIES_LABELS_CENTERED:
+	case GOG_SERIES_LABELS_TOP:
+	case GOG_SERIES_LABELS_BOTTOM:
+	case GOG_SERIES_LABELS_LEFT:
+	case GOG_SERIES_LABELS_RIGHT:
+	case GOG_SERIES_LABELS_OUTSIDE:
+	case GOG_SERIES_LABELS_INSIDE:
+	case GOG_SERIES_LABELS_NEAR_ORIGIN:
+		break;
+	default:
+		return;
+	}
+	if ((lbls->allowed_pos & pos) != 0  && lbls->position != pos) {
+		lbls->position = (pos == lbls->default_pos)? GOG_SERIES_LABELS_DEFAULT_POS: pos;
+		if (gog_series_labels_get_position (lbls) == GOG_SERIES_LABELS_CENTERED)
+			lbls->offset = 0;
+		gog_object_emit_changed (gog_object_get_parent (GOG_OBJECT (lbls)), TRUE);
+	}
+}
+
+void
+gog_series_labels_set_default_position (GogSeriesLabels *lbls, GogSeriesLabelsPos pos)
+{
+	switch (pos) {
+	case GOG_SERIES_LABELS_CENTERED:
+	case GOG_SERIES_LABELS_TOP:
+	case GOG_SERIES_LABELS_BOTTOM:
+	case GOG_SERIES_LABELS_LEFT:
+	case GOG_SERIES_LABELS_RIGHT:
+	case GOG_SERIES_LABELS_OUTSIDE:
+	case GOG_SERIES_LABELS_INSIDE:
+	case GOG_SERIES_LABELS_NEAR_ORIGIN:
+		break;
+	case GOG_SERIES_LABELS_DEFAULT_POS:
+	default:
+		return;
+	}
+	if (lbls->default_pos != pos) {
+		lbls->default_pos = pos;
+		if ((lbls->allowed_pos & lbls->position) == 0  && lbls->position != GOG_SERIES_LABELS_DEFAULT_POS) {
+			lbls->position = GOG_SERIES_LABELS_DEFAULT_POS;
+			if (pos == GOG_SERIES_LABELS_CENTERED)
+				lbls->offset = 0;
+		}
+		if (lbls->position == GOG_SERIES_LABELS_DEFAULT_POS)
+			gog_object_emit_changed (gog_object_get_parent (GOG_OBJECT (lbls)), TRUE);
+	}
+}
+
+GogSeriesLabelsPos
+gog_series_labels_get_position (GogSeriesLabels const *lbls)
+{
+	return (lbls->position == GOG_SERIES_LABELS_DEFAULT_POS)?
+		lbls->default_pos: lbls->position;
+}
diff --git a/goffice/graph/gog-series-labels.h b/goffice/graph/gog-series-labels.h
new file mode 100644
index 0000000..5fa7585
--- /dev/null
+++ b/goffice/graph/gog-series-labels.h
@@ -0,0 +1,55 @@
+/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * gog-series-labels.h
+ *
+ * Copyright (C) 2011 Jean Brefort (jean brefort normalesup org)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+ * USA
+ */
+
+#ifndef GOG_SERIES_LABELS_H
+#define GOG_SERIES_LABELS_H
+
+#include <goffice/goffice.h>
+
+G_BEGIN_DECLS
+
+struct _GogSeriesLabels {
+	GogOutlinedObject base;
+
+	/* private */
+	GogSeriesLabelsPos position;
+	GogSeriesLabelsPos default_pos;
+	unsigned allowed_pos;
+	unsigned offset; /* position offset in pixels */
+	char *format;
+	GogDatasetElement custom_labels;
+};
+
+#define GOG_TYPE_SERIES_LABELS		(gog_series_labels_get_type ())
+#define GOG_SERIES_LABELS(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_SERIES_LABELS, GogSeriesLabels))
+#define GOG_IS_SERIES_LABELS(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_SERIES_LABELS))
+
+GType gog_series_labels_get_type (void);
+
+void gog_series_labels_set_allowed_position (GogSeriesLabels *lbls, unsigned allowed);
+void gog_series_labels_set_position (GogSeriesLabels *lbls, GogSeriesLabelsPos pos);
+void gog_series_labels_set_default_position (GogSeriesLabels *lbls, GogSeriesLabelsPos pos);
+GogSeriesLabelsPos gog_series_labels_get_position (GogSeriesLabels const *lbls);
+
+G_END_DECLS
+
+#endif  /* GOG_SERIES_LABELS_H */
diff --git a/goffice/graph/gog-series.c b/goffice/graph/gog-series.c
index 0b961e4..e954ada 100644
--- a/goffice/graph/gog-series.c
+++ b/goffice/graph/gog-series.c
@@ -300,6 +300,23 @@ role_series_element_pre_remove (GogObject *parent, GogObject *child)
 	series->overrides = g_list_remove (series->overrides, child);
 }
 
+static gboolean
+role_series_labels_can_add (GogObject const *parent)
+{
+	GogSeries *series = GOG_SERIES (parent);
+
+	return (series->allowed_pos != 0);
+}
+
+static void
+role_series_labels_post_add (GogObject *parent, GogObject *child)
+{
+	GogSeries *series = GOG_SERIES (parent);
+	GogSeriesLabels *labels = GOG_SERIES_LABELS (child);
+	gog_series_labels_set_allowed_position (labels, series->allowed_pos);
+	gog_series_labels_set_default_position (labels, series->default_pos);
+}
+
 static void
 gog_series_finalize (GObject *obj)
 {
@@ -657,6 +674,14 @@ gog_series_class_init (GogSeriesClass *klass)
 		  regression_curve_post_add,
 		  regression_curve_pre_remove,
 		  NULL },
+		{ N_("Data labels"), "GogSeriesLabels",	3,
+		  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
+		  role_series_labels_can_add,
+		  NULL,
+		  NULL,
+		  role_series_labels_post_add,
+		  NULL,
+		  NULL }
 	};
 	unsigned int i;
 
@@ -724,6 +749,7 @@ gog_series_init (GogSeries *series)
 	series->index = -1;
 	series->acceptable_children = 0;
 	series->interpolation = GO_LINE_INTERPOLATION_LINEAR;
+	series->default_pos = GOG_SERIES_LABELS_CENTERED;
 }
 
 static void
diff --git a/goffice/graph/gog-theme.c b/goffice/graph/gog-theme.c
index 774f001..0df3451 100644
--- a/goffice/graph/gog-theme.c
+++ b/goffice/graph/gog-theme.c
@@ -811,6 +811,16 @@ static void build_predefined_themes (void)
 	gog_theme_add_element (theme, style,
 		NULL, "GogRegEqn", NULL);
 
+	/* series labels */
+	style = go_style_new ();
+	style->line.dash_type = GO_LINE_NONE;
+	style->line.width = 0; /* none */
+	style->line.color = GO_COLOR_BLACK;
+	style->fill.type = GO_STYLE_FILL_NONE;
+	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
+	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
+	gog_theme_add_element (theme, style, NULL, "GogSeriesLabels", NULL);
+
 #ifdef GOFFICE_WITH_LASEM
 	/* Equations */
 	style = go_style_new ();
@@ -936,6 +946,16 @@ static void build_predefined_themes (void)
 	gog_theme_add_element (theme, style,
 		NULL, "GogRegEqn", NULL);
 
+	/* series labels */
+	style = go_style_new ();
+	style->line.dash_type = GO_LINE_NONE;
+	style->line.width = 0; /* none */
+	style->line.color = GO_COLOR_BLACK;
+	style->fill.type = GO_STYLE_FILL_NONE;
+	go_pattern_set_solid (&style->fill.pattern, GO_COLOR_WHITE);
+	go_style_set_font_desc (style, pango_font_description_from_string ("Sans 6"));
+	gog_theme_add_element (theme, style, NULL, "GogSeriesLabels", NULL);
+
 #ifdef GOFFICE_WITH_LASEM
 	/* Equations */
 	style = go_style_new ();
diff --git a/plugins/plot_barcol/gog-1.5d.c b/plugins/plot_barcol/gog-1.5d.c
index 88d0fab..9284e68 100644
--- a/plugins/plot_barcol/gog-1.5d.c
+++ b/plugins/plot_barcol/gog-1.5d.c
@@ -204,7 +204,7 @@ gog_plot1_5d_update (GogObject *obj)
 		if (model->fmt == NULL)
 			model->fmt = go_data_preferred_fmt (series->base.values[1].data);
 		model->date_conv = go_data_date_conv (series->base.values[1].data);
-		index_dim = GOG_SERIES (series)->values[0].data;
+		index_dim = series->base.values[0].data;
 	}
 	axis = gog_plot1_5d_get_index_axis (model);
 	if (model->num_elements != num_elements ||
@@ -391,8 +391,7 @@ series_lines_can_add (GogObject const *parent)
 	plots and lines with dropbars and high-low lines */
 	if (GOG_IS_PLOT_BARCOL (plot) && plot->type == GOG_1_5D_NORMAL)
 		return FALSE;
-	return (plot->support_series_lines &&
-								!series->has_series_lines);
+	return (plot->support_series_lines && !series->has_series_lines);
 }
 
 static void
@@ -483,29 +482,29 @@ gog_series1_5d_update (GogObject *obj)
 {
 	double *vals;
 	int len = 0;
-	GogSeries1_5d *series = GOG_SERIES1_5D (obj);
-	unsigned old_num = series->base.num_elements;
+	GogSeries *series = GOG_SERIES (obj);
+	unsigned old_num = series->num_elements;
 
-	if (series->base.values[1].data != NULL) {
-		vals = go_data_get_values (series->base.values[1].data);
-		len = go_data_get_vector_size (series->base.values[1].data);
+	if (series->values[1].data != NULL) {
+		vals = go_data_get_values (series->values[1].data);
+		len = go_data_get_vector_size (series->values[1].data);
 	}
-	series->base.num_elements = len;
+	series->num_elements = len;
 
-	if (series->base.plot->desc.series.num_dim == 3) {
+	if (series->plot->desc.series.num_dim == 3) {
 		int tmp = 0;
-		if (series->base.values[2].data != NULL) {
-			vals = go_data_get_values (series->base.values[2].data);
-			tmp = go_data_get_vector_size (series->base.values[2].data);
+		if (series->values[2].data != NULL) {
+			vals = go_data_get_values (series->values[2].data);
+			tmp = go_data_get_vector_size (series->values[2].data);
 		}
 		if (tmp < len)
 			len = tmp;
 	}
 
 	/* queue plot for redraw */
-	gog_object_request_update (GOG_OBJECT (series->base.plot));
-	if (old_num != series->base.num_elements)
-		gog_plot_request_cardinality_update (series->base.plot);
+	gog_object_request_update (GOG_OBJECT (series->plot));
+	if (old_num != series->num_elements)
+		gog_plot_request_cardinality_update (series->plot);
 
 	if (gog_series1_5d_parent_klass->update)
 		gog_series1_5d_parent_klass->update (obj);
diff --git a/plugins/plot_barcol/gog-barcol.c b/plugins/plot_barcol/gog-barcol.c
index a7027b8..a940a01 100644
--- a/plugins/plot_barcol/gog-barcol.c
+++ b/plugins/plot_barcol/gog-barcol.c
@@ -67,8 +67,15 @@ gog_barcol_series_class_init (GogSeriesClass *series_klass)
 	series_klass->series_element_type = GOG_TYPE_BARCOL_SERIES_ELEMENT;
 }
 
+static void
+gog_barcol_series_init (GogSeries *series)
+{
+	series->allowed_pos = GOG_SERIES_LABELS_CENTERED | GOG_SERIES_LABELS_OUTSIDE
+		| GOG_SERIES_LABELS_INSIDE | GOG_SERIES_LABELS_NEAR_ORIGIN;
+}
+
 GSF_DYNAMIC_CLASS (GogBarColSeries, gog_barcol_series,
-	gog_barcol_series_class_init, NULL,
+	gog_barcol_series_class_init, gog_barcol_series_init,
 	GOG_SERIES1_5D_TYPE)
 
 /******************************************************************************/
@@ -368,12 +375,20 @@ typedef struct {
 	double		y;
 } ErrorBarData;
 
+typedef struct {
+	double		 x;
+	double 		 y;
+	char		*str;
+	GOAnchorType    anchor;
+} LabelData;
+
 static int
 gog_barcol_view_get_data_at_point (GogPlotView *view, double x, double y, GogSeries **series)
 {
 	GogBarColPlot const *model = GOG_BARCOL_PLOT (view->base.model);
 	GogPlot1_5d const *gog_1_5d_model = GOG_PLOT1_5D (model);
 	GogSeries1_5d const *pseries;
+	GogSeries const *base_series;
 	GogChart *chart = GOG_CHART (view->base.model->parent);
 	GogChartMap *chart_map;
 	GogAxisMap *x_map, *y_map, *map;
@@ -413,10 +428,11 @@ gog_barcol_view_get_data_at_point (GogPlotView *view, double x, double y, GogSer
 	i = 0;
 	for (ptr = gog_1_5d_model->base.series ; ptr != NULL ; ptr = ptr->next) {
 		pseries = ptr->data;
-		if (!gog_series_is_valid (GOG_SERIES (pseries)))
+		base_series = GOG_SERIES (pseries);
+		if (!gog_series_is_valid (base_series))
 			continue;
-		vals[i] = go_data_get_values (pseries->base.values[1].data);
-		lengths[i] = go_data_get_vector_size (pseries->base.values[1].data);
+		vals[i] = go_data_get_values (base_series->values[1].data);
+		lengths[i] = go_data_get_vector_size (base_series->values[1].data);
 		i++;
 	}
 
@@ -533,13 +549,14 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 	GogBarColPlot const *model = GOG_BARCOL_PLOT (view->model);
 	GogPlot1_5d const *gog_1_5d_model = GOG_PLOT1_5D (view->model);
 	GogSeries1_5d const *series;
+	GogSeries const *base_series;
 	GogChart *chart = GOG_CHART (view->model->parent);
 	GogChartMap *chart_map;
 	GogViewAllocation work;
 	GogViewAllocation const *area;
 	GogRenderer *rend = view->renderer;
 	GogAxisMap *x_map, *y_map, *map;
-	gboolean is_vertical = ! (model->horizontal), valid;
+	gboolean is_vertical = ! (model->horizontal), valid, inverted;
 	double **vals, sum, neg_base, pos_base, tmp;
 	double x;
 	double col_step, group_step, offset, data_scale;
@@ -555,9 +572,11 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 	GSList *ptr;
 	unsigned *lengths;
 	double plus, minus;
-	GogObjectRole const *role = NULL;
+	GogObjectRole const *role = NULL, *lbl_role = NULL;
 	GogSeriesElement *gse;
 	GList const **overrides;
+	GogSeriesLabels **labels;
+	LabelData **label_pos;
 
 	if (num_elements <= 0 || num_series <= 0)
 		return;
@@ -576,6 +595,9 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 	y_map = gog_chart_map_get_axis_map (chart_map, 1);
 
 	map = is_vertical ? y_map : x_map;
+	inverted = gog_axis_is_inverted (is_vertical?
+	                                 GOG_PLOT (model)->axis[GOG_AXIS_Y]:
+		                         GOG_PLOT (model)->axis[GOG_AXIS_X]);
 
 	vals = g_alloca (num_series * sizeof (double *));
 	lengths = g_alloca (num_series * sizeof (unsigned));
@@ -585,14 +607,19 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 	lines = g_alloca (num_series * sizeof (GogSeriesLines *));
 	paths = g_alloca (num_series * sizeof (GOPath *));
 	overrides = g_alloca (num_series * sizeof (GSList *));
+	labels = g_alloca (num_series * sizeof (GogSeriesLabels *));
+	label_pos = g_alloca (num_series * sizeof (gpointer));
 
 	i = 0;
-	for (ptr = gog_1_5d_model->base.series ; ptr != NULL ; ptr = ptr->next) {
+	for (ptr = gog_1_5d_model->base.series ; ptr != NULL ; ptr = ptr->next, i++) {
 		series = ptr->data;
-		if (!gog_series_is_valid (GOG_SERIES (series)))
+		base_series = GOG_SERIES (series);
+		if (!gog_series_is_valid (base_series)) {
+			lengths[i] = 0;
 			continue;
-		vals[i] = go_data_get_values (series->base.values[1].data);
-		lengths[i] = go_data_get_vector_size (series->base.values[1].data);
+		}
+		vals[i] = go_data_get_values (base_series->values[1].data);
+		lengths[i] = go_data_get_vector_size (base_series->values[1].data);
 		styles[i] = GOG_STYLED_OBJECT (series)->style;
 		errors[i] = series->errors;
 		overrides[i] = gog_series_get_overrides (GOG_SERIES (series));
@@ -609,7 +636,15 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 			paths[i] = go_path_new ();
 		} else
 			lines[i] = NULL;
-		i++;
+		if (!lbl_role)
+			lbl_role = gog_object_find_role_by_name (GOG_OBJECT (series), "Data labels");
+		labels[i] = (GogSeriesLabels *) gog_object_get_child_by_role (GOG_OBJECT (series), lbl_role);
+		if (labels[i]) {
+			label_pos[i] = g_malloc (sizeof (LabelData) * lengths[i]);
+			for (j = 0; j < lengths[i]; j++)
+				label_pos[i][j].str = go_data_get_vector_string (base_series->values[1].data, j);
+		} else
+			label_pos[i] = NULL;
 	}
 
 	/* work in coordinates drawing bars from the top */
@@ -712,6 +747,92 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 							 gog_axis_map_to_view (y_map, work.y + work.h));
 				}
 			}
+			if (labels[j] != NULL) {
+				unsigned offset;
+				g_object_get (labels[j], "offset", &offset, NULL);
+				switch (gog_series_labels_get_position (labels[j])) {
+				default:
+				case GOG_SERIES_LABELS_CENTERED:
+					label_pos[j][i].anchor = GO_ANCHOR_CENTER;
+					if (is_vertical) {
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.y + work.h / 2.);
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.x + work.w / 2.);
+					} else {
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.y + work.h / 2.);
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.x + work.w / 2.);
+					}
+					break;
+				case GOG_SERIES_LABELS_OUTSIDE:
+					if (is_vertical) {
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.y + work.h / 2.);
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.x + work.w);
+						if (inverted) {
+							label_pos[j][i].anchor =  GO_ANCHOR_NORTH;
+							label_pos[j][i].y += offset;
+						} else {
+							label_pos[j][i].anchor = GO_ANCHOR_SOUTH;
+							label_pos[j][i].y -= offset;
+						}
+					} else {
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.y + work.h / 2.);
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.x + work.w);
+						if (inverted) {
+							label_pos[j][i].anchor = GO_ANCHOR_EAST;
+							label_pos[j][i].x -= offset;
+						} else {
+							label_pos[j][i].anchor = GO_ANCHOR_WEST;
+							label_pos[j][i].x += offset;
+						}
+					}
+					break;
+				case GOG_SERIES_LABELS_INSIDE:
+					if (is_vertical) {
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.y + work.h / 2.);
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.x + work.w);
+						if (inverted) {
+							label_pos[j][i].anchor = GO_ANCHOR_SOUTH;
+							label_pos[j][i].y -= offset;
+						} else {
+							label_pos[j][i].anchor =  GO_ANCHOR_NORTH;
+							label_pos[j][i].y += offset;
+						}
+					} else {
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.y + work.h / 2.);
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.x + work.w);
+						if (inverted) {
+							label_pos[j][i].anchor = GO_ANCHOR_WEST;
+							label_pos[j][i].x += offset;
+						} else {
+							label_pos[j][i].anchor = GO_ANCHOR_EAST;
+							label_pos[j][i].x -= offset;
+						}
+					}
+					break;
+				case GOG_SERIES_LABELS_NEAR_ORIGIN:
+					if (is_vertical) {
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.y + work.h / 2.);
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.x);
+						if (inverted) {
+							label_pos[j][i].anchor =  GO_ANCHOR_NORTH;
+							label_pos[j][i].y += offset;
+						} else {
+							label_pos[j][i].anchor = GO_ANCHOR_SOUTH;
+							label_pos[j][i].y -= offset;
+						}
+					} else {
+						label_pos[j][i].y =  gog_axis_map_to_view (y_map, work.y + work.h / 2.);
+						label_pos[j][i].x =  gog_axis_map_to_view (x_map, work.x);
+						if (inverted) {
+							label_pos[j][i].anchor = GO_ANCHOR_EAST;
+							label_pos[j][i].x -= offset;
+						} else {
+							label_pos[j][i].anchor = GO_ANCHOR_WEST;
+							label_pos[j][i].x += offset;
+						}
+					}
+					break;
+				}
+			}
 		}
 	}
 	/*Now draw error bars and clean*/
@@ -733,6 +854,20 @@ gog_barcol_view_render (GogView *view, GogViewAllocation const *bbox)
 			go_path_free (paths[i]);
 		}
 
+	/* Draw data labels if any */
+	for (i = 0; i < num_series; i++)
+		if (labels[i] != NULL) {
+			GogViewAllocation alloc;
+			gog_renderer_push_style (view->renderer, go_styled_object_get_style (GO_STYLED_OBJECT (labels[i])));
+			for (j = 0; j < lengths[i]; j++) {
+				alloc.x = label_pos[i][j].x;
+				alloc.y = label_pos[i][j].y;
+				gog_renderer_draw_text (view->renderer, label_pos[i][j].str, &alloc, label_pos[i][j].anchor, FALSE);
+				g_free (label_pos[i][j].str);
+			}
+			gog_renderer_pop_style (view->renderer);
+		}
+			
 	gog_chart_map_free (chart_map);
 }
 
diff --git a/plugins/plot_barcol/gog-dropbar.c b/plugins/plot_barcol/gog-dropbar.c
index bf4de3c..830b007 100644
--- a/plugins/plot_barcol/gog-dropbar.c
+++ b/plugins/plot_barcol/gog-dropbar.c
@@ -256,6 +256,7 @@ gog_dropbar_view_render (GogView *view, GogViewAllocation const *bbox)
 	GogBarColPlot const *model = GOG_BARCOL_PLOT (view->model);
 	GogPlot1_5d const *gog_1_5d_model = GOG_PLOT1_5D (view->model);
 	GogSeries1_5d const *series;
+	GogSeries const *base_series;
 	GogAxisMap *x_map, *y_map, *val_map;
 	GogViewAllocation work;
 	double *start_vals, *end_vals;
@@ -302,7 +303,8 @@ gog_dropbar_view_render (GogView *view, GogViewAllocation const *bbox)
 
 	for (ptr = gog_1_5d_model->base.series ; ptr != NULL ; ptr = ptr->next) {
 		series = ptr->data;
-		if (!gog_series_is_valid (GOG_SERIES (series)))
+		base_series = GOG_SERIES (series);
+		if (!gog_series_is_valid (base_series))
 			continue;
 		prec_valid = FALSE;
 		neg_style = go_style_dup ((GOG_STYLED_OBJECT (series))->style);
@@ -310,10 +312,10 @@ gog_dropbar_view_render (GogView *view, GogViewAllocation const *bbox)
 		neg_style->fill.pattern.back ^= 0xffffff00;
 		neg_style->fill.pattern.fore ^= 0xffffff00;
 		x = offset;
-		start_vals = go_data_get_values (series->base.values[1].data);
-		n = go_data_get_vector_size (series->base.values[1].data);
-		end_vals = go_data_get_values (series->base.values[2].data);
-		tmp = go_data_get_vector_size (series->base.values[2].data);
+		start_vals = go_data_get_values (base_series->values[1].data);
+		n = go_data_get_vector_size (base_series->values[1].data);
+		end_vals = go_data_get_values (base_series->values[2].data);
+		tmp = go_data_get_vector_size (base_series->values[2].data);
 		if (n > tmp)
 			n = tmp;
 
diff --git a/plugins/plot_barcol/gog-line.c b/plugins/plot_barcol/gog-line.c
index 06e1595..cd23ded 100644
--- a/plugins/plot_barcol/gog-line.c
+++ b/plugins/plot_barcol/gog-line.c
@@ -164,11 +164,12 @@ static void
 gog_line_series_update (GogObject *obj)
 {
 	GogLineSeries *series = GOG_LINE_SERIES (obj);
-	unsigned i, nb = series->base.base.num_elements;
+	GogSeries *base_series = GOG_SERIES (obj);
+	unsigned i, nb = base_series->num_elements;
 	GSList *ptr;
 	(GOG_OBJECT_CLASS (series_parent_klass))->update (obj);
-	if (nb != series->base.base.num_elements) {
-		nb = series->base.base.num_elements;
+	if (nb != base_series->num_elements) {
+		nb = base_series->num_elements;
 		g_free (series->x);
 		series->x = g_new (double, nb);
 		for (i = 0; i < nb; i++)
@@ -499,6 +500,7 @@ gog_line_view_render (GogView *view, GogViewAllocation const *bbox)
 	GogPlot1_5d const *model = GOG_PLOT1_5D (view->model);
 	GogPlot1_5dType const type = model->type;
 	GogSeries1_5d const *series;
+	GogSeries const *base_series;
 	GogChart *chart = GOG_CHART (view->model->parent);
 	GogChartMap *chart_map;
 	GogViewAllocation const *area;
@@ -571,15 +573,16 @@ gog_line_view_render (GogView *view, GogViewAllocation const *bbox)
 	i = 0;
 	for (ptr = model->base.series ; ptr != NULL ; ptr = ptr->next) {
 		series = ptr->data;
+		base_series = GOG_SERIES (series);
 
-		if (!gog_series_is_valid (GOG_SERIES (series))) {
+		if (!gog_series_is_valid (base_series)) {
 			vals[i] = NULL;
 			lengths[i] = 0;
 			continue;
 		}
 
-		vals[i] = go_data_get_values (series->base.values[1].data);
-		lengths[i] = go_data_get_vector_size (series->base.values[1].data);
+		vals[i] = go_data_get_values (base_series->values[1].data);
+		lengths[i] = go_data_get_vector_size (base_series->values[1].data);
 		styles[i] = GOG_STYLED_OBJECT (series)->style;
 
 		paths[i] = go_path_new ();
diff --git a/plugins/plot_barcol/gog-minmax.c b/plugins/plot_barcol/gog-minmax.c
index a53ff65..868ea6b 100644
--- a/plugins/plot_barcol/gog-minmax.c
+++ b/plugins/plot_barcol/gog-minmax.c
@@ -306,6 +306,7 @@ gog_minmax_view_render (GogView *view, GogViewAllocation const *bbox)
 	GogMinMaxPlot const *model = GOG_MINMAX_PLOT (view->model);
 	GogPlot1_5d const *gog_1_5d_model = GOG_PLOT1_5D (view->model);
 	GogSeries1_5d const *series;
+	GogSeries const *base_series;
 	GogAxisMap *x_map, *y_map;
 	gboolean is_vertical = ! (model->horizontal);
 	double *max_vals, *min_vals;
@@ -345,14 +346,15 @@ gog_minmax_view_render (GogView *view, GogViewAllocation const *bbox)
 
 	for (ptr = gog_1_5d_model->base.series ; ptr != NULL ; ptr = ptr->next) {
 		series = ptr->data;
-		if (!gog_series_is_valid (GOG_SERIES (series)))
+		base_series = GOG_SERIES (series);
+		if (!gog_series_is_valid (base_series))
 			continue;
 		style = go_styled_object_get_style (GO_STYLED_OBJECT (series));
 		x = offset;
-		min_vals = go_data_get_values (series->base.values[1].data);
-		n = go_data_get_vector_size (series->base.values[1].data);
-		max_vals = go_data_get_values (series->base.values[2].data);
-		tmp = go_data_get_vector_size (series->base.values[2].data);
+		min_vals = go_data_get_values (base_series->values[1].data);
+		n = go_data_get_vector_size (base_series->values[1].data);
+		max_vals = go_data_get_values (base_series->values[2].data);
+		tmp = go_data_get_vector_size (base_series->values[2].data);
 		if (n > tmp)
 			n = tmp;
 		mpath = go_path_new ();



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]