Initial xentral_oss_20.3.c9ffacf
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
/**
|
||||
* Spezielles Dataset für Balkendiagramme
|
||||
*
|
||||
* Besonderheiten:
|
||||
* * Balkenhintergrund und -rahmen haben keine Transparenz
|
||||
*/
|
||||
class BarDataset extends Dataset
|
||||
{
|
||||
/** @var array $defaultAlphaValues Transparenz-Werte */
|
||||
protected static $defaultAlphaValues = [
|
||||
'borderColor' => 1.0,
|
||||
'backgroundColor' => 1.0,
|
||||
'hoverBorderColor' => 1.0,
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class Chart
|
||||
{
|
||||
/** @var array $validTypes */
|
||||
private static $validTypes = [
|
||||
'line',
|
||||
'bar',
|
||||
'radar',
|
||||
'pie',
|
||||
'doughnut',
|
||||
'polarArea',
|
||||
//'bubble',
|
||||
//'scatter',
|
||||
];
|
||||
|
||||
/** @var array $defaultColors In RGB */
|
||||
private static $defaultColors = [
|
||||
[162, 197, 90],
|
||||
[69, 185, 211],
|
||||
[246, 158, 6],
|
||||
[14, 131, 148],
|
||||
];
|
||||
|
||||
/** @var array $defaultOptions */
|
||||
private static $defaultOptions = [
|
||||
'responsive' => true,
|
||||
'responsiveAnimationDuration' => 0,
|
||||
'maintainAspectRatio' => true,
|
||||
'tooltips' => [
|
||||
'enabled' => true,
|
||||
'mode' => 'nearest',
|
||||
'backgroundColor' => 'rgba(0, 0, 0, 0.5)',
|
||||
],
|
||||
'legend' => [
|
||||
'display' => true,
|
||||
],
|
||||
'animation' => [
|
||||
'duration' => 1000,
|
||||
'animateRotate' => true, // Pie + Doughnut
|
||||
],
|
||||
'scales' => [
|
||||
'xAxes' => [
|
||||
[
|
||||
'display' => true,
|
||||
],
|
||||
],
|
||||
'yAxes' => [
|
||||
[
|
||||
'display' => true,
|
||||
'ticks' => [
|
||||
'beginAtZero' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
protected $type;
|
||||
protected $labels;
|
||||
protected $datasets;
|
||||
protected $options;
|
||||
|
||||
/** @var int $currentColor Zähler für die letzte Default-Farbe die verwendet wurde */
|
||||
private $currentColor;
|
||||
|
||||
/**
|
||||
* @param string $type Chart-Typ
|
||||
* @param array $labels
|
||||
* @param array|Dataset[] $datasets
|
||||
* @param array $options chart.js Optionen
|
||||
*/
|
||||
public function __construct($type = 'line', $labels = [], array $datasets = [], array $options = [])
|
||||
{
|
||||
if (!in_array($type, self::$validTypes, true)) {
|
||||
throw new InvalidArgumentException(sprintf('Chart type "%s" is not valid.', $type));
|
||||
}
|
||||
// Ungültige Werte in leeres Array wandeln
|
||||
if (!is_array($labels)) {
|
||||
$labels = [];
|
||||
}
|
||||
|
||||
$this->type = $type;
|
||||
$this->labels = $labels;
|
||||
$this->datasets = $datasets;
|
||||
$this->options = array_replace_recursive(self::$defaultOptions, $options);
|
||||
|
||||
if ($type === 'line') {
|
||||
$this->options['tooltips']['mode'] = 'index';
|
||||
}
|
||||
if ($type === 'doughnut' || $type === 'pie') {
|
||||
$this->options['scales']['xAxes'][0]['display'] = false;
|
||||
$this->options['scales']['yAxes'][0]['display'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $labels
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addLabels($labels)
|
||||
{
|
||||
// Ungültige Werte in leeres Array wandeln
|
||||
if (!is_array($labels)) {
|
||||
$labels = [];
|
||||
}
|
||||
|
||||
foreach ($labels as $label) {
|
||||
$this->addLabel($label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addLabel($label)
|
||||
{
|
||||
$this->labels[] = (string)$label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dataset $dataset
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addDataset(Dataset $dataset)
|
||||
{
|
||||
$this->datasets[] = $dataset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie setYAxis(), nur dass das Dataset zusätzlich noch zum Chart hinzugefügt wird
|
||||
*
|
||||
* @see setYAxis()
|
||||
*
|
||||
* @param Dataset $dataset
|
||||
* @param string $position
|
||||
* @param string $type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addDatasetAsYAxis(Dataset $dataset, $position = 'left', $type = 'linear')
|
||||
{
|
||||
$this->setYAxis($dataset, $position, $type);
|
||||
$this->addDataset($dataset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Übergebenes Dataset für die Anzeige der Y-Achse verwenden
|
||||
*
|
||||
* * Mehrere Y-Achsen sind möglich
|
||||
* * Dataset wird aber nicht zum Chart hinzugefügt; muss über addDataset() passieren
|
||||
*
|
||||
* @param Dataset $dataset
|
||||
* @param string $position
|
||||
* @param string $type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setYAxis(Dataset $dataset, $position = 'left', $type = 'linear')
|
||||
{
|
||||
// Falls vorher noch kein Dataset als y-Achse definiert wurde, Default-y-Achsen-Config löschen.
|
||||
// (Ansonsten wird eine y-Achse zu viel angezeigt)
|
||||
$hasMultipleAxesConfig = array_key_exists('id', $this->options['scales']['yAxes'][0]);
|
||||
if (!$hasMultipleAxesConfig) {
|
||||
unset($this->options['scales']['yAxes']);
|
||||
}
|
||||
|
||||
$id = $dataset->generateYAxisId();
|
||||
$this->options['scales']['yAxes'][] = [
|
||||
'id' => $id,
|
||||
'display' => true,
|
||||
'type' => $type,
|
||||
'position' => $position,
|
||||
'ticks' => [
|
||||
'beginAtZero' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function accumulateData()
|
||||
{
|
||||
foreach ($this->datasets as $dataset) {
|
||||
$dataset->accumulateData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
foreach ($this->datasets as $dataset) {
|
||||
/** @var Dataset $dataset */
|
||||
// Default-Farben durchiterieren, wenn noch keine Farbe gesetzt
|
||||
if (!$dataset->hasColorAssigned()) {
|
||||
$color = $this->getNextDefaultColor();
|
||||
$dataset->setColorByRgb(...$color);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'labels' => $this->labels,
|
||||
'datasets' => $this->datasets,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function toJson()
|
||||
{
|
||||
return json_encode([
|
||||
'type' => $this->getType(),
|
||||
'data' => $this->getData(),
|
||||
'options' => $this->getOptions(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert bei jedem Aufruf die nächste Farbe aus den Standard-Farben
|
||||
*
|
||||
* @return array Array mit drei Farbwerten [R, G, B]
|
||||
*/
|
||||
private function getNextDefaultColor()
|
||||
{
|
||||
// Bei jedem Aufruf Index hochzählen
|
||||
$this->currentColor = $this->currentColor === null ? 0 : $this->currentColor + 1;
|
||||
|
||||
// Wieder vorne anfangen wenn letzte Farbe ausgeliefert wurde
|
||||
if ($this->currentColor >= count(self::$defaultColors)) {
|
||||
$this->currentColor = 0;
|
||||
}
|
||||
|
||||
return self::$defaultColors[$this->currentColor];
|
||||
}
|
||||
|
||||
/**
|
||||
* Beim Klonen die Dataset-Objekte einzeln klonen
|
||||
*
|
||||
* Ansonsten beinhaltet das geklonte Chart-Objekt die Referenzen zum Ursprungsobjekt!
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->datasets as $key => $dataset) {
|
||||
$this->datasets[$key] = clone $dataset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
|
||||
class Color implements JsonSerializable
|
||||
{
|
||||
private $red;
|
||||
private $green;
|
||||
private $blue;
|
||||
private $alpha;
|
||||
|
||||
/**
|
||||
* @param int $red Farbwert von 0 bis 255
|
||||
* @param int $green Farbwert von 0 bis 255
|
||||
* @param int $blue Farbwert von 0 bis 255
|
||||
* @param float $alpha Transparenzwert von 0 bis 1
|
||||
*/
|
||||
public function __construct($red = 0, $green = 0, $blue = 0, $alpha = 0.1)
|
||||
{
|
||||
$this->red = $this->ensureColorValue($red);
|
||||
$this->green = $this->ensureColorValue($green);
|
||||
$this->blue = $this->ensureColorValue($blue);
|
||||
$this->setAlpha($alpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hexColor Beispiel: "#112233"
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromHex($hexColor)
|
||||
{
|
||||
$hexColor = str_replace('#', '', $hexColor);
|
||||
if (strlen($hexColor) !== 6) {
|
||||
throw new InvalidArgumentException('Only full length hex values are supported.');
|
||||
}
|
||||
|
||||
$parts = str_split($hexColor, 2);
|
||||
$red = hexdec($parts[0]);
|
||||
$green = hexdec($parts[1]);
|
||||
$blue = hexdec($parts[2]);
|
||||
|
||||
return new self($red, $green, $blue, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cssRgba Beispiel: rgba(255, 128, 0, 0.5)
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromCssRgba($cssRgba)
|
||||
{
|
||||
$cssRgba = str_replace(' ', '', $cssRgba);
|
||||
preg_match('/^rgba\((\d+),(\d+),(\d+),([\d\.]+)\);?/i', $cssRgba, $colors);
|
||||
|
||||
return new self((int)$colors[1], (int)$colors[2], (int)$colors[3], (float)$colors[4]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cssRgb Beispiel: "rgb(255, 128, 0)"
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function createFromCssRgb($cssRgb)
|
||||
{
|
||||
$cssRgb = str_replace(' ', '', $cssRgb);
|
||||
preg_match('/^rgb\((\d+),(\d+),(\d+)\);?/i', $cssRgb, $colors);
|
||||
|
||||
return new self((int)$colors[1], (int)$colors[2], (int)$colors[3], 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $value
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function ensureColorValue($value)
|
||||
{
|
||||
$value = (int)$value;
|
||||
if ($value < 0) {
|
||||
$value = 0;
|
||||
}
|
||||
if ($value > 255) {
|
||||
$value = 255;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param float $alpha Wert zwischen 0 und 1
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAlpha($alpha)
|
||||
{
|
||||
$alpha = (float)$alpha;
|
||||
if ($alpha < 0.0) {
|
||||
$alpha = 0.0;
|
||||
}
|
||||
if ($alpha > 1.0) {
|
||||
$alpha = 1.0;
|
||||
}
|
||||
|
||||
$this->alpha = $alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Farbwerte per Zufall variieren
|
||||
*
|
||||
* @param int $difference
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function makeVariant($difference)
|
||||
{
|
||||
$difference = (int)$difference;
|
||||
|
||||
$redVariant = $this->red + mt_rand($difference * -1, $difference);
|
||||
$this->red = $this->ensureColorValue($redVariant);
|
||||
|
||||
$greenVariant = $this->green + mt_rand($difference * -1, $difference);
|
||||
$this->green = $this->ensureColorValue($greenVariant);
|
||||
|
||||
$blueVariant = $this->blue + mt_rand($difference * -1, $difference);
|
||||
$this->blue = $this->ensureColorValue($blueVariant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Farbe heller machen; verändert nicht die Transparenz
|
||||
*
|
||||
* @param float $percent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function makeLighter($percent = 10.0)
|
||||
{
|
||||
$percent = (float)$percent;
|
||||
$difference = (int)($percent * 2.55 / 2);
|
||||
$this->red = $this->ensureColorValue($this->red + $difference);
|
||||
$this->blue = $this->ensureColorValue($this->blue + $difference);
|
||||
$this->green = $this->ensureColorValue($this->green + $difference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Farbe dunkler machen; verändert nicht die Transparenz
|
||||
*
|
||||
* @param float $percent
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function makeDarker($percent = 10.0)
|
||||
{
|
||||
$percent = (float)$percent;
|
||||
$difference = (int)($percent * 2.55 / 2);
|
||||
$this->red = $this->ensureColorValue($this->red - $difference);
|
||||
$this->blue = $this->ensureColorValue($this->blue - $difference);
|
||||
$this->green = $this->ensureColorValue($this->green - $difference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabe in CSS rgba() Notation
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toCssRgba()
|
||||
{
|
||||
return sprintf(
|
||||
'rgba(%s, %s, %s, %s)',
|
||||
$this->red,
|
||||
$this->green,
|
||||
$this->blue,
|
||||
number_format($this->alpha, 3, '.', '')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabe in CSS rgb() Notation; Transparenz geht verloren
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toCssRgb()
|
||||
{
|
||||
return sprintf(
|
||||
'rgb(%s, %s, %s)',
|
||||
$this->red,
|
||||
$this->green,
|
||||
$this->blue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ausgabe als Hex-Farbwert; Transparenz geht verloren
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toHex()
|
||||
{
|
||||
return sprintf(
|
||||
'#%s%s%s',
|
||||
str_pad(dechex($this->red), 2, '0', STR_PAD_LEFT),
|
||||
str_pad(dechex($this->green), 2, '0', STR_PAD_LEFT),
|
||||
str_pad(dechex($this->blue), 2, '0', STR_PAD_LEFT)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->toCssRgba();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use JsonSerializable;
|
||||
|
||||
class Dataset implements JsonSerializable
|
||||
{
|
||||
const LINE_STYLE_SOLID = 'solid';
|
||||
const LINE_STYLE_DASHED = 'dashed';
|
||||
const LINE_STYLE_DOTTED = 'dotted';
|
||||
|
||||
const COLOR_GREEN = 'green';
|
||||
const COLOR_BLUE = 'blue';
|
||||
const COLOR_ORANGE = 'orange';
|
||||
const COLOR_DARKBLUE = 'darkblue';
|
||||
|
||||
/** @var array $validLineStyles */
|
||||
protected static $validLineStyles = [
|
||||
self::LINE_STYLE_SOLID,
|
||||
self::LINE_STYLE_DOTTED,
|
||||
self::LINE_STYLE_DASHED,
|
||||
];
|
||||
|
||||
/** @var array $colorMap */
|
||||
protected static $colorMap = [
|
||||
self::COLOR_GREEN => [162, 197, 90],
|
||||
self::COLOR_BLUE => [69, 185, 211],
|
||||
self::COLOR_ORANGE => [246, 158, 6],
|
||||
self::COLOR_DARKBLUE => [14, 131, 148],
|
||||
];
|
||||
|
||||
/** @var array $defaultOptions Standardwerte für Dataset-Optionen */
|
||||
protected static $defaultOptions = [
|
||||
'pointBorderWidth' => 1,
|
||||
'pointHoverBorderWidth' => 2,
|
||||
'pointRadius' => 1,
|
||||
'pointHoverRadius' => 5,
|
||||
'pointHitRadius' => 15,
|
||||
'lineTension' => 0.2,
|
||||
'fill' => true,
|
||||
'borderWidth' => 3,
|
||||
'borderDash' => [],
|
||||
'borderCapStyle' => 'butt',
|
||||
'borderColor' => 'rgba(0, 0, 0, 0.1)',
|
||||
'backgroundColor' => 'rgba(0, 0, 0, 0.1)',
|
||||
'pointBackgroundColor' => 'rgba(0, 0, 0, 0.1)',
|
||||
];
|
||||
|
||||
/** @var array $defaultAlphaValues Transparenz-Werte */
|
||||
protected static $defaultAlphaValues = [
|
||||
'borderColor' => 1.0,
|
||||
'backgroundColor' => 0.1,
|
||||
'pointBackgroundColor' => 1.0,
|
||||
];
|
||||
|
||||
protected $data;
|
||||
protected $label;
|
||||
protected $options;
|
||||
|
||||
/** @var bool $isDataAccumulated Gibt an ob Daten bereits kumuliert wurden */
|
||||
protected $isDataAccumulated = false;
|
||||
|
||||
/** @var bool $hasColorSet Gibt an ob bereits eine Farbe manuell gesetzt wurde */
|
||||
protected $hasColorAssigned = false;
|
||||
|
||||
/**
|
||||
* @param string $label
|
||||
* @param array $data
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($label, $data, array $options = [])
|
||||
{
|
||||
// Ungültige Werte in leeres Array wandeln
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
$this->label = $label;
|
||||
$this->options = array_replace(self::$defaultOptions, $options);
|
||||
|
||||
$defaultColor = new Color(0, 0, 0, 0.1);
|
||||
if (!is_object($this->options['borderColor'])) {
|
||||
$this->options['borderColor'] = $defaultColor;
|
||||
}
|
||||
if (!is_object($this->options['backgroundColor'])) {
|
||||
$this->options['backgroundColor'] = $defaultColor;
|
||||
}
|
||||
if (!is_object($this->options['pointBackgroundColor'])) {
|
||||
$this->options['pointBackgroundColor'] = $defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDataCount()
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Wurde bereits eine Farbe gesetzt
|
||||
*/
|
||||
public function hasColorAssigned()
|
||||
{
|
||||
return $this->hasColorAssigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function accumulateData()
|
||||
{
|
||||
if ($this->isDataAccumulated === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sum = 0;
|
||||
foreach ($this->data as $key => $value) {
|
||||
$sum += (float)$value;
|
||||
$this->data[$key] = $sum;
|
||||
}
|
||||
|
||||
$this->isDataAccumulated = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $lineStyle
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLineStyle($lineStyle)
|
||||
{
|
||||
if (!in_array($lineStyle, self::$validLineStyles, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Line style "%s" is not valid.', $lineStyle
|
||||
));
|
||||
}
|
||||
|
||||
if ($lineStyle === self::LINE_STYLE_DOTTED) {
|
||||
$this->options['borderDash'] = [1, 15];
|
||||
$this->options['borderCapStyle'] = 'round';
|
||||
}
|
||||
if ($lineStyle === self::LINE_STYLE_DASHED) {
|
||||
$this->options['borderDash'] = [15, 10];
|
||||
$this->options['borderCapStyle'] = 'butt';
|
||||
}
|
||||
if ($lineStyle === self::LINE_STYLE_SOLID) {
|
||||
$this->options['borderDash'] = [];
|
||||
$this->options['borderCapStyle'] = 'butt';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Color $color
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColor(Color $color)
|
||||
{
|
||||
$borderColor = clone $color;
|
||||
$backgroundColor = clone $color;
|
||||
$pointBackgroundColor = clone $color;
|
||||
|
||||
$borderColor->setAlpha($this->getDefaultAlphaValue('borderColor'));
|
||||
$backgroundColor->setAlpha($this->getDefaultAlphaValue('backgroundColor'));
|
||||
$pointBackgroundColor->setAlpha($this->getDefaultAlphaValue('pointBackgroundColor'));
|
||||
|
||||
$this->options['borderColor'] = $borderColor;
|
||||
$this->options['backgroundColor'] = $backgroundColor;
|
||||
$this->options['pointBackgroundColor'] = $pointBackgroundColor;
|
||||
|
||||
$this->hasColorAssigned = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $red
|
||||
* @param int $green
|
||||
* @param int $blue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColorByRgb($red, $green, $blue)
|
||||
{
|
||||
$this->setColor(new Color($red, $green, $blue, 1.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hexColor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColorByHex($hexColor)
|
||||
{
|
||||
$hexColor = str_replace('#', '', $hexColor);
|
||||
if (strlen($hexColor) !== 6) {
|
||||
throw new InvalidArgumentException('Only full length hex values are supported.');
|
||||
}
|
||||
|
||||
$parts = str_split($hexColor, 2);
|
||||
$red = hexdec($parts[0]);
|
||||
$green = hexdec($parts[1]);
|
||||
$blue = hexdec($parts[2]);
|
||||
|
||||
$this->setColorByRgb($red, $green, $blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $colorName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColorByName($colorName)
|
||||
{
|
||||
if (!isset(self::$colorMap[$colorName])) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Color name "%s" is not valid.', $colorName
|
||||
));
|
||||
}
|
||||
|
||||
$rgb = self::$colorMap[$colorName];
|
||||
$this->setColorByRgb(...$rgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zufällige ID generieren; wird für Multiple Axes Config benötigt
|
||||
*
|
||||
* @see http://www.chartjs.org/docs/latest/axes/cartesian/#axis-id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateYAxisId()
|
||||
{
|
||||
if (!isset($this->options['yAxisID'])) {
|
||||
$this->options['yAxisID'] = uniqid('', false);
|
||||
}
|
||||
|
||||
return $this->options['yAxisID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray()
|
||||
{
|
||||
$result = $this->options;
|
||||
$result['data'] = $this->data;
|
||||
if (!empty($this->label)) {
|
||||
$result['label'] = $this->label;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $colorType
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function getDefaultAlphaValue($colorType)
|
||||
{
|
||||
if (!isset(static::$defaultAlphaValues[$colorType])) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Wichtig: static nicht self!
|
||||
// Sonst hat Überschreiben von $defaultAlphaValues in abgeleiteten Klassen keine Auswirkung.
|
||||
return static::$defaultAlphaValues[$colorType];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
class HtmlRenderer
|
||||
{
|
||||
/** @var Chart $chart */
|
||||
protected $chart;
|
||||
|
||||
/** @var string $title */
|
||||
protected $title;
|
||||
|
||||
/** @var int $width */
|
||||
protected $width;
|
||||
|
||||
/** @var int $height */
|
||||
protected $height;
|
||||
|
||||
/** @var array $attributes */
|
||||
protected $attributes;
|
||||
|
||||
/**
|
||||
* @param Chart $chart
|
||||
* @param string $title
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param array $attributes Zusätzliche HTML-Attribute als assoziatives Array
|
||||
*/
|
||||
public function __construct(Chart $chart, $title = '', $width = 400, $height = 200, array $attributes = [])
|
||||
{
|
||||
$this->chart = $chart;
|
||||
$this->title = $title;
|
||||
$this->width = (int)$width;
|
||||
$this->height = (int)$height;
|
||||
$this->attributes = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return sprintf(
|
||||
'<div class="chart-wrapper" %s>%s' .
|
||||
' <div class="chart-content">' .
|
||||
' <canvas data-graph-id="%s" width="%s" height="%s"></canvas>' .
|
||||
' <script type="application/json">%s</script>' .
|
||||
' </div>' .
|
||||
'</div>',
|
||||
$this->renderAttributes(),
|
||||
$this->renderTitle(),
|
||||
uniqid(null, false),
|
||||
$this->width,
|
||||
$this->height,
|
||||
$this->chart->toJson()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function renderAttributes()
|
||||
{
|
||||
$result = '';
|
||||
foreach ($this->attributes as $key => $value) {
|
||||
$result .= sprintf(' %s="%s"', $key, $value);
|
||||
}
|
||||
|
||||
return trim($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function renderTitle()
|
||||
{
|
||||
if (empty($this->title)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf('<div class="chart-title">%s</div>', $this->title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
use DateInterval;
|
||||
use DatePeriod;
|
||||
use DateTimeInterface;
|
||||
|
||||
class PeriodMatcher
|
||||
{
|
||||
/** @var DateTimeInterface $start */
|
||||
protected $start;
|
||||
|
||||
/** @var DateTimeInterface $end */
|
||||
protected $end;
|
||||
|
||||
/** @var DateInterval $interval */
|
||||
protected $interval;
|
||||
|
||||
/** @var string $format */
|
||||
protected $format;
|
||||
|
||||
/** @var DatePeriod $range */
|
||||
protected $range;
|
||||
|
||||
/**
|
||||
* @param DateTimeInterface $start
|
||||
* @param DateTimeInterface $end
|
||||
* @param DateInterval $interval
|
||||
* @param string $format Formate der PHP date()-Funktion
|
||||
*/
|
||||
public function __construct(
|
||||
DateTimeInterface $start,
|
||||
DateTimeInterface $end,
|
||||
DateInterval $interval,
|
||||
$format = 'Y.m.d'
|
||||
) {
|
||||
$this->start = $start;
|
||||
$this->end = $end;
|
||||
$this->interval = $interval;
|
||||
$this->format = $format;
|
||||
$this->range = new DatePeriod($start, $interval, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param string $dateKey
|
||||
* @param string $valueKey
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function matchData($data, $dateKey, $valueKey)
|
||||
{
|
||||
if ($data === null) {
|
||||
$data = [];
|
||||
}
|
||||
$dates = array_column($data, $dateKey);
|
||||
$values = array_column($data, $valueKey);
|
||||
|
||||
$result = [];
|
||||
foreach ($this->getDates() as $date) {
|
||||
$matchedKey = array_search($date, $dates, true);
|
||||
$result[] = $matchedKey !== false ? (float)$values[$matchedKey] : 0.0;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $format Datumsformat überschreiben
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDates($format = null)
|
||||
{
|
||||
$result = [];
|
||||
|
||||
/** @var DateTimeInterface $date */
|
||||
foreach ($this->range as $date) {
|
||||
$result[] = $date->format($format !== null ? $format : $this->format);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Xentral\Widgets\Chart;
|
||||
|
||||
/**
|
||||
* Spezielles Dataset für Pie-/Doughnut-Charts
|
||||
*
|
||||
* Besonderheiten:
|
||||
* * Farben müssen als Array angegeben werden; pro Wert eine Farbe
|
||||
* * Strichbreiten ('borderWidth') müssen als Array angegeben werden; pro Wert eine Breite
|
||||
*
|
||||
* http://www.chartjs.org/docs/master/charts/doughnut.html#dataset-properties
|
||||
*/
|
||||
class PieDataset extends Dataset
|
||||
{
|
||||
/** @var array $defaultAlphaValues Transparenz-Werte */
|
||||
protected static $defaultAlphaValues = [
|
||||
'borderColor' => 1.0,
|
||||
'backgroundColor' => 1.0,
|
||||
'hoverBorderColor' => 1.0,
|
||||
];
|
||||
|
||||
/** @var int $colorPointer Merkt sich die zuletzt verwendete Default-Color */
|
||||
private $colorPointer = 0;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function __construct($label, $data, array $options = [])
|
||||
{
|
||||
parent::__construct($label, $data, $options);
|
||||
|
||||
$this->options['borderWidth'] = 3;
|
||||
|
||||
unset($this->options['pointBackgroundColor']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Color $color
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColor(Color $color)
|
||||
{
|
||||
$backgroundColor = clone $color;
|
||||
$borderColor = new Color(255, 255, 255);
|
||||
$hoverBorderColor = new Color(255, 255, 255);
|
||||
|
||||
$borderColor->setAlpha($this->getDefaultAlphaValue('borderColor'));
|
||||
$backgroundColor->setAlpha($this->getDefaultAlphaValue('backgroundColor'));
|
||||
$hoverBorderColor->setAlpha($this->getDefaultAlphaValue('hoverBorderColors'));
|
||||
|
||||
// Hintergrundfarbe als Array setzen
|
||||
// Pro Datensatz Farbe etwas heller machen
|
||||
$backgroundColors = [];
|
||||
$dataCount = $this->getDataCount();
|
||||
foreach ($this->data as $key => $value) {
|
||||
$difference = $key * 75 / $dataCount;
|
||||
$backgroundColorLighter = clone $backgroundColor;
|
||||
$backgroundColorLighter->makeLighter($difference);
|
||||
$backgroundColors[$key] = $backgroundColorLighter;
|
||||
}
|
||||
|
||||
$this->options['borderColor'] = $borderColor;
|
||||
$this->options['backgroundColor'] = $backgroundColors;
|
||||
$this->options['hoverBorderColor'] = $hoverBorderColor;
|
||||
|
||||
$this->hasColorAssigned = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hintergrundfarben pro Wert hinterlegen
|
||||
*
|
||||
* Die Anzahl der Farben sollte der Anzahl der Daten entsprechen
|
||||
*
|
||||
* @param array|string[] $colors Hexadecimal- oder RGB-Schreibweise
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setColors(array $colors)
|
||||
{
|
||||
// Rahmenfarbe auf Weiß setzen
|
||||
$borderColor = new Color(255, 255, 255);
|
||||
$hoverBorderColor = new Color(255, 255, 255);
|
||||
$borderColor->setAlpha($this->getDefaultAlphaValue('borderColor'));
|
||||
$hoverBorderColor->setAlpha($this->getDefaultAlphaValue('hoverBorderColors'));
|
||||
|
||||
// Nur Hintergrundfarbe/Füllfarbe variieren
|
||||
$backgroundColors = [];
|
||||
//foreach ($colors as $color) {
|
||||
foreach ($this->data as $key => $value) {
|
||||
$color = $colors[$key];
|
||||
if (strpos($color, '#', 0) === 0) {
|
||||
$backgroundColor = Color::createFromHex($color);
|
||||
} else {
|
||||
$rgb = $this->getNextDefaultColor();
|
||||
$backgroundColor = new Color($rgb[0], $rgb[1], $rgb[2], 1);
|
||||
}
|
||||
|
||||
$backgroundColor->setAlpha($this->getDefaultAlphaValue('backgroundColor'));
|
||||
$backgroundColors[] = $backgroundColor;
|
||||
}
|
||||
|
||||
$this->options['borderColor'] = $borderColor;
|
||||
$this->options['hoverBorderColor'] = $hoverBorderColor;
|
||||
$this->options['backgroundColor'] = $backgroundColors;
|
||||
|
||||
$this->hasColorAssigned = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array RGB-Array
|
||||
*/
|
||||
private function getNextDefaultColor()
|
||||
{
|
||||
$this->colorPointer++;
|
||||
if ($this->colorPointer >= count(self::$colorMap)) {
|
||||
$this->colorPointer = 0;
|
||||
}
|
||||
|
||||
$colors = array_values(self::$colorMap);
|
||||
|
||||
return $colors[$this->colorPointer];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user