/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * gog-chart.c :
 *
 * Copyright (C) 2003-2004 Jody Goldberg (jody@gnome.org)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * 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-chart-impl.h>
#include <goffice/graph/gog-chart-map.h>
#include <goffice/graph/gog-chart-map-3d.h>
#include <goffice/graph/gog-3d-box.h>
#include <goffice/graph/gog-plot-impl.h>
#include <goffice/graph/gog-graph-impl.h>
#include <goffice/utils/go-style.h>
#include <goffice/graph/gog-view.h>
#include <goffice/graph/gog-axis.h>
#include <goffice/graph/gog-axis-line-impl.h>
#include <goffice/graph/gog-grid.h>
#include <goffice/graph/gog-grid-line.h>
#include <goffice/graph/gog-renderer.h>
#include <goffice/math/go-math.h>
#include <goffice/math/go-matrix3x3.h>
#include <goffice/utils/go-persist.h>

#include <gsf/gsf-impl-utils.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#include <math.h>

#ifdef GOFFICE_WITH_LASEM
#include <goffice/graph/gog-equation.h>
#endif

#ifdef GOFFICE_WITH_GTK
#include <goffice/gtk/goffice-gtk.h>
#endif

/**
 * SECTION: gog-chart
 * @short_description: A chart.
 * @See_also: #GogPlot
 *
 * #GogChart wraps one or more #GogPlot objects, so that you can superimpose
 * them on top of each other. In addition, the chart can have a title and a legend.
*/

static const struct {
	char const *name;
	GogAxisSet const axis_set;
} axis_set_desc[] = {
	{ "none",	GOG_AXIS_SET_NONE},
	{ "x",		GOG_AXIS_SET_X},
	{ "xy", 	GOG_AXIS_SET_XY},
	{ "xyz",	GOG_AXIS_SET_XYZ},
	{ "radar",	GOG_AXIS_SET_RADAR},
	{ "pseudo-3d",	GOG_AXIS_SET_XY_pseudo_3d},
	{ "xy-color",	GOG_AXIS_SET_XY_COLOR}
};

GogAxisSet
gog_axis_set_from_str (char const *str)
{
	GogAxisSet axis_set = GOG_AXIS_SET_NONE;
	unsigned i;
	gboolean found = FALSE;

	if (str == NULL)
		return GOG_AXIS_SET_NONE;

	for (i = 0; i < G_N_ELEMENTS (axis_set_desc); i++)
		if (strcmp (axis_set_desc[i].name, str) == 0) {
			axis_set = axis_set_desc[i].axis_set;
			found = TRUE;
			break;
		}
	if (!found)
		g_warning ("[GogAxisSet::from_str] unknown axis set (%s)", str);
	return axis_set;
}

/*****************************************************************************/
enum {
	CHART_PROP_0,
	CHART_PROP_CARDINALITY_VALID,
	CHART_PROP_PLOT_AREA,
	CHART_PROP_PLOT_AREA_IS_MANUAL,
	CHART_PROP_X_POS,
	CHART_PROP_Y_POS,
	CHART_PROP_ROWS,
	CHART_PROP_COLUMNS
};

static GType gog_chart_view_get_type (void);
static GObjectClass *chart_parent_klass;

static void
gog_chart_update (GogObject *obj)
{
	GogChart *chart = GOG_CHART (obj);
	unsigned full = chart->full_cardinality;
	unsigned visible = chart->visible_cardinality;

	gog_chart_get_cardinality (chart, NULL, NULL);

	if (full != chart->full_cardinality ||
	    visible != chart->visible_cardinality)
		g_object_notify (G_OBJECT (chart), "cardinality-valid");
}

static void
gog_chart_finalize (GObject *obj)
{
	GogChart *chart = GOG_CHART (obj);

	/* on exit the role remove routines are not called */
	g_slist_free (chart->plots);
	g_slist_free (chart->axes);

	(chart_parent_klass->finalize) (obj);
}

static void
gog_chart_set_property (GObject *obj, guint param_id,
			 GValue const *value, GParamSpec *pspec)
{
	GogChart *chart = GOG_CHART (obj);
	char **str_doubles;
	char const *str;
	gboolean changed = FALSE;

	switch (param_id) {
	case CHART_PROP_PLOT_AREA:
		str = g_value_get_string (value);
		str_doubles = g_strsplit (str, " ", 4);
		if (g_strv_length (str_doubles) != 4) {
			g_strfreev (str_doubles);
			break;
		}
		chart->plot_area.x = g_ascii_strtod (str_doubles[0], NULL);
		chart->plot_area.y = g_ascii_strtod (str_doubles[1], NULL);
		chart->plot_area.w = g_ascii_strtod (str_doubles[2], NULL);
		chart->plot_area.h = g_ascii_strtod (str_doubles[3], NULL);
		g_strfreev (str_doubles);
		break;
	case CHART_PROP_PLOT_AREA_IS_MANUAL:
		chart->is_plot_area_manual = g_value_get_boolean (value);
		break;
	case CHART_PROP_X_POS:
		chart->x_pos = g_value_get_int (value);
		changed = TRUE;
		break;
	case CHART_PROP_Y_POS:
		chart->y_pos = g_value_get_int (value);
		changed = TRUE;
		break;
	case CHART_PROP_COLUMNS:
		chart->cols = g_value_get_int (value);
		changed = TRUE;
		break;
	case CHART_PROP_ROWS:
		chart->rows = g_value_get_int (value);
		changed = TRUE;
		break;
	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
		 return; /* NOTE : RETURN */
	}

	if (changed) {
		gog_graph_validate_chart_layout (GOG_GRAPH (GOG_OBJECT (chart)->parent));
		gog_object_emit_changed (GOG_OBJECT (obj), TRUE);
	}
}

static void
gog_chart_get_property (GObject *obj, guint param_id,
			GValue *value, GParamSpec *pspec)
{
	GogChart *chart = GOG_CHART (obj);
	GString *string;
	char buffer[G_ASCII_DTOSTR_BUF_SIZE];

	switch (param_id) {
	case CHART_PROP_CARDINALITY_VALID:
		g_value_set_boolean (value, chart->cardinality_valid);
		break;
	case CHART_PROP_PLOT_AREA:
		string = g_string_new ("");
		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.x));
		g_string_append_c (string, ' ');
		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.y));
		g_string_append_c (string, ' ');
		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.w));
		g_string_append_c (string, ' ');
		g_string_append (string, g_ascii_dtostr (buffer, sizeof (buffer), chart->plot_area.h));
		g_value_set_string (value, string->str);
		g_string_free (string, TRUE);
		break;
	case CHART_PROP_PLOT_AREA_IS_MANUAL:
		g_value_set_boolean (value, chart->is_plot_area_manual);
		break;
	case CHART_PROP_X_POS:
		g_value_set_int (value, chart->x_pos);
		break;
	case CHART_PROP_Y_POS:
		g_value_set_int (value, chart->y_pos);
		break;
	case CHART_PROP_COLUMNS:
		g_value_set_int (value, chart->cols);
		break;
	case CHART_PROP_ROWS:
		g_value_set_int (value, chart->rows);
		break;

	default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, param_id, pspec);
		 break;
	}
}

#ifdef GOFFICE_WITH_GTK
typedef struct {
	GtkBuilder	*gui;
	GtkWidget	*x_spin, *y_spin, *w_spin, *h_spin;
	gulong		 w_spin_signal, h_spin_signal;
	GtkWidget	*position_select_combo;
	GtkWidget	*manual_setting_table;
	GogChart	*chart;
} PlotAreaPrefState;

static void
plot_area_pref_state_free (PlotAreaPrefState *state)
{
	g_object_unref (state->chart);
	g_object_unref (state->gui);
}

static void
cb_plot_area_changed (GtkWidget *spin, PlotAreaPrefState *state)
{
	GogViewAllocation pos;
	double value;
	double max;

       	value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin)) / 100.0;

       	gog_chart_get_plot_area (state->chart, &pos);
	if (spin == state->x_spin) {
		pos.x = value;
		max = 1.0 - pos.x;
		g_signal_handler_block (state->w_spin, state->w_spin_signal);
		gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->w_spin), 0.0, max * 100.0);
		if (pos.w > max) pos.w = max;
		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->w_spin), pos.w * 100.0);
		g_signal_handler_unblock (state->w_spin, state->w_spin_signal);
	}
	else if (spin == state->y_spin) {
		pos.y = value;
		max = 1.0 - pos.y;
		g_signal_handler_block (state->h_spin, state->h_spin_signal);
		gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->h_spin), 0.0, max * 100.0);
		if (pos.h > max) pos.h = max;
		gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->h_spin), pos.w * 100.0);
		g_signal_handler_unblock (state->h_spin, state->h_spin_signal);
	}
	else if (spin == state->w_spin) {
		pos.w = value;
	}
	else if (spin == state->h_spin) {
		pos.h = value;
	}
	gog_chart_set_plot_area (state->chart, &pos);
	gtk_combo_box_set_active (GTK_COMBO_BOX (state->position_select_combo), 1);
	gtk_widget_show (state->manual_setting_table);
}

static void
cb_manual_position_changed (GtkComboBox *combo, PlotAreaPrefState *state)
{
	if (gtk_combo_box_get_active (combo) == 1) {
		GogViewAllocation plot_area;

		gog_chart_get_plot_area (state->chart, &plot_area);
		gog_chart_set_plot_area (state->chart, &plot_area);
		gtk_widget_show (state->manual_setting_table);
	} else {
		gog_chart_set_plot_area (state->chart, NULL);
		gtk_widget_hide (state->manual_setting_table);
	}
}

static void
gog_chart_populate_editor (GogObject *gobj,
			   GOEditor *editor,
			   G_GNUC_UNUSED GogDataAllocator *dalloc,
			   GOCmdContext *cc)
{
	static guint chart_pref_page = 0;

	GtkBuilder *gui;
	PlotAreaPrefState *state;
	gboolean is_plot_area_manual;
	GogViewAllocation plot_area;
	GtkWidget *w;
	GogChart *chart = GOG_CHART (gobj);

	g_return_if_fail (chart != NULL);

	gui = go_gtk_builder_new ("gog-plot-prefs.ui", GETTEXT_PACKAGE, cc);
	g_return_if_fail (gui != NULL);

	(GOG_OBJECT_CLASS(chart_parent_klass)->populate_editor) (gobj, editor, dalloc, cc);

	state = g_new  (PlotAreaPrefState, 1);
	state->chart = chart;
	state->gui = gui;

	g_object_ref (G_OBJECT (chart));
	is_plot_area_manual = gog_chart_get_plot_area (chart, &plot_area);

	state->x_spin = go_gtk_builder_get_widget (gui, "x_spin");
	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->x_spin),
				   plot_area.x * 100.0);
	g_signal_connect (G_OBJECT (state->x_spin), "value-changed",
			  G_CALLBACK (cb_plot_area_changed), state);

	state->y_spin = go_gtk_builder_get_widget (gui, "y_spin");
	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->y_spin),
				   plot_area.y * 100.0);
	g_signal_connect (G_OBJECT (state->y_spin), "value-changed",
			  G_CALLBACK (cb_plot_area_changed), state);

	state->w_spin = go_gtk_builder_get_widget (gui, "w_spin");
	gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->w_spin),
				   0.0, (1.0 - plot_area.x) * 100.0);
	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->w_spin),
				   100.0 * plot_area.w);
	state->w_spin_signal = g_signal_connect (G_OBJECT (state->w_spin), "value-changed",
						 G_CALLBACK (cb_plot_area_changed), state);

	state->h_spin = go_gtk_builder_get_widget (gui, "h_spin");
	gtk_spin_button_set_range (GTK_SPIN_BUTTON (state->h_spin),
				   0.0, (1.0 - plot_area.y) * 100.0);
	gtk_spin_button_set_value (GTK_SPIN_BUTTON (state->h_spin),
				   100.0 * plot_area.h);
	state->h_spin_signal = g_signal_connect (G_OBJECT (state->h_spin), "value-changed",
						 G_CALLBACK (cb_plot_area_changed), state);

	state->position_select_combo = go_gtk_builder_get_widget (gui, "position_select_combo");
	gtk_combo_box_set_active (GTK_COMBO_BOX (state->position_select_combo),
				  is_plot_area_manual ? 1 : 0);
	state->manual_setting_table = go_gtk_builder_get_widget (gui, "manual_setting_table");
	if (!is_plot_area_manual)
		gtk_widget_hide (state->manual_setting_table);

	g_signal_connect (G_OBJECT (state->position_select_combo),
			  "changed", G_CALLBACK (cb_manual_position_changed), state);

	w = go_gtk_builder_get_widget (gui, "gog_plot_prefs");
	g_signal_connect_swapped (G_OBJECT (w), "destroy", G_CALLBACK (plot_area_pref_state_free), state);
	go_editor_add_page (editor, w, _("Plot area"));

	go_editor_set_store_page (editor, &chart_pref_page);
}
#endif

static void
gog_chart_children_reordered (GogObject *obj)
{
	GSList *ptr, *accum = NULL;
	GogChart *chart = GOG_CHART (obj);

	for (ptr = obj->children; ptr != NULL ; ptr = ptr->next)
		if (GOG_IS_PLOT (ptr->data))
			accum = g_slist_prepend (accum, ptr->data);
	g_slist_free (chart->plots);
	chart->plots = g_slist_reverse (accum);

	gog_chart_request_cardinality_update (chart);
}

static gboolean
role_plot_can_add (GogObject const *parent)
{
	GogChart *chart = GOG_CHART (parent);
	return (chart->axis_set & (1 << GOG_AXIS_Z)) == 0 || g_slist_length (chart->plots) == 0;
}

static void
role_plot_post_add (GogObject *parent, GogObject *plot)
{
	GogChart *chart = GOG_CHART (parent);
	gboolean ok = TRUE;
	GogPlotClass *plot_klass = GOG_PLOT_CLASS (G_OBJECT_GET_CLASS (plot));
	GogAxisSet axis_set = plot_klass->axis_set & ~GOG_AXIS_SET_FUNDAMENTAL;

	if (axis_set) {
		int i = GOG_AXIS_VIRTUAL, j = 1 << GOG_AXIS_VIRTUAL;
		for (; i < GOG_AXIS_TYPES; i++, j <<= 1)
			if ((axis_set & j) != 0 && (chart->axis_set & j) == 0) {
				GogObject *axis = GOG_OBJECT (g_object_new (GOG_TYPE_AXIS, "type", i, NULL));
				chart->axis_set |= j;
				switch (i) {
				case GOG_AXIS_PSEUDO_3D:
					gog_object_add_by_name (GOG_OBJECT (chart), "Pseudo-3D-Axis", axis);
					break;
				case GOG_AXIS_COLOR:
					gog_object_add_by_name (GOG_OBJECT (chart), "Color-Axis", axis);
					break;
				case GOG_AXIS_BUBBLE:
					gog_object_add_by_name (GOG_OBJECT (chart), "Bubble-Axis", axis);
					break;
				default:
					g_warning ("Unknown axis type: %x\n", i);
				}
			}
	}
	/* APPEND to keep order, there won't be that many */
	chart->plots = g_slist_append (chart->plots, plot);
	gog_chart_request_cardinality_update (chart);

	if (chart->plots->next == NULL)
		ok = gog_chart_axis_set_assign (chart,
			gog_plot_axis_set_pref (GOG_PLOT (plot)));
	ok |= gog_plot_axis_set_assign (GOG_PLOT (plot),
		chart->axis_set);

	/* a quick post condition to keep us on our toes */
	g_return_if_fail (ok);
}

static void
role_plot_pre_remove (GogObject *parent, GogObject *plot)
{
	GogChart *chart = GOG_CHART (parent);
	gog_plot_axis_clear (GOG_PLOT (plot), GOG_AXIS_SET_ALL);
	chart->plots = g_slist_remove (chart->plots, plot);
	gog_chart_request_cardinality_update (chart);

	if (chart->plots == NULL)
		gog_chart_axis_set_assign (chart, GOG_AXIS_SET_UNKNOWN);

	if (chart->grid != NULL &&
	    chart->axis_set != GOG_AXIS_SET_XY &&
	    chart->axis_set != GOG_AXIS_SET_X &&
	    chart->axis_set != GOG_AXIS_SET_XY_pseudo_3d &&
	    chart->axis_set != GOG_AXIS_SET_XY_COLOR &&
	    chart->axis_set != GOG_AXIS_SET_RADAR) {
		GogObject *grid = chart->grid; /* clear_parent clears ::grid */
		gog_object_clear_parent (GOG_OBJECT (grid));
		g_object_unref (grid);
	}
}

static gboolean
role_grid_can_add (GogObject const *parent)
{
	GogChart const *chart = GOG_CHART (parent);
	return chart->grid == NULL &&
		(chart->axis_set == GOG_AXIS_SET_XY ||
		 chart->axis_set == GOG_AXIS_SET_X ||
		 chart->axis_set == GOG_AXIS_SET_XY_pseudo_3d ||
		 chart->axis_set == GOG_AXIS_SET_XY_COLOR ||
		 chart->axis_set == GOG_AXIS_SET_RADAR);
}

static void
role_grid_post_add (GogObject *parent, GogObject *child)
{
	GogChart *chart = GOG_CHART (parent);
	g_return_if_fail (chart->grid == NULL);
	chart->grid = child;
}

static void
role_grid_pre_remove (GogObject *parent, GogObject *grid)
{
	GogChart *chart = GOG_CHART (parent);
	g_return_if_fail (chart->grid == grid);
	chart->grid = NULL;
}

static gboolean
xy_grid_3d_can_add (GogObject const *parent)
{
	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
		NULL == gog_object_get_child_by_name (parent, "XY-Backplane"));
}

static gboolean
yz_grid_3d_can_add (GogObject const *parent)
{
	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
		NULL == gog_object_get_child_by_name (parent, "YZ-Backplane"));
}

static gboolean
zx_grid_3d_can_add (GogObject const *parent)
{
	return (GOG_CHART (parent)->axis_set == GOG_AXIS_SET_XYZ &&
		NULL == gog_object_get_child_by_name (parent, "ZX-Backplane"));
}

static void
grid_3d_post_add (GogObject *child, GogGridType t)
{
	g_object_set (G_OBJECT (child), "type", (int)t, NULL);
}

static void xy_grid_3d_post_add    (GogObject *parent, GogObject *child)
{ grid_3d_post_add (child, GOG_GRID_XY); }
static void yz_grid_3d_post_add    (GogObject *parent, GogObject *child)
{ grid_3d_post_add (child, GOG_GRID_YZ); }
static void zx_grid_3d_post_add    (GogObject *parent, GogObject *child)
{ grid_3d_post_add (child, GOG_GRID_ZX); }

static gboolean
axis_can_add (GogObject const *parent, GogAxisType t)
{
	GogChart *chart = GOG_CHART (parent);
	if (chart->axis_set == GOG_AXIS_SET_UNKNOWN
	    || chart->axis_set == GOG_AXIS_SET_XYZ)
		return FALSE;
	return (chart->axis_set & (1 << t)) != 0;
}

static gboolean
axis_can_remove (GogObject const *child)
{
	return NULL == gog_axis_contributors (GOG_AXIS (child));
}

static void
axis_post_add (GogObject *axis, GogAxisType t)
{
	GogChart *chart = GOG_CHART (axis->parent);
	g_object_set (G_OBJECT (axis), "type", (int)t, NULL);
	chart->axes = g_slist_prepend (chart->axes, axis);

	gog_axis_base_set_position (GOG_AXIS_BASE (axis), GOG_AXIS_AUTO);
}

static void
axis_pre_remove (GogObject *parent, GogObject *axis)
{
	GogChart *chart = GOG_CHART (parent);
	gog_axis_clear_contributors (GOG_AXIS (axis));
	chart->axes = g_slist_remove (chart->axes, axis);
}

static gboolean x_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_X); }
static void x_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_X); }
static gboolean y_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_Y); }
static void y_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_Y); }
static gboolean z_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_Z); }
static void z_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_Z); }
static gboolean circular_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_CIRCULAR); }
static void circular_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_CIRCULAR); }
static gboolean radial_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_RADIAL); }
static void radial_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_RADIAL); }
static gboolean pseudo_3d_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_PSEUDO_3D); }
static void pseudo_3d_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_PSEUDO_3D); }
static gboolean bubble_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_BUBBLE); }
static void bubble_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_BUBBLE); }
static gboolean color_axis_can_add (GogObject const *parent) { return axis_can_add (parent, GOG_AXIS_COLOR); }
static void color_axis_post_add    (GogObject *parent, GogObject *child)  { axis_post_add   (child, GOG_AXIS_COLOR); }
static gboolean role_3d_box_can_add	(GogObject const *parent) {return FALSE;}
static gboolean role_3d_box_can_remove	(GogObject const *parent) {return FALSE;}

static GogObjectRole const roles[] = {
	{ N_("Backplane"), "GogGrid",	1,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  role_grid_can_add, NULL, NULL, role_grid_post_add, role_grid_pre_remove, NULL, { -1 } },
	{ N_("XY-Backplane"), "GogGrid",	1,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  xy_grid_3d_can_add, NULL, NULL, xy_grid_3d_post_add, NULL, NULL,
	  { GOG_GRID_XY } },
	{ N_("YZ-Backplane"), "GogGrid",	1,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  yz_grid_3d_can_add, NULL, NULL, yz_grid_3d_post_add, NULL, NULL,
	  { GOG_GRID_YZ } },
	{ N_("ZX-Backplane"), "GogGrid",	1,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  zx_grid_3d_can_add, NULL, NULL, zx_grid_3d_post_add, NULL, NULL,
	  { GOG_GRID_ZX } },
	{ N_("X-Axis"), "GogAxis",	2,
	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
	  x_axis_can_add, axis_can_remove, NULL, x_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_X } },
	{ N_("Y-Axis"), "GogAxis",	3,
	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
	  y_axis_can_add, axis_can_remove, NULL, y_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_Y } },
	{ N_("Z-Axis"), "GogAxis",	4,
	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
	  z_axis_can_add, axis_can_remove, NULL, z_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_Z } },
	{ N_("Circular-Axis"), "GogAxis", 2,
	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
	  circular_axis_can_add, axis_can_remove, NULL, circular_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_CIRCULAR } },
	{ N_("Radial-Axis"), "GogAxis",	3,
	  GOG_POSITION_PADDING, GOG_POSITION_PADDING, GOG_OBJECT_NAME_BY_ROLE,
	  radial_axis_can_add, axis_can_remove, NULL, radial_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_RADIAL } },
	{ N_("Pseudo-3D-Axis"), "GogAxis", 4,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  pseudo_3d_axis_can_add, axis_can_remove, NULL, pseudo_3d_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_PSEUDO_3D } },
	{ N_("Bubble-Axis"), "GogAxis", 4,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  bubble_axis_can_add, axis_can_remove, NULL, bubble_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_BUBBLE } },
	{ N_("Color-Axis"), "GogAxis", 4,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  color_axis_can_add, axis_can_remove, NULL, color_axis_post_add, axis_pre_remove, NULL,
	  { GOG_AXIS_COLOR } },
	{ N_("Plot"), "GogPlot",	6,	/* keep the axis before the plots */
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_TYPE,
	  role_plot_can_add, NULL, NULL, role_plot_post_add, role_plot_pre_remove, NULL, { -1 } },
	{ N_("Title"), "GogLabel",	0,
	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
	  GOG_POSITION_N|GOG_POSITION_ALIGN_CENTER,
	  GOG_OBJECT_NAME_BY_ROLE,
	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
	{ N_("Legend"), "GogLegend",	11,
	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
	  GOG_POSITION_E|GOG_POSITION_ALIGN_CENTER,
	  GOG_OBJECT_NAME_BY_ROLE,
	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
#ifdef GOFFICE_WITH_LASEM
	{ N_("Equation"), "GogEquation",	11,
	  GOG_POSITION_COMPASS|GOG_POSITION_ANY_MANUAL,
	  GOG_POSITION_S|GOG_POSITION_ALIGN_CENTER,
	  GOG_OBJECT_NAME_BY_ROLE,
	  NULL, NULL, NULL, NULL, NULL, NULL, { -1 } },
#endif
	{ N_("3D-Box"), "Gog3DBox",	1,
	  GOG_POSITION_SPECIAL, GOG_POSITION_SPECIAL, GOG_OBJECT_NAME_BY_ROLE,
	  role_3d_box_can_add, role_3d_box_can_remove, NULL, NULL, NULL, NULL, { -1 } }
};

static void
gog_chart_class_init (GogObjectClass *gog_klass)
{
	GObjectClass *gobject_klass = (GObjectClass *)gog_klass;

	chart_parent_klass = g_type_class_peek_parent (gog_klass);
	gobject_klass->finalize = gog_chart_finalize;
	gobject_klass->set_property = gog_chart_set_property;
	gobject_klass->get_property = gog_chart_get_property;

#ifdef GOFFICE_WITH_GTK
	gog_klass->populate_editor = gog_chart_populate_editor;
#else
	gog_klass->populate_editor = NULL;
#endif

	gog_klass->can_manual_size = TRUE;

	g_object_class_install_property (gobject_klass, CHART_PROP_CARDINALITY_VALID,
		g_param_spec_boolean ("cardinality-valid",
				      _("Valid cardinality"),
				      _("Is the charts cardinality currently valid"),
				      FALSE,
				      GSF_PARAM_STATIC | G_PARAM_READABLE));
	g_object_class_install_property (gobject_klass, CHART_PROP_PLOT_AREA,
		g_param_spec_string ("plot-area",
				     _("Plot area"),
				     _("Position and size of plot area, in percentage of chart size"),
				     "0 0 1 1",
				     GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
	g_object_class_install_property (gobject_klass, CHART_PROP_PLOT_AREA_IS_MANUAL,
		g_param_spec_boolean ("is-plot-area-manual",
				      _("Manual plot area"),
				      _("Is plot area manual"),
				      FALSE,
				      GSF_PARAM_STATIC | G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
	g_object_class_install_property (gobject_klass, CHART_PROP_X_POS,
		g_param_spec_int ("xpos", _("xpos"),
			_("Horizontal chart position in graph grid"),
			0, G_MAXINT, 0, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
	/* we need to force saving of ypos since the default is not constant */
	g_object_class_install_property (gobject_klass, CHART_PROP_Y_POS,
		g_param_spec_int ("ypos", _("ypos"),
			_("Vertical chart position in graph grid"),
			0, G_MAXINT, 0, G_PARAM_READWRITE | GO_PARAM_PERSISTENT | GOG_PARAM_FORCE_SAVE));
	g_object_class_install_property (gobject_klass, CHART_PROP_COLUMNS,
		g_param_spec_int ("columns", _("columns"),
			_("Number of columns in graph grid"),
			1, G_MAXINT, 1, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));
	g_object_class_install_property (gobject_klass, CHART_PROP_ROWS,
		g_param_spec_int ("rows", _("rows"),
			_("Number of rows in graph grid"),
			1, G_MAXINT, 1, G_PARAM_READWRITE | GO_PARAM_PERSISTENT));

	gog_klass->view_type = gog_chart_view_get_type ();
	gog_klass->update    = gog_chart_update;
	gog_klass->children_reordered = gog_chart_children_reordered;
	gog_object_register_roles (gog_klass, roles, G_N_ELEMENTS (roles));
}

static void
gog_chart_init (GogChart *chart)
{
	chart->x_pos =
	chart->y_pos = 
	chart->cols  = 		
	chart->rows  =
	chart->x_pos_actual = 
	chart->y_pos_actual = 0;

	/* start as true so that we can queue an update when it changes */
	chart->cardinality_valid = TRUE;
	chart->axis_set = GOG_AXIS_SET_UNKNOWN;

	chart->is_plot_area_manual = FALSE;
	chart->plot_area.x =
	chart->plot_area.y = 0.0;
	chart->plot_area.w =
	chart->plot_area.h = 1.0;
}

GSF_CLASS (GogChart, gog_chart,
	   gog_chart_class_init, gog_chart_init,
	   GOG_TYPE_OUTLINED_OBJECT)

/**
 * gog_chart_get_position :
 * @chart : const #GogChart
 * @x :
 * @y :
 * @cols :
 * @rows :
 *
 * Returns: TRUE if the chart has been positioned.
 **/
gboolean
gog_chart_get_position (GogChart const *chart,
			unsigned *x, unsigned *y, unsigned *cols, unsigned *rows)
{
	g_return_val_if_fail (GOG_CHART (chart), FALSE);

	if (chart->cols <= 0 || chart->rows <= 0)
		return FALSE;

	if (x != NULL)	  *x	= chart->x_pos;
	if (y != NULL)	  *y	= chart->y_pos;
	if (cols != NULL) *cols	= chart->cols;
	if (rows != NULL) *rows	= chart->rows;

	return TRUE;
}

/**
 * gog_chart_set_position :
 * @chart: #GogChart
 * @x:
 * @y:
 * @cols:
 * @rows:
 *
 **/
void
gog_chart_set_position (GogChart *chart,
			unsigned int x, unsigned int y, unsigned int cols, unsigned int rows)
{
	g_return_if_fail (GOG_IS_CHART (chart));

	if (chart->x_pos == x && chart->y_pos == y &&
	    chart->cols == cols && chart->rows == rows)
		return;

	chart->x_pos = x;
	chart->y_pos = y;
	chart->cols = cols;
	chart->rows = rows;

	gog_graph_validate_chart_layout (GOG_GRAPH (GOG_OBJECT (chart)->parent));
	gog_object_emit_changed (GOG_OBJECT (chart), TRUE);
}

/**
 * gog_chart_get_plot_area :
 * @chart : #GogChart
 * @plot_area  : #GogViewAllocation
 *
 * Stores plot area in plot_area, in fraction of chart size.
 *
 * Returns: %TRUE if plot area position is manual.
 **/
gboolean
gog_chart_get_plot_area (GogChart *chart, GogViewAllocation *plot_area)
{
	if (plot_area != NULL)
		*plot_area = chart->plot_area;

	return chart->is_plot_area_manual;
}

/**
 * gog_chart_set_plot_area:
 * @chart : #GogChart
 * @plot_area  : #GogViewAllocation
 *
 * If plot_area != NULL, sets plot area size and location, in fraction
 * of chart size, and sets GogChart::is_plot_area_manual flag to TRUE.
 * If plot_area == NULL, sets GogChart::is_plot_area_manual to FALSE.
 **/
void
gog_chart_set_plot_area (GogChart *chart, GogViewAllocation const *plot_area)
{
	if (plot_area == NULL) {
		chart->is_plot_area_manual = FALSE;
	} else {
		chart->plot_area = *plot_area;
		chart->is_plot_area_manual = TRUE;
	}
	gog_object_emit_changed (GOG_OBJECT (chart), TRUE);
}

/**
 * gog_chart_get_cardinality:
 * @chart: a #GogChart
 * @full: placeholder for full cardinality
 * @visible: placeholder for visible cardinality
 *
 * Update and cache cardinality values if required, and returns
 * full and visible cardinality. Full cardinality is the number of
 * chart elements that require a different style. Visible cardinality is
 * the number of chart elements shown in chart legend.
 *
 * @full and @visible may be NULL.
 **/
void
gog_chart_get_cardinality (GogChart *chart, unsigned *full, unsigned *visible)
{
	GSList *ptr;
	unsigned tmp_full, tmp_visible;

	g_return_if_fail (GOG_IS_CHART (chart));

	if (!chart->cardinality_valid) {
		chart->cardinality_valid = TRUE;
		chart->full_cardinality = chart->visible_cardinality = 0;
		for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next) {
			gog_plot_update_cardinality (ptr->data, chart->full_cardinality);
			gog_plot_get_cardinality (ptr->data, &tmp_full, &tmp_visible);
			chart->full_cardinality += tmp_full;
			chart->visible_cardinality += tmp_visible;
		}
	}

	if (full != NULL)
		*full = chart->full_cardinality;
	if (visible != NULL)
		*visible = chart->visible_cardinality;
}

void
gog_chart_request_cardinality_update (GogChart *chart)
{
	g_return_if_fail (GOG_IS_CHART (chart));

	if (chart->cardinality_valid) {
		chart->cardinality_valid = FALSE;
		gog_object_request_update (GOG_OBJECT (chart));
	}
}

void
gog_chart_foreach_elem (GogChart *chart, gboolean only_visible,
			GogEnumFunc handler, gpointer data)
{
	GSList *ptr;

	g_return_if_fail (GOG_IS_CHART (chart));
	g_return_if_fail (chart->cardinality_valid);

	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
		gog_plot_foreach_elem (ptr->data, only_visible, handler, data);
}

GSList *
gog_chart_get_plots (GogChart const *chart)
{
	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);
	return chart->plots;
}

GogAxisSet
gog_chart_get_axis_set (GogChart const *chart)
{
	g_return_val_if_fail (GOG_IS_CHART (chart), GOG_AXIS_SET_UNKNOWN);
	return chart->axis_set;
}

gboolean
gog_chart_axis_set_is_valid (GogChart const *chart, GogAxisSet type)
{
	GSList *ptr;

	g_return_val_if_fail (GOG_IS_CHART (chart), FALSE);

	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
		if (!gog_plot_axis_set_is_valid (ptr->data, type))
			return FALSE;
	return TRUE;
}

static void
gog_chart_add_axis (GogChart *chart, GogAxisType type)
{
	unsigned i = G_N_ELEMENTS (roles);
	while (i-- > 0)
		if (roles[i].user.i == (int)type) {
			gog_object_add_by_role (GOG_OBJECT (chart), roles + i, NULL);
			return;
		}
	g_warning ("unknown axis type %d", type);
}

gboolean
gog_chart_axis_set_assign (GogChart *chart, GogAxisSet axis_set)
{
	GogAxis *axis;
	GSList  *ptr;
	GogAxisType type;

	g_return_val_if_fail (GOG_IS_CHART (chart), FALSE);

	if (chart->axis_set == axis_set)
		return TRUE;
	chart->axis_set = axis_set;

	if (axis_set == GOG_AXIS_SET_UNKNOWN)
		return TRUE;

	/* Add at least 1 instance of any required axis */
	for (type = 0 ; type < GOG_AXIS_TYPES ; type++)
		if ((axis_set & (1 << type))) {
			GSList *tmp = gog_chart_get_axes (chart, type);
			if (tmp == NULL)
				gog_chart_add_axis (chart, type);
			else
				g_slist_free (tmp);
		}

	/* link the plots */
	for (ptr = chart->plots ; ptr != NULL ; ptr = ptr->next)
		if (!gog_plot_axis_set_assign (ptr->data, axis_set))
			return FALSE;

	/* remove any existing axis that do not fit this scheme */
	for (ptr = GOG_OBJECT (chart)->children ; ptr != NULL ; ) {
		axis = ptr->data;
		ptr = ptr->next; /* list may change under us */
		if (GOG_IS_AXIS (axis)) {
			type = -1;
			g_object_get (G_OBJECT (axis), "type", &type, NULL);
			if (type < 0 || type >= GOG_AXIS_TYPES) {
				g_warning ("Invalid axis");
				continue;
			}

			if (0 == (axis_set & (1 << type))) {
				gog_object_clear_parent (GOG_OBJECT (axis));
				g_object_unref (axis);
			}
		}
	}

	return TRUE;
}

/**
 * gog_chart_get_axes :
 * @chart : #GogChart
 * @target  : #GogAxisType
 *
 * Returns: a list which the caller must free of all axis of type @target
 * associated with @chart.
 **/
GSList *
gog_chart_get_axes (GogChart const *chart, GogAxisType target)
{
	GSList *ptr, *res = NULL;
	GogAxis *axis;
	int type;

	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);

	for (ptr = GOG_OBJECT (chart)->children ; ptr != NULL ; ptr = ptr->next) {
		axis = ptr->data;
		if (GOG_IS_AXIS (axis)) {
			type = -1;
			g_object_get (G_OBJECT (axis), "type", &type, NULL);
			if (type < 0 || type >= GOG_AXIS_TYPES) {
				g_warning ("Invalid axis");
				continue;
			}
			if (type == target)
				res = g_slist_prepend (res, axis);
		}
	}

	return res;
}

/**
 * gog_chart_get_grid :
 * @chart: #GogChart
 *
 * Returns: the grid associated with @chart if one exists
 * otherwise NULL.
 **/
GogGrid  *
gog_chart_get_grid (GogChart const *chart)
{
	g_return_val_if_fail (GOG_IS_CHART (chart), NULL);
	return GOG_GRID (chart->grid);
}

gboolean
gog_chart_is_3d (GogChart const *chart)
{
	return chart->axis_set == GOG_AXIS_SET_XYZ;
}

/*********************************************************************/

static void
gog_chart_view_3d_process (GogView *view, GogViewAllocation *bbox)
{
	/* A XYZ axis set in supposed. If new sets (cylindrical, spherical or
	other are added, we'll need to change this code */
	GogViewAllocation tmp = *bbox;
	GogAxis *axisX, *axisY, *axisZ;
	GSList *axes;
	double xmin, xmax, ymin, ymax, zmin, zmax;
	double o[3], x[3], y[3], z[3], tg, d;
	Gog3DBox *box;
	GogChart *chart = GOG_CHART (gog_view_get_model (view));
	GogObject *obj = gog_object_get_child_by_name (GOG_OBJECT (chart), "3D-Box");
	GSList *ptr;
	GogView *child;
	GogViewPadding padding;

	if (!obj) {
		obj = g_object_new (GOG_3D_BOX_TYPE, NULL);
		gog_object_add_by_name (GOG_OBJECT (chart), "3D-Box", obj);
	}
	box = GOG_3D_BOX (obj);

	/* Only use the first of the axes. */
	axes = gog_chart_get_axes (chart, GOG_AXIS_X);
	axisX = GOG_AXIS (axes->data);
	g_slist_free (axes);
	gog_axis_get_bounds (axisX, &xmin, &xmax);
	axes = gog_chart_get_axes (chart, GOG_AXIS_Y);
	axisY = GOG_AXIS (axes->data);
	g_slist_free (axes);
	gog_axis_get_bounds (axisY, &ymin, &ymax);
	axes = gog_chart_get_axes (chart, GOG_AXIS_Z);
	axisZ = GOG_AXIS (axes->data);
	g_slist_free (axes);
	gog_axis_get_bounds (axisZ, &zmin, &zmax);
	/* define the 3d box */
	/* FIXME: take axes into account */
	box->dz = tmp.h;
	if (ymax - ymin > xmax - xmin) {
		box->dy = tmp.w;
		box->dx = (xmax - xmin) / (ymax - ymin) * tmp.w;
	} else {
		box->dx = tmp.w;
		box->dy = (ymax - ymin) / (xmax - xmin) * tmp.w;
	}

	/* now compute the position of each vertex, ignoring the fov */
	go_matrix3x3_transform (&box->mat, -box->dx, -box->dy, -box->dz, o, o + 1, o + 2);
	go_matrix3x3_transform (&box->mat, box->dx, -box->dy, -box->dz, x, x + 1, x + 2);
	go_matrix3x3_transform (&box->mat, -box->dx, box->dy, -box->dz, y, y + 1, y + 2);
	go_matrix3x3_transform (&box->mat, -box->dx, -box->dy, box->dz, z, z + 1, z + 2);
	/* for each diagonal, we need to take the vertex closer to the view point */
	if (o[1] > 0) {
		o[0] = -o[0];
		o[1] = -o[1];
		o[2] = -o[2];
	}
	if (x[1] > 0) {
		x[0] = -x[0];
		x[1] = -x[1];
		x[2] = -x[2];
	}
	if (y[1] > 0) {
		y[0] = -y[0];
		y[1] = -y[1];
		y[2] = -y[2];
	}
	if (z[1] > 0) {
		z[0] = -z[0];
		z[1] = -z[1];
		z[2] = -z[2];
	}
	/* if the fov is positive, calculate the position of the viewpoint */
	if (box->fov > 0.) {
		tg = tan (box->fov / 2.);
		box->r = -sqrt (o[0] * o[0] + o[2] * o[2]) / tg + o[1];
		d = -sqrt (x[0] * x[0] + x[2] * x[2]) / tg + x[1];
		if (d < box->r)
			box->r = d;
		d = -sqrt (y[0] * y[0] + y[2] * y[2]) / tg + y[1];
		if (d < box->r)
			box->r = d;
		d = -sqrt (z[0] *z[0] + z[2] * z[2]) / tg + z[1];
		if (d < box->r)
			box->r = d;
		/* also calculate the reduction factor we need to make things fit in the bbox */
		xmax = fabs (o[0]) / (1. - o[1] / box->r);
		zmax = fabs (o[2]) / (1. - o[1] / box->r);
		d = fabs (x[0]) / (1. - x[1] / box->r);
		if (d > xmax)
			xmax = d;
		d = fabs (x[2]) / (1. - x[1] / box->r);
		if (d > zmax)
			zmax = d;
		d = fabs (y[0]) / (1. - y[1] / box->r);
		if (d > xmax)
			xmax = d;
		d = fabs (y[2]) / (1. - y[1] / box->r);
		if (d > zmax)
			zmax = d;
		d = fabs (z[0]) / (1. - z[1] / box->r);
		if (d > xmax)
			xmax = d;
		d = fabs (z[2]) / (1. - z[1] / box->r);
		if (d > zmax)
			zmax = d;
	} else {
	    /* calculate the reduction factor we need to make things fit in the bbox */
		xmax = fabs (o[0]);
		zmax = fabs (o[2]);
		d = fabs (x[0]);
		if (d > xmax)
			xmax = d;
		d = fabs (x[2]);
		if (d > zmax)
			zmax = d;
		d = fabs (y[0]);
		if (d > xmax)
			xmax = d;
		d = fabs (y[2]);
		if (d > zmax)
			zmax = d;
		d = fabs (z[0]);
		if (d > xmax)
			xmax = d;
		d = fabs (z[2]);
		if (d > zmax)
			zmax = d;
	}
	/* use d and tg as x and z ratios, respectively */
	d = xmax / tmp.w;
	tg = zmax / tmp.h;
	box->ratio = (d > tg)? d: tg;

	gog_view_padding_request (view, bbox, &padding);

	if (!chart->is_plot_area_manual) {
		bbox->x += padding.wl;
		bbox->w -= padding.wl + padding.wr;
		bbox->y += padding.ht;
		bbox->h -= padding.ht + padding.hb;
	} else {
		tmp.x -= padding.wl;
		tmp.w += padding.wl + padding.wr;
		tmp.y -= padding.ht;
		tmp.h += padding.ht + padding.hb;
	}

	/* Recalculating ratio */
	d = xmax / bbox->w;
	tg = zmax / bbox->h;
	box->ratio = (d > tg)? d: tg;

	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
		child = ptr->data;
		if (GOG_POSITION_IS_PADDING (child->model->position)) {
			gog_view_size_allocate (child, &tmp);
		}
	}

	/* by default, overlay all GOG_POSITION_SPECIAL children in residual */
	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
		child = ptr->data;
		if (GOG_POSITION_IS_SPECIAL (child->model->position))
			gog_view_size_allocate (child, bbox);
	}
}

typedef struct {
	GogOutlinedView base;

	GogViewAllocation	plot_area;
} GogChartView;
typedef GogOutlinedViewClass	GogChartViewClass;

#define GOG_TYPE_CHART_VIEW	(gog_chart_view_get_type ())
#define GOG_CHART_VIEW(o)	(G_TYPE_CHECK_INSTANCE_CAST ((o), GOG_TYPE_CHART_VIEW, GogChartView))
#define GOG_IS_CHART_VIEW(o)	(G_TYPE_CHECK_INSTANCE_TYPE ((o), GOG_TYPE_CHART_VIEW))

static GogViewClass *cview_parent_klass;

static void
gog_chart_view_size_allocate (GogView *view, GogViewAllocation const *bbox)
{
	GSList *ptr;
	GogView *child;
	GogChartView *chart_view = GOG_CHART_VIEW (view);
	GogViewAllocation tmp, *plot_area = &chart_view->plot_area;
	GogViewPadding padding;
	GogChart *chart = GOG_CHART (gog_view_get_model (view));

	(cview_parent_klass->size_allocate) (view, bbox);

	if (chart->is_plot_area_manual) {
		plot_area->x = bbox->x + chart->plot_area.x * bbox->w;
		plot_area->y = bbox->y + chart->plot_area.y * bbox->h;
		plot_area->w = chart->plot_area.w * bbox->w;
		plot_area->h = chart->plot_area.h * bbox->h;
	} else
		*plot_area = view->residual;

	/* special treatment for 3d charts */
	if (gog_chart_is_3d (chart)) {
		gog_chart_view_3d_process (view, plot_area);
		return;
	}

	tmp = *plot_area;
	gog_view_padding_request (view, plot_area, &padding);

	if (!chart->is_plot_area_manual) {
		plot_area->x += padding.wl;
		plot_area->w -= padding.wl + padding.wr;
		plot_area->y += padding.ht;
		plot_area->h -= padding.ht + padding.hb;
	} else {
		tmp.x -= padding.wl;
		tmp.w += padding.wl + padding.wr;
		tmp.y -= padding.ht;
		tmp.h += padding.ht + padding.hb;
	}

	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
		child = ptr->data;
		if (GOG_POSITION_IS_PADDING (child->model->position)) {
			gog_view_size_allocate (child, &tmp);
		}
	}

	/* by default, overlay all GOG_POSITION_SPECIAL children in residual */
	for (ptr = view->children; ptr != NULL ; ptr = ptr->next) {
		child = ptr->data;
		if (GOG_POSITION_IS_SPECIAL (child->model->position))
			gog_view_size_allocate (child, plot_area);
	}
}

static void
gog_chart_view_init (GogChartView *cview)
{
}

static void
grid_line_render (GSList *start_ptr, GogViewAllocation const *bbox)
{
	GSList *ptr, *child_ptr;
	GSList *minor_grid_lines = NULL;
	GSList *major_grid_lines = NULL;
	GogView *child_view, *axis_child_view;

	for (ptr = start_ptr; ptr != NULL; ptr = ptr->next) {
		child_view = ptr->data;
		if (GOG_IS_AXIS (child_view->model)) {
			for (child_ptr = child_view->children; child_ptr != NULL; child_ptr = child_ptr->next) {
				axis_child_view = child_ptr->data;
				if (GOG_IS_GRID_LINE (axis_child_view->model)) {
					if (gog_grid_line_is_minor (GOG_GRID_LINE (axis_child_view->model)))
						minor_grid_lines = g_slist_prepend (minor_grid_lines,
										    axis_child_view);
					else
						major_grid_lines = g_slist_prepend (major_grid_lines,
										    axis_child_view);
				}
			}
		}
	}

	/* Render stripes, minor first then major */
	for (ptr = minor_grid_lines; ptr != NULL; ptr = ptr->next) {
		gog_grid_line_view_render_stripes (ptr->data);
	}
	for (ptr = major_grid_lines; ptr != NULL; ptr = ptr->next) {
		gog_grid_line_view_render_stripes (ptr->data);
	}

	/* Render lines, minor first then major */
	for (ptr = minor_grid_lines; ptr != NULL; ptr = ptr->next) {
		gog_grid_line_view_render_lines (ptr->data);
	}
	for (ptr = major_grid_lines; ptr != NULL; ptr = ptr->next) {
		gog_grid_line_view_render_lines (ptr->data);
	}

	g_slist_free (minor_grid_lines);
	g_slist_free (major_grid_lines);
}

static void
plot_render (GogView *view, GogViewAllocation const *bbox, GogPlotRenderingOrder order)
{
	GSList *ptr;
	GogView *child_view;

	/* Render some plots before axes */
	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
		child_view = ptr->data;
		if (GOG_IS_PLOT (child_view->model) &&
		    GOG_PLOT (child_view->model)->rendering_order == order)
			gog_view_render	(ptr->data, bbox);
	}
}

static void
gog_chart_view_render (GogView *view, GogViewAllocation const *bbox)
{
	GSList *ptr;
	GogView *child_view;
	gboolean grid_line_rendered = FALSE;
	GogChart *chart = GOG_CHART (gog_view_get_model (view));

	cview_parent_klass->render (view, bbox);

	if (gog_chart_is_3d (chart)) {
		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
			child_view = ptr->data;
			if (!GOG_IS_AXIS (child_view->model) && !GOG_IS_PLOT (child_view->model) && !GOG_IS_LABEL (child_view->model))
				gog_view_render	(ptr->data, bbox);
		}
		/* now render plot and axes */
		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
			child_view = ptr->data;
			if (!GOG_IS_AXIS (child_view->model))
				continue;
			gog_view_render (ptr->data, bbox);
			grid_line_render (ptr, bbox);
		}
		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
			child_view = ptr->data;
			if (!GOG_IS_PLOT (child_view->model))
				continue;
			gog_view_render	(ptr->data, bbox);
		}
	} else {
		/* KLUDGE: render grid lines before axis */
		for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
			child_view = ptr->data;
			if (!grid_line_rendered && GOG_IS_AXIS (child_view->model)) {
				plot_render (view, bbox, GOG_PLOT_RENDERING_BEFORE_GRID);
				grid_line_render (ptr, bbox);
				plot_render (view, bbox, GOG_PLOT_RENDERING_BEFORE_AXIS);
				grid_line_rendered = TRUE;
			}
			if (GOG_IS_PLOT (child_view->model)) {
			    if (!GOG_PLOT (child_view->model)->rendering_order)
				gog_view_render	(ptr->data, bbox);
			} else if (!GOG_IS_LABEL (child_view->model))
				gog_view_render	(ptr->data, bbox);
		}
	}
	for (ptr = view->children ; ptr != NULL ; ptr = ptr->next) {
		child_view = ptr->data;
		if (!GOG_IS_LABEL (child_view->model))
			continue;
		gog_view_render	(ptr->data, bbox);
	}
}

static void
gog_chart_view_class_init (GogChartViewClass *gview_klass)
{
	GogViewClass *view_klass = (GogViewClass *) gview_klass;
	GogOutlinedViewClass *oview_klass = (GogOutlinedViewClass *) gview_klass;

	cview_parent_klass = g_type_class_peek_parent (gview_klass);

	view_klass->size_allocate   	= gog_chart_view_size_allocate;
	view_klass->render 		= gog_chart_view_render;
	view_klass->clip 		= FALSE;
	oview_klass->call_parent_render = FALSE;
}

static GSF_CLASS (GogChartView, gog_chart_view,
		  gog_chart_view_class_init, gog_chart_view_init,
		  GOG_TYPE_OUTLINED_VIEW)

GogViewAllocation const *
gog_chart_view_get_plot_area (GogView const *view)
{
	g_return_val_if_fail (GOG_IS_CHART_VIEW (view), NULL);

	return &(GOG_CHART_VIEW (view)->plot_area);
}
