Passed
Push — EXTRACT_CLASSES ( 0382f2...c25e41 )
by Rafael
52:18
created

DolGraph::draw_jflot()   F

Complexity

Conditions 51
Paths > 20000

Size

Total Lines 267
Code Lines 139

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 51
eloc 139
nc 3334501
nop 2
dl 0
loc 267
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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