xlsx-write-drawing.c 29.8 KB
Newer Older
1 2 3 4 5 6 7 8
/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
 * xlsx-drawing-write.c : export MS Office Open xlsx drawings and charts.
 *
 * Copyright (C) 2006-2007 Jody Goldberg (jody@gnome.org)
 * Copyright (C) 2011 Jean Brefort (jean.brefort@normalesup.org)
 *
 * This program is free software; you can redistribute it and/or
9 10 11
 * 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) version 3.
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
 *
 * 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
 */

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

static void
xlsx_write_chart_cstr_unchecked (GsfXMLOut *xml, char const *name, char const *val)
{
	gsf_xml_out_start_element (xml, name);
	gsf_xml_out_add_cstr_unchecked (xml, "val", val);
	gsf_xml_out_end_element (xml);
}
static void
xlsx_write_chart_bool (GsfXMLOut *xml, char const *name, gboolean val)
{
	gsf_xml_out_start_element (xml, name);
	xlsx_add_bool (xml, "val", val);
	gsf_xml_out_end_element (xml);
}

static void
xlsx_write_chart_int (GsfXMLOut *xml, char const *name, int def_val, int val)
{
	gsf_xml_out_start_element (xml, name);
	if (val != def_val)
		gsf_xml_out_add_int (xml, "val", val);
	gsf_xml_out_end_element (xml);
}

static void
xlsx_write_chart_uint (GsfXMLOut *xml, char const *name, int def_val, int val)
{
	gsf_xml_out_start_element (xml, name);
	if (val != def_val)
		gsf_xml_out_add_uint (xml, "val", val);
	gsf_xml_out_end_element (xml);
}

static void
xlsx_write_chart_float (GsfXMLOut *xml, char const *name, double def_val, double val)
{
	gsf_xml_out_start_element (xml, name);
	if (val != def_val)
		gsf_xml_out_add_float (xml, "val", val, -1);
	gsf_xml_out_end_element (xml);
}

static void
69
xlsx_write_plot_1_5_type (GsfXMLOut *xml, GogObject const *plot, gboolean is_barcol)
70 71 72 73 74 75 76 77
{
	char const *type;
	g_object_get (G_OBJECT (plot), "type", &type, NULL);
	if (0 == strcmp (type, "as_percentage"))
		type = "percentStacked";
	else if (0 == strcmp (type, "stacked"))
		type = "stacked";
	else
78
		type = (is_barcol)? "clustered": "standard";
79 80 81 82 83 84 85 86
	xlsx_write_chart_cstr_unchecked (xml, "c:grouping", type);
}

static void
xlsx_write_series_dim (XLSXWriteState *state, GsfXMLOut *xml, GogSeries const *series,
		       char const *name, GogMSDimType ms_type)
{
	GogSeriesDesc const *desc = &gog_plot_description (gog_series_get_plot (series))->series;
87
	int dim;
88 89
	GOData const *dat;

90
	for (dim = -1; dim < (int) desc->num_dim; dim++)
91 92
		if (desc->dim[dim].ms_type == ms_type)
			break;
93
	if (dim == (int) desc->num_dim)
94 95 96 97 98 99 100 101 102 103
		return;
	dat = gog_dataset_get_dim (GOG_DATASET (series), dim);
	if (NULL != dat) {
		GnmExprTop const *texpr = gnm_go_data_get_expr (dat);
		if (NULL != texpr) {
			GnmParsePos pp;
			char *str = gnm_expr_top_as_string (texpr,
				parse_pos_init (&pp, (Workbook *)state->base.wb, NULL, 0,0 ),
				state->convs);
			gsf_xml_out_start_element (xml, name);
104
			gsf_xml_out_start_element (xml, (strcmp (name, "c:tx") && strcmp (name, "c:cat"))? "c:numRef": "c:strRef");
105 106
			gsf_xml_out_simple_element (xml, "c:f", str);
			gsf_xml_out_end_element (xml);
107
			/* FIXME: write values, they are mandatory, according to the schema */
108 109 110 111 112 113 114 115 116 117 118
			gsf_xml_out_end_element (xml);

			g_free (str);
		}
	}
}

static void
xlsx_write_rgbarea (GsfXMLOut *xml, GOColor color)
{
	char *buf = g_strdup_printf ("%06x", (guint) color >> 8);
Morten Welinder's avatar
Morten Welinder committed
119
	int alpha = GO_COLOR_UINT_A (color);
120 121 122 123 124 125 126 127 128 129 130 131
	gsf_xml_out_start_element (xml, "a:srgbClr");
	gsf_xml_out_add_cstr_unchecked (xml, "val", buf);
	g_free (buf);
	if (alpha < 255) {
		gsf_xml_out_start_element (xml, "a:alpha");
		gsf_xml_out_add_int (xml, "val", alpha * 100000 / 255);
		gsf_xml_out_end_element (xml);
	}
	gsf_xml_out_end_element (xml);
}

static void
132 133
xlsx_write_go_style_full (GsfXMLOut *xml, GOStyle *style,
			  gboolean def_has_markers)
134 135
{
	gsf_xml_out_start_element (xml, "c:spPr");
136

137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
	if ((style->interesting_fields & GO_STYLE_FILL) &&
	    style->fill.type != GO_STYLE_FILL_NONE) {/* TODO add tests for transparent backgrounds */
		switch (style->fill.type) {
		default :
			g_warning ("invalid fill type, saving as none");
		case GO_STYLE_FILL_IMAGE:
			/* FIXME: export image */
		case GO_STYLE_FILL_PATTERN:
			switch (style->fill.pattern.pattern) {
			case GO_PATTERN_SOLID:
				if (!style->fill.auto_back) {
					gsf_xml_out_start_element (xml, "a:solidFill");
					xlsx_write_rgbarea (xml, style->fill.pattern.back);
					gsf_xml_out_end_element (xml);
				}
				break;
			case GO_PATTERN_FOREGROUND_SOLID:
				if (!style->fill.auto_fore) {
					gsf_xml_out_start_element (xml, "a:solidFill");
					xlsx_write_rgbarea (xml, style->fill.pattern.fore);
					gsf_xml_out_end_element (xml);
				}
				break;
			}
			break;
		case GO_STYLE_FILL_GRADIENT:
			break;
		}
	}

167
	if ((style->interesting_fields & (GO_STYLE_LINE | GO_STYLE_OUTLINE)) &&
168 169 170
	    (!style->line.auto_dash ||
	     !style->line.auto_width ||
	     !style->line.auto_color)) {
171 172 173 174 175 176 177 178 179 180 181 182 183 184
		static const char * const dashes[] = {
			NULL,            /* GO_LINE_NONE */
			"solid",         /* GO_LINE_SOLID */
			"sysDot",        /* GO_LINE_S_DOT */
			"sysDashDot",    /* GO_LINE_S_DASH_DOT */
			"sysDashDotDot", /* GO_LINE_S_DASH_DOT_DOT */
			"lgDashDotDot",  /* GO_LINE_DASH_DOT_DOT_DOT */
			"dot",           /* GO_LINE_DOT */
			"sysDash",       /* GO_LINE_S_DASH */
			"dash",          /* GO_LINE_DASH */
			"lgDash",        /* GO_LINE_LONG_DASH */
			"dashDot",       /* GO_LINE_DASH_DOT */
			"lgDashDot",     /* GO_LINE_DASH_DOT_DOT */
		};
185
		gboolean is_none = (style->line.dash_type == GO_LINE_NONE);
186

187
		gsf_xml_out_start_element (xml, "a:ln");
188 189 190 191
		if (is_none) {
			/* Special meaning of zero width  */
			gsf_xml_out_add_int (xml, "w", 0);
		} else if (!style->line.auto_width && style->line.width > 0)
192
			gsf_xml_out_add_int (xml, "w", style->line.width * 12700);
193

194 195 196 197 198
		if (!style->line.auto_color) {
			gsf_xml_out_start_element (xml, "a:solidFill");
			xlsx_write_rgbarea (xml, style->line.color);
			gsf_xml_out_end_element (xml);
		}
Morten Welinder's avatar
Morten Welinder committed
199

200 201
		if (!style->line.auto_dash &&
		    style->line.dash_type < G_N_ELEMENTS (dashes) &&
202
		    dashes[style->line.dash_type]) {
203 204 205
			xlsx_write_chart_cstr_unchecked (xml,
							 "a:prstDash",
							 dashes[style->line.dash_type]);
206 207
		}

208 209
		gsf_xml_out_end_element (xml);
	}
210 211 212

	gsf_xml_out_end_element (xml);  /* "c:spPr" */

213
	if (style->interesting_fields & GO_STYLE_MARKER) {
214
		static const char *const markers[] = {
215
			"none",       /* GO_MARKER_NONE */
216 217 218
			"square",     /* GO_MARKER_SQUARE */
			"diamond",    /* GO_MARKER_DIAMOND */
			"triangle",   /* GO_MARKER_TRIANGLE_DOWN */   /* FIXME: rotation */
219
			"triangle",   /* GO_MARKER_TRIANGLE_UP */
220 221 222 223 224 225
			"triangle",   /* GO_MARKER_TRIANGLE_RIGHT */  /* FIXME: rotation */
			"triangle",   /* GO_MARKER_TRIANGLE_LEFT */   /* FIXME: rotation */
			"circle",     /* GO_MARKER_CIRCLE */
			"x",          /* GO_MARKER_X */
			"plus",       /* GO_MARKER_CROSS */
			"star",       /* GO_MARKER_ASTERISK */
226 227 228 229 230
			"dash",       /* GO_MARKER_BAR */
			"dot",        /* GO_MARKER_HALF_BAR */
			"diamond",    /* GO_MARKER_BUTTERFLY */       /* FIXME: dubious */
			"diamond",    /* GO_MARKER_HOURGLASS */       /* FIXME: dubious */
			"dot"         /* GO_MARKER_LEFT_HALF_BAR */
231
		};
232
		gboolean need_spPr;
233
		GOMarkerShape s = style->marker.auto_shape
234
			? (def_has_markers ? GO_MARKER_MAX : GO_MARKER_NONE)
235
			: go_marker_get_shape (style->marker.mark);
236 237 238

		gsf_xml_out_start_element (xml, "c:marker");

239 240
		xlsx_write_chart_cstr_unchecked
			(xml, "c:symbol",
241 242 243
			 (s < G_N_ELEMENTS (markers) && markers[s]
			  ? markers[s]
			  : "auto"));
244

245
		/* We don't have an auto_size flag */
246 247 248 249
		if (TRUE) {
			int def = 5, s = go_marker_get_size (style->marker.mark);
			xlsx_write_chart_int (xml, "c:size", def, s);
		}
250

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
		need_spPr = (!style->marker.auto_fill_color ||
			     !style->marker.auto_outline_color);
		if (need_spPr) {
			gsf_xml_out_start_element (xml, "c:spPr");

			if (!style->marker.auto_fill_color) {
				gsf_xml_out_start_element (xml, "a:solidFill");
				xlsx_write_rgbarea (xml, go_marker_get_fill_color (style->marker.mark));
				gsf_xml_out_end_element (xml);
			}

			if (!style->marker.auto_outline_color) {
				gsf_xml_out_start_element (xml, "a:ln");
				gsf_xml_out_start_element (xml, "a:solidFill");
				xlsx_write_rgbarea (xml, go_marker_get_outline_color (style->marker.mark));
				gsf_xml_out_end_element (xml);
				gsf_xml_out_end_element (xml);
			}

			gsf_xml_out_end_element (xml);
		}

273
		gsf_xml_out_end_element (xml);
274 275 276
	}
}

277 278 279 280 281 282
static void
xlsx_write_go_style (GsfXMLOut *xml, GOStyle *style)
{
	xlsx_write_go_style_full (xml, style, FALSE);
}

283 284
static void
xlsx_write_chart_text (XLSXWriteState *state, GsfXMLOut *xml,
285
		       GOData *data, GOStyle *style)
286 287 288
{
	/* I don't really know what I am doing here.  */
	char *text = go_data_get_scalar_string (data);
289 290 291 292
	gboolean has_font_color = ((style->interesting_fields & GO_STYLE_FONT) &&
				   !style->font.auto_color);
	gboolean has_font = ((style->interesting_fields & GO_STYLE_FONT) &&
			     TRUE /* !style->font.auto_font */);
293 294 295 296

	gsf_xml_out_start_element (xml, "c:tx");
	gsf_xml_out_start_element (xml, "c:rich");

297
	gsf_xml_out_simple_element (xml, "a:bodyPr", NULL);
298 299 300 301

	gsf_xml_out_start_element (xml, "a:p");
	gsf_xml_out_start_element (xml, "a:r");

302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339
	if (has_font_color || has_font) {
		GOFont const *font = style->font.font;
		PangoFontDescription *desc = font->desc;

		gsf_xml_out_start_element (xml, "a:rPr");
		if (has_font) {
			int sz = pango_font_description_get_size (desc);
			if (sz > 0) {
				sz = CLAMP (sz, 1 * PANGO_SCALE, 4000 * PANGO_SCALE);
				gsf_xml_out_add_uint (xml, "sz", sz * 100 / PANGO_SCALE);
			}

			if (pango_font_description_get_weight (desc) > PANGO_WEIGHT_NORMAL)
				xlsx_add_bool (xml, "b", TRUE);
			if (pango_font_description_get_style (desc) > PANGO_STYLE_NORMAL)
				xlsx_add_bool (xml, "i", TRUE);
		}
		if (has_font_color) {
			gsf_xml_out_start_element (xml, "a:solidFill");
			xlsx_write_rgbarea (xml, style->font.color);
			gsf_xml_out_end_element (xml);
		}
		if (has_font) {
			gsf_xml_out_start_element (xml, "a:latin");
			gsf_xml_out_add_cstr (xml, "typeface",
					      pango_font_description_get_family (desc));
			gsf_xml_out_end_element (xml);
		}
		gsf_xml_out_end_element (xml); /* </a:rPr> */
	}

	gsf_xml_out_simple_element (xml, "a:t", text);

	gsf_xml_out_end_element (xml); /* </a:r> */
	gsf_xml_out_end_element (xml); /* </a:p> */

	gsf_xml_out_end_element (xml); /* </c:rich> */
	gsf_xml_out_end_element (xml); /* </c:tx> */
340

341 342
	xlsx_write_go_style (xml, style);

343 344 345
	g_free (text);
}

346 347 348 349 350 351 352 353 354 355 356
static unsigned
xlsx_get_axid (XLSXWriteState *state, GogAxis *axis)
{
	gpointer l = g_hash_table_lookup (state->axids, axis);
	if (!l) {
		l = GUINT_TO_POINTER (1 + g_hash_table_size (state->axids));
		g_hash_table_insert (state->axids, axis, l);
	}
	return GPOINTER_TO_UINT (l);
}

357 358 359 360 361 362 363 364 365 366

static void
xlsx_write_axis (XLSXWriteState *state, GsfXMLOut *xml, GogAxis *axis, GogAxisType at)
{
	GogAxis *crossed = gog_axis_base_get_crossed_axis (GOG_AXIS_BASE (axis));
	GogAxisPosition pos;
	GogGridLine *grid;
	GogObject *label;
	GOFormat *format;

367 368 369 370 371 372
#ifdef DEBUG_AXIS
	g_printerr ("Writing axis %s.  (discrete = %d)\n",
		    gog_object_get_name (GOG_OBJECT (axis)),
		    gog_axis_is_discrete (axis));
#endif

373 374 375 376
	if (gog_axis_is_discrete (axis))
		gsf_xml_out_start_element (xml, "c:catAx");
	else
		gsf_xml_out_start_element (xml, "c:valAx");
377
	xlsx_write_chart_uint (xml, "c:axId", 0, xlsx_get_axid (state, axis));
378 379 380 381 382 383
	gsf_xml_out_start_element (xml, "c:scaling");
	xlsx_write_chart_cstr_unchecked (xml, "c:orientation", gog_axis_is_inverted (axis)? "maxMin": "minMax");
	// TODO: export min, max, an others
	gsf_xml_out_end_element (xml);
	/* FIXME position might be "t" or "r" */
	xlsx_write_chart_cstr_unchecked (xml, "c:axPos", (at == GOG_AXIS_X || at == GOG_AXIS_CIRCULAR)? "b": "l");
384

385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
	/* grids */
	grid = gog_axis_get_grid_line (axis, TRUE);
	if (grid) {
		gsf_xml_out_start_element (xml, "c:majorGridlines");
		xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (grid)));
		gsf_xml_out_end_element (xml);
	}
	grid = gog_axis_get_grid_line (axis, FALSE);
	if (grid) {
		gsf_xml_out_start_element (xml, "c:minorGridlines");
		xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (grid)));
		gsf_xml_out_end_element (xml);
	}

	label = gog_object_get_child_by_name (GOG_OBJECT (axis), "Label");
	if (label) {
		GOData *text = gog_dataset_get_dim (GOG_DATASET (label), 0);
		if (text != NULL) {
403
			GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (label));
404
			gsf_xml_out_start_element (xml, "c:title");
405
			xlsx_write_chart_text (state, xml, text, style);
406 407 408 409 410 411
			gsf_xml_out_end_element (xml);
		}
	}

	gsf_xml_out_start_element (xml, "c:numFmt");
	format = gog_axis_get_format (axis);
412
	xlsx_add_bool (xml, "sourceLinked", format == NULL || go_format_is_general (format));
413 414 415 416
	format = gog_axis_get_effective_format (axis);
	gsf_xml_out_add_cstr (xml, "formatCode", (format)? go_format_as_XL (format): "General");
	gsf_xml_out_end_element (xml);

417 418
	xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (axis)));

419
	xlsx_write_chart_int (xml, "c:crossAx", 0, xlsx_get_axid (state, crossed));
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
	g_object_get (G_OBJECT (axis), "pos", &pos, NULL);
	switch (pos) {
	default:
	case GOG_AXIS_AT_LOW:
		/* FIXME: might be wrong if the axis is inverted */
		xlsx_write_chart_cstr_unchecked (xml, "c:crosses", "min");
		break;
	case GOG_AXIS_CROSS: {
		double cross = gog_axis_base_get_cross_location (GOG_AXIS_BASE (axis));
		if (cross == 0.)
			xlsx_write_chart_cstr_unchecked (xml, "c:crosses", "autoZero");
		else
			xlsx_write_chart_float (xml, "c:crossesAt", 0., cross);
		break;
	}
	case GOG_AXIS_AT_HIGH:
		xlsx_write_chart_cstr_unchecked (xml, "c:crosses", "max");
		break;
	}

440 441 442 443 444
	/* finished with axis */
	gsf_xml_out_end_element (xml);
}


445
static void
446
xlsx_write_one_plot (XLSXWriteState *state, GsfXMLOut *xml, GogObject const *chart, GogObject const *plot)
447 448
{
	double explosion = 0.;
449
	gboolean vary_by_element;
450 451
	GogAxisType axis_type[3] = {GOG_AXIS_X, GOG_AXIS_Y, GOG_AXIS_UNKNOWN};
	unsigned i;
Morten Welinder's avatar
Morten Welinder committed
452
	XLSXPlotType plot_type;
453 454 455
	const char *plot_type_name;
	GSList const *series;
	unsigned count;
456 457
	gboolean use_xy = FALSE;
	gboolean set_smooth = FALSE;
458
	gboolean has_markers = FALSE;
459

460 461 462
	g_object_get (G_OBJECT (plot),
		      "vary-style-by-element", &vary_by_element,
		      NULL);
463
	plot_type_name = G_OBJECT_TYPE_NAME (plot);
Morten Welinder's avatar
Morten Welinder committed
464 465 466 467 468
	plot_type = xlsx_plottype_from_type_name (plot_type_name);

	switch (plot_type) {
	default:
	case XLSX_PT_UNKNOWN:
469 470
		g_warning ("unexpected plot type %s", plot_type_name);
		return;
471

Morten Welinder's avatar
Morten Welinder committed
472
	case XLSX_PT_GOGAREAPLOT:
473
		gsf_xml_out_start_element (xml, "c:areaChart");
474
		xlsx_write_plot_1_5_type (xml, plot, FALSE);
475
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
476 477
		break;

Morten Welinder's avatar
Morten Welinder committed
478
	case XLSX_PT_GOGBARCOLPLOT: {
479
		gboolean horizontal;
480 481

		g_object_get (G_OBJECT (plot), "horizontal", &horizontal, NULL);
482 483 484 485 486
		if (horizontal) {
			axis_type[0] = GOG_AXIS_Y;
			axis_type[1] = GOG_AXIS_X;
		}
		gsf_xml_out_start_element (xml, "c:barChart");
487

488 489
		xlsx_write_chart_cstr_unchecked (xml, "c:barDir",
						 horizontal ? "bar" : "col");
490

491
		xlsx_write_plot_1_5_type (xml, plot, TRUE);
492
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
493 494
		break;
	}
495

Morten Welinder's avatar
Morten Welinder committed
496
	case XLSX_PT_GOGLINEPLOT:
497
		gsf_xml_out_start_element (xml, "c:lineChart");
498
		xlsx_write_plot_1_5_type (xml, plot, FALSE);
499
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
500
		set_smooth = TRUE;
501 502 503
		g_object_get (G_OBJECT (plot),
		              "default-style-has-markers", &has_markers,
		              NULL);
504 505
		break;

Morten Welinder's avatar
Morten Welinder committed
506
	case XLSX_PT_GOGPIEPLOT:
507
	case XLSX_PT_GOGRINGPLOT:
Morten Welinder's avatar
Morten Welinder committed
508
		if (plot_type == XLSX_PT_GOGRINGPLOT) {
509
			gint16 center;
510
			double center_size;
511 512 513 514
			gsf_xml_out_start_element (xml, "c:doughnutChart");
			g_object_get (G_OBJECT (plot), "center-size", &center_size, NULL);
			center = (int)floor (center_size * 100. + .5);
			xlsx_write_chart_int (xml, "c:holeSize", 10,
515
					      CLAMP (center, 10, 90));
516 517 518
		} else
			gsf_xml_out_start_element (xml, "c:pieChart");

519
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
520 521 522 523 524 525 526
#if 0
		double default_separation = 0.;
		/* handled in series ? */
		"default-separation",	&default_separation,
		xlsx_write_chart_int (xml, "c:explosion", 0, default_separation);
#endif
		axis_type[0] = axis_type[1] = GOG_AXIS_UNKNOWN;
527 528 529
		g_object_get (G_OBJECT (plot), "default-separation", &explosion, NULL);
		break;

Morten Welinder's avatar
Morten Welinder committed
530 531
	case XLSX_PT_GOGRADARPLOT:
	case XLSX_PT_GOGRADARAREAPLOT:
532
		gsf_xml_out_start_element (xml, "c:radarChart");
533
		xlsx_write_chart_cstr_unchecked (xml, "c:radarStyle", "standard");
534 535
		axis_type[0] = GOG_AXIS_CIRCULAR;
		axis_type[1] = GOG_AXIS_RADIAL;
536 537
		break;

Morten Welinder's avatar
Morten Welinder committed
538
	case XLSX_PT_GOGBUBBLEPLOT:
539
		gsf_xml_out_start_element (xml, "c:bubbleChart");
540
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
541
		use_xy = TRUE;
542 543
		break;

Morten Welinder's avatar
Morten Welinder committed
544
	case XLSX_PT_GOGXYPLOT: {
545
		gboolean has_lines, use_splines;
546 547 548 549 550 551 552 553 554 555
		char const *style;
		g_object_get (G_OBJECT (plot),
		              "default-style-has-lines", &has_lines,
		              "default-style-has-markers", &has_markers,
		              "use-splines", &use_splines,
		              NULL);
		style = (has_lines)?
				(use_splines?
					(has_markers? "smoothMarker": "smooth"):
					(has_markers? "lineMarker": "line")):
556
				(has_markers? "marker": "none");
557
		use_xy = TRUE;
558
		set_smooth = TRUE;
559 560
		gsf_xml_out_start_element (xml, "c:scatterChart");
		xlsx_write_chart_cstr_unchecked (xml, "c:scatterStyle", style);
561
		xlsx_write_chart_bool (xml, "c:varyColors", vary_by_element);
562 563 564
		break;
	}

Morten Welinder's avatar
Morten Welinder committed
565 566
	case XLSX_PT_GOGCONTOURPLOT:
	case XLSX_PT_XLCONTOURPLOT:
567
		gsf_xml_out_start_element (xml, "c:surfaceChart");
568
		break;
569
	}
570 571 572 573 574

	count = 0;
	for (series = gog_plot_get_series (GOG_PLOT (plot));
	     NULL != series;
	     series = series->next) {
575 576
		GogSeries *ser = series->data;
		GSList *l, *children;
577
		GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (ser));
578

579 580 581 582
		gsf_xml_out_start_element (xml, "c:ser");

		xlsx_write_chart_int (xml, "c:idx", -1, count);
		xlsx_write_chart_int (xml, "c:order", -1, count);
583
		xlsx_write_series_dim (state, xml, ser, "c:tx", GOG_MS_DIM_LABELS);
584
		if (!vary_by_element) /* FIXME: we might loose some style elements */
585
			xlsx_write_go_style_full (xml, style, has_markers);
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615

		children = gog_object_get_children (GOG_OBJECT (ser), NULL);
		for (l = children; l; l = l->next) {
			GogObject *trend = l->data;
			const char *trend_type_name = G_OBJECT_TYPE_NAME (trend);
			const char *trend_type;
			GogObject *eq;

			if (!GOG_IS_TREND_LINE (trend))
				continue;

			if (strcmp (trend_type_name, "GogExpRegCurve") == 0)
				trend_type = "exp";
			else if (strcmp (trend_type_name, "GogLinRegCurve") == 0)
				trend_type = "linear";
			else if (strcmp (trend_type_name, "GogLogRegCurve") == 0)
				trend_type = "log";
			else if (strcmp (trend_type_name, "GogMovingAvg") == 0)
				trend_type = "movingAvg";
			else if (strcmp (trend_type_name, "GogPolynomRegCurve") == 0)
				trend_type = "poly";
			else if (strcmp (trend_type_name, "GogPowerRegCurve") == 0)
				trend_type = "power";
			else {
				trend_type = "linear";
				g_warning ("Unknown regression mapped to %s\n", trend_type);
			}


			gsf_xml_out_start_element (xml, "c:trendline");
616
			xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (trend)));
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631
			xlsx_write_chart_cstr_unchecked (xml, "c:trendlineType", trend_type);
			gsf_xml_out_end_element (xml); /* </c:trendline> */

			eq = gog_object_get_child_by_name (trend, "Equation");
			if (eq) {
				gboolean has_r2, has_eq;
				g_object_get (eq, "show-r2", &has_r2, "show-eq", &has_eq, NULL);
				if (has_r2)
					xlsx_write_chart_bool (xml, "c:dispRSqr", TRUE);
				if (has_eq)
					xlsx_write_chart_bool (xml, "c:dispEq", TRUE);
			}
		}
		g_slist_free (children);

632 633 634 635 636 637 638 639 640 641 642
		if (explosion > 0.)
			xlsx_write_chart_uint (xml, "c:explosion", 0, (unsigned) (explosion * 100));
		if (use_xy) {
			xlsx_write_series_dim (state, xml, ser, "c:xVal", GOG_MS_DIM_CATEGORIES);
			xlsx_write_series_dim (state, xml, ser, "c:yVal", GOG_MS_DIM_VALUES);
			xlsx_write_series_dim (state, xml, ser, "c:bubbleSize", GOG_MS_DIM_BUBBLES);
		} else {
			xlsx_write_series_dim (state, xml, ser, "c:cat", GOG_MS_DIM_CATEGORIES);
			xlsx_write_series_dim (state, xml, ser, "c:val", GOG_MS_DIM_VALUES);
		}

643 644 645 646 647 648 649 650 651 652 653 654 655
		if (set_smooth) {
			gboolean smooth;
			GOLineInterpolation inter;
			char *s;

			g_object_get (ser, "interpolation", &s, NULL);
			inter = go_line_interpolation_from_str (s);
			g_free (s);

			smooth = inter != GO_LINE_INTERPOLATION_LINEAR;
			xlsx_write_chart_bool (xml, "c:smooth", smooth);
		}

656 657 658 659
		gsf_xml_out_end_element (xml); /* </c:ser> */
	}

	switch (plot_type) {
Morten Welinder's avatar
Morten Welinder committed
660
	case XLSX_PT_GOGBARCOLPLOT: {
661 662 663 664 665 666 667
		int overlap_percentage, gap_percentage;

		g_object_get (G_OBJECT (plot),
			"overlap-percentage",	&overlap_percentage,
			"gap-percentage",	&gap_percentage,
			NULL);

668 669
		/* Spec says add "%" at end; XL cannot handle that. */
		xlsx_write_chart_int (xml, "c:gapWidth", 150, CLAMP (gap_percentage, 0, 500));
670

671 672
		/* Spec says add "%" at end; XL cannot handle that. */
		xlsx_write_chart_int (xml, "c:overlap", 0, CLAMP (overlap_percentage, 0, 100));
673
		break;
674
	}
675

Morten Welinder's avatar
Morten Welinder committed
676 677
	case XLSX_PT_GOGPIEPLOT:
	case XLSX_PT_GOGRINGPLOT: {
678 679 680 681 682 683 684 685
		double initial_angle = 0;
		g_object_get (G_OBJECT (plot),
			      "initial-angle", &initial_angle,
			      NULL);
		xlsx_write_chart_int (xml, "c:firstSliceAng", 0, (int) initial_angle);
		break;
	}

Morten Welinder's avatar
Morten Welinder committed
686
	case XLSX_PT_GOGBUBBLEPLOT: {
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
		gboolean show_neg = FALSE, in_3d = FALSE, as_area = TRUE;
		g_object_get (G_OBJECT (plot),
			      "show-negatives",	&show_neg,
			      "in-3d",		&in_3d,
			      "size-as-area",	&as_area,
			      NULL);
		if (in_3d)
			xlsx_write_chart_bool (xml, "c:bubble3D", TRUE);
		xlsx_write_chart_bool (xml, "c:showNegBubbles", show_neg);
		xlsx_write_chart_cstr_unchecked (xml, "c:sizeRepresents",
			as_area ? "area" : "w");
		break;
	}

	default:
		break; /* Nothing */
	}

	/* write axes Ids */
	for (i = 0; i < 3; i++)
		if (axis_type[i] != GOG_AXIS_UNKNOWN)
708
			xlsx_write_chart_uint (xml, "c:axId", 0, xlsx_get_axid (state, gog_plot_get_axis (GOG_PLOT (plot), axis_type[i])));
709 710 711 712


	gsf_xml_out_end_element (xml);

713 714 715
	/* Write axes */
	/* first category axis */
	/* FIXME: might be a date axis? */
716
	for (i = 0; i < 3; i++) {
717 718 719
		if (axis_type[i] != GOG_AXIS_UNKNOWN) {
			GSList *axes = gog_chart_get_axes (GOG_CHART (chart), axis_type[i]), *ptr;
			for (ptr = axes; ptr; ptr = ptr->next) {
720 721
				GogAxis *axis = ptr->data;
				xlsx_write_axis (state, xml, axis, axis_type[i]);
722 723
			}
		}
724
	}
725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
}

static void
xlsx_write_plots (XLSXWriteState *state, GsfXMLOut *xml, GogObject const *chart)
{
	GSList *plots;
	GogObject const *plot;

	plots = gog_object_get_children
		(GOG_OBJECT (chart),
		 gog_object_find_role_by_name (GOG_OBJECT (chart), "Plot"));
	if (plots != NULL && plots->data != NULL) {
		plot = plots->data;
		if (plots->next != NULL) {
			int n = g_slist_length (plots) - 1;
			g_warning ("Dropping %d plots from a chart.", n);
		}
		xlsx_write_one_plot (state, xml, chart, plot);
	}
	g_slist_free (plots);
}

static void
xlsx_write_one_chart (XLSXWriteState *state, GsfXMLOut *xml, GogObject const *chart)
{
	GogObject const *obj;

	gsf_xml_out_start_element (xml, "c:chart");
753 754 755 756 757

	obj = gog_object_get_child_by_name (chart, "Title");
	if (obj) {
		GOData *text = gog_dataset_get_dim (GOG_DATASET (obj), 0);
		if (text != NULL) {
758
			GOStyle *style = go_styled_object_get_style (GO_STYLED_OBJECT (obj));
759
			gsf_xml_out_start_element (xml, "c:title");
760
			xlsx_write_chart_text (state, xml, text, style);
761 762 763 764
			gsf_xml_out_end_element (xml);
		}
	}

765 766
	gsf_xml_out_start_element (xml, "c:plotArea");
	/* save grid style here */
767 768 769

	xlsx_write_plots (state, xml, chart);

770 771 772 773
	obj = gog_object_get_child_by_name (GOG_OBJECT (chart), "Backplane");
	if (obj)
		xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (obj)));

774 775 776 777 778 779 780
	gsf_xml_out_end_element (xml); /* </c:plotArea> */

	if ((obj = gog_object_get_child_by_name (chart, "Legend"))) {
		gsf_xml_out_start_element (xml, "c:legend");
		gsf_xml_out_end_element (xml); /* </c:legend> */
	}
	gsf_xml_out_end_element (xml); /* </c:chart> */
781 782

	xlsx_write_go_style (xml, go_styled_object_get_style (GO_STYLED_OBJECT (chart)));
783 784 785 786 787
}

static void
xlsx_write_chart (XLSXWriteState *state, GsfOutput *chart_part, SheetObject *so)
{
788
	GogGraph const *graph;
789 790
	GogObject const	*chart;
	GsfXMLOut *xml;
791

792 793 794 795 796 797 798 799 800 801 802 803
	xml = gsf_xml_out_new (chart_part);
	gsf_xml_out_start_element (xml, "c:chartSpace");
	gsf_xml_out_add_cstr_unchecked (xml, "xmlns:c", ns_chart);
	gsf_xml_out_add_cstr_unchecked (xml, "xmlns:a", ns_drawing);
	gsf_xml_out_add_cstr_unchecked (xml, "xmlns:r", ns_rel);

	graph = sheet_object_graph_get_gog (so);
	if (graph != NULL) {
		chart = gog_object_get_child_by_name (GOG_OBJECT (graph), "Chart");
		if (chart != NULL)
			xlsx_write_one_chart (state, xml, chart);
	}
804 805 806 807
	gsf_xml_out_end_element (xml); /* </c:chartSpace> */
	g_object_unref (xml);
}

808 809 810 811 812 813
static int
xlsx_pts_to_emu (double pts)
{
	return (double) gnm_floor (12700. * pts);
}

814
static void
815
xlsx_write_object_anchor (GsfXMLOut *xml, GnmCellPos const *pos, char const *element, double col_off_pts, double row_off_pts)
816
{
817 818
	/* For some reason we are using this additional scaling factor when we read!? */
	/* FIXME: scaling horizontally just like in xlsx_CT_Col */
819 820
	gsf_xml_out_start_element (xml, element);
	gsf_xml_out_simple_int_element (xml, "xdr:col", pos->col);
821
	gsf_xml_out_simple_int_element (xml, "xdr:colOff",
822
					xlsx_pts_to_emu (col_off_pts * 1.16191275167785));
823
	gsf_xml_out_simple_int_element (xml, "xdr:row", pos->row);
824 825
	gsf_xml_out_simple_int_element (xml, "xdr:rowOff",
					xlsx_pts_to_emu (row_off_pts));
826 827 828 829 830 831 832 833 834 835 836 837 838
	gsf_xml_out_end_element (xml);
}

static char const *
xlsx_write_objects (XLSXWriteState *state, GsfOutput *sheet_part, GSList *objects)
{
	GSList *obj, *chart_id, *chart_ids = NULL;
	char *name, *tmp;
	char const *rId, *rId1;
	int count = 1;
	GsfOutput *drawing_part, *chart_part;
	GsfXMLOut *xml;
	SheetObjectAnchor const *anchor;
839
	double res_pts[4] = {0.,0.,0.,0.};
840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879

	if (NULL == state->drawing.dir)
		state->drawing.dir = (GsfOutfile *)gsf_outfile_new_child (state->xl_dir, "drawings", TRUE);
	if (NULL == state->chart.dir)
		state->chart.dir = (GsfOutfile *)gsf_outfile_new_child (state->xl_dir, "charts", TRUE);

	name = g_strdup_printf ("drawing%u.xml", ++state->drawing.count);
	drawing_part = gsf_outfile_new_child_full (state->drawing.dir, name, FALSE,
		"content-type", "application/vnd.openxmlformats-officedocument.drawing+xml",
		NULL);
	g_free (name);

	rId = gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (drawing_part),
		GSF_OUTFILE_OPEN_PKG (sheet_part), ns_rel_draw);

	objects = sheet_objects_get (state->sheet, NULL, SHEET_OBJECT_GRAPH_TYPE);
	for (obj = objects ; obj != NULL ; obj = obj->next) {
		char *name = g_strdup_printf ("chart%u.xml", ++state->chart.count);
		chart_part = gsf_outfile_new_child_full (state->chart.dir, name, FALSE,
			"content-type", "application/vnd.openxmlformats-officedocument.drawingml.chart+xml",
			NULL);
		g_free (name);
		rId1 = gsf_outfile_open_pkg_relate (GSF_OUTFILE_OPEN_PKG (chart_part),
			GSF_OUTFILE_OPEN_PKG (drawing_part), ns_rel_chart);

		chart_ids = g_slist_prepend (chart_ids, (gpointer)rId1);

		xlsx_write_chart (state, chart_part, obj->data);
		gsf_output_close (chart_part);
		g_object_unref (chart_part);
	}

	xml = gsf_xml_out_new (drawing_part);
	gsf_xml_out_start_element (xml, "xdr:wsDr");
	gsf_xml_out_add_cstr_unchecked (xml, "xmlns:xdr", ns_ss_drawing);
	gsf_xml_out_add_cstr_unchecked (xml, "xmlns:a", ns_drawing);

	chart_id = g_slist_reverse (chart_ids);
	for (obj = objects; obj != NULL ; obj = obj->next, chart_id = chart_id->next) {
		anchor = sheet_object_get_anchor (obj->data);
880
		sheet_object_anchor_to_offset_pts (anchor, state->sheet, res_pts);
881 882

		gsf_xml_out_start_element (xml, "xdr:twoCellAnchor");
883 884 885 886
		xlsx_write_object_anchor (xml, &anchor->cell_bound.start, "xdr:from",
					  res_pts[0], res_pts[1]);
		xlsx_write_object_anchor (xml, &anchor->cell_bound.end, "xdr:to",
					  res_pts[2], res_pts[3]);
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942

		gsf_xml_out_start_element (xml, "xdr:graphicFrame");
		gsf_xml_out_add_cstr_unchecked (xml, "macro", "");

		gsf_xml_out_start_element (xml, "xdr:nvGraphicFramePr");

		gsf_xml_out_start_element (xml, "xdr:cNvPr");
		gsf_xml_out_add_int (xml, "id",  count+1);
		gsf_xml_out_add_cstr_unchecked (xml, "name",
			(tmp = g_strdup_printf ("Chart %d", count)));
		g_free (tmp);
		count++;
		gsf_xml_out_end_element (xml);

		gsf_xml_out_simple_element (xml, "xdr:cNvGraphicFramePr", NULL);
		gsf_xml_out_end_element (xml); /* </xdr:nvGraphicFramePr> */

		gsf_xml_out_start_element (xml, "xdr:xfrm");

		gsf_xml_out_start_element (xml, "a:off");
		gsf_xml_out_add_int (xml, "x", 0);
		gsf_xml_out_add_int (xml, "y", 0);
		gsf_xml_out_end_element (xml); /* </a:off> */

		gsf_xml_out_start_element (xml, "a:ext");
		gsf_xml_out_add_int (xml, "cx", 0);
		gsf_xml_out_add_int (xml, "cy", 0);
		gsf_xml_out_end_element (xml); /* </a:ext> */

		gsf_xml_out_end_element (xml); /* </xdr:xfrm> */

		gsf_xml_out_start_element (xml, "a:graphic");
		gsf_xml_out_start_element (xml, "a:graphicData");
		gsf_xml_out_add_cstr_unchecked (xml, "uri", ns_chart);
		gsf_xml_out_start_element (xml, "c:chart");
		gsf_xml_out_add_cstr_unchecked (xml, "xmlns:c", ns_chart);
		gsf_xml_out_add_cstr_unchecked (xml, "xmlns:r", ns_rel);

		gsf_xml_out_add_cstr_unchecked (xml, "r:id", chart_id->data);
		gsf_xml_out_end_element (xml); /* </c:chart> */
		gsf_xml_out_end_element (xml); /* </a:graphicData> */
		gsf_xml_out_end_element (xml); /* </a:graphic> */
		gsf_xml_out_end_element (xml); /* </xdr:graphicFrame> */
		gsf_xml_out_simple_element (xml, "xdr:clientData", NULL);
		gsf_xml_out_end_element (xml); /* </xdr:twoCellAnchor> */
	}
	g_slist_free (objects);
	g_slist_free (chart_ids);

	gsf_xml_out_end_element (xml); /* </wsDr> */
	g_object_unref (xml);
	gsf_output_close (drawing_part);
	g_object_unref (drawing_part);

	return rId;
}