1<?php
2/* Copyright (c) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (c) 2004-2015 Laurent Destailleur  <eldy@users.sourceforge.net>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/**
20 *	\file       htdocs/core/class/dolgraph.class.php
21 *  \ingroup    core
22 *	\brief      File for class to generate graph
23 */
24
25
26/**
27 * Class to build graphs.
28 * Usage is:
29 *    $dolgraph=new DolGraph();
30 *    $dolgraph->SetTitle($langs->transnoentities('MyTitle').'<br>'.$langs->transnoentities('MyTitlePercent').'%');
31 *    $dolgraph->SetMaxValue(50);
32 *    $dolgraph->SetData($data);
33 *    $dolgraph->setShowLegend(2);
34 *    $dolgraph->setShowPercent(1);
35 *    $dolgraph->SetType(array('pie'));
36 *    $dolgraph->setHeight('200');
37 *    $dolgraph->draw('idofgraph');
38 *    print $dolgraph->show($total?0:1);
39 */
40class DolGraph
41{
42	public $type = array(); // Array with type of each series. Example: array('bars', 'horizontalbars', 'lines', 'pies', 'piesemicircle', 'polar'...)
43	public $mode = 'side'; // Mode bars graph: side, depth
44	private $_library = 'chart'; // Graphic library to use (jflot, chart, artichow)
45
46	//! Array of data
47	public $data; // Data of graph: array(array('abs1',valA1,valB1), array('abs2',valA2,valB2), ...)
48	public $title; // Title of graph
49	public $cssprefix = ''; // To add into css styles
50
51	/**
52	 * @var int|string 		Width of graph. It can be a numeric for pixels or a string like '100%'
53	 */
54	public $width = 380;
55	/**
56	 * @var int 			Height of graph
57	 */
58	public $height = 200;
59
60	public $MaxValue = 0;
61	public $MinValue = 0;
62	public $SetShading = 0;
63
64	public $horizTickIncrement = -1;
65	public $SetNumXTicks = -1;
66	public $labelInterval = -1;
67
68	public $hideXGrid = false;
69	public $hideYGrid = false;
70
71	public $Legend = array();
72	public $LegendWidthMin = 0;
73	public $showlegend = 1;
74	public $showpointvalue = 1;
75	public $showpercent = 0;
76	public $combine = 0; // 0.05 if you want to combine records < 5% into "other"
77	public $graph; // Objet Graph (Artichow, Phplot...)
78
79	/**
80	 * @var string Error code (or message)
81	 */
82	public $error = '';
83
84	public $bordercolor; // array(R,G,B)
85	public $bgcolor; // array(R,G,B)
86	public $bgcolorgrid = array(255, 255, 255); // array(R,G,B)
87	public $datacolor; // array(array(R,G,B),...)
88
89	private $stringtoshow; // To store string to output graph into HTML page
90
91
92	/**
93	 * Constructor
94	 *
95	 * @param	string	$library		'auto' (default)
96	 */
97	public function __construct($library = 'auto')
98	{
99		global $conf;
100		global $theme_bordercolor, $theme_datacolor, $theme_bgcolor;
101
102		$this->bordercolor = array(235, 235, 224);
103		$this->datacolor = array(array(120, 130, 150), array(160, 160, 180), array(190, 190, 220));
104		$this->bgcolor = array(235, 235, 224);
105
106		$color_file = DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/theme_vars.inc.php';
107		if (is_readable($color_file)) {
108			include_once $color_file;
109			if (isset($theme_bordercolor)) $this->bordercolor = $theme_bordercolor;
110			if (isset($theme_datacolor))   $this->datacolor   = $theme_datacolor;
111			if (isset($theme_bgcolor))     $this->bgcolor     = $theme_bgcolor;
112		}
113		//print 'bgcolor: '.join(',',$this->bgcolor).'<br>';
114
115		$this->_library = $library;
116		if ($this->_library == 'auto') {
117			$this->_library = (empty($conf->global->MAIN_JS_GRAPH) ? 'chart' : $conf->global->MAIN_JS_GRAPH);
118		}
119	}
120
121
122	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
123	/**
124	 * Utiliser SetNumTicks ou SetHorizTickIncrement mais pas les 2
125	 *
126	 * @param 	float 		$xi		Xi
127	 * @return	boolean				True
128	 */
129	public function SetHorizTickIncrement($xi)
130	{
131		// phpcs:enable
132		$this->horizTickIncrement = $xi;
133		return true;
134	}
135
136	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
137	/**
138	 * Utiliser SetNumTicks ou SetHorizTickIncrement mais pas les 2
139	 *
140	 * @param 	float 		$xt		Xt
141	 * @return	boolean				True
142	 */
143	public function SetNumXTicks($xt)
144	{
145		// phpcs:enable
146		$this->SetNumXTicks = $xt;
147		return true;
148	}
149
150	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
151	/**
152	 * Set label interval to reduce number of labels
153	 *
154	 * @param 	float 		$x		Label interval
155	 * @return	boolean				True
156	 */
157	public function SetLabelInterval($x)
158	{
159		// phpcs:enable
160		$this->labelInterval = $x;
161		return true;
162	}
163
164	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
165	/**
166	 * Hide X grid
167	 *
168	 * @param	boolean		$bool	XGrid or not
169	 * @return	boolean				true
170	 */
171	public function SetHideXGrid($bool)
172	{
173		// phpcs:enable
174		$this->hideXGrid = $bool;
175		return true;
176	}
177
178	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
179	/**
180	 * Hide Y grid
181	 *
182	 * @param	boolean		$bool	YGrid or not
183	 * @return	boolean				true
184	 */
185	public function SetHideYGrid($bool)
186	{
187		// phpcs:enable
188		$this->hideYGrid = $bool;
189		return true;
190	}
191
192	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
193	/**
194	 * Set y label
195	 *
196	 * @param 	string	$label		Y label
197	 * @return	boolean|null				True
198	 */
199	public function SetYLabel($label)
200	{
201		// phpcs:enable
202		$this->YLabel = $label;
203	}
204
205	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
206	/**
207	 * Set width
208	 *
209	 * @param 	int|string		$w			Width (Example: 320 or '100%')
210	 * @return	boolean|null				True
211	 */
212	public function SetWidth($w)
213	{
214		// phpcs:enable
215		$this->width = $w;
216	}
217
218	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
219	/**
220	 * Set title
221	 *
222	 * @param 	string	$title		Title
223	 * @return	void
224	 */
225	public function SetTitle($title)
226	{
227		// phpcs:enable
228		$this->title = $title;
229	}
230
231	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
232	/**
233	 * Set data
234	 *
235	 * @param 	array	$data		Data
236	 * @return	void
237	 * @see draw_jflot() for syntax of data array
238	 */
239	public function SetData($data)
240	{
241		// phpcs:enable
242		$this->data = $data;
243	}
244
245	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
246	/**
247	 * Set data
248	 *
249	 * @param 	array	$datacolor		Data color array(array(R,G,B),array(R,G,B)...)
250	 * @return	void
251	 */
252	public function SetDataColor($datacolor)
253	{
254		// phpcs:enable
255		$this->datacolor = $datacolor;
256	}
257
258	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
259	/**
260	 * Set type
261	 *
262	 * @param 	array	$type		Array with type for each serie. Example: array('type1', 'type2', ...) where type can be:
263	 * 								'pie', 'piesemicircle', 'polar', 'lines', 'linesnopoint', 'bars', 'horizontalbars'...
264	 * @return	void
265	 */
266	public function SetType($type)
267	{
268		// phpcs:enable
269		$this->type = $type;
270	}
271
272	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
273	/**
274	 * Set legend
275	 *
276	 * @param 	array	$legend		Legend. Example: array('seriename1','seriname2',...)
277	 * @return	void
278	 */
279	public function SetLegend($legend)
280	{
281		// phpcs:enable
282		$this->Legend = $legend;
283	}
284
285	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
286	/**
287	 * Set min width
288	 *
289	 * @param 	int		$legendwidthmin		Min width
290	 * @return	void
291	 */
292	public function SetLegendWidthMin($legendwidthmin)
293	{
294		// phpcs:enable
295		$this->LegendWidthMin = $legendwidthmin;
296	}
297
298	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
299	/**
300	 * Set max value
301	 *
302	 * @param 	int		$max			Max value
303	 * @return	void
304	 */
305	public function SetMaxValue($max)
306	{
307		// phpcs:enable
308		$this->MaxValue = $max;
309	}
310
311	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
312	/**
313	 * Get max value
314	 *
315	 * @return	int		Max value
316	 */
317	public function GetMaxValue()
318	{
319		// phpcs:enable
320		return $this->MaxValue;
321	}
322
323	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
324	/**
325	 * Set min value
326	 *
327	 * @param 	int		$min			Min value
328	 * @return	void
329	 */
330	public function SetMinValue($min)
331	{
332		// phpcs:enable
333		$this->MinValue = $min;
334	}
335
336	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
337	/**
338	 * Get min value
339	 *
340	 * @return	int		Max value
341	 */
342	public function GetMinValue()
343	{
344		// phpcs:enable
345		return $this->MinValue;
346	}
347
348	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
349	/**
350	 * Set height
351	 *
352	 * @param 	int		$h				Height
353	 * @return	void
354	 */
355	public function SetHeight($h)
356	{
357		// phpcs:enable
358		$this->height = $h;
359	}
360
361	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
362	/**
363	 * Set shading
364	 *
365	 * @param 	string	$s				Shading
366	 * @return	void
367	 */
368	public function SetShading($s)
369	{
370		// phpcs:enable
371		$this->SetShading = $s;
372	}
373
374	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
375	/**
376	 * Set shading
377	 *
378	 * @param 	string	$s				Shading
379	 * @return	void
380	 */
381	public function SetCssPrefix($s)
382	{
383		// phpcs:enable
384		$this->cssprefix = $s;
385	}
386
387	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
388	/**
389	 * Reset bg color
390	 *
391	 * @return	void
392	 */
393	public function ResetBgColor()
394	{
395		// phpcs:enable
396		unset($this->bgcolor);
397	}
398
399	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
400	/**
401	 * Reset bgcolorgrid
402	 *
403	 * @return	void
404	 */
405	public function ResetBgColorGrid()
406	{
407		// phpcs:enable
408		unset($this->bgcolorgrid);
409	}
410
411	/**
412	 * Is graph ko
413	 *
414	 * @return	string		Error
415	 */
416	public function isGraphKo()
417	{
418		return $this->error;
419	}
420
421	/**
422	 * Show legend or not
423	 *
424	 * @param	int		$showlegend		1=Show legend (default), 0=Hide legend, 2=Show legend on right
425	 * @return	void
426	 */
427	public function setShowLegend($showlegend)
428	{
429		$this->showlegend = $showlegend;
430	}
431
432	/**
433	 * Show pointvalue or not
434	 *
435	 * @param	int		$showpointvalue		1=Show value for each point, as tooltip or inline (default), 0=Hide value
436	 * @return	void
437	 */
438	public function setShowPointValue($showpointvalue)
439	{
440		$this->showpointvalue = $showpointvalue;
441	}
442
443	/**
444	 * Show percent or not
445	 *
446	 * @param	int		$showpercent		1=Show percent for each point, as tooltip or inline, 0=Hide percent (default)
447	 * @return	void
448	 */
449	public function setShowPercent($showpercent)
450	{
451		$this->showpercent = $showpercent;
452	}
453
454
455
456	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
457	/**
458	 * Define background color of complete image
459	 *
460	 * @param	array	$bg_color		array(R,G,B) ou 'onglet' ou 'default'
461	 * @return	void
462	 */
463	public function SetBgColor($bg_color = array(255, 255, 255))
464	{
465		// phpcs:enable
466		global $theme_bgcolor, $theme_bgcoloronglet;
467
468		if (!is_array($bg_color)) {
469			if ($bg_color == 'onglet') {
470				//print 'ee'.join(',',$theme_bgcoloronglet);
471				$this->bgcolor = $theme_bgcoloronglet;
472			} else {
473				$this->bgcolor = $theme_bgcolor;
474			}
475		} else {
476			$this->bgcolor = $bg_color;
477		}
478	}
479
480	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
481	/**
482	 * Define background color of grid
483	 *
484	 * @param	array	$bg_colorgrid		array(R,G,B) ou 'onglet' ou 'default'
485	 * @return	void
486	 */
487	public function SetBgColorGrid($bg_colorgrid = array(255, 255, 255))
488	{
489		// phpcs:enable
490		global $theme_bgcolor, $theme_bgcoloronglet;
491
492		if (!is_array($bg_colorgrid)) {
493			if ($bg_colorgrid == 'onglet') {
494				//print 'ee'.join(',',$theme_bgcoloronglet);
495				$this->bgcolorgrid = $theme_bgcoloronglet;
496			} else {
497				$this->bgcolorgrid = $theme_bgcolor;
498			}
499		} else {
500			$this->bgcolorgrid = $bg_colorgrid;
501		}
502	}
503
504	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
505	/**
506	 * Reset data color
507	 *
508	 * @return	void
509	 */
510	public function ResetDataColor()
511	{
512		// phpcs:enable
513		unset($this->datacolor);
514	}
515
516	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
517	/**
518	 * Get max value
519	 *
520	 * @return	int		Max value
521	 */
522	public function GetMaxValueInData()
523	{
524		// phpcs:enable
525		if (!is_array($this->data)) return 0;
526
527		$k = 0;
528		$vals = array();
529
530		$nblines = count($this->data);
531		$nbvalues = (empty($this->data[0]) ? 0 : count($this->data[0]) - 1);
532
533		for ($j = 0; $j < $nblines; $j++) {
534			for ($i = 0; $i < $nbvalues; $i++) {
535				$vals[$k] = $this->data[$j][$i + 1];
536				$k++;
537			}
538		}
539		rsort($vals);
540		return $vals[0];
541	}
542
543	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
544	/**
545	 * Return min value of all data
546	 *
547	 * @return	int		Min value of all data
548	 */
549	public function GetMinValueInData()
550	{
551		// phpcs:enable
552		if (!is_array($this->data)) return 0;
553
554		$k = 0;
555		$vals = array();
556
557		$nblines = count($this->data);
558		$nbvalues = (empty($this->data[0]) ? 0 : count($this->data[0]) - 1);
559
560		for ($j = 0; $j < $nblines; $j++) {
561			for ($i = 0; $i < $nbvalues; $i++) {
562				$vals[$k] = $this->data[$j][$i + 1];
563				$k++;
564			}
565		}
566		sort($vals);
567		return $vals[0];
568	}
569
570	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
571	/**
572	 * Return max value of all data
573	 *
574	 * @return 	int		Max value of all data
575	 */
576	public function GetCeilMaxValue()
577	{
578		// phpcs:enable
579		$max = $this->GetMaxValueInData();
580		if ($max != 0) $max++;
581		$size = dol_strlen(abs(ceil($max)));
582		$factor = 1;
583		for ($i = 0; $i < ($size - 1); $i++) {
584			$factor *= 10;
585		}
586
587		$res = 0;
588		if (is_numeric($max)) $res = ceil($max / $factor) * $factor;
589
590		//print "max=".$max." res=".$res;
591		return $res;
592	}
593
594	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
595	/**
596	 * Return min value of all data
597	 *
598	 * @return 	double		Max value of all data
599	 */
600	public function GetFloorMinValue()
601	{
602		// phpcs:enable
603		$min = $this->GetMinValueInData();
604		if ($min == '') $min = 0;
605		if ($min != 0) $min--;
606		$size = dol_strlen(abs(floor($min)));
607		$factor = 1;
608		for ($i = 0; $i < ($size - 1); $i++) {
609			$factor *= 10;
610		}
611
612		$res = floor($min / $factor) * $factor;
613
614		//print "min=".$min." res=".$res;
615		return $res;
616	}
617
618	/**
619	 * Build a graph into memory using correct library  (may also be wrote on disk, depending on library used)
620	 *
621	 * @param	string	$file    	Image file name to use to save onto disk (also used as javascript unique id)
622	 * @param	string	$fileurl	Url path to show image if saved onto disk
623	 * @return	integer|null
624	 */
625	public function draw($file, $fileurl = '')
626	{
627		if (empty($file)) {
628			$this->error = "Call to draw method was made with empty value for parameter file.";
629			dol_syslog(get_class($this) . "::draw " . $this->error, LOG_ERR);
630			return -2;
631		}
632		if (!is_array($this->data)) {
633			$this->error = "Call to draw method was made but SetData was not called or called with an empty dataset for parameters";
634			dol_syslog(get_class($this) . "::draw " . $this->error, LOG_ERR);
635			return -1;
636		}
637		if (count($this->data) < 1) {
638			$this->error = "Call to draw method was made but SetData was is an empty dataset";
639			dol_syslog(get_class($this) . "::draw " . $this->error, LOG_WARNING);
640		}
641		$call = "draw_" . $this->_library;
642		call_user_func_array(array($this, $call), array($file, $fileurl));
643	}
644
645	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
646	/**
647	 * Build a graph using JFlot library. Input when calling this method should be:
648	 *	$this->data  = array(array(0=>'labelxA',1=>yA),  array('labelxB',yB));
649	 *	$this->data  = array(array(0=>'labelxA',1=>yA1,...,n=>yAn), array('labelxB',yB1,...yBn));   // or when there is n series to show for each x
650	 *  $this->data  = array(array('label'=>'labelxA','data'=>yA),  array('labelxB',yB));			// Syntax deprecated
651	 *  $this->legend= array("Val1",...,"Valn");													// list of n series name
652	 *  $this->type  = array('bars',...'lines','linesnopoint'); or array('pie') or array('polar')
653	 *  $this->mode = 'depth' ???
654	 *  $this->bgcolorgrid
655	 *  $this->datacolor
656	 *  $this->shownodatagraph
657	 *
658	 * @param	string	$file    	Image file name to use to save onto disk (also used as javascript unique id)
659	 * @param	string	$fileurl	Url path to show image if saved onto disk. Never used here.
660	 * @return	void
661	 */
662	private function draw_jflot($file, $fileurl)
663	{
664		// phpcs:enable
665		global $conf, $langs;
666
667		dol_syslog(get_class($this) . "::draw_jflot this->type=" . join(',', $this->type) . " this->MaxValue=" . $this->MaxValue);
668
669		if (empty($this->width) && empty($this->height)) {
670			print 'Error width or height not set';
671			return;
672		}
673
674		$legends = array();
675		$nblot = 0;
676		if (is_array($this->data) && is_array($this->data[0])) {
677			$nblot = count($this->data[0]) - 1; // -1 to remove legend
678		}
679		if ($nblot < 0) dol_syslog('Bad value for property ->data. Must be set by mydolgraph->SetData before calling mydolgrapgh->draw', LOG_WARNING);
680		$firstlot = 0;
681		// Works with line but not with bars
682		//if ($nblot > 2) $firstlot = ($nblot - 2);        // We limit nblot to 2 because jflot can't manage more than 2 bars on same x
683
684		$i = $firstlot;
685		$serie = array();
686		while ($i < $nblot)	// Loop on each serie
687		{
688			$values = array(); // Array with horizontal y values (specific values of a serie) for each abscisse x
689			$serie[$i] = "var d" . $i . " = [];\n";
690
691			// Fill array $values
692			$x = 0;
693			foreach ($this->data as $valarray)	// Loop on each x
694			{
695				$legends[$x] = $valarray[0];
696				$values[$x]  = (is_numeric($valarray[$i + 1]) ? $valarray[$i + 1] : null);
697				$x++;
698			}
699
700			if (isset($this->type[$firstlot]) && in_array($this->type[$firstlot], array('pie', 'piesemicircle', 'polar'))) {
701				foreach ($values as $x => $y) {
702					if (isset($y)) $serie[$i] .= 'd' . $i . '.push({"label":"' . dol_escape_js($legends[$x]) . '", "data":' . $y . '});' . "\n";
703				}
704			} else {
705				foreach ($values as $x => $y) {
706					if (isset($y)) $serie[$i] .= 'd' . $i . '.push([' . $x . ', ' . $y . ']);' . "\n";
707				}
708			}
709
710			unset($values);
711			$i++;
712		}
713		$tag = dol_escape_htmltag(dol_string_unaccent(dol_string_nospecial(basename($file), '_', array('-', '.'))));
714
715		$this->stringtoshow = '<!-- Build using jflot -->' . "\n";
716		if (!empty($this->title)) $this->stringtoshow .= '<div class="center dolgraphtitle' . (empty($this->cssprefix) ? '' : ' dolgraphtitle' . $this->cssprefix) . '">' . $this->title . '</div>';
717		if (!empty($this->shownographyet)) {
718			$this->stringtoshow .= '<div style="width:' . $this->width . 'px;height:' . $this->height . 'px;" class="nographyet"></div>';
719			$this->stringtoshow .= '<div class="nographyettext margintoponly">' . $langs->trans("NotEnoughDataYet") . '...</div>';
720			return;
721		}
722
723		// Start the div that will contains all the graph
724		$dolxaxisvertical = '';
725		if (count($this->data) > 20) $dolxaxisvertical = 'dol-xaxis-vertical';
726		$this->stringtoshow .= '<div id="placeholder_' . $tag . '" style="width:' . $this->width . 'px;height:' . $this->height . 'px;" class="dolgraph' . (empty($dolxaxisvertical) ? '' : ' ' . $dolxaxisvertical) . (empty($this->cssprefix) ? '' : ' dolgraph' . $this->cssprefix) . ' center"></div>' . "\n";
727
728		$this->stringtoshow .= '<script id="' . $tag . '">' . "\n";
729		$this->stringtoshow .= '$(function () {' . "\n";
730		$i = $firstlot;
731		if ($nblot < 0) {
732			$this->stringtoshow .= '<!-- No series of data -->' . "\n";
733		} else {
734			while ($i < $nblot) {
735				$this->stringtoshow .= '<!-- Serie ' . $i . ' -->' . "\n";
736				$this->stringtoshow .= $serie[$i] . "\n";
737				$i++;
738			}
739		}
740		$this->stringtoshow .= "\n";
741
742		// Special case for Graph of type 'pie'
743		if (isset($this->type[$firstlot]) && in_array($this->type[$firstlot], array('pie', 'piesemicircle', 'polar'))) {
744			$datacolor = array();
745			foreach ($this->datacolor as $val) {
746				if (is_array($val)) $datacolor[] = "#" . sprintf("%02x%02x%02x", $val[0], $val[1], $val[2]); // If datacolor is array(R, G, B)
747				else $datacolor[] = "#" . str_replace(array('#', '-'), '', $val); // If $val is '124' or '#124'
748			}
749
750			$urltemp = ''; // TODO Add support for url link into labels
751			$showlegend = $this->showlegend;
752			$showpointvalue = $this->showpointvalue;
753			$showpercent = $this->showpercent;
754
755			$this->stringtoshow .= '
756			function plotWithOptions_' . $tag . '() {
757			$.plot($("#placeholder_' . $tag . '"), d0,
758			{
759				series: {
760					pie: {
761						show: true,
762						radius: 0.8,
763						' . ($this->combine ? '
764						combine: {
765						 	threshold: ' . $this->combine . '
766						},' : '') . '
767						label: {
768							show: true,
769							radius: 0.9,
770							formatter: function(label, series) {
771								var percent=Math.round(series.percent);
772								var number=series.data[0][1];
773								return \'';
774			$this->stringtoshow .= '<span style="font-size:8pt;text-align:center;padding:2px;color:black;">';
775			if ($urltemp) $this->stringtoshow .= '<a style="color: #FFFFFF;" border="0" href="' . $urltemp . '">';
776			$this->stringtoshow .= '\'+';
777			$this->stringtoshow .= ($showlegend ? '' : 'label+\' \'+'); // Hide label if already shown in legend
778			$this->stringtoshow .= ($showpointvalue ? 'number+' : '');
779			$this->stringtoshow .= ($showpercent ? '\'<br/>\'+percent+\'%\'+' : '');
780			$this->stringtoshow .= '\'';
781			if ($urltemp) $this->stringtoshow .= '</a>';
782			$this->stringtoshow .= '</span>\';
783							},
784							background: {
785							opacity: 0.0,
786							color: \'#000000\'
787						}
788					}
789				}
790			},
791			zoom: {
792				interactive: true
793			},
794			pan: {
795				interactive: true
796			},';
797			if (count($datacolor)) {
798				$this->stringtoshow .= 'colors: ' . (!empty($data['seriescolor']) ? json_encode($data['seriescolor']) : json_encode($datacolor)) . ',';
799			}
800			$this->stringtoshow .= 'legend: {show: ' . ($showlegend ? 'true' : 'false') . ', position: \'ne\' }
801		});
802		}' . "\n";
803		}
804		// Other cases, graph of type 'bars', 'lines'
805		else {
806			// Add code to support tooltips
807			// TODO: remove js css and use graph-tooltip-inner class instead by adding css in each themes
808			$this->stringtoshow .= '
809			function showTooltip_' . $tag . '(x, y, contents) {
810				$(\'<div class="graph-tooltip-inner" id="tooltip_' . $tag . '">\' + contents + \'</div>\').css({
811					position: \'absolute\',
812					display: \'none\',
813					top: y + 10,
814					left: x + 15,
815					border: \'1px solid #000\',
816					padding: \'5px\',
817					\'background-color\': \'#000\',
818					\'color\': \'#fff\',
819					\'font-weight\': \'bold\',
820					width: 200,
821					opacity: 0.80
822				}).appendTo("body").fadeIn(100);
823			}
824
825			var previousPoint = null;
826			$("#placeholder_' . $tag . '").bind("plothover", function (event, pos, item) {
827				$("#x").text(pos.x.toFixed(2));
828				$("#y").text(pos.y.toFixed(2));
829
830				if (item) {
831					if (previousPoint != item.dataIndex) {
832						previousPoint = item.dataIndex;
833
834						$("#tooltip").remove();
835						/* console.log(item); */
836						var x = item.datapoint[0].toFixed(2);
837						var y = item.datapoint[1].toFixed(2);
838						var z = item.series.xaxis.ticks[item.dataIndex].label;
839						';
840			if ($this->showpointvalue > 0) $this->stringtoshow .= '
841							showTooltip_' . $tag . '(item.pageX, item.pageY, item.series.label + "<br>" + z + " => " + y);
842						';
843			$this->stringtoshow .= '
844					}
845				}
846				else {
847					$("#tooltip_' . $tag . '").remove();
848					previousPoint = null;
849				}
850			});
851			';
852
853			$this->stringtoshow .= 'var stack = null, steps = false;' . "\n";
854
855			$this->stringtoshow .= 'function plotWithOptions_' . $tag . '() {' . "\n";
856			$this->stringtoshow .= '$.plot($("#placeholder_' . $tag . '"), [ ' . "\n";
857			$i = $firstlot;
858			while ($i < $nblot) {
859				if ($i > $firstlot) $this->stringtoshow .= ', ' . "\n";
860				$color = sprintf("%02x%02x%02x", $this->datacolor[$i][0], $this->datacolor[$i][1], $this->datacolor[$i][2]);
861				$this->stringtoshow .= '{ ';
862				if (!isset($this->type[$i]) || $this->type[$i] == 'bars') {
863					if ($nblot == 3) {
864						if ($i == $firstlot) $align = 'right';
865						elseif ($i == $firstlot + 1) $align = 'center';
866						else $align = 'left';
867						$this->stringtoshow .= 'bars: { lineWidth: 1, show: true, align: "' . $align . '", barWidth: 0.45 }, ';
868					} else $this->stringtoshow .= 'bars: { lineWidth: 1, show: true, align: "' . ($i == $firstlot ? 'center' : 'left') . '", barWidth: 0.5 }, ';
869				}
870				if (isset($this->type[$i]) && ($this->type[$i] == 'lines' || $this->type[$i] == 'linesnopoint')) $this->stringtoshow .= 'lines: { show: true, fill: false }, points: { show: ' . ($this->type[$i] == 'linesnopoint' ? 'false' : 'true') . ' }, ';
871				$this->stringtoshow .= 'color: "#' . $color . '", label: "' . (isset($this->Legend[$i]) ? dol_escape_js($this->Legend[$i]) : '') . '", data: d' . $i . ' }';
872				$i++;
873			}
874			// shadowSize: 0 -> Drawing is faster without shadows
875			$this->stringtoshow .= "\n" . ' ], { series: { shadowSize: 0, stack: stack, lines: { fill: false, steps: steps }, bars: { barWidth: 0.6,  fillColor: { colors: [{opacity: 0.9 }, {opacity: 0.85}] }} }' . "\n";
876
877			// Xaxis
878			$this->stringtoshow .= ', xaxis: { ticks: [' . "\n";
879			$x = 0;
880			foreach ($this->data as $key => $valarray) {
881				if ($x > 0) $this->stringtoshow .= ', ' . "\n";
882				$this->stringtoshow .= ' [' . $x . ', "' . $valarray[0] . '"]';
883				$x++;
884			}
885			$this->stringtoshow .= '] }' . "\n";
886
887			// Yaxis
888			$this->stringtoshow .= ', yaxis: { min: ' . $this->MinValue . ', max: ' . ($this->MaxValue) . ' }' . "\n";
889
890			// Background color
891			$color1 = sprintf("%02x%02x%02x", $this->bgcolorgrid[0], $this->bgcolorgrid[0], $this->bgcolorgrid[2]);
892			$color2 = sprintf("%02x%02x%02x", $this->bgcolorgrid[0], $this->bgcolorgrid[1], $this->bgcolorgrid[2]);
893			$this->stringtoshow .= ', grid: { hoverable: true, backgroundColor: { colors: ["#' . $color1 . '", "#' . $color2 . '"] }, borderWidth: 1, borderColor: \'#e6e6e6\', tickColor  : \'#e6e6e6\' }' . "\n";
894			$this->stringtoshow .= '});' . "\n";
895			$this->stringtoshow .= '}' . "\n";
896		}
897
898		$this->stringtoshow .= 'plotWithOptions_' . $tag . '();' . "\n";
899		$this->stringtoshow .= '});' . "\n";
900		$this->stringtoshow .= '</script>' . "\n";
901	}
902
903
904	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
905	/**
906	 * Build a graph using Chart library. Input when calling this method should be:
907	 *	$this->data  = array(array(0=>'labelxA',1=>yA),  array('labelxB',yB));
908	 *	$this->data  = array(array(0=>'labelxA',1=>yA1,...,n=>yAn), array('labelxB',yB1,...yBn));   // or when there is n series to show for each x
909	 *  $this->data  = array(array('label'=>'labelxA','data'=>yA),  array('labelxB',yB));			// Syntax deprecated
910	 *  $this->legend= array("Val1",...,"Valn");													// list of n series name
911	 *  $this->type  = array('bars',...'lines', 'linesnopoint'); or array('pie') or array('polar') or array('piesemicircle');
912	 *  $this->mode = 'depth' ???
913	 *  $this->bgcolorgrid
914	 *  $this->datacolor
915	 *  $this->shownodatagraph
916	 *
917	 * @param	string	$file    	Image file name to use to save onto disk (also used as javascript unique id)
918	 * @param	string	$fileurl	Url path to show image if saved onto disk. Never used here.
919	 * @return	void
920	 */
921	private function draw_chart($file, $fileurl)
922	{
923		// phpcs:enable
924		global $conf, $langs;
925
926		dol_syslog(get_class($this) . "::draw_chart this->type=" . join(',', $this->type) . " this->MaxValue=" . $this->MaxValue);
927
928		if (empty($this->width) && empty($this->height)) {
929			print 'Error width or height not set';
930			return;
931		}
932
933		$showlegend = $this->showlegend;
934
935		$legends = array();
936		$nblot = 0;
937		if (is_array($this->data)) {
938			foreach ($this->data as $valarray)      // Loop on each x
939			{
940				$nblot = max($nblot, count($valarray) - 1); // -1 to remove legend
941			}
942		}
943		//var_dump($nblot);
944		if ($nblot < 0) dol_syslog('Bad value for property ->data. Must be set by mydolgraph->SetData before calling mydolgrapgh->draw', LOG_WARNING);
945		$firstlot = 0;
946		// Works with line but not with bars
947		//if ($nblot > 2) $firstlot = ($nblot - 2);        // We limit nblot to 2 because jflot can't manage more than 2 bars on same x
948
949		$serie = array();
950		$arrayofgroupslegend = array();
951		//var_dump($this->data);
952
953		$i = $firstlot;
954		while ($i < $nblot)	// Loop on each serie
955		{
956			$values = array(); // Array with horizontal y values (specific values of a serie) for each abscisse x (with x=0,1,2,...)
957			$serie[$i] = "";
958
959			// Fill array $values
960			$x = 0;
961			foreach ($this->data as $valarray)	// Loop on each x
962			{
963				$legends[$x] = (array_key_exists('label', $valarray) ? $valarray['label'] : $valarray[0]);
964				$array_of_ykeys = array_keys($valarray);
965				$alabelexists = 1;
966				$tmpykey = explode('_', ($array_of_ykeys[$i + ($alabelexists ? 1 : 0)]), 3);
967				if (!empty($tmpykey[2]) || $tmpykey[2] == '0') {		// This is a 'Group by' array
968					$tmpvalue = (array_key_exists('y_' . $tmpykey[1] . '_' . $tmpykey[2], $valarray) ? $valarray['y_' . $tmpykey[1] . '_' . $tmpykey[2]] : $valarray[$i + 1]);
969					$values[$x] = (is_numeric($tmpvalue) ? $tmpvalue : null);
970					$arrayofgroupslegend[$i] = array(
971						'stacknum' => $tmpykey[1],
972						'legend' => $this->Legend[$tmpykey[1]],
973						'legendwithgroup' => $this->Legend[$tmpykey[1]] . ' - ' . $tmpykey[2]
974					);
975				} else {
976					$tmpvalue = (array_key_exists('y_' . $i, $valarray) ? $valarray['y_' . $i] : $valarray[$i + 1]);
977					//var_dump($i.'_'.$x.'_'.$tmpvalue);
978					$values[$x] = (is_numeric($tmpvalue) ? $tmpvalue : null);
979				}
980				$x++;
981			}
982			//var_dump($values);
983			$j = 0;
984			foreach ($values as $x => $y) {
985				if (isset($y)) {
986					$serie[$i] .= ($j > 0 ? ", " : "") . $y;
987				} else {
988					$serie[$i] .= ($j > 0 ? ", " : "") . 'null';
989				}
990				$j++;
991			}
992
993			$values = null; // Free mem
994			$i++;
995		}
996		//var_dump($serie);
997		//var_dump($arrayofgroupslegend);
998
999		$tag = dol_escape_htmltag(dol_string_unaccent(dol_string_nospecial(basename($file), '_', array('-', '.'))));
1000
1001		$this->stringtoshow = '<!-- Build using chart -->' . "\n";
1002		if (!empty($this->title)) $this->stringtoshow .= '<div class="center dolgraphtitle' . (empty($this->cssprefix) ? '' : ' dolgraphtitle' . $this->cssprefix) . '">' . $this->title . '</div>';
1003		if (!empty($this->shownographyet)) {
1004			$this->stringtoshow .= '<div style="width:' . $this->width . (strpos($this->width, '%') > 0 ? '' : 'px') . '; height:' . $this->height . 'px;" class="nographyet"></div>';
1005			$this->stringtoshow .= '<div class="nographyettext margintoponly">' . $langs->trans("NotEnoughDataYet") . '...</div>';
1006			return;
1007		}
1008
1009		// Start the div that will contains all the graph
1010		$dolxaxisvertical = '';
1011		if (count($this->data) > 20) $dolxaxisvertical = 'dol-xaxis-vertical';
1012		// No height for the pie grah
1013		$cssfordiv = 'dolgraphchart';
1014		if (isset($this->type[$firstlot])) $cssfordiv .= ' dolgraphchar' . $this->type[$firstlot];
1015		$this->stringtoshow .= '<div id="placeholder_' . $tag . '" style="min-height: ' . $this->height . (strpos($this->height, '%') > 0 ? '' : 'px') . '; width:' . $this->width . (strpos($this->width, '%') > 0 ? '' : 'px') . ';" class="' . $cssfordiv . ' dolgraph' . (empty($dolxaxisvertical) ? '' : ' ' . $dolxaxisvertical) . (empty($this->cssprefix) ? '' : ' dolgraph' . $this->cssprefix) . ' center"><canvas id="canvas_' . $tag . '"></canvas></div>' . "\n";
1016
1017		$this->stringtoshow .= '<script id="' . $tag . '">' . "\n";
1018		$i = $firstlot;
1019		if ($nblot < 0) {
1020			$this->stringtoshow .= '<!-- No series of data -->';
1021		} else {
1022			while ($i < $nblot) {
1023				//$this->stringtoshow .= '<!-- Series '.$i.' -->'."\n";
1024				//$this->stringtoshow .= $serie[$i]."\n";
1025				$i++;
1026			}
1027		}
1028		$this->stringtoshow .= "\n";
1029
1030		// Special case for Graph of type 'pie', 'piesemicircle', or 'polar'
1031		if (isset($this->type[$firstlot]) && (in_array($this->type[$firstlot], array('pie', 'polar', 'piesemicircle')))) {
1032			$type = $this->type[$firstlot]; // pie or polar
1033			$this->stringtoshow .= 'var options = {' . "\n";
1034			$legendMaxLines = 0; // Does not work
1035			if (empty($showlegend)) {
1036				$this->stringtoshow .= 'legend: { display: false }, ';
1037			} else {
1038				$this->stringtoshow .= 'legend: { position: \'' . ($showlegend == 2 ? 'right' : 'top') . '\'';
1039				if (!empty($legendMaxLines)) {
1040					$this->stringtoshow .= ', maxLines: ' . $legendMaxLines . '';
1041				}
1042				$this->stringtoshow .= ' }, ' . "\n";
1043			}
1044
1045			if ($this->type[$firstlot] == 'piesemicircle') {
1046				$this->stringtoshow .= 'circumference: Math.PI,' . "\n";
1047				$this->stringtoshow .= 'rotation: -Math.PI,' . "\n";
1048			}
1049			$this->stringtoshow .= 'elements: { arc: {' . "\n";
1050			// Color of earch arc
1051			$this->stringtoshow .= 'backgroundColor: [';
1052			$i = 0;
1053			$foundnegativecolor = 0;
1054			foreach ($legends as $val)	// Loop on each serie
1055			{
1056				if ($i > 0) $this->stringtoshow .= ', ' . "\n";
1057				if (is_array($this->datacolor[$i])) $color = 'rgb(' . $this->datacolor[$i][0] . ', ' . $this->datacolor[$i][1] . ', ' . $this->datacolor[$i][2] . ')'; // If datacolor is array(R, G, B)
1058				else {
1059					$tmp = str_replace('#', '', $this->datacolor[$i]);
1060					if (strpos($tmp, '-') !== false) {
1061						$foundnegativecolor++;
1062						$color = '#FFFFFF'; // If $val is '-123'
1063					} else $color = "#" . $tmp; // If $val is '123' or '#123'
1064				}
1065				$this->stringtoshow .= "'" . $color . "'";
1066				$i++;
1067			}
1068			$this->stringtoshow .= '], ' . "\n";
1069			// Border color
1070			if ($foundnegativecolor) {
1071				$this->stringtoshow .= 'borderColor: [';
1072				$i = 0;
1073				foreach ($legends as $val)	// Loop on each serie
1074				{
1075					if ($i > 0) $this->stringtoshow .= ', ' . "\n";
1076					if (is_array($this->datacolor[$i])) $color = 'null'; // If datacolor is array(R, G, B)
1077					else {
1078						$tmp = str_replace('#', '', $this->datacolor[$i]);
1079						if (strpos($tmp, '-') !== false) $color = '#' . str_replace('-', '', $tmp); // If $val is '-123'
1080						else $color = 'null'; // If $val is '123' or '#123'
1081					}
1082					$this->stringtoshow .= ($color == 'null' ? "'rgba(0,0,0,0.2)'" : "'" . $color . "'");
1083					$i++;
1084				}
1085				$this->stringtoshow .= ']';
1086			}
1087			$this->stringtoshow .= '} } };' . "\n";
1088
1089			$this->stringtoshow .= '
1090				var ctx = document.getElementById("canvas_' . $tag . '").getContext("2d");
1091				var chart = new Chart(ctx, {
1092			    // The type of chart we want to create
1093    			type: \'' . (in_array($type, array('pie', 'piesemicircle')) ? 'doughnut' : 'polarArea') . '\',
1094				// Configuration options go here
1095    			options: options,
1096				data: {
1097					labels: [';
1098
1099			$i = 0;
1100			foreach ($legends as $val)	// Loop on each serie
1101			{
1102				if ($i > 0) $this->stringtoshow .= ', ';
1103				$this->stringtoshow .= "'" . dol_escape_js(dol_trunc($val, 32)) . "'";
1104				$i++;
1105			}
1106
1107			$this->stringtoshow .= '],
1108					datasets: [';
1109			$i = 0;
1110			$i = 0;
1111			while ($i < $nblot)	// Loop on each serie
1112			{
1113				$color = 'rgb(' . $this->datacolor[$i][0] . ', ' . $this->datacolor[$i][1] . ', ' . $this->datacolor[$i][2] . ')';
1114				//$color = (!empty($data['seriescolor']) ? json_encode($data['seriescolor']) : json_encode($datacolor));
1115
1116				if ($i > 0) $this->stringtoshow .= ', ' . "\n";
1117				$this->stringtoshow .= '{' . "\n";
1118				//$this->stringtoshow .= 'borderColor: \''.$color.'\', ';
1119				//$this->stringtoshow .= 'backgroundColor: \''.$color.'\', ';
1120				$this->stringtoshow .= '  data: [' . $serie[$i] . ']';
1121				$this->stringtoshow .= '}' . "\n";
1122				$i++;
1123			}
1124			$this->stringtoshow .= ']' . "\n";
1125			$this->stringtoshow .= '}' . "\n";
1126			$this->stringtoshow .= '});' . "\n";
1127		}
1128		// Other cases, graph of type 'bars', 'lines', 'linesnopoint'
1129		else {
1130			$type = 'bar';
1131
1132			$isfunnel = false;
1133			if ($file == 'idgraphleadfunnel') $isfunnel = true;
1134
1135			if (!isset($this->type[$firstlot]) || $this->type[$firstlot] == 'bars') $type = 'bar';
1136			if (isset($this->type[$firstlot]) && $this->type[$firstlot] == 'horizontalbars') $type = 'horizontalBar';
1137			if (isset($this->type[$firstlot]) && ($this->type[$firstlot] == 'lines' || $this->type[$firstlot] == 'linesnopoint')) $type = 'line';
1138
1139			$this->stringtoshow .= 'var options = { maintainAspectRatio: false, aspectRatio: 2.5, ';
1140			if (empty($showlegend)) {
1141				$this->stringtoshow .= 'legend: { display: false }, ';
1142			}
1143			$this->stringtoshow .= 'scales: { xAxes: [{ ';
1144			if ($isfunnel) {	// FIXME Remove isfunnel by introducing a method hideXValues() on dolgraph
1145				$this->stringtoshow .= ' ticks: { display: false }, display: true,';
1146			}
1147			//$this->stringtoshow .= 'type: \'time\', ';		// Need Moment.js
1148			$this->stringtoshow .= 'distribution: \'linear\'';
1149			if ($type == 'bar' && count($arrayofgroupslegend) > 0) {
1150				$this->stringtoshow .= ', stacked: true';
1151			}
1152			$this->stringtoshow .= ' }]';
1153			$this->stringtoshow .= ', yAxes: [{ ticks: { beginAtZero: true }';
1154			if ($type == 'bar' && count($arrayofgroupslegend) > 0) {
1155				$this->stringtoshow .= ', stacked: true';
1156			}
1157			$this->stringtoshow .= ' }] }';
1158			// Add a callback to change label to show only positive value
1159			if ($isfunnel) {
1160				$this->stringtoshow .= ', tooltips: { mode: \'nearest\',
1161					callbacks: {
1162						title: function(tooltipItem, data) {
1163							return data.datasets[tooltipItem[0].datasetIndex].label;
1164						},
1165						label: function(tooltipItem, data) {
1166							return data.datasets[tooltipItem.datasetIndex].data[0][1];
1167						}
1168					}
1169				},';
1170			}
1171			$this->stringtoshow .= '};';
1172			$this->stringtoshow .= '
1173				var ctx = document.getElementById("canvas_' . $tag . '").getContext("2d");
1174				var chart = new Chart(ctx, {
1175			    // The type of chart we want to create
1176    			type: \'' . $type . '\',
1177				// Configuration options go here
1178    			options: options,
1179				data: {
1180					labels: [';
1181
1182			$i = 0;
1183			if (!$isfunnel) {
1184				foreach ($legends as $val)	// Loop on each serie
1185					{
1186					if ($i > 0) $this->stringtoshow .= ', ';
1187					$this->stringtoshow .= "'".dol_escape_js(dol_trunc($val, 32))."'";
1188					$i++;
1189				}
1190			}
1191
1192			//var_dump($arrayofgroupslegend);
1193
1194			$this->stringtoshow .= '],
1195					datasets: [';
1196
1197			global $theme_datacolor;
1198			//var_dump($arrayofgroupslegend);
1199			$i = 0; $iinstack = 0;
1200			$oldstacknum = -1;
1201			while ($i < $nblot)	// Loop on each serie
1202			{
1203				$foundnegativecolor = 0;
1204				$usecolorvariantforgroupby = 0;
1205				// We used a 'group by' and we have too many colors so we generated color variants per
1206				if (is_array($arrayofgroupslegend[$i]) && count($arrayofgroupslegend[$i]) > 0) {	// If we used a group by.
1207					$nbofcolorneeds = count($arrayofgroupslegend);
1208					$nbofcolorsavailable = count($theme_datacolor);
1209					if ($nbofcolorneeds > $nbofcolorsavailable) {
1210						$usecolorvariantforgroupby = 1;
1211					}
1212
1213					$textoflegend = $arrayofgroupslegend[$i]['legendwithgroup'];
1214				} else {
1215					$textoflegend = $this->Legend[$i];
1216				}
1217
1218				if ($usecolorvariantforgroupby) {
1219					$newcolor = $this->datacolor[$arrayofgroupslegend[$i]['stacknum']];
1220					// If we change the stack
1221					if ($oldstacknum == -1 || $arrayofgroupslegend[$i]['stacknum'] != $oldstacknum) {
1222						$iinstack = 0;
1223					}
1224
1225					//var_dump($iinstack);
1226					if ($iinstack) {
1227						// Change color with offset of $$iinstack
1228						//var_dump($newcolor);
1229						if ($iinstack % 2) {	// We increase agressiveness of reference color for color 2, 4, 6, ...
1230							$ratio = min(95, 10 + 10 * $iinstack); // step of 20
1231							$brightnessratio = min(90, 5 + 5 * $iinstack); // step of 10
1232						} else {				// We decrease agressiveness of reference color for color 3, 5, 7, ..
1233							$ratio = max(-100, -15 * $iinstack + 10); // step of -20
1234							$brightnessratio = min(90, 10 * $iinstack); // step of 20
1235						}
1236						//var_dump('Color '.($iinstack+1).' : '.$ratio.' '.$brightnessratio);
1237
1238						$newcolor = array_values(colorHexToRgb(colorAgressiveness(colorArrayToHex($newcolor), $ratio, $brightnessratio), false, true));
1239					}
1240					$oldstacknum = $arrayofgroupslegend[$i]['stacknum'];
1241
1242					$color = 'rgb(' . $newcolor[0] . ', ' . $newcolor[1] . ', ' . $newcolor[2] . ', 0.9)';
1243					$bordercolor = 'rgb(' . $newcolor[0] . ', ' . $newcolor[1] . ', ' . $newcolor[2] . ')';
1244				} else { // We do not use a 'group by'
1245					if ($isfunnel) {
1246						$bordercolor == 'null';
1247						if (is_array($this->datacolor[$i])) {
1248							$color = 'rgb(' . $this->datacolor[$i][0] . ', ' . $this->datacolor[$i][1] . ', ' . $this->datacolor[$i][2] . ', 0.9)'; // If datacolor is array(R, G, B)
1249						} else {
1250							// TODO FIXME This logic must be in the caller that set $this->datacolor
1251							$tmp = str_replace('#', '', $this->datacolor[$i]);
1252							if (strpos($tmp, '-') !== false) {
1253								$foundnegativecolor++;
1254								$color = '#FFFFFF'; // If $val is '-123'
1255							} else {
1256								$color = "#" . $tmp; // If $val is '123' or '#123'
1257								$bordercolor = $color;
1258							}
1259							if ($foundnegativecolor) {
1260								if (is_array($this->datacolor[$i])) $color = 'null'; // If datacolor is array(R, G, B)
1261								else {
1262									$tmp = str_replace('#', '', $this->datacolor[$i]);
1263									if (strpos($tmp, '-') !== false) $bordercolor = '#' . str_replace('-', '', $tmp); // If $val is '-123'
1264									else $bordercolor = 'null'; // If $val is '123' or '#123'
1265								}
1266							}
1267						}
1268						$bordercolor == 'null' ? "'rgba(0,0,0,0.2)'" : "'" . $bordercolor . "'";
1269					} else {
1270						$color = 'rgb('.$this->datacolor[$i][0].', '.$this->datacolor[$i][1].', '.$this->datacolor[$i][2].', 0.9)';
1271						$bordercolor = $color;
1272						//$color = (!empty($data['seriescolor']) ? json_encode($data['seriescolor']) : json_encode($datacolor));
1273					}
1274				}
1275
1276				if ($i > 0) $this->stringtoshow .= ', ';
1277				$this->stringtoshow .= "\n";
1278				$this->stringtoshow .= '{';
1279				$this->stringtoshow .= 'dolibarrinfo: \'y_' . $i . '\', ';
1280				$this->stringtoshow .= 'label: \'' . dol_escape_js(dol_string_nohtmltag($textoflegend)) . '\', ';
1281				$this->stringtoshow .= 'pointStyle: \'' . ($this->type[$i] == 'linesnopoint' ? 'line' : 'circle') . '\', ';
1282				$this->stringtoshow .= 'fill: ' . ($type == 'bar' ? 'true' : 'false') . ', ';
1283				if ($isfunnel) {
1284					$this->stringtoshow .= 'borderWidth: \'2\', ';
1285				} elseif ($type == 'bar' || $type == 'horizontalBar') {
1286					$this->stringtoshow .= 'borderWidth: \'1\', ';
1287				}
1288				$this->stringtoshow .= 'borderColor: \'' . $bordercolor . '\', ';
1289				$this->stringtoshow .= 'backgroundColor: \'' . $color . '\', ';
1290				if ($arrayofgroupslegend[$i]) $this->stringtoshow .= 'stack: \'' . $arrayofgroupslegend[$i]['stacknum'] . '\', ';
1291				$this->stringtoshow .='data: [';
1292				if ($isfunnel) {
1293					$this->stringtoshow .= '['.-$serie[$i].','.$serie[$i].']';
1294				} else {
1295					$this->stringtoshow .= $serie[$i];
1296				}
1297				$this->stringtoshow .=']';
1298				$this->stringtoshow .= '}' . "\n";
1299
1300				$i++;
1301				$iinstack++;
1302			}
1303			$this->stringtoshow .= ']' . "\n";
1304			$this->stringtoshow .= '}' . "\n";
1305			$this->stringtoshow .= '});' . "\n";
1306		}
1307
1308		$this->stringtoshow .= '</script>' . "\n";
1309	}
1310
1311
1312	/**
1313	 * Output HTML string to total value
1314	 *
1315	 * @return	string							HTML string to total value
1316	 */
1317	public function total()
1318	{
1319		$value = 0;
1320		foreach ($this->data as $valarray)	// Loop on each x
1321			{
1322			$value += $valarray[1];
1323		}
1324		return $value;
1325	}
1326
1327	/**
1328	 * Output HTML string to show graph
1329	 *
1330	 * @param	int|string		$shownographyet    Show graph to say there is not enough data or the message in $shownographyet if it is a string.
1331	 * @return	string							   HTML string to show graph
1332	 */
1333	public function show($shownographyet = 0)
1334	{
1335		global $langs;
1336
1337		if ($shownographyet) {
1338			$s = '<div class="nographyet" style="width:' . (preg_match('/%/', $this->width) ? $this->width : $this->width . 'px') . '; height:' . (preg_match('/%/', $this->height) ? $this->height : $this->height . 'px') . ';"></div>';
1339			$s .= '<div class="nographyettext margintoponly">';
1340			if (is_numeric($shownographyet)) {
1341				$s .= $langs->trans("NotEnoughDataYet") . '...';
1342			} else {
1343				$s .= $shownographyet . '...';
1344			}
1345			$s .= '</div>';
1346			return $s;
1347		}
1348
1349		return $this->stringtoshow;
1350	}
1351
1352
1353	/**
1354	 * getDefaultGraphSizeForStats
1355	 *
1356	 * @param	string	$direction		'width' or 'height'
1357	 * @param	string	$defaultsize	Value we want as default size
1358	 * @return	int						Value of width or height to use by default
1359	 */
1360	public static function getDefaultGraphSizeForStats($direction, $defaultsize = '')
1361	{
1362		global $conf;
1363
1364		if ($direction == 'width')
1365		{
1366			if (empty($conf->dol_optimize_smallscreen)) return ($defaultsize ? $defaultsize : '500');
1367			else return (empty($_SESSION['dol_screen_width']) ? '280' : ($_SESSION['dol_screen_width'] - 40));
1368		}
1369		if ($direction == 'height')
1370		{
1371			return (empty($conf->dol_optimize_smallscreen) ? ($defaultsize ? $defaultsize : '200') : '160');
1372		}
1373		return 0;
1374	}
1375}
1376