Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

<?php
/*
pDraw - class to manipulate data arrays

Version     : 2.1.4
Made by     : Jean-Damien POGOLOTTI
Last Update : 19/01/2014

This file can be distributed under the license you can find at :

http://www.pchart.net/license

You can find the whole class documentation on the pChart web site.
*/
/* Axis configuration */
define("AXIS_FORMAT_DEFAULT", 680001);
define("AXIS_FORMAT_TIME", 680002);
define("AXIS_FORMAT_DATE", 680003);
define("AXIS_FORMAT_METRIC", 680004);
define("AXIS_FORMAT_CURRENCY", 680005);
define("AXIS_FORMAT_TRAFFIC", 680006);
define("AXIS_FORMAT_CUSTOM", 680007);
/* Axis position */
define("AXIS_POSITION_LEFT", 681001);
define("AXIS_POSITION_RIGHT", 681002);
define("AXIS_POSITION_TOP", 681001);
define("AXIS_POSITION_BOTTOM", 681002);
/* Families of data points */
define("SERIE_SHAPE_FILLEDCIRCLE", 681011);
define("SERIE_SHAPE_FILLEDTRIANGLE", 681012);
define("SERIE_SHAPE_FILLEDSQUARE", 681013);
define("SERIE_SHAPE_FILLEDDIAMOND", 681017);
define("SERIE_SHAPE_CIRCLE", 681014);
define("SERIE_SHAPE_TRIANGLE", 681015);
define("SERIE_SHAPE_SQUARE", 681016);
define("SERIE_SHAPE_DIAMOND", 681018);
/* Axis position */
define("AXIS_X", 682001);
define("AXIS_Y", 682002);
/* Define value limits */
define("ABSOLUTE_MIN", -10000000000000);
define("ABSOLUTE_MAX", 10000000000000);
/* Replacement to the PHP NULL keyword */
define("VOID", 0.123456789);
/* Euro symbol for GD fonts */
define("EURO_SYMBOL", utf8_encode("&#8364;"));

/* pData class definition */
class pData
{
        var $Data;
        var $Palette = [
                "0" => ["R" => 188,"G" => 224,"B" => 46,"Alpha" => 100],
                "1" => ["R" => 224,"G" => 100,"B" => 46,"Alpha" => 100],
                "2" => ["R" => 224,"G" => 214,"B" => 46,"Alpha" => 100],
                "3" => ["R" => 46,"G" => 151,"B" => 224,"Alpha" => 100],
                "4" => ["R" => 176,"G" => 46,"B" => 224,"Alpha" => 100],
                "5" => ["R" => 224,"G" => 46,"B" => 117,"Alpha" => 100],
                "6" => ["R" => 92,"G" => 224,"B" => 46, "Alpha" => 100],
                "7" => ["R" => 224,"G" => 176,"B" => 46,"Alpha" => 100]
        ];
        /* Class creator */
        function __construct()
        {
                $this->Data = [
                        "XAxisDisplay" => AXIS_FORMAT_DEFAULT,
                        "XAxisFormat" => NULL,
                        "XAxisName" => NULL,
                        "XAxisUnit" => NULL,
                        "Abscissa" => NULL,
                        "AbsicssaPosition" => AXIS_POSITION_BOTTOM,
                        "Axis" => [0 => [
                                        "Display" => AXIS_FORMAT_DEFAULT,
                                        "Position" => AXIS_POSITION_LEFT,
                                        "Identity" => AXIS_Y
                                ]
                        ]
                ];
        }

        /* Add a single point or an array to the given serie */
        function addPoints($Values, $SerieName = "Serie1")
        {
                if (!isset($this->Data["Series"][$SerieName])){
                        $this->initialise($SerieName);
                }
                
                if (is_array($Values)) {
                        foreach($Values as $Key => $Value) {
                                $this->Data["Series"][$SerieName]["Data"][] = $Value;
                        }
                } else {
                        $this->Data["Series"][$SerieName]["Data"][] = $Values;
                }
                
                if ($Values != VOID) {
                        $StrippedData = $this->stripVOID($this->Data["Series"][$SerieName]["Data"]);
                        if (empty($StrippedData)) {
                                $this->Data["Series"][$SerieName]["Max"] = 0;
                                $this->Data["Series"][$SerieName]["Min"] = 0;
                        } else {
                                $this->Data["Series"][$SerieName]["Max"] = max($StrippedData);
                                $this->Data["Series"][$SerieName]["Min"] = min($StrippedData);
                        }
                }
        }

        /* Strip VOID values */
        function stripVOID($Values)
        {
                if (!is_array($Values)) {
                        return [];
                }

                $Result = [];
                foreach($Values as $Key => $Value) {
                        ($Value != VOID) AND $Result[] = $Value;
                }

                return $Result;
        }

        /* Return the number of values contained in a given serie */
        function getSerieCount($Serie)
        {
                return (isset($this->Data["Series"][$Serie]["Data"])) ? count($this->Data["Series"][$Serie]["Data"]) : 0;
        }

        /* Remove a serie from the pData object */
        function removeSerie($Series)
        {

                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                unset($this->Data["Series"][$Serie]);
                        }
                }
        }

        /* Return a value from given serie & index */
        function getValueAt($Serie, $Index = 0)
        {
                return (isset($this->Data["Series"][$Serie]["Data"][$Index])) ? $this->Data["Series"][$Serie]["Data"][$Index] : NULL;
        }

        /* Return the values array */
        function getValues($Serie)
        {
                return (isset($this->Data["Series"][$Serie]["Data"])) ? $this->Data["Series"][$Serie]["Data"] : NULL;
        }

        /* Reverse the values in the given serie */
        function reverseSerie($Series)
        {

                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie]["Data"])) {
                                $this->Data["Series"][$Serie]["Data"] = array_reverse($this->Data["Series"][$Serie]["Data"]);
                        }
                }
        }

        /* Return the sum of the serie values */
        function getSum($Serie)
        {
                return (isset($this->Data["Series"][$Serie])) ? array_sum($this->Data["Series"][$Serie]["Data"]) : NULL;
        }

        /* Return the max value of a given serie */
        function getMax($Serie)
        {
                return (isset($this->Data["Series"][$Serie]["Max"])) ? $this->Data["Series"][$Serie]["Max"] : NULL;
        }

        /* Return the min value of a given serie */
        function getMin($Serie)
        {
                return (isset($this->Data["Series"][$Serie]["Min"])) ? $this->Data["Series"][$Serie]["Min"] : NULL;
        }

        /* Set the description of a given serie */
        function setSerieShape($Series, $Shape = SERIE_SHAPE_FILLEDCIRCLE)
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["Shape"] = $Shape;
                        }
                }
        }

        /* Set the description of a given serie */
        function setSerieDescription($Series, $Description = "My serie")
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["Description"] = $Description;
                        }
                }
        }

        /* Set a serie as "drawable" while calling a rendering function */
        function setSerieDrawable($Series, $Drawable = TRUE)
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["isDrawable"] = $Drawable;
                        }
                }
        }

        /* Set the icon associated to a given serie */
        function setSeriePicture($Series, $Picture = NULL)
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["Picture"] = $Picture;
                        }
                }
        }

        /* Set the name of the X Axis */
        function setXAxisName($Name)
        {
                $this->Data["XAxisName"] = $Name;
        }

        /* Set the display mode of the  X Axis */
        function setXAxisDisplay($Mode, $Format = NULL)
        {
                $this->Data["XAxisDisplay"] = $Mode;
                $this->Data["XAxisFormat"] = $Format;
        }

        /* Set the unit that will be displayed on the X axis */
        function setXAxisUnit($Unit)
        {
                $this->Data["XAxisUnit"] = $Unit;
        }

        /* Set the serie that will be used as abscissa */
        function setAbscissa($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $this->Data["Abscissa"] = $Serie;
                }
        }

        function setAbsicssaPosition($Position = AXIS_POSITION_BOTTOM)
        {
                $this->Data["AbsicssaPosition"] = $Position;
        }

        /* Set the name of the abscissa axis */
        function setAbscissaName($Name)
        {
                $this->Data["AbscissaName"] = $Name;
        }

        /* Create a scatter group specifyin X and Y data series */
        function setScatterSerie($SerieX, $SerieY, $ID = 0)
        {
                if (isset($this->Data["Series"][$SerieX]) && isset($this->Data["Series"][$SerieY])) {
                        $this->initScatterSerie($ID);
                        $this->Data["ScatterSeries"][$ID]["X"] = $SerieX;
                        $this->Data["ScatterSeries"][$ID]["Y"] = $SerieY;
                }
        }

        /* Set the shape of a given sctatter serie */
        function setScatterSerieShape($ID, $Shape = SERIE_SHAPE_FILLEDCIRCLE)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Shape"] = $Shape;
                }
        }

        /* Set the description of a given scatter serie */
        function setScatterSerieDescription($ID, $Description = "My serie")
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Description"] = $Description;
                }
        }

        /* Set the icon associated to a given scatter serie */
        function setScatterSeriePicture($ID, $Picture = NULL)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Picture"] = $Picture;
                }
        }

        /* Set a scatter serie as "drawable" while calling a rendering function */
        function setScatterSerieDrawable($ID, $Drawable = TRUE)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["isDrawable"] = $Drawable;
                }
        }

        /* Define if a scatter serie should be draw with ticks */
        function setScatterSerieTicks($ID, $Width = 0)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Ticks"] = $Width;
                }
        }

        /* Define if a scatter serie should be draw with a special weight */
        function setScatterSerieWeight($ID, $Weight = 0)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Weight"] = $Weight;
                }
        }

        /* Associate a color to a scatter serie */
        function setScatterSerieColor($ID, array $Format)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        $this->Data["ScatterSeries"][$ID]["Color"] = [
                                "R" => isset($Format["R"]) ? $Format["R"] : 0,
                                "G" => isset($Format["G"]) ? $Format["G"] : 0,
                                "B" => isset($Format["B"]) ? $Format["B"] : 0,
                                "Alpha" => isset($Format["Alpha"]) ? $Format["Alpha"] : 100
                        ];
                }
        }

        /* Compute the series limits for an individual and global point of view */
        function limits()
        {
                $GlobalMin = ABSOLUTE_MAX;
                $GlobalMax = ABSOLUTE_MIN;
                foreach($this->Data["Series"] as $Key => $Value) {
                        if ($this->Data["Abscissa"] != $Key && $this->Data["Series"][$Key]["isDrawable"] == TRUE) {
                                if ($GlobalMin > $this->Data["Series"][$Key]["Min"]) {
                                        $GlobalMin = $this->Data["Series"][$Key]["Min"];
                                }

                                if ($GlobalMax < $this->Data["Series"][$Key]["Max"]) {
                                        $GlobalMax = $this->Data["Series"][$Key]["Max"];
                                }
                        }
                }

                $this->Data["Min"] = $GlobalMin;
                $this->Data["Max"] = $GlobalMax;
                return [$GlobalMin,$GlobalMax];
        }

        /* Mark all series as drawable */
        function drawAll()
        {
                foreach($this->Data["Series"] as $Key => $Value) {
                        if ($this->Data["Abscissa"] != $Key) {
                                $this->Data["Series"][$Key]["isDrawable"] = TRUE;
                        }
                }
        }

        /* Return the average value of the given serie */
        function getSerieAverage($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
                        return (array_sum($SerieData) / count($SerieData));
                } else {
                        return NULL;
                }
        }

        /* Return the geometric mean of the given serie */
        function getGeometricMean($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
                        $Seriesum = 1;
                        foreach($SerieData as $Key => $Value) {
                                $Seriesum = $Seriesum * $Value;
                        }

                        return (pow($Seriesum, 1 / count($SerieData)));
                } else {
                        return NULL;
                }
        }

        /* Return the harmonic mean of the given serie */
        function getHarmonicMean($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
                        $Seriesum = 0;
                        foreach($SerieData as $Key => $Value) {
                                $Seriesum = $Seriesum + 1 / $Value;
                        }

                        return (count($SerieData) / $Seriesum);
                } else {
                        return NULL;
                }
        }

        /* Return the standard deviation of the given serie */
        function getStandardDeviation($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $Average = $this->getSerieAverage($Serie);
                        $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
                        $DeviationSum = 0;
                        foreach($SerieData as $Key => $Value) {
                                $DeviationSum = $DeviationSum + ($Value - $Average) * ($Value - $Average);
                        }

                        return sqrt($DeviationSum / count($SerieData));
                } else {
                        return NULL;
                }
        }

        /* Return the Coefficient of variation of the given serie */
        function getCoefficientOfVariation($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $Average = $this->getSerieAverage($Serie);
                        $StandardDeviation = $this->getStandardDeviation($Serie);
                        return ($StandardDeviation != 0) ? ($StandardDeviation / $Average) : NULL;
                } else {
                        return NULL;
                }
        }

        /* Return the median value of the given serie */
        function getSerieMedian($Serie)
        {
                if (isset($this->Data["Series"][$Serie])) {
                        $SerieData = $this->stripVOID($this->Data["Series"][$Serie]["Data"]);
                        sort($SerieData);
                        $SerieCenter = floor(count($SerieData) / 2);
                        return (isset($SerieData[$SerieCenter])) ? $SerieData[$SerieCenter] : NULL;
                } else {
                        return NULL;
                }
        }

        /* Return the x th percentil of the given serie */
        function getSeriePercentile($Serie = "Serie1", $Percentil = 95)
        {
                if (!isset($this->Data["Series"][$Serie]["Data"])) {
                        return NULL;
                }

                $Values = count($this->Data["Series"][$Serie]["Data"]) - 1;
                ($Values < 0) AND $Values = 0;
                $PercentilID = floor(($Values / 100) * $Percentil + .5);
                $SortedValues = $this->Data["Series"][$Serie]["Data"];
                sort($SortedValues);
                return (is_numeric($SortedValues[$PercentilID])) ? $SortedValues[$PercentilID] : NULL;
        }

        /* Add random values to a given serie */
        function addRandomValues($SerieName = "Serie1", array $Options = [])
        {
                $Values = isset($Options["Values"]) ? $Options["Values"] : 20;
                $Min = isset($Options["Min"]) ? $Options["Min"] : 0;
                $Max = isset($Options["Max"]) ? $Options["Max"] : 100;
                $withFloat = isset($Options["withFloat"]) ? $Options["withFloat"] : FALSE;
                for ($i = 0; $i <= $Values; $i++) {
                        $Value = ($withFloat) ? (rand($Min * 100, $Max * 100) / 100) : rand($Min, $Max);
                        $this->addPoints($Value, $SerieName);
                }
        }

        /* Test if we have valid data */
        function containsData()
        {
                if (!isset($this->Data["Series"])) {
                        return FALSE;
                }

                foreach($this->Data["Series"] as $Key => $Value) {
                        if ($this->Data["Abscissa"] != $Key && $this->Data["Series"][$Key]["isDrawable"] == TRUE) {
                                return TRUE;
                        }
                }

                return FALSE;
        }

        /* Set the display mode of an Axis */
        function setAxisDisplay($AxisID, $Mode = AXIS_FORMAT_DEFAULT, $Format = NULL)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Display"] = $Mode;
                        if ($Format != NULL) {
                                $this->Data["Axis"][$AxisID]["Format"] = $Format;
                        }
                }
        }

        /* Set the position of an Axis */
        function setAxisPosition($AxisID, $Position = AXIS_POSITION_LEFT)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Position"] = $Position;
                }
        }

        /* Associate an unit to an axis */
        function setAxisUnit($AxisID, $Unit)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Unit"] = $Unit;
                }
        }

        /* Associate a name to an axis */
        function setAxisName($AxisID, $Name)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Name"] = $Name;
                }
        }

        /* Associate a color to an axis */
        function setAxisColor($AxisID, array $Format)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Color"] = [
                                "R" => isset($Format["R"]) ? $Format["R"] : 0,
                                "G" => isset($Format["G"]) ? $Format["G"] : 0,
                                "B" => isset($Format["B"]) ? $Format["B"] : 0,
                                "Alpha" => isset($Format["Alpha"]) ? $Format["Alpha"] : 100
                        ];
                }
        }

        /* Design an axis as X or Y member */
        function setAxisXY($AxisID, $Identity = AXIS_Y)
        {
                if (isset($this->Data["Axis"][$AxisID])) {
                        $this->Data["Axis"][$AxisID]["Identity"] = $Identity;
                }
        }

        /* Associate one data serie with one axis */
        function setSerieOnAxis($Series, $AxisID)
        {
                $Series = $this->convertToArray($Series);
                
                foreach($Series as $Key => $Serie) {
                        $PreviousAxis = $this->Data["Series"][$Serie]["Axis"];
                        /* Create missing axis */
                        if (!isset($this->Data["Axis"][$AxisID])) {
                                $this->Data["Axis"][$AxisID]["Position"] = AXIS_POSITION_LEFT;
                                $this->Data["Axis"][$AxisID]["Identity"] = AXIS_Y;
                        }

                        $this->Data["Series"][$Serie]["Axis"] = $AxisID;
                        /* Cleanup unused axis */
                        $Found = FALSE;
                        foreach($this->Data["Series"] as $SerieName => $Values) {
                                if ($Values["Axis"] == $PreviousAxis) {
                                        $Found = TRUE;
                                }
                        }

                        if (!$Found) {
                                unset($this->Data["Axis"][$PreviousAxis]);
                        }
                }
        }

        /* Define if a serie should be draw with ticks */
        function setSerieTicks($Series, $Width = 0)
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["Ticks"] = $Width;
                        }
                }
        }

        /* Define if a serie should be draw with a special weight */
        function setSerieWeight($Series, $Weight = 0)
        {
                $Series = $this->convertToArray($Series);
        
                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $this->Data["Series"][$Serie]["Weight"] = $Weight;
                        }
                }
        }

        /* Returns the palette of the given serie */
        function getSeriePalette($Serie)
        {
                if (!isset($this->Data["Series"][$Serie])) {
                        return NULL;
                } else {
                        return [
                                "R" => $this->Data["Series"][$Serie]["Color"]["R"],
                                "G" => $this->Data["Series"][$Serie]["Color"]["G"],
                                "B" => $this->Data["Series"][$Serie]["Color"]["B"],
                                "Alpha" => $this->Data["Series"][$Serie]["Color"]["Alpha"]
                        ];
                }
                
        }

        /* Set the color of one serie */
        function setPalette($Series, array $Format = [])
        {
                $Series = $this->convertToArray($Series);
                $R = isset($Format["R"]) ? $Format["R"] : 0;
                $G = isset($Format["G"]) ? $Format["G"] : 0;
                $B = isset($Format["B"]) ? $Format["B"] : 0;
                $Alpha = isset($Format["Alpha"]) ? $Format["Alpha"] : 100;
                        
                foreach($Series as $Key => $Serie) {
                        if (isset($this->Data["Series"][$Serie])) {
                                $OldR = $this->Data["Series"][$Serie]["Color"]["R"];
                                $OldG = $this->Data["Series"][$Serie]["Color"]["G"];
                                $OldB = $this->Data["Series"][$Serie]["Color"]["B"];
                                $this->Data["Series"][$Serie]["Color"]["R"] = $R;
                                $this->Data["Series"][$Serie]["Color"]["G"] = $G;
                                $this->Data["Series"][$Serie]["Color"]["B"] = $B;
                                $this->Data["Series"][$Serie]["Color"]["Alpha"] = $Alpha;
                                /* Do reverse processing on the internal palette array */
                                foreach($this->Palette as $Key => $Value) {
                                        if ($Value["R"] == $OldR && $Value["G"] == $OldG && $Value["B"] == $OldB) {
                                                $this->Palette[$Key]["R"] = $R;
                                                $this->Palette[$Key]["G"] = $G;
                                                $this->Palette[$Key]["B"] = $B;
                                                $this->Palette[$Key]["Alpha"] = $Alpha;
                                        }
                                }
                        }
                }
        }

        /* Load a palette file */
        function loadPalette($FileName, $Overwrite = FALSE)
        {
                if (!file_exists($FileName)) {
                        die("Palette not found");
                }

                $buffer = file_get_contents($FileName);
                if ($buffer === false) {
                        die("Invalid palette");
                }

                if ($Overwrite) {
                        $this->Palette = [];
                }

                $lines = explode("\n", $buffer);
                $ID = 0;

                foreach($lines as $line){
                        $pal = explode(",", $line);
                        if (count($pal) == 4) {
                                list($R, $G, $B, $Alpha) = $pal;
                                $this->Palette[$ID] = ["R" => intval($R),"G" => intval($G),"B" => intval($B),"Alpha" => intval($Alpha)];
                                $ID++;
                        }
                }

                /* Apply changes to current series */
                $ID = 0;
                if (isset($this->Data["Series"])) {
                        foreach($this->Data["Series"] as $Key => $Value) {
                                $this->Data["Series"][$Key]["Color"] = (!isset($this->Palette[$ID])) ? ["R" => 0,"G" => 0,"B" => 0,"Alpha" => 0] : $this->Palette[$ID];
                                $ID++;
                        }
                }

        }

        /* Initialise a given scatter serie */
        function initScatterSerie($ID)
        {
                if (isset($this->Data["ScatterSeries"][$ID])) {
                        return 0;
                }

                $this->Data["ScatterSeries"][$ID] = [
                        "Description" => "Scatter " . $ID,
                        "isDrawable" => TRUE,
                        "Picture" => NULL,
                        "Ticks" => 0,
                        "Weight" => 0,
                        "Color" => (isset($this->Palette[$ID])) ? $this->Palette[$ID] : ["R" => rand(0, 255), "B" => rand(0, 255), "G" => rand(0, 255), "Alpha" => 100]
                ];
        }

        /* Initialise a given serie */
        function initialise($Serie)
        {
                $ID = (isset($this->Data["Series"])) ? count($this->Data["Series"]) : 0;
                
                $this->Data["Series"][$Serie] = [
                        "Description" => $Serie,
                        "isDrawable" => TRUE,
                        "Picture" => NULL,
                        "Max" => NULL,
                        "Min" => NULL,
                        "Axis" => 0,
                        "Ticks" => 0,
                        "Weight" => 0,
                        "Shape" => SERIE_SHAPE_FILLEDCIRCLE,
                        "Color" => (isset($this->Palette[$ID])) ? $this->Palette[$ID] : ["R" => rand(0, 255), "B" => rand(0, 255), "G" => rand(0, 255), "Alpha" => 100]
                ];
        }

        function normalize($NormalizationFactor = 100, $UnitChange = NULL, $Round = 1)
        {
                $Abscissa = $this->Data["Abscissa"];
                $SelectedSeries = [];
                $MaxVal = 0;
                foreach($this->Data["Axis"] as $AxisID => $Axis) {
                        
                        ($UnitChange != NULL) AND $this->Data["Axis"][$AxisID]["Unit"] = $UnitChange;
                
                        foreach($this->Data["Series"] as $SerieName => $Serie) {
                                if ($Serie["Axis"] == $AxisID && $Serie["isDrawable"] == TRUE && $SerieName != $Abscissa) {
                                        $SelectedSeries[$SerieName] = $SerieName;
                                        if (count($Serie["Data"]) > $MaxVal) {
                                                $MaxVal = count($Serie["Data"]);
                                        }
                                }
                        }
                }

                for ($i = 0; $i <= $MaxVal - 1; $i++) {
                        $Factor = 0;
                        foreach($SelectedSeries as $Key => $SerieName) {
                                $Value = $this->Data["Series"][$SerieName]["Data"][$i];
                                ($Value != VOID) AND $Factor = $Factor + abs($Value);
                        }

                        if ($Factor != 0) {
                                $Factor = $NormalizationFactor / $Factor;
                                foreach($SelectedSeries as $Key => $SerieName) {
                                        $Value = $this->Data["Series"][$SerieName]["Data"][$i];
                                        if ($Value != VOID && $Factor != $NormalizationFactor) {
                                                $this->Data["Series"][$SerieName]["Data"][$i] = round(abs($Value) * $Factor, $Round);
                                        } elseif ($Value == VOID || $Value == 0) {
                                                $this->Data["Series"][$SerieName]["Data"][$i] = VOID;
                                        } elseif ($Factor == $NormalizationFactor) {
                                                $this->Data["Series"][$SerieName]["Data"][$i] = $NormalizationFactor;
                                        }
                                }
                        }
                }

                foreach($SelectedSeries as $Key => $SerieName) {
                        $data = $this->stripVOID($this->Data["Series"][$SerieName]["Data"]);
                        $this->Data["Series"][$SerieName]["Max"] = max($data);
                        $this->Data["Series"][$SerieName]["Min"] = min($data);
                }
        }

        /* Load data from a CSV (or similar) data source */
        function importFromCSV($FileName, array $Options = [])
        {
                $Delimiter = isset($Options["Delimiter"]) ? $Options["Delimiter"] : ",";
                $GotHeader = isset($Options["GotHeader"]) ? $Options["GotHeader"] : FALSE;
                $SkipColumns = isset($Options["SkipColumns"]) ? $Options["SkipColumns"] : [-1];
                $DefaultSerieName = isset($Options["DefaultSerieName"]) ? $Options["DefaultSerieName"] : "Serie";
                $Handle = fopen($FileName, "r");
                if ($Handle) {
                        $HeaderParsed = FALSE;
                        $SerieNames = [];
                        while (!feof($Handle)) {
                                $Buffer = fgets($Handle, 4096);
                                $Buffer = str_replace([chr(10),chr(13)], ["",""], $Buffer); # TODO consider stream_get_line
                                $Values = preg_split("/" . $Delimiter . "/", $Buffer); #TODO consider explode
                                if ($Buffer != "") {
                                        if ($GotHeader && !$HeaderParsed) {
                                                foreach($Values as $Key => $Name) {
                                                        (!in_array($Key, $SkipColumns)) AND $SerieNames[$Key] = $Name;
                                                }

                                                $HeaderParsed = TRUE;
                                        } else {
                                                if (count($SerieNames) == 0) {
                                                        foreach($Values as $Key => $Name) {
                                                                (!in_array($Key, $SkipColumns)) AND $SerieNames[$Key] = $DefaultSerieName . $Key;
                                                        }
                                                }

                                                foreach($Values as $Key => $Value) {
                                                        (!in_array($Key, $SkipColumns)) AND $this->addPoints($Value, $SerieNames[$Key]);
                                                }
                                        } 
                                } # $Buffer != ""
                        } # while

                        fclose($Handle);
                }
        }

        /* Create a dataset based on a formula */
        function createFunctionSerie($SerieName, $Formula = "", array $Options = [])
        {
                
                if ($Formula == "") {
                        return 0;
                }
                
                $MinX = isset($Options["MinX"]) ? $Options["MinX"] : -10;
                $MaxX = isset($Options["MaxX"]) ? $Options["MaxX"] : 10;
                $XStep = isset($Options["XStep"]) ? $Options["XStep"] : 1;
                $AutoDescription = isset($Options["AutoDescription"]) ? $Options["AutoDescription"] : FALSE;
                $RecordAbscissa = isset($Options["RecordAbscissa"]) ? $Options["RecordAbscissa"] : FALSE;
                $AbscissaSerie = isset($Options["AbscissaSerie"]) ? $Options["AbscissaSerie"] : "Abscissa";
                
                $Result = [];
                $Abscissa = [];

                for ($i = $MinX; $i <= $MaxX; $i = $i + $XStep) {
                                                        
                        $Expression = "\$return = '!'.(" . str_replace("z", $i, $Formula) . ");";

                        if (substr($Expression, -4, 4) == "/0);"){ # Division by zero in ..\pData.class.php(849) : eval()'d code on line 1
                                $return = VOID;
                        } else {
                                if (eval($Expression) === FALSE) {
                                        $return = VOID;
                                }

                                $return = ($return == "!") ? VOID : $this->right($return, strlen($return) - 1);
                                
                                if (in_array($return, ["NAN", "INF", "-INF"])){
                                        $return = VOID;
                                }
                        }
                                
                        $Abscissa[] = $i;
                        $Result[] = $return;
                }

                $this->addPoints($Result, $SerieName);
                if ($AutoDescription) {
                        $this->setSerieDescription($SerieName, $Formula);
                }

                if ($RecordAbscissa) {
                        $this->addPoints($Abscissa, $AbscissaSerie);
                }

        }

        function negateValues($Series)
        {
                $Series = $this->convertToArray($Series);

                foreach($Series as $Key => $SerieName) {
                        if (isset($this->Data["Series"][$SerieName])) {
                                $Data = [];
                                foreach($this->Data["Series"][$SerieName]["Data"] as $Key => $Value) {
                                        $Data[] = ($Value == VOID) ? VOID : - $Value;
                                }

                                $this->Data["Series"][$SerieName]["Data"] = $Data;
                                $Data = $this->stripVOID($Data);
                                $this->Data["Series"][$SerieName]["Max"] = max($Data);
                                $this->Data["Series"][$SerieName]["Min"] = min($Data);
                        }
                }
        }

        /* Return the data & configuration of the series */
        function getData()
        {
                return $this->Data;
        }

        /* Save a palette element */
        function savePalette($ID, $Color)
        {
                $this->Palette[$ID] = $Color;
        }

        /* Return the palette of the series */
        function getPalette()
        {
                return $this->Palette;
        }

        /* Called by the scaling algorithm to save the config */
        function saveAxisConfig($Axis)
        {
                $this->Data["Axis"] = $Axis;
        }

        /* Save the Y Margin if set */
        function saveYMargin($Value)
        {
                $this->Data["YMargin"] = $Value;
        }

        /* Save extended configuration to the pData object */
        function saveExtendedData($Tag, $Values)
        {
                $this->Data["Extended"][$Tag] = $Values;
        }

        /* Called by the scaling algorithm to save the orientation of the scale */
        function saveOrientation($Orientation)
        {
                $this->Data["Orientation"] = $Orientation;
        }

        /* Convert a string to a single elements array */
        function convertToArray($Value)
        {
                return (is_array($Value)) ? $Value : [$Value];
        }

        /* Class string wrapper */
        function __toString()
        {
                return "pData object.";
        }

        function left($value, $NbChar)
        {
                return substr($value, 0, $NbChar);
        }

        function right($value, $NbChar)
        {
                return substr($value, strlen($value) - $NbChar, $NbChar);
        }

        function mid($value, $Depart, $NbChar)
        {
                return substr($value, $Depart - 1, $NbChar);
        }
}

?>