Passed
Push — master ( 9c4b3c...2ae703 )
by Sebastian
02:13
created

Calculator::getPixelValues()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * File containing the {@see Mistralys\WidthsCalculator\Calculator} class.
4
 *
5
 * @package WidthsCalculator
6
 * @see Mistralys\WidthsCalculator\Calculator
7
 */
8
9
declare (strict_types=1);
10
11
namespace Mistralys\WidthsCalculator;
12
13
use AppUtils\Traits_Optionable;
14
use AppUtils\Interface_Optionable;
15
use Mistralys\WidthsCalculator\Calculator\Column;
16
use Mistralys\WidthsCalculator\Calculator\Operations;
17
use Mistralys\WidthsCalculator\Calculator\OverflowFixer;
18
use Mistralys\WidthsCalculator\Calculator\SurplusRemover;
19
use Mistralys\WidthsCalculator\Calculator\MissingFiller;
20
use Mistralys\WidthsCalculator\Calculator\LeftoverFiller;
21
22
/**
23
 * Calculates percentual column widths given a list of 
24
 * column names with user width values.
25
 *
26
 * Columns with 0 width are filled automatically with
27
 * the leftover percent. Values out of bounds are 
28
 * normalized proportionally, allowing the use of an
29
 * arbitrary numering system to convert to percent.
30
 *
31
 * @package WidthsCalculator
32
 * @author Sebastian Mordziol <[email protected]>
33
 */
34
class Calculator implements Interface_Optionable
35
{
36
    const ERROR_INVALID_MIN_WIDTH = 61501;
37
    
38
    use Traits_Optionable;
39
    
40
   /**
41
    * @var Column[]
42
    */
43
    private $columns = array();
44
    
45
   /**
46
    * @var boolean
47
    */
48
    private $calculated = false;
49
    
50
   /**
51
    * @var Operations
52
    */
53
    private $operations;
54
    
55
   /**
56
    * @param array<string,float> $columnValues
57
    */
58
    private function __construct(array $columnValues)
59
    {
60
        foreach($columnValues as $name => $value)
61
        {
62
            $this->addColumn(
63
                (string)$name, 
64
                floatval($value)
65
            );
66
        }
67
        
68
        $this->operations = new Operations($this);
69
    }
70
    
71
   /**
72
    * Creates an instance of the calculator.
73
    * 
74
    * @param array<string,float> $columnValues
75
    * @return Calculator
76
    */
77
    public static function create(array $columnValues) : Calculator
78
    {
79
        return new Calculator($columnValues);
80
    }
81
    
82
   /**
83
    * @return array<string,mixed>
84
    */
85
    public function getDefaultOptions(): array
86
    {
87
        return array(
88
            'maxTotal' => 100,
89
            'minPerCol' => 1,
90
            'integerValues' => true
91
        );
92
    }
93
    
94
    public function getMaxTotal() : float
95
    {
96
        return floatval($this->getOption('maxTotal'));
97
    }
98
    
99
    public function setMaxTotal(float $total) : Calculator
100
    {
101
        $this->setOption('maxTotal', $total);
102
        return $this;
103
    }
104
    
105
    public function getOperations() : Operations
106
    {
107
        return $this->operations;
108
    }
109
    
110
    public function setFloatValues(bool $enable=true) : Calculator
111
    {
112
        $this->setOption('integerValues', !$enable);
113
        return $this;
114
    }
115
    
116
   /**
117
    * Sets the minimum width to enforce for columns, 
118
    * when there already are other columns that take
119
    * up all the available width.
120
    * 
121
    * @param float $width
122
    * @return Calculator
123
    */
124
    public function setMinWidth(float $width) : Calculator
125
    {
126
        $max = $this->getMaxMinWidth();
127
        
128
        if($width > $max)
129
        {
130
            throw new \Exception(
131
                sprintf('Minimum width cannot be set above %s.', number_format($max, 4)),
132
                self::ERROR_INVALID_MIN_WIDTH
133
            );
134
        }
135
        
136
        $this->setOption('minPerCol', $width);
137
        return $this;
138
    }
139
    
140
    public function getMaxMinWidth() : float
141
    {
142
        return $this->getMaxTotal() / $this->operations->countColumns();
143
    }
144
    
145
    private function addColumn(string $name, float $value) : void
146
    {
147
        $col = new Column(
148
            $this,
149
            $name,
150
            $value
151
        );
152
        
153
        $this->columns[] = $col;
154
    }
155
    
156
   /**
157
    * Retrieves the minimum width for columns, in percent.
158
    * @return float
159
    */
160
    public function getMinWidth() : float
161
    {
162
        return floatval($this->getOption('minPerCol'));
163
    }
164
    
165
    private function calculate() : void
166
    {
167
        if($this->calculated)
168
        {
169
            return;
170
        }
171
        
172
        if($this->operations->calcTotal() > $this->getMaxTotal())
173
        {
174
            $this->fixOverflow();
175
        }
176
        
177
        $this->fillMissing();
178
        $this->removeSurplus();
179
        $this->convertToInteger();
180
        $this->fillLeftover();
181
        
182
        $this->calculated = true;
183
    }
184
    
185
   /**
186
    * Adjusts the individual column values to match
187
    * the expected output format, for example ensuring
188
    * integer values if we are in integer mode.
189
    */
190
    private function convertToInteger() : void
191
    {
192
        // convert all columns to integer values as required
193
        if($this->isIntegerMode())
194
        {
195
            foreach($this->columns as $col)
196
            {
197
                $val = intval(floor($col->getValue()));
198
                $col->setValue(floatval($val));
199
            }
200
        }
201
    }
202
    
203
   /**
204
    * Retrieves the updated list of column values, 
205
    * retaining the original keys.
206
    * 
207
    * @return array<string,int|float>
208
    */
209
    public function getValues() : array
210
    {
211
        $this->calculate();
212
        
213
        $result = array();
214
        
215
        foreach($this->columns as $col)
216
        {
217
            $val = $col->getValue();
218
            
219
            if($this->isIntegerMode())
220
            {
221
                $val = intval($val);
222
            }
223
            
224
            $result[$col->getName()] = $val;
225
        }
226
        
227
        return $result;
228
    }
229
    
230
    public function isIntegerMode() : bool
231
    {
232
        return $this->getBoolOption('integerValues');
233
    }
234
    
235
   /**
236
    * Retrieves the column values as pixel values, based on
237
    * the target available pixel width.
238
    * 
239
    * @param int $targetWidth
240
    * @return array<string,int>
241
    */
242
    public function getPixelValues(int $targetWidth) : array
243
    {
244
        $calc = Calculator::create($this->getValues());
245
        $calc->setMaxTotal($targetWidth);
246
        
247
        return $calc->getValues();
248
    }
249
    
250
   /**
251
    * @return Column[]
252
    */
253
    public function getColumns() : array 
254
    {
255
        return $this->columns;
256
    }
257
    
258
    private function removeSurplus() : void
259
    {
260
        $surplus = new SurplusRemover($this);
261
        $surplus->remove();
262
    }
263
    
264
   /**
265
    * Detects any leftover percentages that still need
266
    * to be filled, in case we are not at 100% yet. It
267
    * distributes the missing percentages evenly over the
268
    * available columns, from the last one upwards.
269
    */
270
    private function fillLeftover() : void
271
    {
272
        $filler = new LeftoverFiller($this);
273
        $filler->fill();
274
    }
275
    
276
    private function fillMissing() : void
277
    {
278
        $filling = new MissingFiller($this);
279
        $filling->fill();
280
    }
281
    
282
    private function fixOverflow() : void
283
    {
284
        $overflow = new OverflowFixer($this);
285
        $overflow->fix();
286
    }
287
}
288