CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

BarChart.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: BarChart.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2017 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9
13 class BarChart extends Chart_Base
14 {
15 # ---- PUBLIC INTERFACE --------------------------------------------------
16
26 public function __construct($Data)
27 {
28 # if data provided is an array of arrays, then just copy it in
29 if (is_array(reset($Data)))
30 {
31 $this->Data = $Data;
32 }
33 # otherwise, normalize to array of arrays format
34 else
35 {
36 $this->SingleCategory = TRUE;
37 $this->Stacked = TRUE;
38 $this->LegendPosition = static::LEGEND_NONE;
39
40 $this->Data = [];
41 foreach ($Data as $Name => $Val)
42 {
43 $this->Data[$Name] = [$Name => $Val];
44 }
45 }
46 }
47
56 public function AxisType($NewValue)
57 {
58 $ValidTypes = [
59 self::AXIS_CATEGORY,
60 self::AXIS_TIME_DAILY,
61 self::AXIS_TIME_WEEKLY,
62 self::AXIS_TIME_MONTHLY,
63 self::AXIS_TIME_YEARLY,
64 ];
65
66 # toss exception if the given type is not valid
67 if (!in_array($NewValue, $ValidTypes))
68 {
69 throw new Exception("Invalid axis type for bar charts: ".$NewValue);
70 }
71
72 # otherwise, save the new type and clear any cached chart data
73 $this->AxisType = $NewValue;
74 }
75
80 public function YLabel($NewValue)
81 {
82 $this->YLabel = $NewValue;
83 }
84
89 public function Zoom($NewValue)
90 {
91 $this->Zoom = $NewValue;
92 }
93
98 public function Stacked($NewValue)
99 {
100 $this->Stacked = $NewValue;
101 }
102
107 public function Horizontal($NewValue)
108 {
109 $this->Horizontal = TRUE;
110 }
111
116 public function Gridlines($NewValue)
117 {
118 $this->Gridlines = $NewValue;
119 }
120
126 public function ShowCategoryLabels($NewValue)
127 {
128 $this->ShowCategoryLabels = $NewValue;
129 }
130
131 # axis types
132 const AXIS_CATEGORY = "category";
133 const AXIS_TIME_DAILY = "daily";
134 const AXIS_TIME_WEEKLY = "weekly";
135 const AXIS_TIME_MONTHLY = "monthly";
136 const AXIS_TIME_YEARLY = "yearly";
137
138 # ---- PRIVATE INTERFACE --------------------------------------------------
139
144 protected function PrepareData()
145 {
146 # input data is
147 # [ CatNameOrTimestamp => [Data1 => Val, Data2 => Val, ...], ... ]
148 #
149 # and the format that C3 expects is
150 # [ "Data1", Val, Val, ... ]
151 # [ "Data2", Val, Val, ... ]
152
153 # extract the names of all the bars
154 $BarNames = [];
155 foreach ($this->Data as $Entries)
156 {
157 foreach ($Entries as $BarName => $YVal)
158 {
159 $BarNames[$BarName] = 1;
160 }
161 }
162 $BarNames = array_keys($BarNames);
163
164 # start the chart off with no data
165 $this->Chart["data"]["columns"] = [];
166
167 if ($this->AxisType == self::AXIS_CATEGORY)
168 {
169 # for categorical plots, data stays in place
170 $Data = $this->Data;
171
172 # set up X labels
173 if ($this->ShowCategoryLabels)
174 {
175 $this->Chart["axis"]["x"]["categories"] =
176 $this->ShortCategoryNames($BarNames);
177 }
178 else
179 {
180 $this->Chart["axis"]["x"]["categories"] =
181 array_fill(0, count($BarNames), "");
182 }
183
184 # and fix up the display for single-category charts
185 if ($this->SingleCategory)
186 {
187 $this->Chart["tooltip"]["grouped"] = FALSE;
188 }
189 }
190 else
191 {
192 # for time series data, we need to sort our data into bins
193 $Data = $this->SortDataIntoBins($BarNames);
194
195 # convert our timestamps to JS-friendly date strings
196 $Timestamps = array_keys($Data);
197 array_walk($Timestamps, function(&$Val, $Key)
198 {
199 $Val = strftime("%Y-%m-%d", $Val);
200 });
201 array_unshift($Timestamps, "x-timestamp-x");
202
203 # add this in to our data columns
204 $this->Chart["data"]["columns"][]= $Timestamps;
205 }
206
207 # generate one row of data per bar to use for plotting
208 foreach ($BarNames as $BarName)
209 {
210 if (isset($this->LegendLabels[$BarName]))
211 {
212 $MyLabel = $this->LegendLabels[$BarName];
213 $this->LabelLUT[$MyLabel] = $BarName;
214 }
215 else
216 {
217 $MyLabel = $BarName;
218 }
219
220 $DataRow = [$MyLabel];
221 foreach ($Data as $Entries)
222 {
223 $DataRow[]= isset($Entries[$BarName]) ? $Entries[$BarName] : 0;
224 }
225 $this->Chart["data"]["columns"][] = $DataRow;
226 }
227
228 $this->Chart["data"]["type"] = "bar";
229
230 if ($this->AxisType == self::AXIS_CATEGORY)
231 {
232 $this->Chart["axis"]["x"]["type"] = "category";
233 }
234 else
235 {
236 $this->AddToChart([
237 "data" => [
238 "x" => "x-timestamp-x",
239 "xFormat" => "%Y-%m-%d",
240 ],
241 "axis" => [
242 "x" => [
243 "type" => "timeseries",
244 ],
245 ],
246 ]);
247 }
248
249 if (!is_null($this->YLabel))
250 {
251 $this->Chart["axis"]["y"]["label"] = $this->YLabel;
252 }
253
254 if ($this->Zoom)
255 {
256 $this->Chart["zoom"]["enabled"] = TRUE;
257 }
258
259 if ($this->Stacked)
260 {
261 $this->Chart["data"]["groups"] = [
262 $this->ShortCategoryNames($BarNames),
263 ];
264 }
265
266 if ($this->Horizontal)
267 {
268 $this->Chart["axis"]["rotated"] = TRUE;
269 }
270
271 if ($this->Gridlines)
272 {
273 $this->Chart["grid"]["y"]["show"] = TRUE;
274 }
275 }
276
284 protected function SortDataIntoBins($BarNames)
285 {
286 # create an array to store the binned data
287 $BinnedData = [];
288
289 # iterate over all our input data.
290 foreach ($this->Data as $TS => $Entries)
291 {
292 # place this timestamp in the appropriate bin
293 $TS = $this->BinTimestamp($TS);
294
295 # if we have no results in this bin, then these are
296 # the first
297 if (!isset($BinnedData[$TS]))
298 {
299 $BinnedData[$TS] = $Entries;
300 }
301 else
302 {
303 # otherwise, iterate over the keys we were given
304 foreach ($Entries as $Key => $Val)
305 {
306 # if we have a value for this key
307 if (isset($BinnedData[$TS][$Key]))
308 {
309 # then add this new value to it
310 $BinnedData[$TS][$Key] += $Val;
311 }
312 else
313 {
314 # otherwise, insert the new value
315 $BinnedData[$TS][$Key] = $Val;
316 }
317 }
318 }
319 }
320
321 ksort($BinnedData);
322 reset($BinnedData);
323
324 # build up a revised data set with no gaps
325 $GaplessData = [];
326 # prime the revised set with the first element
327 $GaplessData[key($BinnedData)] = current($BinnedData);
328
329 # iterate over the remaining elements
330 while (($Row = next($BinnedData)) !== FALSE)
331 {
332 $BinsAdded = 0;
333
334 # if the next element is not the next bin, add an empty element
335 while (key($BinnedData) != $this->NextBin(key($GaplessData)))
336 {
337 $GaplessData[$this->NextBin(key($GaplessData))] =
338 array_fill_keys($BarNames, 0);
339 end($GaplessData);
340
341 if ($BinsAdded > 1000)
342 {
343 throw new Exception(
344 "Over 1000 empty bins added. "
345 ."Terminating possible infinite loop.");
346 }
347 }
348
349 # and add the current element
350 $GaplessData[key($BinnedData)] = $Row;
351 end($GaplessData);
352 }
353
354 return $GaplessData;
355 }
356
362 protected function BinTimestamp($TS)
363 {
364 if (!preg_match("/^[0-9]+$/", $TS))
365 {
366 $TS = strtotime($TS);
367 }
368
369 switch ($this->AxisType)
370 {
371 case self::AXIS_TIME_DAILY:
372 return strtotime(strftime("%Y-%m-%d 00:00:00", $TS));
373 break;
374
375 case self::AXIS_TIME_WEEKLY:
376 $DateInfo = strptime(strftime(
377 "%Y-%m-%d 00:00:00", $TS), "%Y-%m-%d %H:%M:%S");
378
379 $Year = $DateInfo["tm_year"] + 1900;
380 $Month = $DateInfo["tm_mon"] + 1;
381 $Day = $DateInfo["tm_mday"] - $DateInfo["tm_wday"];
382
383 return mktime(0, 0, 0, $Month, $Day, $Year);
384 break;
385
386 case self::AXIS_TIME_MONTHLY:
387 return strtotime(strftime("%Y-%m-01 00:00:00", $TS));
388 break;
389
390 case self::AXIS_TIME_YEARLY:
391 return strtotime(strftime("%Y-01-01 00:00:00", $TS));
392 break;
393 }
394 }
395
401 protected function NextBin($BinTS)
402 {
403 $ThisBin = strftime("%Y-%m-%d %H:%M:%S", $BinTS);
404 $Units = [
405 self::AXIS_TIME_DAILY => "day",
406 self::AXIS_TIME_WEEKLY => "week",
407 self::AXIS_TIME_MONTHLY => "month",
408 self::AXIS_TIME_YEARLY => "year",
409 ];
410
411 return strtotime($ThisBin." + 1 ".$Units[$this->AxisType]);
412 }
413
419 protected function ShortCategoryNames($LongNames)
420 {
421 $ShortNames = [];
422
423 foreach ($LongNames as $Name)
424 {
425 $ShortNames[]= isset($this->LegendLabels[$Name]) ?
426 $this->LegendLabels[$Name] : $Name ;
427 }
428
429 return $ShortNames;
430 }
431
432 protected $AxisType = self::AXIS_CATEGORY;
433 protected $YLabel = NULL;
434 protected $Zoom = FALSE;
435 protected $Stacked = FALSE;
436 protected $Horizontal = FALSE;
437 protected $SingleCategory = FALSE;
438 protected $Gridlines = TRUE;
439 protected $ShowCategoryLabels = TRUE;
440 }
BarChart\__construct
__construct($Data)
Class constructor.
Definition: BarChart.php:26
BarChart\AXIS_TIME_WEEKLY
const AXIS_TIME_WEEKLY
Definition: BarChart.php:134
BarChart\YLabel
YLabel($NewValue)
Set the Y axis label for a bar chart.
Definition: BarChart.php:80
Chart_Base
Base class for generating and displaying a chart.
Definition: Chart_Base.php:13
BarChart\Zoom
Zoom($NewValue)
Enable/Disable zooming for this chart.
Definition: BarChart.php:89
BarChart\ShowCategoryLabels
ShowCategoryLabels($NewValue)
Enable/disable display of category labels along the X axis on categorical charts (by default...
Definition: BarChart.php:126
BarChart\$YLabel
$YLabel
Definition: BarChart.php:433
Chart_Base\LegendLabels
LegendLabels($LegendLabels)
Set shortened labels to be used in the legend of the chart.
Definition: Chart_Base.php:43
BarChart\AXIS_TIME_YEARLY
const AXIS_TIME_YEARLY
Definition: BarChart.php:136
BarChart\AXIS_TIME_MONTHLY
const AXIS_TIME_MONTHLY
Definition: BarChart.php:135
BarChart\AXIS_TIME_DAILY
const AXIS_TIME_DAILY
Definition: BarChart.php:133
BarChart\NextBin
NextBin($BinTS)
Get the next bin.
Definition: BarChart.php:401
BarChart\$Gridlines
$Gridlines
Definition: BarChart.php:438
PHP
PHP
Definition: RSSClient--Test.php:12
BarChart\Gridlines
Gridlines($NewValue)
Enable/disable display of grid lines.
Definition: BarChart.php:116
BarChart\BinTimestamp
BinTimestamp($TS)
Determine which bin a specified timestamp belongs in.
Definition: BarChart.php:362
Chart_Base\AddToChart
AddToChart($Data)
Merge an array of settings into $this->Chart.
Definition: Chart_Base.php:261
BarChart\$ShowCategoryLabels
$ShowCategoryLabels
Definition: BarChart.php:439
Chart_Base\$Data
$Data
Definition: Chart_Base.php:288
BarChart\AXIS_CATEGORY
const AXIS_CATEGORY
Definition: BarChart.php:132
BarChart\AxisType
AxisType($NewValue)
Set the axis type of a bar chart (default is AXIS_CATEGORY).
Definition: BarChart.php:56
BarChart\Horizontal
Horizontal($NewValue)
Enable/disable horizontal display.
Definition: BarChart.php:107
BarChart\ShortCategoryNames
ShortCategoryNames($LongNames)
Get abbreviated category names (e.g., for the legend).
Definition: BarChart.php:419
BarChart\$Horizontal
$Horizontal
Definition: BarChart.php:436
BarChart\Stacked
Stacked($NewValue)
Enable/Disable bar stacking.
Definition: BarChart.php:98
BarChart\$AxisType
$AxisType
Definition: BarChart.php:432
BarChart\$Zoom
$Zoom
Definition: BarChart.php:434
BarChart\PrepareData
PrepareData()
Prepare data for plotting.
Definition: BarChart.php:144
BarChart\$SingleCategory
$SingleCategory
Definition: BarChart.php:437
Chart_Base\LegendPosition
LegendPosition($Position)
Set legend position.
Definition: Chart_Base.php:26
BarChart\$Stacked
$Stacked
Definition: BarChart.php:435
BarChart
Class for generating and displaying a bar chart.
Definition: BarChart.php:13
BarChart\SortDataIntoBins
SortDataIntoBins($BarNames)
Sort the user-provided data into bins with sizes given by $this->AxisType, filling in any gaps in the...
Definition: BarChart.php:284