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/ 15 # ---- PUBLIC INTERFACE -------------------------------------------------- 28 # if data provided is an array of arrays, then just copy it in 29 if (is_array(reset(
$Data)))
33 # otherwise, normalize to array of arrays format 36 $this->SingleCategory = TRUE;
41 foreach (
$Data as $Name => $Val)
43 $this->Data[$Name] = [$Name => $Val];
59 if (func_num_args()>0)
63 self::AXIS_TIME_DAILY,
64 self::AXIS_TIME_WEEKLY,
65 self::AXIS_TIME_MONTHLY,
66 self::AXIS_TIME_YEARLY,
69 # toss exception if the given type is not valid 70 if (!in_array($NewValue, $ValidTypes))
72 throw new Exception(
"Invalid axis type for bar charts: ".$NewValue);
86 public function YLabel($NewValue=NULL)
88 if (func_num_args()>0)
104 if (func_num_args()>0)
106 if (!is_null($NewValue) &&
107 $NewValue <= 0 || $NewValue > 100)
109 throw new Exception(
"Invalid bar width: ".$NewValue);
122 public function Zoom($NewValue=NULL)
124 if (func_num_args()>0)
126 if (!is_bool($NewValue))
128 throw new Exception(
"Invalid Zoom setting -- must be boolean");
130 $this->
Zoom = $NewValue;
143 if (func_num_args()>0)
145 if (!is_bool($NewValue))
147 throw new Exception(
"Invalid Stacked setting -- must be boolean");
164 if (func_num_args()>0)
166 if (!is_bool($NewValue))
168 throw new Exception(
"Invalid Horizontal setting -- must be boolean");
184 if (func_num_args()>0)
186 if (!is_bool($NewValue))
188 throw new Exception(
"Invalid Gridlines setting -- must be boolean");
205 if (func_num_args()>0)
207 if (!is_bool($NewValue))
210 "Invalid ShowCategoryLabels setting -- must be boolean");
225 # ---- PRIVATE INTERFACE -------------------------------------------------- 234 # [ CatNameOrTimestamp => [Data1 => Val, Data2 => Val, ...], ... ] 236 # and the format that C3 expects is 237 # [ "Data1", Val, Val, ... ] 238 # [ "Data2", Val, Val, ... ] 240 # extract the names of all the bars 242 foreach ($this->Data as $Entries)
244 foreach ($Entries as $BarName => $YVal)
246 $BarNames[$BarName] = 1;
249 $BarNames = array_keys($BarNames);
251 # start the chart off with no data 252 $this->Chart[
"data"][
"columns"] = [];
254 if ($this->
AxisType == self::AXIS_CATEGORY)
256 # for categorical plots, data stays in place 262 $this->Chart[
"axis"][
"x"][
"categories"] =
267 $this->Chart[
"axis"][
"x"][
"categories"] =
268 array_fill(0, count($BarNames),
"");
271 # and fix up the display for single-category charts 272 if ($this->SingleCategory)
274 $this->Chart[
"tooltip"][
"grouped"] = FALSE;
279 # for time series data, we need to sort our data into bins 282 # convert our timestamps to JS-friendly date strings 283 $Timestamps = array_keys(
$Data);
284 array_walk($Timestamps,
function(&$Val, $Key)
286 $Val = strftime(
"%Y-%m-%d", $Val);
288 array_unshift($Timestamps,
"x-timestamp-x");
290 # add this in to our data columns 291 $this->Chart[
"data"][
"columns"][]= $Timestamps;
294 # generate one row of data per bar to use for plotting 296 # see http://c3js.org/reference.html#data-columns for format of 'columns' element. 297 # since C3 always uses the label in 'columns' for the legend, 298 # we'll need to populate the TooltipLabels array that is keyed 299 # by legend label where values give the tooltip label 300 foreach ($BarNames as $BarName)
302 $Label = isset($this->
Labels[$BarName]) ?
303 $this->
Labels[$BarName] : $BarName ;
308 $this->TooltipLabels[$MyLabel] = $Label;
315 $DataRow = [$MyLabel];
316 foreach (
$Data as $Entries)
318 $DataRow[]= isset($Entries[$BarName]) ? $Entries[$BarName] : 0;
320 $this->Chart[
"data"][
"columns"][] = $DataRow;
323 $this->Chart[
"data"][
"type"] =
"bar";
325 if ($this->
AxisType == self::AXIS_CATEGORY)
327 $this->Chart[
"axis"][
"x"][
"type"] =
"category";
333 "x" =>
"x-timestamp-x",
334 "xFormat" =>
"%Y-%m-%d",
338 "type" =>
"timeseries",
346 $this->Chart[
"bar"][
"width"][
"ratio"] = $this->
BarWidth / 100;
349 if (!is_null($this->
YLabel))
356 $this->Chart[
"zoom"][
"enabled"] = TRUE;
361 $this->Chart[
"data"][
"groups"] = [
368 $this->Chart[
"axis"][
"rotated"] = TRUE;
373 $this->Chart[
"grid"][
"y"][
"show"] = TRUE;
386 # create an array to store the binned data 389 # iterate over all our input data. 390 foreach ($this->Data as $TS => $Entries)
392 # place this timestamp in the appropriate bin 395 # if we have no results in this bin, then these are 397 if (!isset($BinnedData[$TS]))
399 $BinnedData[$TS] = $Entries;
403 # otherwise, iterate over the keys we were given 404 foreach ($Entries as $Key => $Val)
406 # if we have a value for this key 407 if (isset($BinnedData[$TS][$Key]))
409 # then add this new value to it 410 $BinnedData[$TS][$Key] += $Val;
414 # otherwise, insert the new value 415 $BinnedData[$TS][$Key] = $Val;
424 # build up a revised data set with no gaps 426 # prime the revised set with the first element 427 $GaplessData[key($BinnedData)] = current($BinnedData);
429 # iterate over the remaining elements 430 while (($Row = next($BinnedData)) !== FALSE)
434 # if the next element is not the next bin, add an empty element 435 while (key($BinnedData) != $this->
NextBin(key($GaplessData)))
437 $GaplessData[$this->
NextBin(key($GaplessData))] =
438 array_fill_keys($BarNames, 0);
441 if ($BinsAdded > 1000)
444 "Over 1000 empty bins added. " 445 .
"Terminating possible infinite loop.");
449 # and add the current element 450 $GaplessData[key($BinnedData)] = $Row;
464 if (!preg_match(
"/^[0-9]+$/", $TS))
466 $TS = strtotime($TS);
471 case self::AXIS_TIME_DAILY:
472 return strtotime(strftime(
"%Y-%m-%d 00:00:00", $TS));
475 case self::AXIS_TIME_WEEKLY:
476 $DateInfo = strptime(strftime(
477 "%Y-%m-%d 00:00:00", $TS),
"%Y-%m-%d %H:%M:%S");
479 $Year = $DateInfo[
"tm_year"] + 1900;
480 $Month = $DateInfo[
"tm_mon"] + 1;
481 $Day = $DateInfo[
"tm_mday"] - $DateInfo[
"tm_wday"];
483 return mktime(0, 0, 0, $Month, $Day, $Year);
486 case self::AXIS_TIME_MONTHLY:
487 return strtotime(strftime(
"%Y-%m-01 00:00:00", $TS));
490 case self::AXIS_TIME_YEARLY:
491 return strtotime(strftime(
"%Y-01-01 00:00:00", $TS));
503 $ThisBin = strftime(
"%Y-%m-%d %H:%M:%S", $BinTS);
505 self::AXIS_TIME_DAILY =>
"day",
506 self::AXIS_TIME_WEEKLY =>
"week",
507 self::AXIS_TIME_MONTHLY =>
"month",
508 self::AXIS_TIME_YEARLY =>
"year",
511 return strtotime($ThisBin.
" + 1 ".$Units[$this->AxisType]);
523 foreach ($LongNames as $Name)
__construct($Data)
Class constructor.
Stacked($NewValue=NULL)
Get/Set bar stacking setting.
Horizontal($NewValue=NULL)
Get/Set horizontal display setting.
Gridlines($NewValue=NULL)
Enable/disable display of grid lines.
Base class for generating and displaying a chart.
ShowCategoryLabels($NewValue=NULL)
Enable/disable display of category labels along the X axis on categorical charts (by default...
Labels($NewValue=NULL)
Get/set chart element labels (pie slices, bars, etc).
LegendLabels($LegendLabels)
Set shortened labels to be used in the legend of the chart.
BarWidth($NewValue=NULL)
Get/set bar width as a percentage of the distance between ticks.
AxisType($NewValue=NULL)
Get/set the axis type of a bar chart (default is AXIS_CATEGORY).
Zoom($NewValue=NULL)
Enable/Disable zooming for this chart.
NextBin($BinTS)
Get the next bin.
BinTimestamp($TS)
Determine which bin a specified timestamp belongs in.
AddToChart($Data)
Merge an array of settings into $this->Chart.
ShortCategoryNames($LongNames)
Get abbreviated category names (e.g., for the legend).
YLabel($NewValue=NULL)
Get/set the Y axis label for a bar chart.
PrepareData()
Prepare data for plotting.
LegendPosition($Position)
Set legend position.
Class for generating and displaying a bar chart.
SortDataIntoBins($BarNames)
Sort the user-provided data into bins with sizes given by $this->AxisType, filling in any gaps in the...