added simple math and charts modules

This commit is contained in:
2024-12-10 23:53:23 +01:00
parent 62a5f3c97c
commit 688ff42752
196 changed files with 20285 additions and 8 deletions

View File

@@ -18,7 +18,9 @@
"require": { "require": {
"farmos/farmos": "^3.0", "farmos/farmos": "^3.0",
"oomphinc/composer-installers-extender": "^2.0", "oomphinc/composer-installers-extender": "^2.0",
"wikimedia/composer-merge-plugin": "^2.0" "wikimedia/composer-merge-plugin": "^2.0",
"drupal/views_simple_math_field": "^3.0",
"drupal/charts": "^5.1"
}, },
"minimum-stability": "dev", "minimum-stability": "dev",
"prefer-stable": true, "prefer-stable": true,

150
composer.lock generated
View File

@@ -4,8 +4,54 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "103f9b8db31610ca1687c620aeec857d", "content-hash": "b863e8929cb118de1242b4471666ad20",
"packages": [ "packages": [
{
"name": "andileco/eval-math",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/andileco/eval-math.git",
"reference": "52ff5728ad51961a630cc957328764cb02ab5c51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/andileco/eval-math/zipball/52ff5728ad51961a630cc957328764cb02ab5c51",
"reference": "52ff5728ad51961a630cc957328764cb02ab5c51",
"shasum": ""
},
"require": {
"php": "^7 || ^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2",
"phpunit/phpunit": "^5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Andileco\\Util\\EvalMath\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "EvalMath",
"keywords": [
"EvalMath"
],
"support": {
"issues": "https://github.com/andileco/eval-math/issues",
"source": "https://github.com/andileco/eval-math/tree/3.0.1"
},
"time": "2022-07-21T21:04:26+00:00"
},
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
"version": "v2.2.0", "version": "v2.2.0",
@@ -1339,6 +1385,58 @@
"issues": "https://www.drupal.org/project/issues/admin_toolbar" "issues": "https://www.drupal.org/project/issues/admin_toolbar"
} }
}, },
{
"name": "drupal/charts",
"version": "5.1.1",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/charts.git",
"reference": "5.1.1"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/charts-5.1.1.zip",
"reference": "5.1.1",
"shasum": "81529ec926462ddc62e9f9f78d47b036af566b6b"
},
"require": {
"drupal/core": "^10.3 || ^11"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "5.1.1",
"datestamp": "1733170631",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "andileco",
"homepage": "https://www.drupal.org/user/2054544"
},
{
"name": "nikathone",
"homepage": "https://www.drupal.org/user/2421800"
},
{
"name": "quicksketch",
"homepage": "https://www.drupal.org/user/35821"
}
],
"description": "A charting API for Drupal that provides chart elements and integration with Views.",
"homepage": "https://www.drupal.org/project/charts",
"support": {
"source": "https://git.drupalcode.org/project/charts"
}
},
{ {
"name": "drupal/config_update", "name": "drupal/config_update",
"version": "2.0.0-alpha4", "version": "2.0.0-alpha4",
@@ -3415,6 +3513,56 @@
"irc": "irc://irc.freenode.org/drupal-contribute" "irc": "irc://irc.freenode.org/drupal-contribute"
} }
}, },
{
"name": "drupal/views_simple_math_field",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/views_simple_math_field.git",
"reference": "3.0.2"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/views_simple_math_field-3.0.2.zip",
"reference": "3.0.2",
"shasum": "6620ee614bbaf68b9c26227e01c74299be47a6d8"
},
"require": {
"andileco/eval-math": "^3.0.1",
"drupal/core": "^8.7|^9.0 || ^10 || ^11"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "3.0.2",
"datestamp": "1719379402",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0+"
],
"authors": [
{
"name": "andileco",
"homepage": "https://www.drupal.org/user/2054544"
},
{
"name": "Beakerboy",
"homepage": "https://www.drupal.org/user/1700098"
}
],
"description": "Math operations over selected fields",
"homepage": "https://www.drupal.org/project/views_simple_math_field",
"support": {
"source": "http://cgit.drupalcode.org/views_simple_math_field",
"issues": "https://www.drupal.org/project/issues/views_simple_math_field"
}
},
{ {
"name": "drush/drush", "name": "drush/drush",
"version": "12.5.3", "version": "12.5.3",

7
vendor/andileco/eval-math/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/.idea
/vendor
/composer.lock
.project
.settings
.buildpath
/build

21
vendor/andileco/eval-math/.travis.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
language: php
before_script:
- composer self-update
- composer config platform.php $PHP_VERSION
- composer install --prefer-source --no-interaction --dev
matrix:
fast_finish: true
include:
- php: '5.6'
env: PHP_VERSION=5.6
- php: '7.0'
env: PHP_VERSION=7.0
- php: '7.1'
env: PHP_VERSION=7.1
- php: '7.2'
env: PHP_VERSION=7.2
after_success:
- php vendor/bin/php-coveralls -v

25
vendor/andileco/eval-math/LICENSE vendored Normal file
View File

@@ -0,0 +1,25 @@
LICENSE
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1 Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

78
vendor/andileco/eval-math/README.md vendored Normal file
View File

@@ -0,0 +1,78 @@
Composer/Packagist version of EvalMath by Miles Kaufman
Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
NAME
----
EvalMath - safely evaluate math expressions
DESCRIPTION
-----------
Use the EvalMath class when you want to evaluate mathematical expressions
from untrusted sources. You can define your own variables and functions,
which are stored in the object. Try it, it's fun!
SYNOPSIS
--------
`$m = new EvalMath;`
`// basic evaluation:`
`$result = $m->evaluate('2+2');`
`// supports: order of operation; parentheses; negation; built-in functions`
`$result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');`
`// create your own variables`
`$m->evaluate('a = e^(ln(pi))');`
`// or functions`
`$m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');`
`// and then use them`
`$result = $m->evaluate('3*f(42,a)');`
`// use methods (calc functions)
`$m->evaluate('1+max(2,3)') // => 4`
`$m->evaluate('if(1=2, 2+2, 5+5') // => 10
METHODS
-------
`$m->evaluate($expr)`
Evaluates the expression and returns the result. If an error occurs,
prints a warning and returns false. If $expr is a function assignment,
returns true on success.
`$m->e($expr)`
A synonym for $m->evaluate().
`$m->vars()`
Returns an associative array of all user-defined variables and values.
`$m->funcs()`
Returns an array of all user-defined functions.
CALC METHODS (CALC FUNCTIONS)
-----------------------------
- `max(n...,m)` returns one of given arguments with maximal value
- `min(n...,m)` returns one of given arguments with minimal value
- `if(expr, true_value, false_value)` (has a `iif` synonym) returns `true_value` of `false_value` depends of `expr` evaluation result
- `round(n,m)` returns rounded n value with m precision
CREATE YOUR OWN CUSTOM CALC METHODS (CALC FUNCTIONS)
-----------------------------------------------
You can create custom classes, that implements custom calc methods.
TODO
----
- Improve documentation
- Ability to add custom operators
- More tests
CREDITS AND COPYRIGHTS
----------------------
This software integrates several libraries and patches by:
- Original EvalMath library, (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
- Modifications for 'calc functions' by Moodle, <https://github.com/moodle/moodle>
- Composer/Packagist version, (C) Daniel Bojdo, <https://github.com/dbojdo>

26
vendor/andileco/eval-math/composer.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"name" : "andileco/eval-math",
"description" : "EvalMath",
"license": "BSD-3-Clause",
"type" : "library",
"keywords" : [
"EvalMath"
],
"require": {
"php": "^7 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^5",
"php-coveralls/php-coveralls": "^2"
},
"autoload" : {
"psr-4" : {
"Andileco\\Util\\EvalMath\\" : "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
}
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./vendor/autoload.php">
<testsuites>
<testsuite name="EvalMath Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="coverage-clover" target="build/logs/clover.xml" />
</logging>
</phpunit>

View File

@@ -0,0 +1,511 @@
<?php
/**
*
*/
namespace Andileco\Util\EvalMath;
use Andileco\Util\EvalMath\Exception\AbstractEvalMathException;
use Andileco\Util\EvalMath\Exception\BuiltInFunctionRedefinitionException;
use Andileco\Util\EvalMath\Exception\ConstantAssignmentException;
use Andileco\Util\EvalMath\Exception\DivisionByZeroException;
use Andileco\Util\EvalMath\Exception\ExpectingTokenException;
use Andileco\Util\EvalMath\Exception\IllegalCharacterException;
use Andileco\Util\EvalMath\Exception\InternalErrorException;
use Andileco\Util\EvalMath\Exception\InvalidArgumentCountException;
use Andileco\Util\EvalMath\Exception\OperatorLacksOperandException;
use Andileco\Util\EvalMath\Exception\OperatorRequiredException;
use Andileco\Util\EvalMath\Exception\UndefinedVariableException;
use Andileco\Util\EvalMath\Exception\UndefinedVariableInFunctionDefinitionException;
use Andileco\Util\EvalMath\Exception\UnexpectedOperatorException;
use Andileco\Util\EvalMath\Exception\UnexpectedTokenException;
use Andileco\Util\EvalMath\Methods\Conditional;
use Andileco\Util\EvalMath\Methods\Maximum;
use Andileco\Util\EvalMath\Methods\Minimum;
use Andileco\Util\EvalMath\Methods\Round;
/**
* Class EvalMath
*/
class EvalMath
{
const NAME_PATTERN = '[a-z][a-z0-9_]*';
/**
* @deprecated
* @var bool
*/
public $suppress_errors = false;
/**
* @var string
*/
public $last_error = null;
/**
* @var array
*/
public $v = ['e'=>2.71,'pi'=>3.14]; // variables (and constants)
/**
* @var array
*/
public $f = []; // user-defined functions
/**
* @var array
*/
public $vb = ['e', 'pi']; // constants
/**
* @var array
*/
public $fb = [ // built-in functions
'sin','sinh','arcsin','asin','arcsinh','asinh',
'cos','cosh','arccos','acos','arccosh','acosh',
'tan','tanh','arctan','atan','arctanh','atanh',
'sqrt','abs','ln','log'
];
// Calc functions
// public $fc =['average'=>[-1], 'max'=>[-1], 'min'=>[-1], 'iif'=>[3]];
/** @var MethodsRegistry */
protected $fc;
public function __construct()
{
// make the variables a little more accurate
$this->v['pi'] = pi();
$this->v['e'] = exp(1);
$this->fc = (new MethodsRegistry)
->set(new Conditional)
->set((new Conditional)->setName('iif'))
->set(new Maximum)
->set(new Minimum)
->set(new Round);
}
/**
* @return MethodsRegistry
*/
public function getMethodsRegistry()
{
return $this->fc;
}
/**
* @param string $expr
* @return mixed
* @throws BuiltInFunctionRedefinitionException
* @throws ConstantAssignmentException
* @throws DivisionByZeroException
* @throws ExpectingTokenException
* @throws IllegalCharacterException
* @throws InternalErrorException
* @throws InvalidArgumentCountException
* @throws OperatorLacksOperandException
* @throws OperatorRequiredException
* @throws UndefinedVariableException
* @throws UndefinedVariableInFunctionDefinitionException
* @throws UnexpectedOperatorException
* @throws UnexpectedTokenException
*/
public function e($expr)
{
return $this->evaluate($expr);
}
/**
* @param string $expr
* @return mixed
* @throws BuiltInFunctionRedefinitionException
* @throws ConstantAssignmentException
* @throws DivisionByZeroException
* @throws ExpectingTokenException
* @throws IllegalCharacterException
* @throws InternalErrorException
* @throws InvalidArgumentCountException
* @throws OperatorLacksOperandException
* @throws OperatorRequiredException
* @throws UndefinedVariableException
* @throws UndefinedVariableInFunctionDefinitionException
* @throws UnexpectedOperatorException
* @throws UnexpectedTokenException
*/
public function evaluate($expr)
{
$this->last_error = null;
$expr = rtrim(trim($expr), ';'); // strip semicolons at the end
//===============
// is it a variable assignment?
if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
throw new ConstantAssignmentException([':constant' => $matches[1]]);
}
if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
$this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
return $this->v[$matches[1]]; // and return the resulting value
//===============
// is it a function assignment?
} elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
$fnn = $matches[1]; // get the function name
if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
throw new BuiltInFunctionRedefinitionException([':function' =>$matches[1]]);
}
$args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
$token = $stack[$i];
if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
if (array_key_exists($token, $this->v)) {
$stack[$i] = $this->v[$token];
} else {
throw new UndefinedVariableInFunctionDefinitionException([':token'=>$token]);
}
}
}
$this->f[$fnn] = ['args'=>$args, 'func'=>$stack];
return true;
//===============
} else {
return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
}
}
/**
* @return array
*/
public function vars()
{
$output = $this->v;
unset($output['pi'], $output['e']);
return $output;
}
/**
* @return array
*/
public function funcs()
{
$output = [];
foreach ($this->f as $fnn=>$dat)
$output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
return $output;
}
//===================== HERE BE INTERNAL METHODS ====================\\
/**
* Convert infix to postfix notation
*
* @param $expr
* @return array|bool
* @throws ExpectingTokenException
* @throws IllegalCharacterException
* @throws InternalErrorException
* @throws InvalidArgumentCountException
* @throws OperatorLacksOperandException
* @throws OperatorRequiredException
* @throws UnexpectedOperatorException
* @throws UnexpectedTokenException
*/
public function nfx($expr)
{
$index = 0;
$stack = new Stack();
$output = []; // postfix form of expression, to be passed to pfx()
// $expr = trim(strtolower($expr));
$expr = trim($expr);
$ops = ['+', '-', '*', '/', '^', '_', '%', '>', '<', '<=', '>=', '=='];
$ops_r = ['+' => 0, '-' => 0, '*' => 0, '/' => 0, '^' => 1, '%' => 0]; // right-associative operator?
$ops_p = ['+' => 0, '-' => 0, '*' => 1, '/' => 1, '_' => 1, '^' => 2, '%' => 1, '>' => 3, '<' => 3, '<=' => 3, '>=' => 3, '==' => 3]; // operator precedence
$expecting_op = false; // we use this in syntax-checking the expression
// and determining when a - is a negation
if (preg_match('/[^\%\w\s+*^\/()\.,-<>=]/', $expr, $matches)) { // make sure the characters are all good
throw new IllegalCharacterException([':character'=>$matches[0]]);
}
while(1) { // 1 Infinite Loop ;)
$op = substr($expr, $index, 2);
if (!in_array($op, $ops)) {
$op = substr($expr, $index, 1);
}
// find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
// $ex = preg_match('/^([A-Za-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
$ex = preg_match('/^('.static::NAME_PATTERN.'\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr($expr, $index), $match);
//===============
if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
$stack->push('_'); // put a negation on the stack
$index++;
} elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
throw new IllegalCharacterException([':character'=>'_']);
//===============
} elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
throw new OperatorRequiredException();
/*
* Implicit multiplication currently is not supported
*
$op = '*';
$index--; // it's an implicit multiplication
*/
}
// heart of the algorithm:
while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
$output[] = $stack->pop(); // pop stuff off the stack into the output
}
// many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
$stack->push($op); // finally put OUR operator onto the stack
$index += strlen($op);
$expecting_op = false;
//===============
} elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
if (null === $o2) {
throw new UnexpectedTokenException(['token'=>')']);
} else {
$output[] = $o2;
}
}
if (preg_match("/^([A-Za-z]\w*)\($/", $stack->last(2) ?? '', $matches)) { // did we just close a function?
$fnn = $matches[1]; // get the function name
$arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
$fn = $stack->pop();
$output[] = ['fn' => $fn, 'fnn' => $fnn, 'argcount' => $arg_count]; // send function to output
if (in_array($fnn, $this->fb)) { // check the argument count
if ($arg_count > 1) {
throw new InvalidArgumentCountException();
}
} elseif (isset($this->fc[$fnn])) {
$counts = $this->fc[$fnn];
if (in_array(-1, $counts) and $arg_count > 0) {
} elseif (!in_array($arg_count, $counts)) {
throw new InvalidArgumentCountException();
}
} elseif (array_key_exists($fnn, $this->f)) {
if ($arg_count != count($this->f[$fnn]['args'])) {
throw new InvalidArgumentCountException();
}
} else { // did we somehow push a non-function on the stack? this should never happen
throw new InternalErrorException();
}
}
$index++;
//===============
} elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
while (($o2 = $stack->pop()) != '(') {
if (null === $o2) {
throw new UnexpectedTokenException([':token'=>',']);
} // oops, never had a (
else {
$output[] = $o2;
} // pop the argument expression stuff and push onto the output
}
// make sure there was a function
// if (!preg_match("/^([A-Za-z]\w*)\($/", $stack->last(2), $matches)) {
if (!preg_match('/^('.self::NAME_PATTERN.')\($/', $stack->last(2) ?? '', $matches)) {
throw new UnexpectedTokenException([':token'=>',']);
}
$stack->push($stack->pop()+1); // increment the argument count
$stack->push('('); // put the ( back on, we'll need to pop back to it again
$index++;
$expecting_op = false;
//===============
} elseif ($op == '(' and !$expecting_op) {
$stack->push('('); // that was easy
$index++;
$allow_neg = true;
//===============
} elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
$expecting_op = true;
$val = $match[1];
// if (preg_match("/^([A-Za-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
if (preg_match('/^('.static::NAME_PATTERN.')\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f) or isset($this->fc[$matches[1]])) { // it's a func
$stack->push($val);
$stack->push(1);
$stack->push('(');
$expecting_op = false;
} else { // it's a var w/ implicit multiplication
$val = $matches[1];
$output[] = $val;
}
} else { // it's a plain old var or num
$output[] = $val;
}
$index += strlen($val);
//===============
} elseif ($op == ')') { // miscellaneous error checking
if ($stack->last() != '(' or $stack->last(2) != 1) {
throw new UnexpectedTokenException([':token' => ')']);
}
// did we just close a function?
if (preg_match('/^(' . static::NAME_PATTERN . ')\($/', $stack->last(3) ?? '', $matches)) {
$stack->pop();// (
$stack->pop();// 1
$fn = $stack->pop();
$fnn = $matches[1]; // get the function name
$counts = $this->fc[$fnn];
if (!in_array(0, $counts)) {
throw new InvalidArgumentCountException([':given' => 0, ':accept' => $counts]);
}
$output[] = ['fn' => $fn, 'fnn' => $fnn, 'argcount' => 0]; // send function to output
$index++;
$expecting_op = true;
}
throw new UnexpectedTokenException([':token' => ')']);
} elseif (in_array($op, $ops) and !$expecting_op) {
throw new UnexpectedOperatorException([':operator'=>$op]);
} else { // I don't even want to know what you did to get here
throw new InternalErrorException();
}
if ($index == strlen($expr)) {
if (in_array($op, $ops)) { // did we end with an operator? bad.
throw new OperatorLacksOperandException([':operator' => $op]);
}
break;
}
while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
$index++; // into implicit multiplication if no operator is there)
}
}
while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
if ($op == '(') { // if there are (s on the stack, ()s were unbalanced
throw new ExpectingTokenException([':token' => ')']);
}
$output[] = $op;
}
return $output;
}
/**
* evaluate postfix notation
*
* @param $tokens
* @param array $vars
* @return bool|mixed|null
* @throws DivisionByZeroException
* @throws InternalErrorException
* @throws UndefinedVariableException
*/
public function pfx($tokens, $vars = [])
{
if ($tokens == false) {
return false;
}
$stack = new Stack();
foreach ($tokens as $token) { // nice and easy
if(is_array($token)) { // it's a function
$fnn = $token['fnn'];
$argcount = $token['argcount'];
if (in_array($fnn, $this->fb)) { // built-in function
if (is_null($op1 = $stack->pop())) {
throw new InternalErrorException();
}
$fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
if ($fnn == 'ln') {
$fnn = 'log';
}
eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
} elseif (($m = $this->fc->findByName($fnn))) {
$args = [];
for ($i = $argcount-1; $i >= 0; $i--) {
if (null === ($args[] = $stack->pop())) {
throw new InternalErrorException();
}
}
$res = $m->evaluate(...array_reverse($args));
if ($res === FALSE) {
throw new InternalErrorException();
}
$stack->push($res);
}elseif (array_key_exists($fnn, $this->f)) { // user function
// get args
$args = [];
for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
if ( null === ($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) {
throw new InternalErrorException();
}
}
$stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
}
// if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
} elseif (in_array($token, ['+', '-', '*', '/', '^', '>', '<', '==', '<=', '>=', '%'], true)) {
if (is_null($op2 = $stack->pop())) {
throw new InternalErrorException();
}
if (is_null($op1 = $stack->pop())) {
throw new InternalErrorException();
}
switch ($token) {
case '+':
$stack->push($op1 + $op2);
break;
case '-':
$stack->push($op1 - $op2);
break;
case '*':
$stack->push($op1 * $op2);
break;
case '/':
if ($op2 == 0) {
throw new DivisionByZeroException();
}
$stack->push($op1 / $op2);
break;
case '^':
$stack->push(pow($op1, $op2));
break;
case '>':
$stack->push((int)($op1 > $op2));
break;
case '<':
$stack->push((int)($op1 < $op2));
break;
case '==':
$stack->push((int)($op1 == $op2));
break;
case '<=':
$stack->push((int)($op1 <= $op2));
break;
case '>=':
$stack->push((int)($op1 >= $op2));
break;
case '%':
$stack->push($op1 % $op2);
break;
}
// if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
} elseif ($token == "_") {
$stack->push(-1*$stack->pop());
// if the token is a number or variable, push it on the stack
} else {
if (is_numeric($token)) {
$stack->push($token);
} elseif (array_key_exists($token, $this->v)) {
$stack->push($this->v[$token]);
} elseif (array_key_exists($token, $vars)) {
$stack->push($vars[$token]);
} else {
throw new UndefinedVariableException();
}
}
} // when we're out of tokens, the stack should have a single element, the final result
if ($stack->count != 1) {
throw new InternalErrorException();
}
return $stack->pop();
}
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
use Exception;
class AbstractEvalMathException extends Exception implements EvalMathException
{
protected $error = '';
public function __construct(array $parameters=[])
{
$message = strtr($this->error, $parameters);
parent::__construct($message);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class BuiltInFunctionRedefinitionException extends AbstractEvalMathException
{
protected $message = 'Cannot redefine built-in function :function()';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class ConstantAssignmentException extends AbstractEvalMathException
{
protected $message = 'Cannot assign to constant :constant';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class DivisionByZeroException extends AbstractEvalMathException
{
protected $message = 'Division by zero';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
interface EvalMathException
{
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class ExpectingTokenException extends AbstractEvalMathException
{
protected $message = "Expecting ':token'";
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class IllegalCharacterException extends AbstractEvalMathException
{
protected $message = "illegal character ':character'";
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class InternalErrorException extends AbstractEvalMathException
{
protected $message = 'Internal error';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class InvalidArgumentCountException extends AbstractEvalMathException
{
protected $message = 'Wrong number of arguments';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class OperatorLacksOperandException extends AbstractEvalMathException
{
protected $message = "operator ':operator' lacks operand";
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class OperatorRequiredException extends AbstractEvalMathException
{
protected $message = 'Operator required';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class UndefinedVariableException extends AbstractEvalMathException
{
protected $message = "Undefined variable";
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class UndefinedVariableInFunctionDefinitionException extends AbstractEvalMathException
{
protected $message = 'Undefined variable :token in function definition';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class UnexpectedOperatorException extends AbstractEvalMathException
{
protected $message = 'Unexpected operator \':operator\'';
}

View File

@@ -0,0 +1,14 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Exception;
class UnexpectedTokenException extends AbstractEvalMathException
{
protected $message = 'Unexpected \':token\'';
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Methods;
abstract class AbstractMethod
{
protected $name = '';
protected $argument_count = [-1];
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
return clone $this;
}
public function getArgumentCount()
{
return $this->argument_count;
}
abstract public function evaluate(...$args);
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Methods;
class Conditional extends AbstractMethod
{
protected $name='if';
protected $argument_count = [3];
public function evaluate(...$args)
{
return $args[0] ? $args[1] : $args[2];
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Methods;
class Maximum extends AbstractMethod
{
protected $name='max';
public function evaluate(...$args)
{
return max($args);
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Methods;
class Minimum extends AbstractMethod
{
protected $name = 'min';
public function evaluate(...$args)
{
return min($args);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Andileco\Util\EvalMath\Methods;
class Round extends AbstractMethod
{
protected $name = 'round';
public function evaluate(...$args)
{
if (!isset($args[1])) {
return round($args[0]);
}
return round($args[0], $args[1]);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath;
use ArrayAccess;
use Countable;
use RuntimeException;
use Andileco\Util\EvalMath\Methods\AbstractMethod;
class MethodsRegistry implements ArrayAccess, Countable
{
protected $methods = [];
public function set(AbstractMethod $method)
{
$this->methods[$method->getName()] = clone $method;
return $this;
}
public function unsetByName($name)
{
unset($this->methods[$name]);
return $this;
}
/**
* @param $name
* @return AbstractMethod|null
*/
public function findByName($name)
{
return (isset($this->methods[$name]) and $this->methods[$name] instanceof AbstractMethod) ? $this->methods[$name] : null;
}
public function offsetExists($offset): bool
{
return isset($this->methods[$offset]);
}
public function offsetGet($offset): mixed
{
$m = $this->findByName($offset);
return $m ? $m->getArgumentCount() : null;
}
public function offsetSet($offset, $value): void
{
throw new RuntimeException('Use set() method instead');
}
public function offsetUnset($offset): void
{
$this->unsetByName($offset);
}
public function count(): int
{
return count($this->methods);
}
}

45
vendor/andileco/eval-math/src/Stack.php vendored Normal file
View File

@@ -0,0 +1,45 @@
<?php
/**
*
*/
namespace Andileco\Util\EvalMath;
/**
* Class Stack
*/
class Stack
{
/**
* @var array
*/
public $stack = array();
/**
* @var int
*/
public $count = 0;
public function push($val)
{
$this->stack[$this->count] = $val;
$this->count++;
}
public function pop()
{
if ($this->count > 0) {
$this->count--;
return $this->stack[$this->count];
}
return null;
}
public function last($n=1)
{
$key = $this->count - $n;
return array_key_exists($key,$this->stack) ? $this->stack[$key] : null;
}
}

View File

@@ -0,0 +1,216 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use Andileco\Util\EvalMath\EvalMath;
use Andileco\Util\EvalMath\Exception\AbstractEvalMathException;
use Andileco\Util\EvalMath\Exception\BuiltInFunctionRedefinitionException;
use Andileco\Util\EvalMath\Exception\ConstantAssignmentException;
use Andileco\Util\EvalMath\Exception\DivisionByZeroException;
use Andileco\Util\EvalMath\Exception\ExpectingTokenException;
use Andileco\Util\EvalMath\Exception\IllegalCharacterException;
use Andileco\Util\EvalMath\Exception\InternalErrorException;
use Andileco\Util\EvalMath\Exception\InvalidArgumentCountException;
use Andileco\Util\EvalMath\Exception\OperatorLacksOperandException;
use Andileco\Util\EvalMath\Exception\OperatorRequiredException;
use Andileco\Util\EvalMath\Exception\UndefinedVariableException;
use Andileco\Util\EvalMath\Exception\UndefinedVariableInFunctionDefinitionException;
use Andileco\Util\EvalMath\Exception\UnexpectedOperatorException;
use Andileco\Util\EvalMath\Exception\UnexpectedTokenException;
class EvalMathExceptionsTest extends TestCase
{
private $EvalMath;
protected function setUp()
{
$this->EvalMath = new EvalMath();
}
/**
* @throws AbstractEvalMathException
*/
public function testBuiltInFunctionRedefinition()
{
$this->expectException(BuiltInFunctionRedefinitionException::class);
$this->EvalMath->evaluate('cos(x) = x*2');
}
/**
* @throws AbstractEvalMathException
*/
public function testConstantAssignmentException()
{
$this->expectException(ConstantAssignmentException::class);
$this->EvalMath->evaluate('pi = 123');
}
/**
* @throws AbstractEvalMathException
*/
public function testDivisionByZero()
{
$this->expectException(DivisionByZeroException::class);
$this->EvalMath->evaluate('5/0');
}
/**
* @throws AbstractEvalMathException
*/
public function testExpectingTokenException()
{
$this->expectException(ExpectingTokenException::class);
$this->EvalMath->evaluate('p = 6*(3+1');
}
/**
* @throws AbstractEvalMathException
*/
public function testIllegalCharacterException()
{
$this->expectException(IllegalCharacterException::class);
$this->EvalMath->evaluate('$v = 5');
}
/**
* @throws AbstractEvalMathException
*/
public function testInternalErrorException()
{
$this->expectException(InternalErrorException::class);
$this->EvalMath->e('k=');
}
/**
* @throws AbstractEvalMathException
*/
public function testInvalidArgumentCountException()
{
$this->expectException(InvalidArgumentCountException::class);
$this->EvalMath->e('a=sin(5,7,6)');
}
/**
* @throws AbstractEvalMathException
*/
public function testInvalidArgumentFuncCountException()
{
$this->expectException(InvalidArgumentCountException::class);
$this->EvalMath->e('iif(1)');
}
/**
* @throws AbstractEvalMathException
*/
public function testInvalidArgumentFuncCountException2()
{
$this->expectException(InvalidArgumentCountException::class);
$this->EvalMath->e('iif()');
}
/**
* @throws AbstractEvalMathException
*/
public function testInvalidArgumentUserFuncCountException()
{
$this->expectException(InvalidArgumentCountException::class);
$this->EvalMath->e('f(a,b) = a+b');
$this->EvalMath->e('f(1)');
}
/**
* @throws AbstractEvalMathException
*/
public function testOperatorLacksOperandException()
{
$this->expectException(OperatorLacksOperandException::class);
$this->EvalMath->e('k=5+');
}
/**
* @throws AbstractEvalMathException
*/
public function testOperatorRequiredException()
{
$this->expectException(OperatorRequiredException::class);
$this->EvalMath->e('a=5(7+1)');
}
/**
* @throws AbstractEvalMathException
*/
public function testUndefinedVariableException()
{
$this->expectException(UndefinedVariableException::class);
$this->EvalMath->e('b=a+1');
}
/**
* @throws AbstractEvalMathException
*/
public function testUndefinedVariableInFunctionDefinitionException()
{
$this->expectException(UndefinedVariableInFunctionDefinitionException::class);
$this->EvalMath->e('f(a) = b*3');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedOperatorException()
{
$this->expectException(UnexpectedOperatorException::class);
$this->EvalMath->e('1 + * 2');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedTokenException()
{
$this->expectException(UnexpectedTokenException::class);
$this->EvalMath->e('a=(3+)');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedClosingPrenticeException()
{
$this->expectException(UnexpectedTokenException::class);
$this->EvalMath->e('a=5)');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedComma()
{
$this->expectException(UnexpectedTokenException::class);
$this->EvalMath->e('a=5,3)');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedComma2()
{
$this->expectException(UnexpectedTokenException::class);
$this->EvalMath->e('a=sin(0.3),3)');
}
/**
* @throws AbstractEvalMathException
*/
public function testUnexpectedComma3()
{
$this->expectException(UnexpectedTokenException::class);
$this->EvalMath->e('a=sin(0.1,)');
}
}

View File

@@ -0,0 +1,140 @@
<?php
/**
* EvalMathTest.php
*
* @author dbojdo - Daniel Bojdo <daniel.bojdo@8x8.com>
* Created on 02 12, 2016, 17:17
* Copyright (C) 8x8
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use Andileco\Util\EvalMath\EvalMath;
class EvalMathTest extends TestCase
{
/**
* @var EvalMath
*/
private $evalMath;
protected function setUp()
{
$this->evalMath = new EvalMath();
}
/**
* @test
* @dataProvider moduloOperatorData
*/
public function shouldSupportModuloOperator($formula, $values, $expectedResult)
{
foreach ($values as $k => $v) {
$this->evalMath->v[$k] = $v;
}
$this->assertEquals($expectedResult, $this->evalMath->evaluate($formula));
}
public function moduloOperatorData()
{
return array(
array(
'a%b', // 9%3 => 0
array('a' => 9, 'b' => 3),
0
),
array(
'a%b', // 10%3 => 1
array('a' => 10, 'b' => 3),
1
),
array(
'10-a%(b+c*d)', // 10-10%(7-2*2) => 9
array('a' => '10', 'b' => 7, 'c'=> -2, 'd' => 2),
9
)
);
}
/**
* @test
* @dataProvider doubleMinusData
*/
public function shouldConsiderDoubleMinusAsPlus($formula, $values, $expectedResult)
{
foreach ($values as $k => $v) {
$this->evalMath->v[$k] = $v;
}
$this->assertEquals(
$expectedResult,
$this->evalMath->evaluate($formula)
);
}
public function doubleMinusData()
{
return array(
array(
'a+b*c--d', // 1+2*3--4 => 1+6+4 => 11
array(
'a' => 1,
'b' => 2,
'c' => 3,
'd' => 4
),
11
),
array(
'a+b*c--d', // 1+2*3---4 => 1+6-4 => 3
array(
'a' => 1,
'b' => 2,
'c' => 3,
'd' => -4
),
3
)
);
}
/**
* @dataProvider maximumsData
*/
public function testMaxCalcFunction($formula, $expected)
{
$this->assertEquals($expected, $this->evalMath->e($formula));
}
public function maximumsData()
{
return [
['max(1,2,3,4,5)', 5],
['max(5,2,3,1,4)', 5],
['max(5)', 5],
['max(5,5)', 5]
];
}
/**
* @dataProvider comparisonsData
*/
public function testComparisonOperators($formula, $expected)
{
$this->assertEquals($expected, $this->evalMath->e($formula));
}
public function comparisonsData()
{
return [
['1==1', 1],
['1==2', 0],
['1>2', 0],
['1<2', 1],
['1>=1', 1],
['1<=2', 1]
];
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Andileco\Util\EvalMath\Methods\Maximum;
use Andileco\Util\EvalMath\MethodsRegistry;
class MethodRegistryTest extends TestCase
{
private $registry;
protected function setUp()
{
$this->registry = new MethodsRegistry();
}
public function testSetFindUnset()
{
$r = $this->registry->set(new Maximum);
$this->assertInstanceOf(MethodsRegistry::class, $r);
$c = $this->registry->findByName('max');
$this->assertInstanceOf(Maximum::class, $c);
$this->assertTrue(isset($this->registry['max']));
$c = $this->registry['max'];
$this->assertTrue(is_array($c));
// $this->assertInstanceOf(Maximum::class, $c);
$this->assertEquals(1, $this->registry->count());
unset($this->registry['max']);
$this->assertEquals(0, $this->registry->count());
$r = $this->registry->unsetByName('max');
$this->assertInstanceOf(MethodsRegistry::class, $r);
}
/**
* @expectedException RuntimeException
*/
public function testOffsetSetException()
{
$this->registry['max'] = new Maximum();
}
public function testAliases()
{
$this->registry = new MethodsRegistry();
$this->registry->set(new Maximum())->set((new Maximum)->setName('mmaaxx'));
$this->assertEquals(2, $this->registry->count());
$this->assertTrue(isset($this->registry['max']));
$this->assertTrue(isset($this->registry['mmaaxx']));
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use Andileco\Util\EvalMath\Methods\Conditional;
use Andileco\Util\EvalMath\Methods\Maximum;
use Andileco\Util\EvalMath\Methods\Minimum;
use Andileco\Util\EvalMath\Methods\Round;
class MethodsTest extends TestCase
{
public function testConditional()
{
$m = new Conditional();
$this->assertEquals('if', $m->getName());
$this->assertEquals([3], $m->getArgumentCount());
$this->assertEquals(1, $m->evaluate(true, 1, 0));
$this->assertEquals(0, $m->evaluate(false, 1, 0));
}
public function testMaximum()
{
$m = new Maximum();
$this->assertEquals('max', $m->getName());
$this->assertEquals([-1], $m->getArgumentCount());
$this->assertEquals(1, $m->evaluate(1, 0));
$this->assertEquals(10, $m->evaluate(false, 1, 0, 10));
$this->assertEquals(1, $m->evaluate(1));
}
public function testMinimum()
{
$m = new Minimum();
$this->assertEquals('min', $m->getName());
$this->assertEquals([-1], $m->getArgumentCount());
$this->assertEquals(0, $m->evaluate(1, 0));
$this->assertEquals(0, $m->evaluate(false, 1, 0, 10));
$this->assertEquals(1, $m->evaluate(1));
}
public function testRound()
{
$m = new Round();
$this->assertEquals('round', $m->getName());
$this->assertEquals([-1], $m->getArgumentCount());
$this->assertEquals(2, $m->evaluate(1.5321));
$this->assertEquals(1, $m->evaluate(1.4321));
$this->assertEquals(1, $m->evaluate(1.4921));
$this->assertEquals(git add .1.57, $m->evaluate(1.5678, 2));
$this->assertEquals(1.568, $m->evaluate(1.5678, 3));
$this->assertEquals(1.5678, $m->evaluate(1.5678, 4));
$this->assertEquals(1.54, $m->evaluate(1.5378, 2));
$this->assertEquals(1.538, $m->evaluate(1.5378, 3));
$this->assertEquals(1.5378, $m->evaluate(1.5378, 4));
$this->assertEquals(1.53, $m->evaluate(1.5321, 2));
$this->assertEquals(1.532, $m->evaluate(1.5321, 3));
$this->assertEquals(1.5321, $m->evaluate(1.5321, 4));
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use Andileco\Util\EvalMath\EvalMath;
class UserFunctionsTest extends TestCase
{
public function testFunctionsDump()
{
$e = new EvalMath();
$this->assertEmpty($e->funcs());
$e->e('f(x)=2*x');
$this->assertEquals(1, count($e->funcs()));
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @author Serge Rodovnichenko <serge@syrnik.com>
* @copyright Serge Rodovnichenko, 2019
* @license BSD 2.0
*/
namespace Andileco\Util\EvalMath\Tests;
use PHPUnit\Framework\TestCase;
use Andileco\Util\EvalMath\EvalMath;
class UserVariablesTest extends TestCase
{
public function testVariablesDump()
{
$e = new EvalMath();
$this->assertEmpty($e->vars());
$e->evaluate('a=1');
$this->assertArrayHasKey('a',$e->vars());
$this->assertEquals('1', $e->vars()['a']);
}
}

View File

@@ -120,4 +120,5 @@ return array(
'Behat\\Mink\\Driver\\' => array($vendorDir . '/behat/mink-browserkit-driver/src', $vendorDir . '/behat/mink-selenium2-driver/src'), 'Behat\\Mink\\Driver\\' => array($vendorDir . '/behat/mink-browserkit-driver/src', $vendorDir . '/behat/mink-selenium2-driver/src'),
'Behat\\Mink\\' => array($vendorDir . '/behat/mink/src'), 'Behat\\Mink\\' => array($vendorDir . '/behat/mink/src'),
'Asm89\\Stack\\' => array($vendorDir . '/asm89/stack-cors/src'), 'Asm89\\Stack\\' => array($vendorDir . '/asm89/stack-cors/src'),
'Andileco\\Util\\EvalMath\\' => array($vendorDir . '/andileco/eval-math/src'),
); );

View File

@@ -210,6 +210,7 @@ class ComposerStaticInit103f9b8db31610ca1687c620aeec857d
'A' => 'A' =>
array ( array (
'Asm89\\Stack\\' => 12, 'Asm89\\Stack\\' => 12,
'Andileco\\Util\\EvalMath\\' => 23,
), ),
); );
@@ -675,6 +676,10 @@ class ComposerStaticInit103f9b8db31610ca1687c620aeec857d
array ( array (
0 => __DIR__ . '/..' . '/asm89/stack-cors/src', 0 => __DIR__ . '/..' . '/asm89/stack-cors/src',
), ),
'Andileco\\Util\\EvalMath\\' =>
array (
0 => __DIR__ . '/..' . '/andileco/eval-math/src',
),
); );
public static $prefixesPsr0 = array ( public static $prefixesPsr0 = array (

View File

@@ -1,5 +1,54 @@
{ {
"packages": [ "packages": [
{
"name": "andileco/eval-math",
"version": "3.0.1",
"version_normalized": "3.0.1.0",
"source": {
"type": "git",
"url": "https://github.com/andileco/eval-math.git",
"reference": "52ff5728ad51961a630cc957328764cb02ab5c51"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/andileco/eval-math/zipball/52ff5728ad51961a630cc957328764cb02ab5c51",
"reference": "52ff5728ad51961a630cc957328764cb02ab5c51",
"shasum": ""
},
"require": {
"php": "^7 || ^8.0"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2",
"phpunit/phpunit": "^5"
},
"time": "2022-07-21T21:04:26+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Andileco\\Util\\EvalMath\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "EvalMath",
"keywords": [
"EvalMath"
],
"support": {
"issues": "https://github.com/andileco/eval-math/issues",
"source": "https://github.com/andileco/eval-math/tree/3.0.1"
},
"install-path": "../andileco/eval-math"
},
{ {
"name": "asm89/stack-cors", "name": "asm89/stack-cors",
"version": "v2.2.0", "version": "v2.2.0",
@@ -1855,6 +1904,61 @@
}, },
"install-path": "../../web/modules/admin_toolbar" "install-path": "../../web/modules/admin_toolbar"
}, },
{
"name": "drupal/charts",
"version": "5.1.1",
"version_normalized": "5.1.1.0",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/charts.git",
"reference": "5.1.1"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/charts-5.1.1.zip",
"reference": "5.1.1",
"shasum": "81529ec926462ddc62e9f9f78d47b036af566b6b"
},
"require": {
"drupal/core": "^10.3 || ^11"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "5.1.1",
"datestamp": "1733170631",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0-or-later"
],
"authors": [
{
"name": "andileco",
"homepage": "https://www.drupal.org/user/2054544"
},
{
"name": "nikathone",
"homepage": "https://www.drupal.org/user/2421800"
},
{
"name": "quicksketch",
"homepage": "https://www.drupal.org/user/35821"
}
],
"description": "A charting API for Drupal that provides chart elements and integration with Views.",
"homepage": "https://www.drupal.org/project/charts",
"support": {
"source": "https://git.drupalcode.org/project/charts"
},
"install-path": "../../web/modules/charts"
},
{ {
"name": "drupal/coder", "name": "drupal/coder",
"version": "8.3.26", "version": "8.3.26",
@@ -4111,6 +4215,59 @@
}, },
"install-path": "../../web/modules/views_geojson" "install-path": "../../web/modules/views_geojson"
}, },
{
"name": "drupal/views_simple_math_field",
"version": "3.0.2",
"version_normalized": "3.0.2.0",
"source": {
"type": "git",
"url": "https://git.drupalcode.org/project/views_simple_math_field.git",
"reference": "3.0.2"
},
"dist": {
"type": "zip",
"url": "https://ftp.drupal.org/files/projects/views_simple_math_field-3.0.2.zip",
"reference": "3.0.2",
"shasum": "6620ee614bbaf68b9c26227e01c74299be47a6d8"
},
"require": {
"andileco/eval-math": "^3.0.1",
"drupal/core": "^8.7|^9.0 || ^10 || ^11"
},
"type": "drupal-module",
"extra": {
"drupal": {
"version": "3.0.2",
"datestamp": "1719379402",
"security-coverage": {
"status": "covered",
"message": "Covered by Drupal's security advisory policy"
}
}
},
"installation-source": "dist",
"notification-url": "https://packages.drupal.org/8/downloads",
"license": [
"GPL-2.0+"
],
"authors": [
{
"name": "andileco",
"homepage": "https://www.drupal.org/user/2054544"
},
{
"name": "Beakerboy",
"homepage": "https://www.drupal.org/user/1700098"
}
],
"description": "Math operations over selected fields",
"homepage": "https://www.drupal.org/project/views_simple_math_field",
"support": {
"source": "http://cgit.drupalcode.org/views_simple_math_field",
"issues": "https://www.drupal.org/project/issues/views_simple_math_field"
},
"install-path": "../../web/modules/views_simple_math_field"
},
{ {
"name": "drush/drush", "name": "drush/drush",
"version": "12.5.3", "version": "12.5.3",

View File

@@ -1,15 +1,24 @@
<?php return array( <?php return array(
'root' => array( 'root' => array(
'name' => 'farmos/project', 'name' => 'farmos/project',
'pretty_version' => '1.0.0+no-version-set', 'pretty_version' => 'dev-main',
'version' => '1.0.0.0', 'version' => 'dev-main',
'reference' => null, 'reference' => '62a5f3c97c55bae97ffe5b46a8cdb83a78581319',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
'dev' => true, 'dev' => true,
), ),
'versions' => array( 'versions' => array(
'andileco/eval-math' => array(
'pretty_version' => '3.0.1',
'version' => '3.0.1.0',
'reference' => '52ff5728ad51961a630cc957328764cb02ab5c51',
'type' => 'library',
'install_path' => __DIR__ . '/../andileco/eval-math',
'aliases' => array(),
'dev_requirement' => false,
),
'asm89/stack-cors' => array( 'asm89/stack-cors' => array(
'pretty_version' => 'v2.2.0', 'pretty_version' => 'v2.2.0',
'version' => '2.2.0.0', 'version' => '2.2.0.0',
@@ -244,6 +253,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'drupal/charts' => array(
'pretty_version' => '5.1.1',
'version' => '5.1.1.0',
'reference' => '5.1.1',
'type' => 'drupal-module',
'install_path' => __DIR__ . '/../../web/modules/charts',
'aliases' => array(),
'dev_requirement' => false,
),
'drupal/coder' => array( 'drupal/coder' => array(
'pretty_version' => '8.3.26', 'pretty_version' => '8.3.26',
'version' => '8.3.26.0', 'version' => '8.3.26.0',
@@ -685,6 +703,15 @@
'aliases' => array(), 'aliases' => array(),
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'drupal/views_simple_math_field' => array(
'pretty_version' => '3.0.2',
'version' => '3.0.2.0',
'reference' => '3.0.2',
'type' => 'drupal-module',
'install_path' => __DIR__ . '/../../web/modules/views_simple_math_field',
'aliases' => array(),
'dev_requirement' => false,
),
'drush/drush' => array( 'drush/drush' => array(
'pretty_version' => '12.5.3', 'pretty_version' => '12.5.3',
'version' => '12.5.3.0', 'version' => '12.5.3.0',
@@ -740,9 +767,9 @@
'dev_requirement' => false, 'dev_requirement' => false,
), ),
'farmos/project' => array( 'farmos/project' => array(
'pretty_version' => '1.0.0+no-version-set', 'pretty_version' => 'dev-main',
'version' => '1.0.0.0', 'version' => 'dev-main',
'reference' => null, 'reference' => '62a5f3c97c55bae97ffe5b46a8cdb83a78581319',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View File

@@ -0,0 +1,97 @@
################
# DrupalCI GitLabCI template
#
# Gitlab-ci.yml to replicate DrupalCI testing for Contrib
#
# With thanks to:
# * The GitLab Acceleration Initiative participants
# * DrupalSpoons
################
################
# Guidelines
#
# This template is designed to give any Contrib maintainer everything they need to test, without requiring modification. It is also designed to keep up to date with Core Development automatically through the use of include files that can be centrally maintained.
#
# However, you can modify this template if you have additional needs for your project.
################
################
# Includes
#
# Additional configuration can be provided through includes.
# One advantage of include files is that if they are updated upstream, the changes affect all pipelines using that include.
#
# Includes can be overridden by re-declaring anything provided in an include, here in gitlab-ci.yml
# https://docs.gitlab.com/ee/ci/yaml/includes.html#override-included-configuration-values
################
include:
################
# DrupalCI includes:
# As long as you include this, any future includes added by the Drupal Association will be accessible to your pipelines automatically.
# View these include files at https://git.drupalcode.org/project/gitlab_templates/
################
- project: $_GITLAB_TEMPLATES_REPO
ref: $_GITLAB_TEMPLATES_REF
file:
- '/includes/include.drupalci.main.yml'
- '/includes/include.drupalci.variables.yml'
- '/includes/include.drupalci.workflows.yml'
################
# Pipeline configuration variables
#
# These are the variables provided to the Run Pipeline form that a user may want to override.
#
# Docs at https://git.drupalcode.org/project/gitlab_templates/-/blob/1.0.x/includes/include.drupalci.variables.yml
################
# variables:
# SKIP_ESLINT: '1'
###################################################################################
#
# *
# /(
# ((((,
# /(((((((
# ((((((((((*
# ,(((((((((((((((
# ,(((((((((((((((((((
# ((((((((((((((((((((((((*
# *(((((((((((((((((((((((((((((
# ((((((((((((((((((((((((((((((((((*
# *(((((((((((((((((( .((((((((((((((((((
# ((((((((((((((((((. /(((((((((((((((((*
# /((((((((((((((((( .(((((((((((((((((,
# ,(((((((((((((((((( ((((((((((((((((((
# .(((((((((((((((((((( .(((((((((((((((((
# ((((((((((((((((((((((( ((((((((((((((((/
# (((((((((((((((((((((((((((/ ,(((((((((((((((*
# .((((((((((((((/ /(((((((((((((. ,(((((((((((((((
# *(((((((((((((( ,(((((((((((((/ *((((((((((((((.
# ((((((((((((((, /(((((((((((((. ((((((((((((((,
# (((((((((((((/ ,(((((((((((((* ,(((((((((((((,
# *((((((((((((( .((((((((((((((( ,(((((((((((((
# ((((((((((((/ /((((((((((((((((((. ,((((((((((((/
# ((((((((((((( *(((((((((((((((((((((((* *((((((((((((
# ((((((((((((( ,(((((((((((((..((((((((((((( *((((((((((((
# ((((((((((((, /((((((((((((* /((((((((((((/ ((((((((((((
# ((((((((((((( /((((((((((((/ (((((((((((((* ((((((((((((
# (((((((((((((/ /(((((((((((( ,((((((((((((, *((((((((((((
# (((((((((((((( *(((((((((((/ *((((((((((((. ((((((((((((/
# *((((((((((((((((((((((((((, /(((((((((((((((((((((((((
# ((((((((((((((((((((((((( ((((((((((((((((((((((((,
# .(((((((((((((((((((((((/ ,(((((((((((((((((((((((
# ((((((((((((((((((((((/ ,(((((((((((((((((((((/
# *((((((((((((((((((((( (((((((((((((((((((((,
# ,(((((((((((((((((((((, ((((((((((((((((((((/
# ,(((((((((((((((((((((* /((((((((((((((((((((
# ((((((((((((((((((((((, ,/((((((((((((((((((((,
# ,(((((((((((((((((((((((((((((((((((((((((((((((((((
# .(((((((((((((((((((((((((((((((((((((((((((((
# .((((((((((((((((((((((((((((((((((((,.
# .,(((((((((((((((((((((((((.
#
###################################################################################

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

242
web/modules/charts/README.md Executable file
View File

@@ -0,0 +1,242 @@
# Charts
Transform **data** into **information**. The Charts module for Drupal enables
you to build dynamic charts without needing to write a line of code. If you are
comfortable coding, you can use the Charts API to generate or update charts.
Instructions for creating charts are included below.
There are many charting libraries (also sometimes referred to as providers)
that you can find online. Each has their own benefits, drawbacks, and APIs. To
use one of them **without** this module, you would need to 1) be familiar with
the charting library's API, 2) add its JavaScript file(s) in your page, and 3)
write code for every chart you want to include. That's a lot of effort, and it
requires knowledge and access that many Drupal site builders will not have.
Charts is designed so that anyone who wants a chart on their site can have a
chart -- and be happy with it.
## How Does The Charts Module Work (Technically)?
The Charts module takes data and configuration from your site, and with the help
of a submodule, organizes it into a JSON object that is saved as an attribute
on an HTML element on your page; the JSON objected is handed to your selected
charting library, which renders it into a chart.
## Charting Providers / Libraries
Out of the box, you will be able to use 5 charting solutions (referred to as
"providers" or "libraries". Each of them has particular advantages and
disadvantages.
* Billboard.js: This library is a fork of the C3 charting library. It has
additional features, such as radar charts.
* C3.js: This library is a D3-based reusable chart library that makes it easy
to generate D3-based charts.
* Chart.js: This is a simple yet flexible JavaScript charting for designers &
developers.
* Google Charts: This library generates interactive charts using SVG and VML.
* Highcharts: This library is one of the premier solutions for generating
charts. Although it is very powerful and aesthetically pleasing with smooth
animations, it requires a commercial license. It's free for non-commercial
use. See http://www.highcharts.com
## Chart Types
Each charting library has its own set of chart types. The Charts module
focuses on chart types that are available in *most* of the libraries.
To avoid a confusing user interface, the Charts module no longer includes
chart types that are not supported by the selected library in the Chart
Type selection. For example, if you select the Chart.js library, you will
not see the "Gauge" chart type.
## Installing Libraries
All the Charts submodules default to using a content delivery network (CDN) to
pull in the necessary JavaScript files unless a local copy is present, or you
have disabled the CDN option on the "Charts configuration" page
(`/admin/config/content/charts`).
Below are a couple outlines for adding the libraries locally.
### Using Composer
1. Ensure that you have the `composer/installers` package installed.
2. Ensure you have an installer-paths for the drupal-library type. Such as in
the composer.json of
https://github.com/drupal-composer/drupal-project/blob/9.x/composer.json
3. In each submodule, there is a README.md file that has code to add to your
site's composer.json file.
4. After updating your project's composer.json file, run the 'composer require'
command specified in each submodule's README.md file. For example, if you are
using Google Charts, step three would mean adding:
```json
{
"type": "package",
"package": {
"name": "google/charts",
"version": "45",
"type": "drupal-library",
"extra": {
"installer-name": "google_charts"
},
"dist": {
"url": "https://www.gstatic.com/charts/loader.js",
"type": "file"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
}
}
}
```
to your project's composer.json, and then running:
`composer require --prefer-dist google/charts:45`
### Using Composer and wikimedia/composer-merge-plugin:
1. Ensure that you have the `wikimedia/composer-merge-plugin` package
installed.
2. Ensure that you have the `oomphinc/composer-installers-extender` package
installed.
3. Add an "installer-types" section in the "extra" of your project
composer.json file, make sure you have "npm-asset" listed. For example:
```json
"installer-types": [
"npm-asset"
],
```
5. In the "installer-paths" section in the "extra" of your project
composer.json file, ensure you have the types drupal-library and npm-asset.
For example:
```json
"web/libraries/{$name}": [
"type:drupal-library",
"type:npm-asset"
],
```
6. Add a "merge-plugin" section in the "extra" of your project composer.json
file, so that the composer.json file of the submodules you want is included.
For example:
```json
"merge-plugin": {
"include": [
"web/modules/contrib/charts/modules/charts_highcharts/composer.json"
]
},
```
7. Run the `composer require` specified in the submodule's README.md file
## Creating Charts in the UI
This module provides a configuration page at /admin/config/content/charts.
You may set site-wide defaults on this page (for example: set the default color
scheme). It also has library-specific configuration available after saving a
default library.
There are three options for creating a chart within the UI:
1. Using Views
2. Using a Chart Field
3. Using a Chart Block
### Creating Charts with Views
1. Create a new view:
Visit admin/structure/views/add and select the display format of "Chart" for
your new page or block.
2. Add a label field:
Under the "Fields" section, add a field you would like to be used as labels
along one axis of the chart (or slices of the pie). If you are visualizing
an Event content type, you might select `title`.
3. Add a data field or fields:
Now add a second field that will be used to determine the data values. If you
are visualizing an Event content type, this field might be
`field_number_attendees`. The label you give this field will be used in the
chart's legend to represent this series. Do this again for each different
quantity you would like to chart. Note that some chart types (e.g. "pie")
only support a single data column.
4. Configure the chart display:
Click on the "Settings" link in the "Format" section to configure the chart.
Select your chart library, type, the label field, and the data provider.
There are many other options to customize your chart. Some options may not
be available to all chart types and will be adjusted based on the type
selected.
5. Save your view.
As described in the steps above - if you have a field representing a number, you
do not necessarily need to aggregate your view. If the content you want to
visualize does not include a numeric field, you may need to aggregate your view
and use the `count` function to generate a number for your data field.
**Tip:** You may find it easier to start with a "Table" display and convert it
to a chart display after setting up the data. It can be easier to visualize
what the result of the chart will be if it's been laid out in a table first.
### Creating Combo Charts
If you are creating your chart in Views and want to a chart that includes
multiple chart types (such as columns and a line - aka a "combo" chart), add a
new display of type "Chart attachment". Use the middle column of this display
to attach the "Chart attachment" to a parent display (this is required). In
this section, you can also instruct the "Chart attachment" to inherit exposed
or contextual filters and if it should use the primary y-axis or a secondary
y-axis. You still need to configure the "Settings" in the "Format" section.
## Creating Charts in the UI with a Chart Field
If you want a chart in your entity (e.g. "node"), but don't need its data to be
dynamic (like Views could generate), you can use a Chart field. Add this field
to your bundle (e.g. "content type") via the "Manage Fields" tab - like you
would any other field. When you add a new entity, you can select the charting
library, chart type, and other configurations. You can add data using a CSV or
by manually adding it into the table input. The chart will show on your entity
after saving.
## Creating Charts in the UI with a Chart Block
If you are using Layout Builder or want to place a chart on several pages, and
the chart does not need dynamic data (like Views could generate), you can use a
Chart Block. When you add a Chart Block, you can select the charting library,
chart type, and other configurations. You can add data using a CSV or by
manually adding it into the table input.
## Creating Charts Using the API
Charts are elements that can be rendered using a Drupal render array. Please
refer to the `charts.api.php` file and also look at the included submodule,
`charts_api_examples` (specifically the controller, which has many examples).
## Debugging Your Charts
There is a checkbox in the "Advanced" tab of the config form
(/admin/config/content/charts/advanced) that enabled debugging. Checking this
box will enable a collapsible "details" element that includes the JSON
object generated by the Charts module - this can often be added directly
into a code playground provided by the charting library. If you are
implementing your own Charts submodule, you can add a line like this into
your charts_MYMODULE.js file (it may need modification):
```
if (element.nextElementSibling &&
element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(
config,
null,
' '
);
}
```
## Support
For bug reports and feature requests please use the Drupal.org issue tracker:
https://www.drupal.org/project/issues/charts.
We welcome your support in improving code documentation, tests, and providing
example use-cases not addressed by the existing module.
If you are interested in creating your own submodule for a library not
currently supported, please contact
[@andileco](https://www.drupal.org/u/andileco).

View File

@@ -0,0 +1,174 @@
<?php
/**
* @file
* Documentation on hooks provided by the Charts module.
*
* Charts module provides 4 element types that can be used to construct a chart.
* In its most basic form, a chart may be created by specifying an element
* with the #type property "chart". However, for enough information to
* accurately data to an audience, you need a little more. Below is a very
* basic example of a column chart.
*
* @code
* $chart = [
* '#type' => 'chart',
* '#chart_type' => 'column',
* 'series' => [
* '#type' => 'chart_data',
* '#title' => t('Responses'),
* '#data' => [60, 40],
* ],
* 'xaxis' => [
* '#type' => 'chart_xaxis',
* '#labels' => [t('Yes'), t('No')],
* ]
* ];
* @endcode
*
* On charts that have multiple axes, you'll need to add individual sub-elements
* for each series of data. If you desire, you may also customize the axes by
* providing an axis element too.
*
* @code
* $chart = [
* '#type' => 'chart',
* '#chart_type' => 'column',
* ];
* $chart['series'] = [
* '#type' => 'chart_data',
* '#title' => t('Responses'),
* '#data' => [10, 20, 30],
* ];
* $chart['xaxis'] = [
* '#type' => 'chart_xaxis',
* '#title' => t('Month'),
* '#labels' => [t('Jan'), t('Feb'), t('Mar')],
* ];
* @endcode
*
* Once you have generated a chart object, you can run
* \Drupal::service('renderer')->render() on it to turn it into HTML:
*
* @code
* $output = \Drupal::service('renderer')->render($chart);
* @endcode
*
* There are many, many properties available for the four chart types (chart,
* chart_data, chart_xaxis, and chart_yaxis). For a full list, see the
* charts_element_info() function.
*
* This module also includes a number of examples for reference in the
* charts_api_example module, and displayed at /charts/example/display.
*
* @see Drupal\charts\Element folder
*/
/**
* Alter an individual chart before it is printed.
*
* @param array $element
* The chart renderable. Passed in by reference.
* @param string $chart_id
* The chart identifier, pulled from the $chart['#chart_id'] property
* (if any). Not all charts have a chart identifier.
*/
function hook_chart_alter(array &$element, $chart_id) {
if ($chart_id === 'view_name__display_name') {
// Individual properties may be modified.
$element['#title_font_size'] = 20;
}
}
/**
* Alter an individual chart before it's rendered.
*
* Same as hook_chart_alter(), only including the $chart_id in the function
* name instead of being passed in as an argument.
*
* @see hook_chart_alter()
*/
function hook_chart_CHART_ID_alter(array &$element) {
}
/**
* Alter an individual chart's raw library representation.
*
* This hook is called AFTER hook_chart_alter(), after Charts module has
* converted the renderable into the chart definition that will be used by the
* library. Note that the structure of $definition will differ based on the
* charting library used. Switching charting libraries may cause your code
* to break when using this hook.
*
* Even though this hook may be fragile, it may provide developers with access
* to library-specific functionality.
*
* @param array $definition
* The chart definition to be modified. The raw values are passed directly to
* the charting library.
* @param array $element
* The chart renderable. This may be used for reference (or read to add
* support for new properties), but any changes to this variable will not
* have an effect on output.
* @param string $chart_id
* The chart ID, derived from the $chart['#chart_id'] property. Note that not
* all charts may have a $chart_id.
*/
function hook_chart_definition_alter(array &$definition, array $element, $chart_id) {
if ($element['#chart_library'] === 'google') {
$definition['options']['titleTextStyle']['fontSize'] = 20;
}
elseif ($element['#chart_library'] === 'highcharts') {
$definition['title']['style']['fontSize'] = 20;
// Add a caption based on a field on your node containing a Chart field.
$node = $element['#entity'];
if ($node instanceof \Drupal\node\NodeInterface && $node->hasField('field_caption')) {
$definition['caption']['text'] = $node->field_caption->value;
}
}
}
/**
* Alter an individual chart before it's rendered.
*
* Same as hook_chart_definition_alter(), only including the $chart_id in the
* function name instead of being passed in as an argument.
*
* @see hook_chart_definition_alter()
*/
function hook_chart_definition_CHART_ID_alter(array &$definition, array $element, $chart_id) {
}
/**
* Alter the list of supported chart types for a chart plugin.
*
* To use this hook, first define the chart types you want to add in a file
* named <module>.charts_types.yml to your module's root directory. For example,
* if your module is named "charts_foo", the file should be named
* "charts_foo.charts_types.yml".
*
* The contents of this file will look something like:
*
* @code
* candlestick:
* label: 'Candlestick'
* axis: 'xy'
* axis_inverted: false
* stacking: false
* @endcode
*
* @see charts.charts_types.yml
*
* With that file in place, you can now implement this hook. See below for an
* example adding the 'candlestick' chart type to the 'highcharts' chart plugin.
*
* @param array $types
* An array of supported chart types.
* @param string $chart_plugin_id
* The chart plugin ID.
*/
function hook_charts_plugin_supported_chart_types_alter(array &$types, string $chart_plugin_id) {
if ($chart_plugin_id === 'highcharts') {
$types[] = 'candlestick';
}
}

View File

@@ -0,0 +1,50 @@
area:
label: 'Area'
axis: 'xy'
axis_inverted: false
stacking: true
bar:
label: 'Bar'
axis: 'xy'
axis_inverted: true
stacking: true
bubble:
label: 'Bubble'
axis: 'xy'
axis_inverted: false
stacking: false
column:
label: 'Column'
axis: 'xy'
axis_inverted: false
stacking: true
donut:
label: 'Donut'
axis: 'y_only'
axis_inverted: false
stacking: false
gauge:
label: 'Gauge'
axis: 'y_only'
axis_inverted: false
stacking: false
line:
label: 'Line'
axis: 'xy'
axis_inverted: false
stacking: false
pie:
label: 'Pie'
axis: 'y_only'
axis_inverted: false
stacking: false
scatter:
label: 'Scatter'
axis: 'xy'
axis_inverted: false
stacking: false
spline:
label: 'Spline'
axis: 'xy'
axis_inverted: false
stacking: false

View File

@@ -0,0 +1,5 @@
charts.settings:
title: 'Charts settings'
base_route_name: charts.settings
names:
- charts.settings

View File

@@ -0,0 +1,11 @@
name: Charts
type: module
description: 'A charting API for Drupal that provides chart elements and integration with Views.'
package: Charts
core_version_requirement: ^10.3 || ^11
configure: charts.settings
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,15 @@
<?php
/**
* @file
* Install and update functions for the Charts module.
*/
/**
* Implements hook_requirements().
*/
function charts_requirements($phase) {
$requirements = [];
return $requirements;
}

View File

@@ -0,0 +1,7 @@
global:
version: VERSION
js:
js/charts.js: {}
dependencies:
- core/drupal

View File

@@ -0,0 +1,5 @@
charts.settings:
title: 'Charts configuration'
description: 'Set the default library, behavior, and style of charts.'
parent: system.admin_config_content
route_name: charts.settings

View File

@@ -0,0 +1,16 @@
charts.settings:
title: 'Charts Settings'
route_name: charts.settings
base_route: charts.settings
charts.settings.default:
title: 'Default'
route_name: charts.settings
parent_id: charts.settings
weight: 10
charts.settings.advanced:
title: 'Advanced'
route_name: charts.settings.advanced
parent_id: charts.settings
weight: 20

230
web/modules/charts/charts.module Executable file
View File

@@ -0,0 +1,230 @@
<?php
/**
* @file
* Charts - Module.
*/
use Drupal\views\ViewExecutable;
/**
* Implements hook_views_data().
*/
function charts_views_data() {
$data['charts_fields']['table']['group'] = t('Charts');
$data['charts_fields']['table']['join'] = [
// Exist in all views.
'#global' => [],
];
$data['charts_fields']['field_charts_fields_scatter'] = [
'title' => t('Scatter Field'),
'help' => t('Use this field for your data field in a scatter plot.'),
'field' => [
'id' => 'field_charts_fields_scatter',
],
];
$data['charts_fields']['field_charts_fields_bubble'] = [
'title' => t('Bubble Field'),
'help' => t('Use this field for your data field in a bubble chart.'),
'field' => [
'id' => 'field_charts_fields_bubble',
],
];
$data['charts_fields']['field_exposed_chart_type'] = [
'title' => t('Exposed Chart Type'),
'help' => t('Use this field for exposing chart type.'),
'field' => [
'id' => 'field_exposed_chart_type',
],
];
return $data;
}
/**
* Implements hook_theme().
*/
function charts_theme($existing, $type, $theme, $path) {
return [
'charts_chart' => [
'render element' => 'element',
],
];
}
/**
* Implements hook_preprocess_HOOK().
*/
function template_preprocess_charts_chart(&$variables) {
$element = $variables['element'];
$attributes = $element['#attributes'];
$attributes['id'] = $element['#id'];
$attributes['class'][] = 'chart';
$variables['content'] = [
'#type' => 'html_tag',
'#tag' => 'div',
'#attributes' => $attributes,
'#value' => $element['#chart'] ?? '',
];
$variables['content_prefix'] = $element['#content_prefix'];
$variables['content_suffix'] = $element['#content_suffix'];
$config = \Drupal::config('charts.settings');
$variables['debug'] = [];
if (!empty($config->get('advanced.debug'))) {
$variables['debug'] = [
'#type' => 'details',
'#title' => t('Chart JSON'),
'#open' => FALSE,
'#attributes' => [
'data-charts-debug-container' => TRUE,
],
'#collapsible' => TRUE,
'json' => [
'#prefix' => '<pre class="language-json">',
'#type' => 'html_tag',
'#tag' => 'code',
'#attributes' => [
'class' => ['language-json'],
],
'#value' => t('If this is blank, your library will need to implement code in your JavaScript. Please see the README.md.'),
'#suffix' => '</pre>',
],
];
}
}
/**
* Implements hook_views_pre_view().
*/
function charts_views_pre_view(ViewExecutable $view, $display_id, array &$args) {
$hasFields = array_key_exists('fields', $view->display_handler->options);
if ($hasFields) {
$fields = $view->display_handler->getOption('fields');
$hasViewsFieldsOnOffHandler = FALSE;
foreach ($fields as $field) {
if (array_key_exists('plugin_id', $field)) {
if ($field['plugin_id'] == 'field_exposed_chart_type') {
$hasViewsFieldsOnOffHandler = TRUE;
}
}
}
if ($hasViewsFieldsOnOffHandler) {
// Grab the type that has been submitted.
$params = \Drupal::request()->query->all();
// This is for a GET request.
// If the view is submitted through AJAX, like in view preview, it will be
// a POST request. Merge the parameter arrays and we will get our values.
$postParams = \Drupal::request()->request->all();
$params = array_merge($params, $postParams);
foreach ($params as $key => $value) {
if (strpos($key, 'ct') === 0) {
$view->storage->set('exposed_chart_type', $value);
}
}
$view->element['#cache']['contexts'][] = 'url';
}
}
}
/**
* Implements hook_library_info_alter().
*
* Adapted from webform_library_info_alter() to add CDN support.
*/
function charts_library_info_alter(&$libraries, $extension) {
// Only process for charts_* modules.
if (strpos($extension, 'charts_') !== 0) {
return;
}
// Only try to apply cdn changes if the cdn requirements is on.
$config = \Drupal::config('charts.settings');
if (!$config->get('advanced.requirements.cdn')) {
return;
}
foreach ($libraries as &$library) {
if (isset($library['cdn']) && is_array($library['cdn'])) {
_charts_library_info_alter_recursive($library, $library['cdn']);
}
}
}
/**
* Recursive through a charts library.
*
* @param array $library
* A library defined in a chart module.
* @param array $cdn
* A associative array of library paths mapped to CDN URL.
*
* @note copied from _webform_library_info_alter_recursive().
*/
function _charts_library_info_alter_recursive(array &$library, array $cdn) {
foreach ($library as $key => &$value) {
// CSS and JS files and listed in associative arrays keyed via string.
if (!is_string($key) || !is_array($value)) {
continue;
}
// Ignore the CDN's associative array.
if ($key === 'cdn') {
continue;
}
// Replace the CDN sources (i.e. /library/*) with the CDN URL destination
// (https://cdnjs.cloudflare.com/ajax/libs/*).
foreach ($cdn as $source => $destination) {
if (_charts_library_check_source_exists($source)) {
continue;
}
if (strpos($key, $source) === 0) {
$uri = str_replace($source, $destination, $key);
$library[$uri] = $value;
$library[$uri]['type'] = 'external';
unset($library[$key]);
break;
}
}
// Recurse downward to find nested libraries.
_charts_library_info_alter_recursive($value, $cdn);
}
}
/**
* Checks whether a library source directory or file exists locally.
*
* @param string $source
* The source directory or file.
*
* @return bool
* True when the file exist, FALSE otherwise.
*/
function _charts_library_check_source_exists(string $source) {
$source = rtrim($source, '/');
$container = \Drupal::getContainer();
$app_root = $container->getParameter('app.root');
$site_path = $container->getParameter('site.path');
// The following logic is taken from libraries_get_libraries()
$search_dir = [];
$search_dir[] = $app_root;
// Similar to 'modules' and 'themes' directories inside an installation
// profile, installation profiles may want to place libraries into a
// 'libraries' directory.
$search_dir[] = 'profiles/' . \Drupal::installProfile();
// Also search sites/<domain>/*.
$search_dir[] = $site_path;
foreach ($search_dir as $dir) {
if (file_exists($dir . $source) || file_exists($dir . $source)) {
return TRUE;
}
}
return FALSE;
}

View File

@@ -0,0 +1,4 @@
'access all charts':
title: 'Administer Charts'
description: 'This permission needs to be fleshed out.'
'restrict access': true

View File

@@ -0,0 +1,221 @@
<?php
/**
* @file
* Charts post-update file.
*/
use Drupal\charts\ConfigUpdater;
use Drupal\Component\Serialization\Yaml;
/**
* Initialize advanced requirements cdn config value.
*/
function charts_post_update_initialize_advanced_requirements_cdn(&$sandbox) {
$config = \Drupal::service('config.factory')
->getEditable('charts.settings');
if ($config) {
$config->set('advanced', ['requirements' => ['cdn' => TRUE]]);
$config->save();
return 'Successfully initialized the advanced.requirements.cdn option.';
}
return 'No initialization was done. Please check your current config.';
}
/**
* Update the existing default config display colors to increase from 10 to 25.
*/
function charts_post_update_existing_default_colors_to_twenty_five(&$sandbox) {
$config = \Drupal::service('config.factory')->getEditable('charts.settings');
$existing_colors = $config->get('charts_default_settings.display.colors');
if (is_countable($existing_colors) && count($existing_colors) === 10) {
$path = \Drupal::service('extension.list.module')->getPath('charts');
$default_install_settings_file = $path . '/config/install/charts.settings.yml';
if (!file_exists($default_install_settings_file)) {
return 'Failed to update the default colors to 25 because we could not load the charts settings.';
}
$default_install_settings = Yaml::decode(file_get_contents($default_install_settings_file));
$install_colors = $default_install_settings['charts_default_settings']['display']['colors'];
// We only want to add the last 15 colors to make it 25.
$colors = array_merge($existing_colors, array_slice($install_colors, -15));
$config->set('charts_default_settings.display.colors', $colors);
$config->save();
return 'Successfully increased the number of default colors to 25.';
}
return 'We were unable to load existing colors in order to increased them to 25.';
}
/**
* Initialize the config value for the debug setting.
*/
function charts_post_update_initialize_debug_option(&$sandbox) {
$config = \Drupal::service('config.factory')
->getEditable('charts.settings');
if ($config) {
$config->set('advanced.debug', FALSE);
$config->save();
return 'Successfully initialized the advanced.debug option.';
}
return 'No initialization was done. Please check your current config.';
}
/**
* Migrate library default settings to library_config key.
*/
function charts_post_update_migrate_library_default_settings_to_library_config(&$sandbox) {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = \Drupal::service('config.factory');
$config = $config_factory->getEditable('charts.settings');
if ($config) {
$default_settings = $config->get('charts_default_settings');
$library_id = $default_settings['library'] ?? '';
$old_key = $library_id ? 'charts_default_settings.' . $library_id . '_settings' : '';
if ($old_key && empty($default_settings['display']) && $library_config = $config->get($old_key)) {
$config->set('charts_default_settings.library_config', $library_config);
$config->clear($old_key);
$config->save();
return 'Library default settings have been successfully migrated to library_config key.';
}
}
return 'No migration was done';
}
/**
* Clear library_config set on the wrong place. See drupal.org/i/3310145.
*/
function charts_post_update_clear_library_config_key_added_on_wrong_place(&$sandbox) {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = \Drupal::service('config.factory');
$config = $config_factory->getEditable('charts.settings');
if ($config && $config->get('library_config')) {
// Not to be confused with "charts_default_settings.library_config" which is
// the proper key.
$config->clear('library_config');
$config->save();
return 'Successfully cleared the library_config key.';
}
return 'The library_config key was not cleared, probably because it was not set.';
}
/**
* Updates charts config and views with charts style of version 3 to new config.
*/
function charts_post_update_00_update_charts_version_3_to_latest_settings_structure(&$sandbox) {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
$config_factory = \Drupal::service('config.factory');
$config = $config_factory->getEditable('charts.settings');
$default_settings = $config ? $config->get('charts_default_settings') : [];
/** @var \Drupal\charts\ConfigUpdater $config_updater */
$config_updater = \Drupal::classResolver(ConfigUpdater::class);
if ($default_settings && !isset($default_settings['display'])) {
$results = [];
$config->set('charts_default_settings', $config_updater->transformVersion3SettingsToNew($default_settings));
$config->save();
$results[] = 'Default Config: Migration to the new structure successfully done.';
$results[] = $config_updater->updateExistingViewsVersion3ToNewSettings();
return implode(' -- ', $results);
}
elseif (!$default_settings || !$config->getRawData()) {
$config->setData($config_updater->initializedCurrentDefaultSettings());
$config->save();
$results = [];
$results[] = 'Default Config: No config were found and the default one was saved.';
$results[] = $config_updater->updateExistingViewsVersion3ToNewSettings();
return implode(' -- ', $results);
}
return 'No update of the settings structure was done.';
}
/**
* Re-save views that are using the chart style to recalculate dependencies.
*/
function charts_post_update_resave_views_to_account_calculated_library_dependencies(&$sandbox) {
$view_storage = \Drupal::entityTypeManager()->getStorage('view');
$view_ids = $view_storage->getQuery()
->accessCheck(FALSE)
->condition('display.*.display_options.style.type', 'chart', '=')
->execute();
$labels = [];
if ($view_ids) {
foreach ($view_ids as $view_id) {
/** @var \Drupal\views\ViewEntityInterface $view */
$view = $view_storage->load($view_id);
// Re-save the view to update its dependencies.
$view->save();
$labels[] = $view->label();
}
}
if ($labels) {
return t('The following views have been re-saved to re-calculate their dependencies: @views', [
'@views' => implode(',', $labels),
]);
}
return t('This site did not have any chart related views.');
}
/**
* Setting charts config module dependencies based on the selected library.
*/
function charts_post_update_setting_charts_config_module_dependencies(&$sandbox) {
$config = \Drupal::service('config.factory')
->getEditable('charts.settings');
$settings = $config->get('charts_default_settings');
$library = $settings['library'] ?? '';
$type = $settings['type'] ?? '';
if ($library || $type) {
/** @var \Drupal\charts\EventSubscriber\ConfigImportSubscriber $config_import_subscriber */
$config_import_subscriber = \Drupal::service('charts.config_import_subscriber');
$config->set('dependencies', $config_import_subscriber->calculateDependencies($library, $type))
->save();
return 'Updated chart settings dependencies';
}
return 'The default config for charts did not have a library and type set. So no dependencies were set.';
}
/**
* Move color changer from fields to display.
*/
function charts_post_update_move_views_style_color_changer_from_fields_to_display(&$sandbox) {
$view_storage = \Drupal::entityTypeManager()->getStorage('view');
$view_ids = $view_storage->getQuery()
->accessCheck(FALSE)
->condition('display.*.display_options.style.type', 'chart', '=')
->execute();
$labels = [];
if ($view_ids) {
foreach ($view_ids as $view_id) {
/** @var \Drupal\views\ViewEntityInterface $view */
$view = $view_storage->load($view_id);
$changed = FALSE;
foreach (array_keys($view->get('display')) as $display_id) {
$display = &$view->getDisplay($display_id);
if (isset($display['display_options']['style']['options']['chart_settings']['fields']['color_changer'])) {
$changed = TRUE;
$display['display_options']['style']['options']['chart_settings']['display']['color_changer'] = !empty($display['display_options']['style']['options']['chart_settings']['fields']['color_changer']);
unset($display['display_options']['style']['options']['chart_settings']['fields']['color_changer']);
}
}
if ($changed) {
$view->save();
$labels[] = $view->label();
}
}
}
if ($labels) {
return t('The following views have been updated to move the color changer from fields to display: @views', [
'@views' => implode(',', $labels),
]);
}
return t('This site did not have any chart related views.');
}

View File

@@ -0,0 +1,15 @@
charts.settings:
path: /admin/config/content/charts
defaults:
_form: Drupal\charts\Form\ChartsConfigForm
_title: 'Chart configuration'
requirements:
_permission: 'administer site configuration'
charts.settings.advanced:
path: /admin/config/content/charts/advanced
defaults:
_form: Drupal\charts\Form\ChartsConfigAdvancedForm
_title: 'Chart advanced configuration form'
requirements:
_permission: 'administer site configuration'

View File

@@ -0,0 +1,21 @@
services:
plugin.manager.charts:
class: Drupal\charts\ChartManager
parent: default_plugin_manager
plugin.manager.charts_type:
class: Drupal\charts\TypeManager
arguments: ['@module_handler', '@cache.discovery', '@event_dispatcher']
charts.plugins_uninstall_validator:
class: Drupal\charts\PluginsUninstallValidator
tags:
- { name: module_install.uninstall_validator }
arguments: ['@config.factory']
lazy: true
charts.config_import_subscriber:
class: Drupal\charts\EventSubscriber\ConfigImportSubscriber
arguments: ['@plugin.manager.charts', '@plugin.manager.charts_type']
tags:
- { name: event_subscriber }

View File

View File

@@ -0,0 +1,73 @@
dependencies: []
charts_default_settings:
library: ''
type: line
display:
title: ''
subtitle: ''
colors:
- '#006fb0'
- '#f07c33'
- '#342e9c'
- '#579b17'
- '#3f067a'
- '#cbde67'
- '#7643b6'
- '#738d00'
- '#c157c7'
- '#02dab1'
- '#ed56b4'
- '#d8d981'
- '#004695'
- '#736000'
- '#a5a5ff'
- '#833a00'
- '#ff9ee9'
- '#684507'
- '#fe4f85'
- '#5d0011'
- '#ffa67b'
- '#88005c'
- '#ff9b8f'
- '#85000f'
- '#ff7581'
color_changer: false
title_position: out
tooltips: true
tooltips_use_html: false
data_markers: false
data_labels: false
legend: false
legend_position: right
background: ''
three_dimensional: 0
polar: 0
dimensions:
width: ''
width_units: '%'
height: ''
height_units: ''
gauge:
max: '100'
min: '0'
green_from: '85'
green_to: '100'
yellow_from: '50'
yellow_to: '85'
red_from: '0'
red_to: '50'
xaxis:
title: ''
labels_rotation: '0'
yaxis:
title: ''
min: ''
max: ''
prefix: ''
suffix: ''
decimal_count: ''
labels_rotation: '0'
advanced:
debug: false
requirements:
cdn: true

View File

@@ -0,0 +1,216 @@
# Basic data types for charts.
charts_dimensions:
type: mapping
mapping:
width:
type: string
label: 'Width'
width_units:
type: string
label: 'Width units'
height:
type: string
label: 'Height'
height_units:
type: string
label: 'Height units'
charts_gauge:
type: mapping
mapping:
max:
type: string
label: 'Gauge maximum value'
min:
type: string
label: 'Gauge minimum value'
green_from:
type: string
label: 'Green minimum value'
green_to:
type: string
label: 'Green maximum value'
yellow_from:
type: string
label: 'Yellow minimum value'
yellow_to:
type: string
label: 'Yellow maximum value'
red_from:
type: string
label: 'Red minimum value'
red_to:
type: string
label: 'Red maximum value'
charts_display:
type: mapping
mapping:
title:
type: label
label: 'Chart title'
title_position:
type: string
label: 'Title position'
subtitle:
type: string
label: 'Chart subtitle'
data_labels:
type: boolean
label: 'Enable data labels'
data_markers:
type: boolean
label: 'Enable data markers'
legend:
type: boolean
label: 'Legend'
legend_position:
type: string
label: 'Legend position'
background:
type: string
label: 'Background'
three_dimensional:
type: integer
label: 'Make chart three-dimensional (3D)'
colors:
type: sequence
label: 'Chart colors'
sequence:
type: string
label: 'Hexadecimal color'
polar:
type: integer
label: 'Transform cartesian charts into the polar coordinate system'
tooltips:
type: boolean
label: 'Tooltips'
tooltips_use_html:
type: boolean
label: 'Tooltips Use HTML'
dimensions:
type: charts_dimensions
label: 'Dimensions'
gauge:
type: charts_gauge
label: 'Gauge'
color_changer:
type: boolean
label: 'Enable color changer widget'
charts_xaxis:
type: mapping
mapping:
title:
type: label
label: 'X axis title'
labels_rotation:
type: string
label: 'X axis labels rotation'
charts_yaxis:
type: mapping
mapping:
title:
type: label
label: 'Y axis title'
min:
type: string
label: 'Y axis min value'
max:
type: string
label: 'Y axis max value'
prefix:
type: label
label: 'Y axis prefix'
suffix:
type: label
label: 'Y axis suffix'
decimal_count:
type: string
label: 'Y axis decimal count'
labels_rotation:
type: string
label: 'Y axis labels rotation'
charts_views_field_data_provider:
type: mapping
mapping:
enabled:
type: boolean
label: 'Enabled'
color:
type: string
label: Color
weight:
type: integer
label: 'Weight'
charts_views_field_entity_grouping_selected_method_colors:
type: mapping
mapping:
color:
type: string
label: Color
charts_config:
type: mapping
mapping:
library:
type: string
label: 'Charting library'
type:
type: string
label: 'Chart type'
fields:
type: mapping
label: 'Charts fields'
mapping:
label:
type: string
label: 'Label field'
stacking:
type: boolean
label: Stacking
data_providers:
type: sequence
label: 'Data providers'
sequence:
type: charts_views_field_data_provider
entity_grouping:
type: mapping
label: 'Entity grouping settings'
mapping:
color_selection_method:
type: string
label: 'Color selection method'
selected_method:
type: mapping
label: 'Selected method'
mapping:
color_field_name:
type: string
label: 'Color field name'
colors:
type: sequence
label: 'Colors'
sequence:
type: charts_views_field_entity_grouping_selected_method_colors
display:
type: charts_display
label: 'Display'
xaxis:
type: charts_yaxis
label: 'X axis Display'
yaxis:
type: charts_yaxis
label: 'Y axis Display'
library_config:
type: charts.settings.library_plugin.[%parent.library]
charts.settings.library_plugin.*:
type: charts_settings_library_plugin
charts_settings_library_plugin:
type: mapping

View File

@@ -0,0 +1,24 @@
charts.settings:
type: config_object
label: 'Charts settings'
mapping:
charts_default_settings:
type: charts_config
label: 'Charts default settings'
advanced:
type: mapping
label: Advanced
mapping:
debug:
type: boolean
label: 'Enable Charts Debug'
requirements:
type: mapping
label: Requirements
mapping:
cdn:
type: boolean
label: 'Check if CDN is being used for external libraries'
dependencies:
type: config_dependencies
label: 'Dependencies'

View File

@@ -0,0 +1,47 @@
views.style.chart:
type: views_style
label: 'Chart'
mapping:
chart_settings:
type: charts_config
label: 'Settings'
views.display.chart_extension:
type: views_display
label: 'Attachment display options'
views.field.field_exposed_chart_type:
type: views_field
label: 'Exposed Chart Type'
mapping:
chart_types:
type: sequence
label: 'Chart Type Options'
exposed_select_type:
type: string
label: 'Exposed Selection Type'
views.field.field_charts_fields_bubble:
type: views_field
label: 'Bubble Field'
mapping:
x_axis:
type: string
label: 'X Axis Field'
y_axis:
type: string
label: 'Y Axis Field'
z_axis:
type: string
label: 'Z Axis Field'
views.field.field_charts_fields_scatter:
type: views_field
label: 'Scatter Field'
mapping:
x_axis:
type: string
label: 'X Axis Field'
y_axis:
type: string
label: 'Y Axis Field'

View File

@@ -0,0 +1,46 @@
/**
* @file
* Charts API.
*/
((Drupal) => {
Drupal.Charts = Drupal.Charts || {};
Drupal.Charts.Configs = Drupal.Charts.Configs || [];
/**
* @typedef {class} Drupal.Charts.Contents
*/
Drupal.Charts.Contents = class {
constructor() {
const chartsElements = document.querySelectorAll('[data-chart]');
chartsElements.forEach(function (el) {
const id = el.getAttribute('id');
Drupal.Charts.Configs[id] = JSON.parse(el.getAttribute('data-chart'));
Drupal.Charts.Configs[id].drupalChartDivElement = el;
Drupal.Charts.Configs[id].drupalChartDivId = id;
});
}
initialize(id) {
const event = new CustomEvent('drupalChartsConfigsInitialization', {
detail: Drupal.Charts.Configs[id]
});
Drupal.Charts.Configs[id].drupalChartDivElement.dispatchEvent(event);
}
static update(id, data) {
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
Drupal.Charts.Configs[id] = data;
}
}
getData(id) {
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
this.initialize(id);
return Drupal.Charts.Configs[id];
}
return {};
}
};
})(Drupal);

View File

@@ -0,0 +1,45 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(Drupal => {
Drupal.Charts = Drupal.Charts || {};
Drupal.Charts.Configs = Drupal.Charts.Configs || [];
Drupal.Charts.Contents = class {
constructor() {
const charts_elements = document.querySelectorAll('[data-chart]');
charts_elements.forEach(function (el) {
const id = el.getAttribute('id');
Drupal.Charts.Configs[id] = JSON.parse(el.getAttribute('data-chart'));
Drupal.Charts.Configs[id].drupalChartDivElement = el;
Drupal.Charts.Configs[id].drupalChartDivId = id;
});
}
initialize(id) {
const event = new CustomEvent('drupalChartsConfigsInitialization', {
detail: Drupal.Charts.Configs[id]
});
Drupal.Charts.Configs[id].drupalChartDivElement.dispatchEvent(event);
}
static update(id, data) {
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
Drupal.Charts.Configs[id] = data;
}
}
getData(id) {
if (Drupal.Charts.Configs.hasOwnProperty(id)) {
this.initialize(id);
return Drupal.Charts.Configs[id];
}
return {};
}
};
})(Drupal);

View File

@@ -0,0 +1,15 @@
#Charts API Example
This module shows you how to create charts using render arrays. There are
numerous examples in the `ChartsApiExample.php` controller file. In the
`charts_api_example.module` file, you can also see how some hooks described in
`charts.api.php` can be used. One hook overrides a chart using PHP. Another
overrides with JavaScript - this is necessary in some cases when the charting
library API is expecting a function rather than a string. You can read more
about overriding with JavaScript here:
https://www.drupal.org/project/charts/issues/3197574#comment-13999765
To see the examples in your site, navigate to: `/charts/example/display`
Settings for these charts are set in the Charts configuration page
(`/admin/config/content/charts`).

View File

@@ -0,0 +1,12 @@
name: 'Charts API Example'
type: module
description: 'Demonstrates how to use the Charts module without Views.'
core_version_requirement: ^10.3 || ^11
package: Examples
dependencies:
- 'charts:charts'
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,6 @@
charts_api_example:
version: VERSION
js:
js/charts_api_example_js_override.js: { weight: -5 }
dependencies:
- charts/global

View File

@@ -0,0 +1,5 @@
charts_api_example.display:
title: 'Charts API Example'
description: 'Displays charts rendered using the Charts API.'
parent: system.admin_config_content
route_name: charts_api_example.display

View File

@@ -0,0 +1,43 @@
<?php
/**
* @file
* Charts API Example module file.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function charts_api_example_help($route_name, RouteMatchInterface $route_match) {
$output = '';
switch ($route_name) {
// Main module help for the charts_api_example module.
case 'help.page.charts_api_example':
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('A simple example on how to interact with the Charts API') . '</p>';
break;
}
return $output;
}
/**
* Implements hook_chart_definition_CHART_ID_alter().
*/
function charts_api_example_chart_definition_exampleidphp_alter(&$chart) {
// Please note: this will only show when the charts_highcharts module is
// enabled and Highcharts is set as your default library.
$chart['chart']['backgroundColor'] = 'blue';
}
/**
* Implements hook_chart_alter().
*/
function charts_api_example_chart_alter(array &$element, $chart_id) {
// Please note: this will only show when the charts_highcharts module is
// enabled and Highcharts is set as your default library.
if ($chart_id === 'exampleidjschart') {
$element['#attached']['library'][] = 'charts_api_example/charts_api_example';
}
}

View File

@@ -0,0 +1,7 @@
charts_api_example.display:
path: /charts/example/display
defaults:
_controller: '\Drupal\charts_api_example\Controller\ChartsApiExample::display'
_title: 'How to Use the Charts API'
requirements:
_permission: 'access content'

View File

@@ -0,0 +1,16 @@
{
"name": "drupal/charts_api_example",
"type": "drupal-module",
"description": "How to interact with the Charts Api",
"keywords": [
"Drupal"
],
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/charts_api_example",
"minimum-stability": "dev",
"support": {
"issues": "https://www.drupal.org/project/issues/charts_api_example",
"source": "http://cgit.drupalcode.org/charts_api_example"
},
"require": {}
}

View File

@@ -0,0 +1,463 @@
Week,6.x-0.x,6.x-1.x,7.x-1.x,7.x-2.x,8.x-1.x,8.x-3.x,8.x-4.x,5.0.x,Total
21-Aug-21,0,71,12,13458,1,4632,6,637,18817
14-Aug-21,0,77,13,13517,1,4518,9,608,18743
7-Aug-21,0,73,11,13586,1,4651,6,606,18934
31-Jul-21,0,76,13,13479,1,4488,7,619,18683
24-Jul-21,0,70,10,13771,1,4385,7,530,18774
17-Jul-21,0,73,12,13756,1,4430,8,536,18816
10-Jul-21,0,77,13,13596,1,4362,8,519,18576
3-Jul-21,0,75,11,13715,1,4437,8,596,18843
26-Jun-21,0,73,12,13925,1,4470,9,651,19141
19-Jun-21,0,80,13,13886,1,4583,7,499,19069
12-Jun-21,0,79,13,13651,1,4506,9,455,18714
5-Jun-21,0,84,12,13656,1,4525,8,457,18743
29-May-21,0,79,15,13935,1,4554,10,438,19032
22-May-21,0,84,14,13840,1,4511,8,500,18958
15-May-21,0,80,13,13790,2,4524,11,553,18973
8-May-21,0,85,13,13748,1,4448,9,357,18661
1-May-21,0,83,12,13853,0,4371,8,379,18706
24-Apr-21,0,82,14,13835,1,4431,6,340,18709
17-Apr-21,0,86,14,14080,0,4455,10,322,18967
10-Apr-21,0,82,12,13736,0,4326,8,280,18444
3-Apr-21,0,82,14,13815,0,4347,8,289,18555
27-Mar-21,0,78,12,13490,1,4212,7,325,18125
20-Mar-21,0,77,12,13667,0,4389,9,366,18520
13-Mar-21,0,77,12,13671,0,4293,9,269,18331
6-Mar-21,0,75,14,13840,0,4361,8,253,18551
27-Feb-21,0,81,14,14144,1,4413,9,235,18897
20-Feb-21,0,83,14,14151,0,4451,10,217,18926
13-Feb-21,0,80,13,14057,0,4294,10,247,18701
6-Feb-21,0,86,16,14042,0,4271,10,233,18658
30-Jan-21,0,78,13,14221,0,4330,7,257,18906
23-Jan-21,0,85,13,14109,0,4283,9,192,18691
16-Jan-21,0,94,16,14294,0,4227,9,162,18802
9-Jan-21,0,88,15,14244,0,4133,9,154,18643
2-Jan-21,0,81,14,14235,0,4123,15,145,18613
26-Dec-20,0,82,15,13746,0,3721,9,130,17703
19-Dec-20,0,83,15,13904,0,3704,9,128,17843
12-Dec-20,0,86,16,14299,0,4007,16,170,18594
5-Dec-20,0,85,15,14714,0,4047,12,152,19025
28-Nov-20,0,81,14,14852,0,3970,16,148,19081
21-Nov-20,0,78,16,14817,0,4347,17,108,19383
14-Nov-20,0,86,17,14840,0,4567,22,62,19594
7-Nov-20,0,87,18,14286,0,4110,18,80,18599
31-Oct-20,0,86,19,14599,0,4100,25,49,18878
24-Oct-20,0,91,16,14571,0,4157,25,3,18863
17-Oct-20,0,84,16,14434,0,5116,25,0,19675
10-Oct-20,0,87,15,14563,0,5102,33,0,19800
3-Oct-20,0,83,18,14593,0,4059,26,0,18779
26-Sep-20,0,88,17,14717,0,3971,23,0,18816
19-Sep-20,0,91,20,14605,0,4064,22,0,18802
12-Sep-20,0,87,19,14706,0,4865,24,0,19701
5-Sep-20,0,88,20,14661,0,4641,29,0,19439
29-Aug-20,0,91,19,14814,0,4557,28,0,19509
22-Aug-20,0,92,17,14570,0,4572,30,0,19281
15-Aug-20,0,84,20,14743,0,4615,32,0,19494
8-Aug-20,0,92,20,14800,0,4627,28,0,19567
1-Aug-20,0,90,19,14925,0,4433,20,0,19487
25-Jul-20,0,92,19,15013,0,4533,19,0,19676
18-Jul-20,0,89,20,14750,0,4532,22,0,19413
11-Jul-20,0,93,15,14901,0,4594,20,0,19623
4-Jul-20,0,88,21,15080,0,4467,26,0,19682
27-Jun-20,0,93,19,15281,0,4679,26,0,20098
20-Jun-20,0,86,21,15642,0,4835,25,0,20609
13-Jun-20,0,91,25,15979,0,4613,23,0,20731
6-Jun-20,0,92,23,15597,0,4564,20,0,20296
30-May-20,0,86,25,15812,0,4433,17,0,20373
23-May-20,0,94,27,15830,0,4163,15,0,20129
16-May-20,0,98,29,15739,0,4169,14,0,20049
9-May-20,0,98,23,15300,0,4336,12,0,19769
2-May-20,0,94,27,15370,0,4261,12,0,19764
25-Apr-20,0,99,28,14711,0,4043,9,0,18890
18-Apr-20,0,102,25,14857,1,3970,4,0,18959
11-Apr-20,0,107,27,14884,0,3966,4,0,18988
4-Apr-20,0,104,25,14829,0,3928,3,0,18889
28-Mar-20,0,101,25,15302,0,3937,4,0,19369
21-Mar-20,0,103,27,15217,1,4067,3,0,19418
14-Mar-20,0,107,26,15136,0,3813,1,0,19083
7-Mar-20,0,106,26,15202,0,3797,0,0,19131
29-Feb-20,0,106,29,15269,1,3795,0,0,19200
22-Feb-20,0,106,30,15222,0,3726,0,0,19084
15-Feb-20,0,111,29,15289,0,3596,1,0,19026
8-Feb-20,0,108,28,15328,0,3667,0,0,19131
1-Feb-20,0,110,28,15523,1,3673,0,0,19335
25-Jan-20,0,114,25,15279,0,3523,0,0,18941
18-Jan-20,0,108,29,15289,0,3549,0,0,18975
11-Jan-20,0,115,29,15540,1,3561,0,0,19246
4-Jan-20,0,123,26,15612,0,3492,0,0,19253
28-Dec-19,0,117,30,15151,0,3065,0,0,18363
21-Dec-19,0,122,26,15067,0,3179,0,0,18394
14-Dec-19,0,125,27,16078,1,3614,0,0,19845
7-Dec-19,0,124,25,16029,0,3634,0,0,19812
30-Nov-19,0,127,30,15804,0,3591,1,0,19553
23-Nov-19,0,119,28,15640,0,3512,2,0,19301
16-Nov-19,0,119,31,15755,1,3659,1,0,19566
9-Nov-19,0,129,29,15583,0,3449,3,0,19193
2-Nov-19,0,124,32,15773,0,3488,3,0,19420
26-Oct-19,0,138,29,15742,0,3345,0,0,19254
19-Oct-19,0,129,27,15927,1,3347,0,0,19431
12-Oct-19,0,127,31,15761,0,3414,1,0,19334
5-Oct-19,0,143,38,15753,0,3405,1,0,19340
28-Sep-19,0,139,30,15852,1,3428,1,0,19451
21-Sep-19,0,129,29,15753,0,3194,0,0,19105
14-Sep-19,0,131,30,15994,0,3107,0,0,19262
7-Sep-19,0,134,30,15560,0,3222,0,0,18946
31-Aug-19,0,133,35,15642,1,3279,0,0,19090
24-Aug-19,0,131,33,15477,0,3163,0,0,18804
17-Aug-19,0,145,34,15513,0,3095,0,0,18787
10-Aug-19,0,144,29,15331,1,2854,0,0,18359
3-Aug-19,0,150,30,15495,0,2924,0,0,18599
27-Jul-19,0,135,32,15766,0,2942,0,0,18875
20-Jul-19,0,145,35,15715,1,2864,0,0,18760
13-Jul-19,0,141,29,15700,0,2975,0,0,18845
6-Jul-19,0,140,32,15572,0,3223,0,0,18967
29-Jun-19,0,153,32,15719,1,3087,0,0,18992
22-Jun-19,0,151,33,15604,1,3045,0,0,18834
15-Jun-19,0,147,35,15576,0,3226,0,0,18984
8-Jun-19,0,150,29,15515,0,2942,0,0,18636
1-Jun-19,0,140,37,15912,0,2724,0,0,18813
25-May-19,0,145,32,15847,1,2803,0,0,18828
18-May-19,0,142,31,15985,0,3094,0,0,19252
11-May-19,0,142,31,16276,2,3083,0,0,19534
4-May-19,0,131,30,16204,8,3001,0,0,19374
27-Apr-19,0,126,35,15858,7,2788,0,0,18814
20-Apr-19,0,134,30,15809,4,2635,0,0,18612
13-Apr-19,0,136,32,15934,8,2545,0,0,18655
6-Apr-19,0,136,36,15967,7,2652,0,0,18798
30-Mar-19,0,135,37,16346,6,2608,0,0,19132
23-Mar-19,0,135,32,16429,6,2537,0,0,19139
16-Mar-19,0,131,32,16638,4,2370,0,0,19175
9-Mar-19,0,138,32,16351,5,2300,0,0,18826
2-Mar-19,0,141,34,16383,6,2436,0,0,19000
23-Feb-19,0,145,31,16483,7,2458,0,0,19124
16-Feb-19,0,143,32,16565,6,2689,0,0,19435
9-Feb-19,0,143,37,16169,5,2551,0,0,18905
2-Feb-19,0,142,32,16180,4,2437,0,0,18795
26-Jan-19,0,136,34,16314,5,2474,0,0,18963
19-Jan-19,0,138,30,16504,6,2276,0,0,18954
12-Jan-19,0,140,28,16585,7,2294,0,0,19054
5-Jan-19,0,140,28,16175,7,2122,0,0,18472
29-Dec-18,0,145,31,15571,7,1974,0,0,17728
22-Dec-18,0,144,30,15031,4,2009,0,0,17218
15-Dec-18,0,145,30,15884,5,2077,0,0,18141
8-Dec-18,0,140,35,16095,5,2193,0,0,18468
1-Dec-18,0,148,31,16110,3,2268,0,0,18560
24-Nov-18,0,145,27,15817,3,2096,0,0,18088
17-Nov-18,0,153,25,15964,6,2104,0,0,18252
10-Nov-18,0,153,33,15995,7,2074,0,0,18262
3-Nov-18,0,158,34,15944,5,2319,0,0,18460
27-Oct-18,0,153,34,15920,4,2400,0,0,18511
20-Oct-18,0,150,31,16208,5,2237,0,0,18631
13-Oct-18,0,153,30,16470,4,2200,0,0,18857
6-Oct-18,0,153,32,15848,4,1926,0,0,17963
29-Sep-18,0,155,36,15769,5,1929,0,0,17894
22-Sep-18,0,157,29,15572,4,1852,0,0,17614
15-Sep-18,0,158,29,15334,5,1998,0,0,17524
8-Sep-18,0,161,28,15815,4,1974,0,0,17982
1-Sep-18,0,162,30,15825,4,2001,0,0,18022
25-Aug-18,0,156,29,15769,4,1929,0,0,17887
18-Aug-18,0,152,29,15565,4,1775,0,0,17525
11-Aug-18,0,150,30,15460,4,1860,0,0,17504
4-Aug-18,0,157,36,15504,5,1979,0,0,17681
28-Jul-18,0,157,32,15243,2,1898,0,0,17332
21-Jul-18,0,161,31,15322,0,1850,0,0,17364
14-Jul-18,0,138,34,14741,0,1725,0,0,16638
7-Jul-18,0,158,35,15621,0,1700,0,0,17514
30-Jun-18,0,145,31,13791,0,1442,0,0,15409
23-Jun-18,0,165,36,15441,0,1594,0,0,17236
16-Jun-18,0,162,35,15550,0,1633,0,0,17380
9-Jun-18,0,157,39,15502,0,1650,0,0,17348
2-Jun-18,0,167,34,15757,3,1795,0,0,17756
26-May-18,0,176,33,15551,0,1487,0,0,17247
19-May-18,0,173,37,15841,0,1554,0,0,17605
12-May-18,0,168,30,16140,0,1400,0,0,17738
5-May-18,0,174,34,16049,0,1460,0,0,17717
28-Apr-18,0,216,35,16600,0,1485,0,0,18336
21-Apr-18,0,181,33,18067,0,1517,0,0,19798
14-Apr-18,0,181,32,16873,2,1712,0,0,18800
7-Apr-18,0,177,38,16800,4,1558,0,0,18577
31-Mar-18,0,184,32,16736,2,1457,0,0,18411
24-Mar-18,0,195,31,17965,3,1458,0,0,19652
17-Mar-18,0,184,35,16418,2,1380,0,0,18019
10-Mar-18,0,184,32,16795,2,1419,0,0,18432
3-Mar-18,0,193,35,16780,2,1520,0,0,18530
24-Feb-18,0,190,31,16427,2,1338,0,0,17988
17-Feb-18,0,204,34,16367,2,1274,0,0,17881
10-Feb-18,0,196,36,16275,1,1292,0,0,17800
3-Feb-18,0,206,34,16345,3,1527,0,0,18115
27-Jan-18,0,196,33,16268,5,1385,0,0,17887
20-Jan-18,0,202,36,15935,2,1450,0,0,17625
13-Jan-18,0,200,33,15856,7,1320,0,0,17416
6-Jan-18,0,209,38,16034,3,1285,0,0,17569
30-Dec-17,0,204,39,15544,2,1056,0,0,16845
23-Dec-17,0,206,35,15002,2,1039,0,0,16284
16-Dec-17,0,203,35,15809,3,1123,0,0,17173
9-Dec-17,0,206,32,15921,2,1477,0,0,17638
2-Dec-17,0,204,38,16419,2,1339,0,0,18002
25-Nov-17,0,210,33,16087,2,1193,0,0,17525
18-Nov-17,0,205,39,16106,3,1096,0,0,17449
11-Nov-17,0,210,39,16304,2,1081,0,0,17636
4-Nov-17,0,227,40,16241,3,1163,0,0,17674
28-Oct-17,0,217,39,15860,4,909,0,0,17029
21-Oct-17,0,226,39,15948,4,863,0,0,17080
14-Oct-17,0,227,43,16086,3,922,0,0,17281
7-Oct-17,0,235,45,16215,2,932,0,0,17429
30-Sep-17,0,237,38,16037,2,800,0,0,17114
23-Sep-17,0,243,42,15838,4,715,0,0,16842
16-Sep-17,0,239,39,15716,4,962,0,0,16960
9-Sep-17,0,242,44,15784,2,659,0,0,16731
2-Sep-17,0,254,46,15812,2,760,0,0,16874
26-Aug-17,0,254,43,15805,2,681,0,0,16785
19-Aug-17,0,265,45,15748,3,533,0,0,16594
12-Aug-17,0,242,38,14836,2,474,0,0,15592
5-Aug-17,0,264,39,15242,3,572,0,0,16120
29-Jul-17,0,263,41,15266,1,288,0,0,15859
22-Jul-17,0,259,44,15357,2,134,0,0,15796
15-Jul-17,0,269,38,15512,2,132,0,0,15953
8-Jul-17,0,261,47,15717,3,124,0,0,16152
1-Jul-17,0,262,43,15617,3,114,0,0,16039
24-Jun-17,0,261,44,15799,3,113,0,0,16220
17-Jun-17,0,270,42,15745,3,107,0,0,16167
10-Jun-17,0,279,42,15587,4,112,0,0,16024
3-Jun-17,0,278,49,15721,4,105,0,0,16157
27-May-17,0,289,50,15553,4,100,0,0,15996
20-May-17,0,286,45,15504,2,92,0,0,15929
13-May-17,0,290,46,16796,2,93,0,0,17227
6-May-17,0,286,45,17445,4,74,0,0,17854
29-Apr-17,0,297,46,17294,3,62,0,0,17702
22-Apr-17,0,296,46,17500,3,63,0,0,17908
15-Apr-17,0,290,48,17485,3,47,0,0,17873
8-Apr-17,0,293,46,17367,3,36,0,0,17745
1-Apr-17,0,298,43,17508,7,31,0,0,17887
25-Mar-17,0,300,47,17271,10,27,0,0,17655
18-Mar-17,0,304,47,17267,14,10,0,0,17642
11-Mar-17,0,296,43,16712,19,0,0,0,17070
4-Mar-17,0,303,46,16999,3,0,0,0,17351
25-Feb-17,0,307,48,16959,0,0,0,0,17314
18-Feb-17,0,320,45,16806,0,0,0,0,17171
11-Feb-17,0,305,40,16866,0,0,0,0,17211
4-Feb-17,0,294,42,16988,0,0,0,0,17324
28-Jan-17,0,312,43,16604,0,0,0,0,16959
21-Jan-17,0,291,48,16706,0,0,0,0,17045
14-Jan-17,0,310,46,16459,0,0,0,0,16815
7-Jan-17,0,318,51,16878,0,0,0,0,17247
31-Dec-16,0,318,45,15947,0,0,0,0,16310
24-Dec-16,0,311,45,15031,0,0,0,0,15387
17-Dec-16,0,319,46,16005,0,0,0,0,16370
10-Dec-16,0,318,42,16210,0,0,0,0,16570
3-Dec-16,0,332,52,16549,0,0,0,0,16933
26-Nov-16,0,334,46,16549,0,0,0,0,16929
19-Nov-16,0,332,51,15957,0,0,0,0,16340
12-Nov-16,0,342,49,16191,0,0,0,0,16582
5-Nov-16,0,345,56,15339,0,0,0,0,15740
29-Oct-16,0,349,49,14911,0,0,0,0,15309
22-Oct-16,0,350,53,15040,0,0,0,0,15443
15-Oct-16,0,348,53,15238,0,0,0,0,15639
8-Oct-16,0,350,52,14567,0,0,0,0,14969
1-Oct-16,0,352,63,15041,0,0,0,0,15456
24-Sep-16,0,351,60,14551,0,0,0,0,14962
17-Sep-16,0,347,58,14655,0,0,0,0,15060
10-Sep-16,0,338,62,14584,0,0,0,0,14984
3-Sep-16,0,355,62,14582,0,0,0,0,14999
27-Aug-16,0,352,67,14673,0,0,0,0,15092
20-Aug-16,0,359,63,14400,0,0,0,0,14822
13-Aug-16,0,392,61,14415,0,0,0,0,14868
6-Aug-16,0,373,62,14653,0,0,0,0,15088
30-Jul-16,0,367,60,14607,0,0,0,0,15034
23-Jul-16,0,374,58,14741,0,0,0,0,15173
16-Jul-16,0,415,57,14959,0,0,0,0,15431
9-Jul-16,0,439,58,13846,0,0,0,0,14343
2-Jul-16,0,378,59,14610,0,0,0,0,15047
25-Jun-16,0,392,62,14607,0,0,0,0,15061
18-Jun-16,0,380,65,14953,0,0,0,0,15398
11-Jun-16,0,398,58,14869,0,0,0,0,15325
4-Jun-16,0,379,63,14444,0,0,0,0,14886
28-May-16,0,406,68,14443,0,0,0,0,14917
21-May-16,0,396,68,14739,0,0,0,0,15203
14-May-16,0,391,58,14553,0,0,0,0,15002
7-May-16,0,393,72,14577,0,0,0,0,15042
30-Apr-16,0,391,67,14347,0,0,0,0,14805
23-Apr-16,0,412,65,14447,0,0,0,0,14924
16-Apr-16,0,392,65,14611,0,0,0,0,15068
9-Apr-16,0,414,68,14426,0,0,0,0,14908
2-Apr-16,0,410,67,14537,0,0,0,0,15014
26-Mar-16,0,407,74,14040,0,0,0,0,14521
19-Mar-16,0,418,73,13893,0,0,0,0,14384
12-Mar-16,0,419,76,14256,0,0,0,0,14751
5-Mar-16,0,440,73,14272,0,0,0,0,14785
27-Feb-16,0,437,78,14293,0,0,0,0,14808
20-Feb-16,0,448,78,14500,0,0,0,0,15026
13-Feb-16,0,458,74,13949,0,0,0,0,14481
6-Feb-16,0,437,76,13681,0,0,0,0,14194
30-Jan-16,0,428,76,13729,0,0,0,0,14233
23-Jan-16,0,435,80,13393,0,0,0,0,13908
16-Jan-16,0,441,79,13369,0,0,0,0,13889
9-Jan-16,0,443,77,13298,0,0,0,0,13818
2-Jan-16,0,450,77,12385,0,0,0,0,12912
26-Dec-15,0,446,84,11190,0,0,0,0,11720
19-Dec-15,0,452,92,11888,0,0,0,0,12432
12-Dec-15,0,468,84,12623,0,0,0,0,13175
5-Dec-15,0,465,87,12736,0,0,0,0,13288
28-Nov-15,0,460,86,12721,0,0,0,0,13267
21-Nov-15,0,465,88,12423,0,0,0,0,12976
14-Nov-15,0,494,90,12483,0,0,0,0,13067
7-Nov-15,0,489,89,12383,0,0,0,0,12961
31-Oct-15,0,509,87,12512,0,0,0,0,13108
24-Oct-15,0,495,88,12346,0,0,0,0,12929
17-Oct-15,0,505,90,12338,0,0,0,0,12933
10-Oct-15,0,487,96,11995,0,0,0,0,12578
3-Oct-15,0,489,87,11676,0,0,0,0,12252
26-Sep-15,0,480,93,11015,0,0,0,0,11588
19-Sep-15,0,474,85,10385,0,0,0,0,10944
12-Sep-15,0,506,94,11041,0,0,0,0,11641
5-Sep-15,0,519,101,11402,0,0,0,0,12022
29-Aug-15,0,528,106,11556,0,0,0,0,12190
22-Aug-15,0,596,106,11672,0,0,0,0,12374
15-Aug-15,0,530,105,11574,0,0,0,0,12209
8-Aug-15,1,520,103,11089,0,0,0,0,11713
1-Aug-15,1,507,106,10947,0,0,0,0,11561
25-Jul-15,1,593,107,10712,0,0,0,0,11413
18-Jul-15,1,520,104,10533,0,0,0,0,11158
11-Jul-15,1,538,110,10764,0,0,0,0,11413
4-Jul-15,1,550,108,10918,0,0,0,0,11577
27-Jun-15,1,537,114,10783,0,0,0,0,11435
20-Jun-15,1,564,109,10961,0,0,0,0,11635
13-Jun-15,1,550,114,10845,0,0,0,0,11510
6-Jun-15,1,540,112,10369,0,0,0,0,11022
30-May-15,1,537,115,10137,0,0,0,0,10790
23-May-15,1,532,116,10024,0,0,0,0,10673
16-May-15,1,556,115,10051,0,0,0,0,10723
9-May-15,1,549,112,9923,0,0,0,0,10585
2-May-15,1,549,115,9700,0,0,0,0,10365
25-Apr-15,1,555,118,9462,0,0,0,0,10136
18-Apr-15,1,552,121,9363,0,0,0,0,10037
11-Apr-15,1,549,122,9046,0,0,0,0,9718
4-Apr-15,1,561,130,8955,0,0,0,0,9647
28-Mar-15,1,570,125,9064,0,0,0,0,9760
21-Mar-15,1,568,134,9194,0,0,0,0,9897
14-Mar-15,1,561,137,9163,0,0,0,0,9862
7-Mar-15,1,553,127,8826,0,0,0,0,9507
28-Feb-15,1,587,130,8638,0,0,0,0,9356
21-Feb-15,1,584,124,8459,0,0,0,0,9168
14-Feb-15,1,588,136,8314,0,0,0,0,9039
7-Feb-15,1,577,142,8240,0,0,0,0,8960
31-Jan-15,1,583,133,8003,0,0,0,0,8720
24-Jan-15,1,598,130,7738,0,0,0,0,8467
17-Jan-15,1,593,138,7779,0,0,0,0,8511
10-Jan-15,1,579,137,7654,0,0,0,0,8371
3-Jan-15,1,581,127,7500,0,0,0,0,8209
27-Dec-14,1,562,131,6455,0,0,0,0,7149
20-Dec-14,1,582,129,6575,0,0,0,0,7287
13-Dec-14,1,602,134,7342,0,0,0,0,8079
6-Dec-14,1,602,139,7319,0,0,0,0,8061
29-Nov-14,1,609,134,7171,0,0,0,0,7915
22-Nov-14,1,630,144,7088,0,0,0,0,7863
15-Nov-14,1,625,160,7301,0,0,0,0,8087
8-Nov-14,1,617,151,7029,0,0,0,0,7798
1-Nov-14,2,635,166,6769,0,0,0,0,7572
25-Oct-14,1,614,160,6508,0,0,0,0,7283
18-Oct-14,1,609,159,6348,0,0,0,0,7117
11-Oct-14,1,627,155,6349,0,0,0,0,7132
4-Oct-14,1,621,165,5897,0,0,0,0,6684
27-Sep-14,1,622,158,5854,0,0,0,0,6635
20-Sep-14,1,633,164,5881,0,0,0,0,6679
13-Sep-14,1,654,161,5789,0,0,0,0,6605
6-Sep-14,1,652,166,5700,0,0,0,0,6519
30-Aug-14,2,643,171,5580,0,0,0,0,6396
23-Aug-14,2,643,171,5466,0,0,0,0,6282
16-Aug-14,2,633,184,5304,0,0,0,0,6123
9-Aug-14,2,648,185,5183,0,0,0,0,6018
2-Aug-14,2,641,183,5247,0,0,0,0,6073
26-Jul-14,2,643,185,4938,0,0,0,0,5768
19-Jul-14,2,640,183,4814,0,0,0,0,5639
12-Jul-14,2,719,175,4713,0,0,0,0,5609
5-Jul-14,2,652,177,4543,0,0,0,0,5374
28-Jun-14,2,653,182,4526,0,0,0,0,5363
21-Jun-14,2,655,181,4490,0,0,0,0,5328
14-Jun-14,2,651,166,3895,0,0,0,0,4714
7-Jun-14,2,674,187,3866,0,0,0,0,4729
31-May-14,2,665,183,3623,0,0,0,0,4473
24-May-14,2,669,194,3581,0,0,0,0,4446
17-May-14,2,669,191,3346,0,0,0,0,4208
10-May-14,2,671,196,3221,0,0,0,0,4090
3-May-14,2,661,192,3223,0,0,0,0,4078
26-Apr-14,2,681,194,3095,0,0,0,0,3972
19-Apr-14,2,687,201,2981,0,0,0,0,3871
12-Apr-14,2,695,203,2910,0,0,0,0,3810
5-Apr-14,2,694,218,2807,0,0,0,0,3721
29-Mar-14,2,725,222,2807,0,0,0,0,3756
22-Mar-14,2,709,226,2661,0,0,0,0,3598
15-Mar-14,2,712,235,2544,0,0,0,0,3493
8-Mar-14,2,747,241,2405,0,0,0,0,3395
1-Mar-14,2,715,255,2368,0,0,0,0,3340
22-Feb-14,2,748,244,2245,0,0,0,0,3239
15-Feb-14,2,707,248,2107,0,0,0,0,3064
8-Feb-14,2,759,256,2044,0,0,0,0,3061
1-Feb-14,2,734,258,1928,0,0,0,0,2922
25-Jan-14,2,753,263,1781,0,0,0,0,2799
18-Jan-14,2,749,260,1722,0,0,0,0,2733
11-Jan-14,2,728,288,1612,0,0,0,0,2630
4-Jan-14,2,755,276,1343,0,0,0,0,2376
28-Dec-13,2,736,285,1082,0,0,0,0,2105
21-Dec-13,2,711,276,1005,0,0,0,0,1994
14-Dec-13,2,769,285,1118,0,0,0,0,2174
7-Dec-13,2,756,281,1008,0,0,0,0,2047
30-Nov-13,2,799,286,920,0,0,0,0,2007
23-Nov-13,2,898,290,849,0,0,0,0,2039
16-Nov-13,2,956,313,790,0,0,0,0,2061
9-Nov-13,2,939,313,746,0,0,0,0,2000
2-Nov-13,2,963,327,662,0,0,0,0,1954
26-Oct-13,2,992,322,636,0,0,0,0,1952
19-Oct-13,2,977,309,651,0,0,0,0,1939
12-Oct-13,2,990,328,576,0,0,0,0,1896
5-Oct-13,2,887,328,560,0,0,0,0,1777
28-Sep-13,2,1450,331,516,0,0,0,0,2299
21-Sep-13,2,1510,336,449,0,0,0,0,2297
14-Sep-13,2,1530,335,423,0,0,0,0,2290
7-Sep-13,2,874,340,363,0,0,0,0,1579
31-Aug-13,2,887,353,317,0,0,0,0,1559
24-Aug-13,2,870,349,269,0,0,0,0,1490
17-Aug-13,2,877,352,235,0,0,0,0,1466
10-Aug-13,2,862,367,190,0,0,0,0,1421
3-Aug-13,2,841,399,149,0,0,0,0,1391
27-Jul-13,2,876,416,104,0,0,0,0,1398
20-Jul-13,2,887,457,15,0,0,0,0,1361
13-Jul-13,2,879,445,0,0,0,0,0,1326
6-Jul-13,2,876,462,0,0,0,0,0,1340
29-Jun-13,2,884,464,0,0,0,0,0,1350
22-Jun-13,2,901,452,0,0,0,0,0,1355
15-Jun-13,2,893,460,0,0,0,0,0,1355
8-Jun-13,2,956,474,0,0,0,0,0,1432
1-Jun-13,2,1117,473,0,0,0,0,0,1592
25-May-13,2,1063,441,0,0,0,0,0,1506
18-May-13,2,944,383,0,0,0,0,0,1329
11-May-13,2,1109,450,0,0,0,0,0,1561
4-May-13,2,1020,451,0,0,0,0,0,1473
27-Apr-13,2,1003,450,0,0,0,0,0,1455
20-Apr-13,2,967,448,0,0,0,0,0,1417
13-Apr-13,2,1061,469,0,0,0,0,0,1532
6-Apr-13,2,1022,478,0,0,0,0,0,1502
30-Mar-13,2,1030,440,0,0,0,0,0,1472
23-Mar-13,2,1101,464,0,0,0,0,0,1567
16-Mar-13,2,1047,451,0,0,0,0,0,1500
9-Mar-13,2,1046,476,0,0,0,0,0,1524
2-Mar-13,3,1044,469,0,0,0,0,0,1516
23-Feb-13,3,1077,479,0,0,0,0,0,1559
16-Feb-13,3,1094,458,0,0,0,0,0,1555
9-Feb-13,3,1091,468,0,0,0,0,0,1562
2-Feb-13,3,1099,447,0,0,0,0,0,1549
26-Jan-13,3,1132,452,0,0,0,0,0,1587
19-Jan-13,3,1124,455,0,0,0,0,0,1582
12-Jan-13,4,1164,446,0,0,0,0,0,1614
5-Jan-13,3,1167,443,0,0,0,0,0,1613
29-Dec-12,3,1085,411,0,0,0,0,0,1499
22-Dec-12,3,1076,407,0,0,0,0,0,1486
15-Dec-12,4,1173,437,0,0,0,0,0,1614
8-Dec-12,4,1208,441,0,0,0,0,0,1653
1-Dec-12,3,1171,476,0,0,0,0,0,1650
24-Nov-12,3,1185,474,0,0,0,0,0,1662
17-Nov-12,3,1163,460,0,0,0,0,0,1626
10-Nov-12,3,1200,451,0,0,0,0,0,1654
3-Nov-12,3,1247,476,0,0,0,0,0,1726
27-Oct-12,3,1219,456,0,0,0,0,0,1678
20-Oct-12,3,1276,443,0,0,0,0,0,1722
1 Week 6.x-0.x 6.x-1.x 7.x-1.x 7.x-2.x 8.x-1.x 8.x-3.x 8.x-4.x 5.0.x Total
2 21-Aug-21 0 71 12 13458 1 4632 6 637 18817
3 14-Aug-21 0 77 13 13517 1 4518 9 608 18743
4 7-Aug-21 0 73 11 13586 1 4651 6 606 18934
5 31-Jul-21 0 76 13 13479 1 4488 7 619 18683
6 24-Jul-21 0 70 10 13771 1 4385 7 530 18774
7 17-Jul-21 0 73 12 13756 1 4430 8 536 18816
8 10-Jul-21 0 77 13 13596 1 4362 8 519 18576
9 3-Jul-21 0 75 11 13715 1 4437 8 596 18843
10 26-Jun-21 0 73 12 13925 1 4470 9 651 19141
11 19-Jun-21 0 80 13 13886 1 4583 7 499 19069
12 12-Jun-21 0 79 13 13651 1 4506 9 455 18714
13 5-Jun-21 0 84 12 13656 1 4525 8 457 18743
14 29-May-21 0 79 15 13935 1 4554 10 438 19032
15 22-May-21 0 84 14 13840 1 4511 8 500 18958
16 15-May-21 0 80 13 13790 2 4524 11 553 18973
17 8-May-21 0 85 13 13748 1 4448 9 357 18661
18 1-May-21 0 83 12 13853 0 4371 8 379 18706
19 24-Apr-21 0 82 14 13835 1 4431 6 340 18709
20 17-Apr-21 0 86 14 14080 0 4455 10 322 18967
21 10-Apr-21 0 82 12 13736 0 4326 8 280 18444
22 3-Apr-21 0 82 14 13815 0 4347 8 289 18555
23 27-Mar-21 0 78 12 13490 1 4212 7 325 18125
24 20-Mar-21 0 77 12 13667 0 4389 9 366 18520
25 13-Mar-21 0 77 12 13671 0 4293 9 269 18331
26 6-Mar-21 0 75 14 13840 0 4361 8 253 18551
27 27-Feb-21 0 81 14 14144 1 4413 9 235 18897
28 20-Feb-21 0 83 14 14151 0 4451 10 217 18926
29 13-Feb-21 0 80 13 14057 0 4294 10 247 18701
30 6-Feb-21 0 86 16 14042 0 4271 10 233 18658
31 30-Jan-21 0 78 13 14221 0 4330 7 257 18906
32 23-Jan-21 0 85 13 14109 0 4283 9 192 18691
33 16-Jan-21 0 94 16 14294 0 4227 9 162 18802
34 9-Jan-21 0 88 15 14244 0 4133 9 154 18643
35 2-Jan-21 0 81 14 14235 0 4123 15 145 18613
36 26-Dec-20 0 82 15 13746 0 3721 9 130 17703
37 19-Dec-20 0 83 15 13904 0 3704 9 128 17843
38 12-Dec-20 0 86 16 14299 0 4007 16 170 18594
39 5-Dec-20 0 85 15 14714 0 4047 12 152 19025
40 28-Nov-20 0 81 14 14852 0 3970 16 148 19081
41 21-Nov-20 0 78 16 14817 0 4347 17 108 19383
42 14-Nov-20 0 86 17 14840 0 4567 22 62 19594
43 7-Nov-20 0 87 18 14286 0 4110 18 80 18599
44 31-Oct-20 0 86 19 14599 0 4100 25 49 18878
45 24-Oct-20 0 91 16 14571 0 4157 25 3 18863
46 17-Oct-20 0 84 16 14434 0 5116 25 0 19675
47 10-Oct-20 0 87 15 14563 0 5102 33 0 19800
48 3-Oct-20 0 83 18 14593 0 4059 26 0 18779
49 26-Sep-20 0 88 17 14717 0 3971 23 0 18816
50 19-Sep-20 0 91 20 14605 0 4064 22 0 18802
51 12-Sep-20 0 87 19 14706 0 4865 24 0 19701
52 5-Sep-20 0 88 20 14661 0 4641 29 0 19439
53 29-Aug-20 0 91 19 14814 0 4557 28 0 19509
54 22-Aug-20 0 92 17 14570 0 4572 30 0 19281
55 15-Aug-20 0 84 20 14743 0 4615 32 0 19494
56 8-Aug-20 0 92 20 14800 0 4627 28 0 19567
57 1-Aug-20 0 90 19 14925 0 4433 20 0 19487
58 25-Jul-20 0 92 19 15013 0 4533 19 0 19676
59 18-Jul-20 0 89 20 14750 0 4532 22 0 19413
60 11-Jul-20 0 93 15 14901 0 4594 20 0 19623
61 4-Jul-20 0 88 21 15080 0 4467 26 0 19682
62 27-Jun-20 0 93 19 15281 0 4679 26 0 20098
63 20-Jun-20 0 86 21 15642 0 4835 25 0 20609
64 13-Jun-20 0 91 25 15979 0 4613 23 0 20731
65 6-Jun-20 0 92 23 15597 0 4564 20 0 20296
66 30-May-20 0 86 25 15812 0 4433 17 0 20373
67 23-May-20 0 94 27 15830 0 4163 15 0 20129
68 16-May-20 0 98 29 15739 0 4169 14 0 20049
69 9-May-20 0 98 23 15300 0 4336 12 0 19769
70 2-May-20 0 94 27 15370 0 4261 12 0 19764
71 25-Apr-20 0 99 28 14711 0 4043 9 0 18890
72 18-Apr-20 0 102 25 14857 1 3970 4 0 18959
73 11-Apr-20 0 107 27 14884 0 3966 4 0 18988
74 4-Apr-20 0 104 25 14829 0 3928 3 0 18889
75 28-Mar-20 0 101 25 15302 0 3937 4 0 19369
76 21-Mar-20 0 103 27 15217 1 4067 3 0 19418
77 14-Mar-20 0 107 26 15136 0 3813 1 0 19083
78 7-Mar-20 0 106 26 15202 0 3797 0 0 19131
79 29-Feb-20 0 106 29 15269 1 3795 0 0 19200
80 22-Feb-20 0 106 30 15222 0 3726 0 0 19084
81 15-Feb-20 0 111 29 15289 0 3596 1 0 19026
82 8-Feb-20 0 108 28 15328 0 3667 0 0 19131
83 1-Feb-20 0 110 28 15523 1 3673 0 0 19335
84 25-Jan-20 0 114 25 15279 0 3523 0 0 18941
85 18-Jan-20 0 108 29 15289 0 3549 0 0 18975
86 11-Jan-20 0 115 29 15540 1 3561 0 0 19246
87 4-Jan-20 0 123 26 15612 0 3492 0 0 19253
88 28-Dec-19 0 117 30 15151 0 3065 0 0 18363
89 21-Dec-19 0 122 26 15067 0 3179 0 0 18394
90 14-Dec-19 0 125 27 16078 1 3614 0 0 19845
91 7-Dec-19 0 124 25 16029 0 3634 0 0 19812
92 30-Nov-19 0 127 30 15804 0 3591 1 0 19553
93 23-Nov-19 0 119 28 15640 0 3512 2 0 19301
94 16-Nov-19 0 119 31 15755 1 3659 1 0 19566
95 9-Nov-19 0 129 29 15583 0 3449 3 0 19193
96 2-Nov-19 0 124 32 15773 0 3488 3 0 19420
97 26-Oct-19 0 138 29 15742 0 3345 0 0 19254
98 19-Oct-19 0 129 27 15927 1 3347 0 0 19431
99 12-Oct-19 0 127 31 15761 0 3414 1 0 19334
100 5-Oct-19 0 143 38 15753 0 3405 1 0 19340
101 28-Sep-19 0 139 30 15852 1 3428 1 0 19451
102 21-Sep-19 0 129 29 15753 0 3194 0 0 19105
103 14-Sep-19 0 131 30 15994 0 3107 0 0 19262
104 7-Sep-19 0 134 30 15560 0 3222 0 0 18946
105 31-Aug-19 0 133 35 15642 1 3279 0 0 19090
106 24-Aug-19 0 131 33 15477 0 3163 0 0 18804
107 17-Aug-19 0 145 34 15513 0 3095 0 0 18787
108 10-Aug-19 0 144 29 15331 1 2854 0 0 18359
109 3-Aug-19 0 150 30 15495 0 2924 0 0 18599
110 27-Jul-19 0 135 32 15766 0 2942 0 0 18875
111 20-Jul-19 0 145 35 15715 1 2864 0 0 18760
112 13-Jul-19 0 141 29 15700 0 2975 0 0 18845
113 6-Jul-19 0 140 32 15572 0 3223 0 0 18967
114 29-Jun-19 0 153 32 15719 1 3087 0 0 18992
115 22-Jun-19 0 151 33 15604 1 3045 0 0 18834
116 15-Jun-19 0 147 35 15576 0 3226 0 0 18984
117 8-Jun-19 0 150 29 15515 0 2942 0 0 18636
118 1-Jun-19 0 140 37 15912 0 2724 0 0 18813
119 25-May-19 0 145 32 15847 1 2803 0 0 18828
120 18-May-19 0 142 31 15985 0 3094 0 0 19252
121 11-May-19 0 142 31 16276 2 3083 0 0 19534
122 4-May-19 0 131 30 16204 8 3001 0 0 19374
123 27-Apr-19 0 126 35 15858 7 2788 0 0 18814
124 20-Apr-19 0 134 30 15809 4 2635 0 0 18612
125 13-Apr-19 0 136 32 15934 8 2545 0 0 18655
126 6-Apr-19 0 136 36 15967 7 2652 0 0 18798
127 30-Mar-19 0 135 37 16346 6 2608 0 0 19132
128 23-Mar-19 0 135 32 16429 6 2537 0 0 19139
129 16-Mar-19 0 131 32 16638 4 2370 0 0 19175
130 9-Mar-19 0 138 32 16351 5 2300 0 0 18826
131 2-Mar-19 0 141 34 16383 6 2436 0 0 19000
132 23-Feb-19 0 145 31 16483 7 2458 0 0 19124
133 16-Feb-19 0 143 32 16565 6 2689 0 0 19435
134 9-Feb-19 0 143 37 16169 5 2551 0 0 18905
135 2-Feb-19 0 142 32 16180 4 2437 0 0 18795
136 26-Jan-19 0 136 34 16314 5 2474 0 0 18963
137 19-Jan-19 0 138 30 16504 6 2276 0 0 18954
138 12-Jan-19 0 140 28 16585 7 2294 0 0 19054
139 5-Jan-19 0 140 28 16175 7 2122 0 0 18472
140 29-Dec-18 0 145 31 15571 7 1974 0 0 17728
141 22-Dec-18 0 144 30 15031 4 2009 0 0 17218
142 15-Dec-18 0 145 30 15884 5 2077 0 0 18141
143 8-Dec-18 0 140 35 16095 5 2193 0 0 18468
144 1-Dec-18 0 148 31 16110 3 2268 0 0 18560
145 24-Nov-18 0 145 27 15817 3 2096 0 0 18088
146 17-Nov-18 0 153 25 15964 6 2104 0 0 18252
147 10-Nov-18 0 153 33 15995 7 2074 0 0 18262
148 3-Nov-18 0 158 34 15944 5 2319 0 0 18460
149 27-Oct-18 0 153 34 15920 4 2400 0 0 18511
150 20-Oct-18 0 150 31 16208 5 2237 0 0 18631
151 13-Oct-18 0 153 30 16470 4 2200 0 0 18857
152 6-Oct-18 0 153 32 15848 4 1926 0 0 17963
153 29-Sep-18 0 155 36 15769 5 1929 0 0 17894
154 22-Sep-18 0 157 29 15572 4 1852 0 0 17614
155 15-Sep-18 0 158 29 15334 5 1998 0 0 17524
156 8-Sep-18 0 161 28 15815 4 1974 0 0 17982
157 1-Sep-18 0 162 30 15825 4 2001 0 0 18022
158 25-Aug-18 0 156 29 15769 4 1929 0 0 17887
159 18-Aug-18 0 152 29 15565 4 1775 0 0 17525
160 11-Aug-18 0 150 30 15460 4 1860 0 0 17504
161 4-Aug-18 0 157 36 15504 5 1979 0 0 17681
162 28-Jul-18 0 157 32 15243 2 1898 0 0 17332
163 21-Jul-18 0 161 31 15322 0 1850 0 0 17364
164 14-Jul-18 0 138 34 14741 0 1725 0 0 16638
165 7-Jul-18 0 158 35 15621 0 1700 0 0 17514
166 30-Jun-18 0 145 31 13791 0 1442 0 0 15409
167 23-Jun-18 0 165 36 15441 0 1594 0 0 17236
168 16-Jun-18 0 162 35 15550 0 1633 0 0 17380
169 9-Jun-18 0 157 39 15502 0 1650 0 0 17348
170 2-Jun-18 0 167 34 15757 3 1795 0 0 17756
171 26-May-18 0 176 33 15551 0 1487 0 0 17247
172 19-May-18 0 173 37 15841 0 1554 0 0 17605
173 12-May-18 0 168 30 16140 0 1400 0 0 17738
174 5-May-18 0 174 34 16049 0 1460 0 0 17717
175 28-Apr-18 0 216 35 16600 0 1485 0 0 18336
176 21-Apr-18 0 181 33 18067 0 1517 0 0 19798
177 14-Apr-18 0 181 32 16873 2 1712 0 0 18800
178 7-Apr-18 0 177 38 16800 4 1558 0 0 18577
179 31-Mar-18 0 184 32 16736 2 1457 0 0 18411
180 24-Mar-18 0 195 31 17965 3 1458 0 0 19652
181 17-Mar-18 0 184 35 16418 2 1380 0 0 18019
182 10-Mar-18 0 184 32 16795 2 1419 0 0 18432
183 3-Mar-18 0 193 35 16780 2 1520 0 0 18530
184 24-Feb-18 0 190 31 16427 2 1338 0 0 17988
185 17-Feb-18 0 204 34 16367 2 1274 0 0 17881
186 10-Feb-18 0 196 36 16275 1 1292 0 0 17800
187 3-Feb-18 0 206 34 16345 3 1527 0 0 18115
188 27-Jan-18 0 196 33 16268 5 1385 0 0 17887
189 20-Jan-18 0 202 36 15935 2 1450 0 0 17625
190 13-Jan-18 0 200 33 15856 7 1320 0 0 17416
191 6-Jan-18 0 209 38 16034 3 1285 0 0 17569
192 30-Dec-17 0 204 39 15544 2 1056 0 0 16845
193 23-Dec-17 0 206 35 15002 2 1039 0 0 16284
194 16-Dec-17 0 203 35 15809 3 1123 0 0 17173
195 9-Dec-17 0 206 32 15921 2 1477 0 0 17638
196 2-Dec-17 0 204 38 16419 2 1339 0 0 18002
197 25-Nov-17 0 210 33 16087 2 1193 0 0 17525
198 18-Nov-17 0 205 39 16106 3 1096 0 0 17449
199 11-Nov-17 0 210 39 16304 2 1081 0 0 17636
200 4-Nov-17 0 227 40 16241 3 1163 0 0 17674
201 28-Oct-17 0 217 39 15860 4 909 0 0 17029
202 21-Oct-17 0 226 39 15948 4 863 0 0 17080
203 14-Oct-17 0 227 43 16086 3 922 0 0 17281
204 7-Oct-17 0 235 45 16215 2 932 0 0 17429
205 30-Sep-17 0 237 38 16037 2 800 0 0 17114
206 23-Sep-17 0 243 42 15838 4 715 0 0 16842
207 16-Sep-17 0 239 39 15716 4 962 0 0 16960
208 9-Sep-17 0 242 44 15784 2 659 0 0 16731
209 2-Sep-17 0 254 46 15812 2 760 0 0 16874
210 26-Aug-17 0 254 43 15805 2 681 0 0 16785
211 19-Aug-17 0 265 45 15748 3 533 0 0 16594
212 12-Aug-17 0 242 38 14836 2 474 0 0 15592
213 5-Aug-17 0 264 39 15242 3 572 0 0 16120
214 29-Jul-17 0 263 41 15266 1 288 0 0 15859
215 22-Jul-17 0 259 44 15357 2 134 0 0 15796
216 15-Jul-17 0 269 38 15512 2 132 0 0 15953
217 8-Jul-17 0 261 47 15717 3 124 0 0 16152
218 1-Jul-17 0 262 43 15617 3 114 0 0 16039
219 24-Jun-17 0 261 44 15799 3 113 0 0 16220
220 17-Jun-17 0 270 42 15745 3 107 0 0 16167
221 10-Jun-17 0 279 42 15587 4 112 0 0 16024
222 3-Jun-17 0 278 49 15721 4 105 0 0 16157
223 27-May-17 0 289 50 15553 4 100 0 0 15996
224 20-May-17 0 286 45 15504 2 92 0 0 15929
225 13-May-17 0 290 46 16796 2 93 0 0 17227
226 6-May-17 0 286 45 17445 4 74 0 0 17854
227 29-Apr-17 0 297 46 17294 3 62 0 0 17702
228 22-Apr-17 0 296 46 17500 3 63 0 0 17908
229 15-Apr-17 0 290 48 17485 3 47 0 0 17873
230 8-Apr-17 0 293 46 17367 3 36 0 0 17745
231 1-Apr-17 0 298 43 17508 7 31 0 0 17887
232 25-Mar-17 0 300 47 17271 10 27 0 0 17655
233 18-Mar-17 0 304 47 17267 14 10 0 0 17642
234 11-Mar-17 0 296 43 16712 19 0 0 0 17070
235 4-Mar-17 0 303 46 16999 3 0 0 0 17351
236 25-Feb-17 0 307 48 16959 0 0 0 0 17314
237 18-Feb-17 0 320 45 16806 0 0 0 0 17171
238 11-Feb-17 0 305 40 16866 0 0 0 0 17211
239 4-Feb-17 0 294 42 16988 0 0 0 0 17324
240 28-Jan-17 0 312 43 16604 0 0 0 0 16959
241 21-Jan-17 0 291 48 16706 0 0 0 0 17045
242 14-Jan-17 0 310 46 16459 0 0 0 0 16815
243 7-Jan-17 0 318 51 16878 0 0 0 0 17247
244 31-Dec-16 0 318 45 15947 0 0 0 0 16310
245 24-Dec-16 0 311 45 15031 0 0 0 0 15387
246 17-Dec-16 0 319 46 16005 0 0 0 0 16370
247 10-Dec-16 0 318 42 16210 0 0 0 0 16570
248 3-Dec-16 0 332 52 16549 0 0 0 0 16933
249 26-Nov-16 0 334 46 16549 0 0 0 0 16929
250 19-Nov-16 0 332 51 15957 0 0 0 0 16340
251 12-Nov-16 0 342 49 16191 0 0 0 0 16582
252 5-Nov-16 0 345 56 15339 0 0 0 0 15740
253 29-Oct-16 0 349 49 14911 0 0 0 0 15309
254 22-Oct-16 0 350 53 15040 0 0 0 0 15443
255 15-Oct-16 0 348 53 15238 0 0 0 0 15639
256 8-Oct-16 0 350 52 14567 0 0 0 0 14969
257 1-Oct-16 0 352 63 15041 0 0 0 0 15456
258 24-Sep-16 0 351 60 14551 0 0 0 0 14962
259 17-Sep-16 0 347 58 14655 0 0 0 0 15060
260 10-Sep-16 0 338 62 14584 0 0 0 0 14984
261 3-Sep-16 0 355 62 14582 0 0 0 0 14999
262 27-Aug-16 0 352 67 14673 0 0 0 0 15092
263 20-Aug-16 0 359 63 14400 0 0 0 0 14822
264 13-Aug-16 0 392 61 14415 0 0 0 0 14868
265 6-Aug-16 0 373 62 14653 0 0 0 0 15088
266 30-Jul-16 0 367 60 14607 0 0 0 0 15034
267 23-Jul-16 0 374 58 14741 0 0 0 0 15173
268 16-Jul-16 0 415 57 14959 0 0 0 0 15431
269 9-Jul-16 0 439 58 13846 0 0 0 0 14343
270 2-Jul-16 0 378 59 14610 0 0 0 0 15047
271 25-Jun-16 0 392 62 14607 0 0 0 0 15061
272 18-Jun-16 0 380 65 14953 0 0 0 0 15398
273 11-Jun-16 0 398 58 14869 0 0 0 0 15325
274 4-Jun-16 0 379 63 14444 0 0 0 0 14886
275 28-May-16 0 406 68 14443 0 0 0 0 14917
276 21-May-16 0 396 68 14739 0 0 0 0 15203
277 14-May-16 0 391 58 14553 0 0 0 0 15002
278 7-May-16 0 393 72 14577 0 0 0 0 15042
279 30-Apr-16 0 391 67 14347 0 0 0 0 14805
280 23-Apr-16 0 412 65 14447 0 0 0 0 14924
281 16-Apr-16 0 392 65 14611 0 0 0 0 15068
282 9-Apr-16 0 414 68 14426 0 0 0 0 14908
283 2-Apr-16 0 410 67 14537 0 0 0 0 15014
284 26-Mar-16 0 407 74 14040 0 0 0 0 14521
285 19-Mar-16 0 418 73 13893 0 0 0 0 14384
286 12-Mar-16 0 419 76 14256 0 0 0 0 14751
287 5-Mar-16 0 440 73 14272 0 0 0 0 14785
288 27-Feb-16 0 437 78 14293 0 0 0 0 14808
289 20-Feb-16 0 448 78 14500 0 0 0 0 15026
290 13-Feb-16 0 458 74 13949 0 0 0 0 14481
291 6-Feb-16 0 437 76 13681 0 0 0 0 14194
292 30-Jan-16 0 428 76 13729 0 0 0 0 14233
293 23-Jan-16 0 435 80 13393 0 0 0 0 13908
294 16-Jan-16 0 441 79 13369 0 0 0 0 13889
295 9-Jan-16 0 443 77 13298 0 0 0 0 13818
296 2-Jan-16 0 450 77 12385 0 0 0 0 12912
297 26-Dec-15 0 446 84 11190 0 0 0 0 11720
298 19-Dec-15 0 452 92 11888 0 0 0 0 12432
299 12-Dec-15 0 468 84 12623 0 0 0 0 13175
300 5-Dec-15 0 465 87 12736 0 0 0 0 13288
301 28-Nov-15 0 460 86 12721 0 0 0 0 13267
302 21-Nov-15 0 465 88 12423 0 0 0 0 12976
303 14-Nov-15 0 494 90 12483 0 0 0 0 13067
304 7-Nov-15 0 489 89 12383 0 0 0 0 12961
305 31-Oct-15 0 509 87 12512 0 0 0 0 13108
306 24-Oct-15 0 495 88 12346 0 0 0 0 12929
307 17-Oct-15 0 505 90 12338 0 0 0 0 12933
308 10-Oct-15 0 487 96 11995 0 0 0 0 12578
309 3-Oct-15 0 489 87 11676 0 0 0 0 12252
310 26-Sep-15 0 480 93 11015 0 0 0 0 11588
311 19-Sep-15 0 474 85 10385 0 0 0 0 10944
312 12-Sep-15 0 506 94 11041 0 0 0 0 11641
313 5-Sep-15 0 519 101 11402 0 0 0 0 12022
314 29-Aug-15 0 528 106 11556 0 0 0 0 12190
315 22-Aug-15 0 596 106 11672 0 0 0 0 12374
316 15-Aug-15 0 530 105 11574 0 0 0 0 12209
317 8-Aug-15 1 520 103 11089 0 0 0 0 11713
318 1-Aug-15 1 507 106 10947 0 0 0 0 11561
319 25-Jul-15 1 593 107 10712 0 0 0 0 11413
320 18-Jul-15 1 520 104 10533 0 0 0 0 11158
321 11-Jul-15 1 538 110 10764 0 0 0 0 11413
322 4-Jul-15 1 550 108 10918 0 0 0 0 11577
323 27-Jun-15 1 537 114 10783 0 0 0 0 11435
324 20-Jun-15 1 564 109 10961 0 0 0 0 11635
325 13-Jun-15 1 550 114 10845 0 0 0 0 11510
326 6-Jun-15 1 540 112 10369 0 0 0 0 11022
327 30-May-15 1 537 115 10137 0 0 0 0 10790
328 23-May-15 1 532 116 10024 0 0 0 0 10673
329 16-May-15 1 556 115 10051 0 0 0 0 10723
330 9-May-15 1 549 112 9923 0 0 0 0 10585
331 2-May-15 1 549 115 9700 0 0 0 0 10365
332 25-Apr-15 1 555 118 9462 0 0 0 0 10136
333 18-Apr-15 1 552 121 9363 0 0 0 0 10037
334 11-Apr-15 1 549 122 9046 0 0 0 0 9718
335 4-Apr-15 1 561 130 8955 0 0 0 0 9647
336 28-Mar-15 1 570 125 9064 0 0 0 0 9760
337 21-Mar-15 1 568 134 9194 0 0 0 0 9897
338 14-Mar-15 1 561 137 9163 0 0 0 0 9862
339 7-Mar-15 1 553 127 8826 0 0 0 0 9507
340 28-Feb-15 1 587 130 8638 0 0 0 0 9356
341 21-Feb-15 1 584 124 8459 0 0 0 0 9168
342 14-Feb-15 1 588 136 8314 0 0 0 0 9039
343 7-Feb-15 1 577 142 8240 0 0 0 0 8960
344 31-Jan-15 1 583 133 8003 0 0 0 0 8720
345 24-Jan-15 1 598 130 7738 0 0 0 0 8467
346 17-Jan-15 1 593 138 7779 0 0 0 0 8511
347 10-Jan-15 1 579 137 7654 0 0 0 0 8371
348 3-Jan-15 1 581 127 7500 0 0 0 0 8209
349 27-Dec-14 1 562 131 6455 0 0 0 0 7149
350 20-Dec-14 1 582 129 6575 0 0 0 0 7287
351 13-Dec-14 1 602 134 7342 0 0 0 0 8079
352 6-Dec-14 1 602 139 7319 0 0 0 0 8061
353 29-Nov-14 1 609 134 7171 0 0 0 0 7915
354 22-Nov-14 1 630 144 7088 0 0 0 0 7863
355 15-Nov-14 1 625 160 7301 0 0 0 0 8087
356 8-Nov-14 1 617 151 7029 0 0 0 0 7798
357 1-Nov-14 2 635 166 6769 0 0 0 0 7572
358 25-Oct-14 1 614 160 6508 0 0 0 0 7283
359 18-Oct-14 1 609 159 6348 0 0 0 0 7117
360 11-Oct-14 1 627 155 6349 0 0 0 0 7132
361 4-Oct-14 1 621 165 5897 0 0 0 0 6684
362 27-Sep-14 1 622 158 5854 0 0 0 0 6635
363 20-Sep-14 1 633 164 5881 0 0 0 0 6679
364 13-Sep-14 1 654 161 5789 0 0 0 0 6605
365 6-Sep-14 1 652 166 5700 0 0 0 0 6519
366 30-Aug-14 2 643 171 5580 0 0 0 0 6396
367 23-Aug-14 2 643 171 5466 0 0 0 0 6282
368 16-Aug-14 2 633 184 5304 0 0 0 0 6123
369 9-Aug-14 2 648 185 5183 0 0 0 0 6018
370 2-Aug-14 2 641 183 5247 0 0 0 0 6073
371 26-Jul-14 2 643 185 4938 0 0 0 0 5768
372 19-Jul-14 2 640 183 4814 0 0 0 0 5639
373 12-Jul-14 2 719 175 4713 0 0 0 0 5609
374 5-Jul-14 2 652 177 4543 0 0 0 0 5374
375 28-Jun-14 2 653 182 4526 0 0 0 0 5363
376 21-Jun-14 2 655 181 4490 0 0 0 0 5328
377 14-Jun-14 2 651 166 3895 0 0 0 0 4714
378 7-Jun-14 2 674 187 3866 0 0 0 0 4729
379 31-May-14 2 665 183 3623 0 0 0 0 4473
380 24-May-14 2 669 194 3581 0 0 0 0 4446
381 17-May-14 2 669 191 3346 0 0 0 0 4208
382 10-May-14 2 671 196 3221 0 0 0 0 4090
383 3-May-14 2 661 192 3223 0 0 0 0 4078
384 26-Apr-14 2 681 194 3095 0 0 0 0 3972
385 19-Apr-14 2 687 201 2981 0 0 0 0 3871
386 12-Apr-14 2 695 203 2910 0 0 0 0 3810
387 5-Apr-14 2 694 218 2807 0 0 0 0 3721
388 29-Mar-14 2 725 222 2807 0 0 0 0 3756
389 22-Mar-14 2 709 226 2661 0 0 0 0 3598
390 15-Mar-14 2 712 235 2544 0 0 0 0 3493
391 8-Mar-14 2 747 241 2405 0 0 0 0 3395
392 1-Mar-14 2 715 255 2368 0 0 0 0 3340
393 22-Feb-14 2 748 244 2245 0 0 0 0 3239
394 15-Feb-14 2 707 248 2107 0 0 0 0 3064
395 8-Feb-14 2 759 256 2044 0 0 0 0 3061
396 1-Feb-14 2 734 258 1928 0 0 0 0 2922
397 25-Jan-14 2 753 263 1781 0 0 0 0 2799
398 18-Jan-14 2 749 260 1722 0 0 0 0 2733
399 11-Jan-14 2 728 288 1612 0 0 0 0 2630
400 4-Jan-14 2 755 276 1343 0 0 0 0 2376
401 28-Dec-13 2 736 285 1082 0 0 0 0 2105
402 21-Dec-13 2 711 276 1005 0 0 0 0 1994
403 14-Dec-13 2 769 285 1118 0 0 0 0 2174
404 7-Dec-13 2 756 281 1008 0 0 0 0 2047
405 30-Nov-13 2 799 286 920 0 0 0 0 2007
406 23-Nov-13 2 898 290 849 0 0 0 0 2039
407 16-Nov-13 2 956 313 790 0 0 0 0 2061
408 9-Nov-13 2 939 313 746 0 0 0 0 2000
409 2-Nov-13 2 963 327 662 0 0 0 0 1954
410 26-Oct-13 2 992 322 636 0 0 0 0 1952
411 19-Oct-13 2 977 309 651 0 0 0 0 1939
412 12-Oct-13 2 990 328 576 0 0 0 0 1896
413 5-Oct-13 2 887 328 560 0 0 0 0 1777
414 28-Sep-13 2 1450 331 516 0 0 0 0 2299
415 21-Sep-13 2 1510 336 449 0 0 0 0 2297
416 14-Sep-13 2 1530 335 423 0 0 0 0 2290
417 7-Sep-13 2 874 340 363 0 0 0 0 1579
418 31-Aug-13 2 887 353 317 0 0 0 0 1559
419 24-Aug-13 2 870 349 269 0 0 0 0 1490
420 17-Aug-13 2 877 352 235 0 0 0 0 1466
421 10-Aug-13 2 862 367 190 0 0 0 0 1421
422 3-Aug-13 2 841 399 149 0 0 0 0 1391
423 27-Jul-13 2 876 416 104 0 0 0 0 1398
424 20-Jul-13 2 887 457 15 0 0 0 0 1361
425 13-Jul-13 2 879 445 0 0 0 0 0 1326
426 6-Jul-13 2 876 462 0 0 0 0 0 1340
427 29-Jun-13 2 884 464 0 0 0 0 0 1350
428 22-Jun-13 2 901 452 0 0 0 0 0 1355
429 15-Jun-13 2 893 460 0 0 0 0 0 1355
430 8-Jun-13 2 956 474 0 0 0 0 0 1432
431 1-Jun-13 2 1117 473 0 0 0 0 0 1592
432 25-May-13 2 1063 441 0 0 0 0 0 1506
433 18-May-13 2 944 383 0 0 0 0 0 1329
434 11-May-13 2 1109 450 0 0 0 0 0 1561
435 4-May-13 2 1020 451 0 0 0 0 0 1473
436 27-Apr-13 2 1003 450 0 0 0 0 0 1455
437 20-Apr-13 2 967 448 0 0 0 0 0 1417
438 13-Apr-13 2 1061 469 0 0 0 0 0 1532
439 6-Apr-13 2 1022 478 0 0 0 0 0 1502
440 30-Mar-13 2 1030 440 0 0 0 0 0 1472
441 23-Mar-13 2 1101 464 0 0 0 0 0 1567
442 16-Mar-13 2 1047 451 0 0 0 0 0 1500
443 9-Mar-13 2 1046 476 0 0 0 0 0 1524
444 2-Mar-13 3 1044 469 0 0 0 0 0 1516
445 23-Feb-13 3 1077 479 0 0 0 0 0 1559
446 16-Feb-13 3 1094 458 0 0 0 0 0 1555
447 9-Feb-13 3 1091 468 0 0 0 0 0 1562
448 2-Feb-13 3 1099 447 0 0 0 0 0 1549
449 26-Jan-13 3 1132 452 0 0 0 0 0 1587
450 19-Jan-13 3 1124 455 0 0 0 0 0 1582
451 12-Jan-13 4 1164 446 0 0 0 0 0 1614
452 5-Jan-13 3 1167 443 0 0 0 0 0 1613
453 29-Dec-12 3 1085 411 0 0 0 0 0 1499
454 22-Dec-12 3 1076 407 0 0 0 0 0 1486
455 15-Dec-12 4 1173 437 0 0 0 0 0 1614
456 8-Dec-12 4 1208 441 0 0 0 0 0 1653
457 1-Dec-12 3 1171 476 0 0 0 0 0 1650
458 24-Nov-12 3 1185 474 0 0 0 0 0 1662
459 17-Nov-12 3 1163 460 0 0 0 0 0 1626
460 10-Nov-12 3 1200 451 0 0 0 0 0 1654
461 3-Nov-12 3 1247 476 0 0 0 0 0 1726
462 27-Oct-12 3 1219 456 0 0 0 0 0 1678
463 20-Oct-12 3 1276 443 0 0 0 0 0 1722

View File

@@ -0,0 +1,24 @@
(function () {
'use strict';
Drupal.charts_api_example = Drupal.charts_api_example || {};
// PLEASE NOTE: this example is structured to work for Highcharts, but you
// do the same thing for any of the charting libraries.
Drupal.charts_api_example.highchartsTooltipFormatter = function () {
return 'The value for <b>' + this.x +
'</b> is <b>' + this.y + '</b>';
};
Drupal.behaviors.chartsApiExampleCharts = {
attach: function (context, settings) {
let chartcontainer = document.getElementById('exampleidjs');
chartcontainer.addEventListener('drupalChartsConfigsInitialization', function (e) {
let data = e.detail;
const id = data.drupalChartDivId;
// Change the background of the chart to green.
data.chart.backgroundColor = 'green';
// Replace the tooltip formatter.
data.tooltip.formatter = Drupal.charts_api_example.highchartsTooltipFormatter;
Drupal.Charts.Contents.update(id, data);
});
}
};
}());

View File

@@ -0,0 +1,400 @@
<?php
namespace Drupal\charts_api_example\Controller;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Extension\ModuleExtensionList;
use Drupal\Core\Messenger\MessengerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Charts Api Example.
*/
class ChartsApiExample extends ControllerBase {
/**
* The messenger service.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuidService;
/**
* The module extension list.
*
* @var \Drupal\Core\Extension\ModuleExtensionList
*/
protected $moduleList;
/**
* Construct.
*
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger service.
* @param \Drupal\Component\Uuid\UuidInterface $uuidService
* The UUID service.
* @param \Drupal\Core\Extension\ModuleExtensionList $module_list
* The module list.
*/
public function __construct(MessengerInterface $messenger, UuidInterface $uuidService, ModuleExtensionList $module_list) {
$this->messenger = $messenger;
$this->uuidService = $uuidService;
$this->moduleList = $module_list;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('messenger'),
$container->get('uuid'),
$container->get('extension.list.module')
);
}
/**
* Display the dashboard of charts.
*
* @return array
* Array to render.
*/
public function display() {
/*
* If you do not set a library specifically in your render array, Charts
* will use your default. But you do need to set your default library.
*/
$charts_settings = $this->config('charts.settings');
$library = $charts_settings->get('charts_default_settings.library');
if (empty($library)) {
$this->messenger->addError($this->t('You need to first configure Charts default settings'));
return [];
}
// This is a container for all the charts below.
$charts_container = [
'#type' => 'container',
'content' => [],
];
/* Listing all the default chart types except 'gauge' and 'scatter'
* (they come later).
*/
$chart_types = ['area', 'bar', 'column', 'line', 'spline', 'pie', 'donut'];
// Define a series to be used in multiple examples.
$series = [
'#type' => 'chart_data',
'#title' => $this->t('5.0.x'),
'#data' => [257, 235, 325, 340],
'#color' => '#1d84c3',
];
// Define an x-axis to be used in multiple examples.
$xaxis = [
'#type' => 'chart_xaxis',
'#title' => $this->t('Months'),
'#labels' => [
$this->t('January 2021'),
$this->t('February 2021'),
$this->t('March 2021'),
$this->t('April 2021'),
],
];
// Define a y-axis to be used in multiple examples.
$yaxis = [
'#type' => 'chart_yaxis',
'#title' => $this->t('Number of Installs'),
];
// Iterate through the chart types and build a chart for each.
foreach ($chart_types as $type) {
$charts_container['content'][$type] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library @type Chart', [
'@library' => ucfirst($library),
'@type' => ucfirst($type),
]),
'#chart_type' => $type,
'series' => $series,
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#raw_options' => [],
// e.g. ['chart' => ['backgroundColor' => '#000000']].
];
}
// Column chart with two series.
$charts_container['content']['two_series_column'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Column Chart (Two Series)', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series_one' => $series,
'series_two' => [
'#type' => 'chart_data',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
],
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#raw_options' => [],
];
// Stacked Area Chart from a local CSV file.
$file_contents = $this->getCsvContents();
$charts_container['content']['from_csv_file'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Stacked Area Chart from CSV File', ['@library' => ucfirst($library)]),
'#chart_type' => 'area',
'series_seven_2' => [
'#type' => 'chart_data',
'#title' => $this->t('7.x-2.x'),
// Using array_reverse because my file is desc rather than asc.
'#data' => array_reverse($file_contents['7.x-2.x']),
'#color' => '#76b7b2',
],
'series_eight_three' => [
'#type' => 'chart_data',
'#title' => $this->t('8.x-3.x'),
'#data' => array_reverse($file_contents['8.x-3.x']),
'#color' => '#edc949',
],
'series_five_zero' => [
'#type' => 'chart_data',
'#title' => $this->t('5.0.x'),
'#data' => array_reverse($file_contents['5.0.x']),
'#color' => '#ff9da7',
],
'x_axis' => [
'#type' => 'chart_xaxis',
'#title' => $this->t('Week'),
'#labels' => array_reverse($file_contents['Week']),
],
'y_axis' => [
'#type' => 'chart_yaxis',
'#title' => $this->t('Number of Installs'),
],
'#stacking' => TRUE,
];
if ($library === 'chartjs') {
// A real-life #raw_options use-case.
$charts_container['content']['from_csv_file']['#raw_options'] = [
'options' => [
'scales' => [
'x' => [
'ticks' => [
'autoSkip' => TRUE,
],
],
],
],
];
}
elseif ($library === 'billboard' || $library === 'c3') {
$charts_container['content']['from_csv_file']['#raw_options'] = [
'axis' => [
'x' => [
'tick' => [
'culling' => TRUE,
],
],
],
];
}
// Stacked column chart with two series.
$charts_container['content']['stacked_two_series_column'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Stacked Column Chart (Two Series)', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series_one' => $series,
'series_two' => [
'#type' => 'chart_data',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
],
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#stacking' => TRUE,
'#raw_options' => [],
];
// Combination chart (column and line).
$charts_container['content']['combo'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Combination Chart', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series_one' => $series,
'series_two' => [
'#type' => 'chart_data',
'#chart_type' => 'line',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
],
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#raw_options' => [],
];
// Combination chart (column and line) with secondary Y-Axis.
$charts_container['content']['combo_dual_yaxes'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Combination Chart with Secondary Y-Axis', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series_one' => $series,
'series_two' => [
'#type' => 'chart_data',
'#chart_type' => 'line',
'#title' => $this->t('8.x-3.x'),
'#data' => [4330, 4413, 4212, 4431],
'#color' => '#77b259',
'#target_axis' => 'y_axis_secondary',
],
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'y_axis_secondary' => [
'#type' => 'chart_yaxis',
'#title' => $this->t('Secondary Y-Axis'),
'#opposite' => TRUE,
],
'#raw_options' => [],
];
// Radar chart. Not supported by C3.js or Google Charts (natively).
if (!in_array($library, ['c3', 'google'])) {
$charts_container['content']['radar'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Radar Chart', ['@library' => ucfirst($library)]),
'#chart_type' => 'line',
'series' => $series,
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#polar' => TRUE,
'#raw_options' => [],
];
}
// Gauge chart. Not yet supported by Chart.js (as of 3.5.1).
if ($library !== 'chartjs') {
$charts_container['content']['gauge'] = [
'#type' => 'chart',
'#title' => $this->t('@library Gauge Chart', ['@library' => ucfirst($library)]),
'#chart_type' => 'gauge',
'#gauge' => [
'green_to' => 100,
'green_from' => 75,
'yellow_to' => 74,
'yellow_from' => 50,
'red_to' => 49,
'red_from' => 0,
'max' => 100,
'min' => 0,
],
'series' => [
'#type' => 'chart_data',
'#title' => $this->t('Speed'),
'#data' => [65],
],
'#raw_options' => [],
];
}
// Scatter chart.
$charts_container['content']['scatter'] = [
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Scatter Chart', ['@library' => ucfirst($library)]),
'#data_markers' => TRUE,
'#chart_type' => 'scatter',
'series' => [
'#type' => 'chart_data',
'#title' => $this->t('Group 1'),
'#data' => [[162.2, 51.8], [164.5, 58.0], [160.5, 49.6], [154.0, 65.0]],
],
'x_axis' => [
'#type' => 'chart_xaxis',
'#title' => $this->t('Height'),
'#labels' => [],
],
'y_axis' => [
'#type' => 'chart_yaxis',
'#title' => $this->t('Weight'),
],
'#stacking' => TRUE,
'#raw_options' => [],
];
if ($library === 'highcharts') {
$charts_container['content']['php_override'] = [
'#chart_id' => 'exampleidphp',
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Chart, Overridden By PHP Hook', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series' => $series,
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#color_changer' => TRUE,
'#raw_options' => [],
];
$charts_container['content']['js_override'] = [
'#id' => 'exampleidjs',
'#chart_id' => 'exampleidjschart',
'#type' => 'chart',
'#tooltips' => $charts_settings->get('charts_default_settings.display.tooltips'),
'#title' => $this->t('@library Chart, Overridden By JS Function', ['@library' => ucfirst($library)]),
'#chart_type' => 'column',
'series' => $series,
'x_axis' => $xaxis,
'y_axis' => $yaxis,
'#raw_options' => [],
];
}
return $charts_container;
}
/**
* Returns the CSV contents in an array with data organized by column.
*
* @return array
* The array of rows.
*/
private function getCsvContents() {
$file_path = $this->moduleList->getPath('charts_api_example');
$file_name = $file_path . '/fixtures/charts_api_example_file.csv';
$handle = fopen($file_name, 'r');
$all_rows = [];
while ($row = fgetcsv($handle)) {
$all_rows['Week'][] = $row[0];
$all_rows['7.x-2.x'][] = (int) $row[4];
$all_rows['8.x-3.x'][] = (int) $row[6];
$all_rows['5.0.x'][] = (int) $row[8];
}
fclose($handle);
return $all_rows;
}
}

View File

@@ -0,0 +1,117 @@
#Installation Using Composer (recommended)
If you use Composer to manage dependencies, edit your site's "composer.json"
file as follows.
1. Run the following command to ensure that you have the "composer/installers"
package installed. This package facilitates the installation of packages into
directories other than "/vendor" (e.g. "/libraries") using Composer.
composer require --prefer-dist composer/installers
2. Add the following to the "installer-paths" section of "composer.json":
"libraries/{$name}": ["type:drupal-library"],
3. Add the following to the "repositories" section of "composer.json":
{
"type": "package",
"package": {
"name": "billboardjs/billboard",
"version": "3.10.3",
"type": "drupal-library",
"extra": {
"installer-name": "billboard"
},
"dist": {
"url": "https://registry.npmjs.org/billboard.js/-/billboard.js-3.10.3.tgz",
"type": "tar"
}
}
},
{
"type": "package",
"package": {
"name": "d3/d3",
"version": "7.8.5",
"type": "drupal-library",
"extra": {
"installer-name": "d3"
},
"dist": {
"url": "https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.js",
"type": "file"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
}
}
}
4. This and the following step is optional but recommended. The reason for
them is that when installing the Billboard.js package with Composer,
additional files are added into the library directory. These files are not
necessary and can be potentially harmful to your site, so it's best to remove
them. So: create a new directory in your project root called "scripts".
5. Inside that directory, create a new file called "clean-billboardjs.sh" and
paste the following into it:
#!/usr/bin/env bash
set -eu
declare -a directories=(
"web/libraries/billboard/dist-esm"
"web/libraries/billboard/src"
"web/libraries/billboard/types"
"web/libraries/billboard/dist/plugin"
"web/libraries/billboard/dist/theme"
)
counter=0
echo "Deleting unneeded directories inside web/libraries/billboard"
for directory in "${directories[@]}"
do
if [ -d $directory ]; then
echo "Deleting $directory"
rm -rf $directory
counter=$((counter+1))
fi
done
echo "$counter folders were deleted"
declare -a files=(
"web/libraries/billboard/CONTRIBUTING.md"
"web/libraries/billboard/README.md"
"web/libraries/billboard/LICENSE"
"web/libraries/billboard/package.json"
"web/libraries/billboard/dist/package.json"
)
counter=0
echo "Deleting unneeded files inside web/libraries/billboard"
for file in "${files[@]}"
do
if [[ -f $file ]]; then
echo "Deleting $file"
rm $file
counter=$((counter+1))
fi
done
echo "$counter files were deleted"
6. Add a "scripts" entry to your "composer.json" file as shown below. If
"scripts" already exists, you will need to do a little more to incorporate
the code below.
"scripts": {
"clean-billboardjs": "chmod +x scripts/clean-billboardjs.sh &&
./scripts/clean-billboardjs.sh",
"post-install-cmd": [
"@clean-billboardjs"
],
"post-update-cmd": [
"@clean-billboardjs"
]
}
7. Run the following command; you should find that new directories have been
created under "/libraries".
composer require --prefer-dist billboardjs/billboard:3.10.3 d3/d3:7.8.5

View File

@@ -0,0 +1,12 @@
name: 'Billboard.js Charts'
type: module
description: 'Charts module integration with Billboard.js Charts.'
package: Charts
core_version_requirement: ^10.3 || ^11
dependencies:
- 'charts:charts'
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,95 @@
<?php
/**
* @file
* Installation and uninstallation functions.
*/
/**
* Implements hook_requirements().
*/
function charts_billboard_requirements($phase) {
$requirements = [];
switch ($phase) {
case 'runtime':
$library_path = charts_billboard_find_library();
$config = \Drupal::config('charts.settings');
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
if (!$library_path) {
if ($cdn) {
$requirements['charts_billboard_js'] = [
'title' => t('Billboard.js Library'),
'value' => t('Available through a CDN'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You are using the Billboard.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_billboard for instructions to install the library.'),
];
}
else {
$requirements['charts_billboard_js'] = [
'title' => t('Billboard.js Library'),
'value' => t('Not Installed'),
'severity' => REQUIREMENT_ERROR,
'description' => t('You are missing the Billboard.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_billboard for instructions to install the library.'),
];
}
}
else {
// If the library directory contains a demo directory, warn.
if (file_exists($library_path . '/src')) {
$requirements['charts_billboard_js'] = [
'title' => t('Billboard.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You have installed the Billboard.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_billboard.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
];
}
else {
$requirements['charts_billboard_js'] = [
'title' => t('Billboard.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_OK,
'description' => t('You have installed the Billboard.js library in your Drupal installation directory.'),
];
}
}
break;
}
return $requirements;
}
/**
* Get the location of the Billboard.js library.
*
* @return string
* The location of the library, or FALSE if the library isn't installed.
*/
function charts_billboard_find_library() {
// The following logic is taken from libraries_get_libraries()
$searchdir = [];
// Similar to 'modules' and 'themes' directories inside an installation
// profile, installation profiles may want to place libraries into a
// 'libraries' directory.
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
// Always search libraries.
$searchdir[] = 'libraries';
// Also search sites/<domain>/*.
$container = \Drupal::getContainer();
$site_path = $container->getParameter('site.path');
$site_path = explode('/', $site_path);
$site_name = $site_path[1];
$searchdir[] = $site_name . '/libraries';
foreach ($searchdir as $dir) {
if (file_exists($dir . '/billboard/dist/billboard.min.js') || file_exists($dir . '/billboard/dist/billboard.js')) {
return $dir . '/billboard';
}
}
return FALSE;
}

View File

@@ -0,0 +1,36 @@
charts_billboard:
version: 1.x
js:
js/charts_billboard.js: { weight: -1 }
dependencies:
- charts/global
- core/drupal
- core/once
d3:
remote: 'https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js'
version: 7.8.5
license:
name: BSD
url: 'https://en.wikipedia.org/wiki/BSD_licenses'
gpl-compatible: false
cdn:
/libraries/d3/: https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/
js:
/libraries/d3/d3.min.js: { minified: true }
billboard:
remote: 'https://naver.github.io/billboard.js/release/3.10.3/dist/billboard.min.js'
version: 3.10.3
license:
name: MIT
url: 'https://opensource.org/licenses/MIT'
gpl-compatible: true
cdn:
/libraries/billboard/: https://naver.github.io/billboard.js/release/3.10.3/
css:
theme:
/libraries/billboard/dist/billboard.min.css: { minified: true }
js:
/libraries/billboard/dist/billboard.min.js: { minified: true }
dependencies:
- charts_billboard/charts_billboard
- charts_billboard/d3

View File

@@ -0,0 +1,17 @@
<?php
/**
* @file
* Implement module related hooks for the charts_billboard module.
*/
/**
* Implements hook_charts_version3_to_new_settings_structure_alter().
*/
function charts_billboard_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
$config_factory = \Drupal::configFactory();
$billboard_config = $config_factory->getEditable('charts_billboard.settings');
if ($billboard_config) {
$billboard_config->delete();
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "drupal/charts_billboard",
"description": "Charts module integration with Billboard.js Charts.",
"type": "drupal-module",
"homepage": "http://drupal.org/project/charts",
"support": {
"issues": "https://www.drupal.org/project/issues/charts"
},
"license": "GPL-2.0+",
"require": {
"billboardjs/billboard": "3.10.3",
"d3/d3": "7.8.5"
},
"repositories": [
{
"type": "package",
"package": {
"name": "billboardjs/billboard",
"version": "3.10.3",
"type": "drupal-library",
"extra": {
"installer-name": "billboard"
},
"dist": {
"url": "https://github.com/naver/billboard.js/archive/3.10.3.zip",
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "d3/d3",
"version": "7.8.5",
"type": "drupal-library",
"extra": {
"installer-name": "d3"
},
"dist": {
"url": "https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js",
"type": "file"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
}
}
}
]
}

View File

@@ -0,0 +1,26 @@
/**
* @file
* JavaScript integration between Billboard and Drupal.
*/
(function (Drupal, once) {
'use strict';
Drupal.behaviors.chartsBillboard = {
attach: function (context, settings) {
const contents = new Drupal.Charts.Contents();
once('charts-billboard', '.charts-billboard', context).forEach(function (element) {
const config = contents.getData(element.id);
const title = config.title.text;
// If the title contains '\\n', convert it to a line break.
if (title.indexOf('\\n') !== -1) {
config.title.text = title.replace(/\\n/g, '\n');
}
bb.generate(config);
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(config, null, ' ');
}
});
}
};
}(Drupal, once));

View File

@@ -0,0 +1,454 @@
<?php
namespace Drupal\charts_billboard\Plugin\chart\Library;
use Drupal\charts\Plugin\chart\Library\ChartBase;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Define a concrete class for a Chart.
*
* @Chart(
* id = "billboard",
* name = @Translation("Billboard.js"),
* types = {
* "area",
* "bar",
* "bubble",
* "column",
* "donut",
* "gauge",
* "line",
* "pie",
* "scatter",
* "spline",
* },
* )
*/
class Billboard extends ChartBase implements ContainerFactoryPluginInterface {
/**
* The element info manager.
*
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $elementInfo;
/**
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
* The element info manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ElementInfoManagerInterface $element_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->elementInfo = $element_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('element_info')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['intro_text'] = [
'#markup' => $this->t('This is a placeholder for Billboard.js-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.', [
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046983')->toString(),
]),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function preRender(array $element) {
// Populate chart settings.
$chart_definition = [];
$chart_definition = $this->populateOptions($element, $chart_definition);
$chart_definition = $this->populateData($element, $chart_definition);
$chart_definition = $this->populateAxes($element, $chart_definition);
if (!empty($element['#height']) || !empty($element['#width'])) {
$element['#attributes']['style'] = 'height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';';
}
if (!isset($element['#id'])) {
$element['#id'] = Html::getUniqueId('chart-billboard');
}
$chart_definition['bindto'] = '#' . $element['#id'];
$element['#attached']['library'][] = 'charts_billboard/billboard';
$element['#attributes']['class'][] = 'charts-billboard charts-bb';
$element['#chart_definition'] = $chart_definition;
return $element;
}
/**
* Get the chart type.
*
* @param string $chart_type
* The chart type.
* @param bool $is_polar
* Whether the polar is checked.
*
* @return string
* The chart type.
*/
protected function getType($chart_type, $is_polar = FALSE) {
// If Polar is checked, then convert to Radar chart type.
if ($is_polar) {
$type = 'radar';
}
else {
$type = $chart_type == 'column' ? 'bar' : $chart_type;
}
return $type;
}
/**
* Get options.
*
* @param string $type
* The chart type.
* @param array $element
* The element.
*
* @return array
* The returned options.
*/
protected function getOptionsByType($type, array $element) {
$options = $this->getOptionsByCustomProperty($element, $type);
if ($type === 'bar') {
$options['width'] = $element['#width'];
}
return $options;
}
/**
* Get the options by custom property.
*
* @param array $element
* The element.
* @param string $type
* The chart type.
*
* @return array
* The return options.
*/
protected function getOptionsByCustomProperty(array $element, $type) {
$options = [];
$properties = Element::properties($element);
// Remove properties which are not related to this chart type.
$properties = array_filter($properties, function ($property) use ($type) {
$query = '#chart_' . $type . '_';
return substr($property, 0, strlen($query)) === $query;
});
foreach ($properties as $property) {
$query = '#chart_' . $type . '_';
$option_key = substr($property, strlen($query), strlen($property));
$options[$option_key] = $element[$property];
}
return $options;
}
/**
* Populate options.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return the chart definition.
*/
private function populateOptions(array $element, array $chart_definition) {
$type = $this->getType($element['#chart_type'], $element['#polar'] ?? FALSE);
$title = $element['#title'] ?? '';
if (!empty($element['#subtitle'])) {
$title .= '\n' . $element['#subtitle'];
}
$chart_definition['title']['text'] = $title;
$chart_definition['legend']['show'] = !empty($element['#legend_position']);
if (!in_array($type, ['scatter', 'bubble'])) {
$chart_definition['axis']['x']['type'] = 'category';
}
$chart_definition['data']['labels'] = (bool) $element['#data_labels'];
if ($type === 'pie' || $type === 'donut') {
// Do nothing.
}
elseif ($type === 'gauge') {
$chart_definition['gauge']['min'] = $element['#gauge']['min'];
$chart_definition['gauge']['max'] = $element['#gauge']['max'];
$chart_definition['color']['pattern'] = [
'red',
'yellow',
'green',
];
$chart_definition['color']['threshold']['values'] = [
$element['#gauge']['red_from'],
$element['#gauge']['yellow_from'],
$element['#gauge']['green_from'],
];
}
elseif ($type === 'line' || $type === 'spline') {
$chart_definition['point']['show'] = !empty($element['#data_markers']);
}
else {
/*
* Billboard does not use bar, so column must be used. Since 'column'
* is changed
* to 'bar' in getType(), we need to use the value from the element.
*/
if ($element['#chart_type'] === 'bar') {
$chart_definition['axis']['rotated'] = TRUE;
}
elseif ($element['#chart_type'] === 'column') {
$type = 'bar';
$chart_definition['axis']['rotated'] = FALSE;
}
}
$chart_definition['data']['type'] = $type;
// Merge in chart raw options.
if (!empty($element['#raw_options'])) {
$chart_definition = NestedArray::mergeDeepArray([
$chart_definition,
$element['#raw_options'],
]);
}
return $chart_definition;
}
/**
* Populate axes.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return the chart definition.
*/
private function populateAxes(array $element, array $chart_definition) {
$children = Element::children($element);
foreach ($children as $child) {
$type = $element[$child]['#type'];
if ($type === 'chart_xaxis') {
$chart_definition['axis']['x']['label'] = $element[$child]['#title'] ?? '';
$chart_type = $this->getType($element['#chart_type']);
$categories = $this->stripLabelTags($element[$child]['#labels']);
if (!in_array($chart_type, ['pie', 'donut'])) {
if ($chart_type === 'scatter' || $chart_type === 'bubble') {
// Do nothing.
}
else {
$chart_definition['data']['columns'][] = ['x'];
$chart_definition['data']['x'] = 'x';
$categories_keys = array_keys($chart_definition['data']['columns']);
$categories_key = end($categories_keys);
foreach ($categories as $category) {
$chart_definition['data']['columns'][$categories_key][] = $category;
}
}
}
else {
$chart_definition['data']['columns'] = array_map(NULL, $categories, $chart_definition['data']['columns']);
}
}
if ($type === 'chart_yaxis') {
if (!empty($element[$child]['#opposite']) && $element[$child]['#opposite'] === TRUE) {
$chart_definition['axis']['y2']['show'] = TRUE;
$this->setLabelMinMax($chart_definition, 'y2', $element[$child]);
}
else {
$this->setLabelMinMax($chart_definition, 'y', $element[$child]);
}
}
}
return $chart_definition;
}
/**
* Set the label, min, and max.
*
* @param array $chart_definition
* The chart definition.
* @param string $axis
* The axis.
* @param array $element
* The element.
*/
private function setLabelMinMax(array &$chart_definition, string $axis, array $element): void {
$chart_definition['axis'][$axis]['label'] = $element['#title'] ?? '';
if (!empty($element['#min'])) {
$chart_definition['axis'][$axis]['min'] = $element['#min'];
}
if (!empty($element['#max'])) {
$chart_definition['axis'][$axis]['max'] = $element['#max'];
}
}
/**
* Populate data.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return the chart definition.
*/
private function populateData(array &$element, array $chart_definition) {
$type = $this->getType($element['#chart_type'], $element['#polar'] ?? FALSE);
$types = [];
$children = Element::children($element);
$y_axes = [];
foreach ($children as $child) {
$element_type = $element[$child]['#type'];
if ($element_type === 'chart_yaxis') {
$y_axes[] = $child;
}
}
$data_elements = array_filter($children, function ($child) use ($element) {
return $element[$child]['#type'] === 'chart_data';
});
$columns = $chart_definition['data']['columns'] ?? [];
$column_keys = array_keys($columns);
$columns_key_start = $columns ? end($column_keys) + 1 : 0;
foreach ($data_elements as $key) {
$child_element = $element[$key];
// Make sure defaults are loaded.
if (empty($child_element['#defaults_loaded'])) {
$child_element += $this->elementInfo->getInfo($child_element['#type']);
}
if ($child_element['#color'] && $type !== 'gauge') {
$chart_definition['color']['pattern'][] = $child_element['#color'];
}
if (!in_array($type, ['pie', 'donut'])) {
$series_title = isset($child_element['#title']) ? strip_tags($child_element['#title']) : '';
$types[$series_title] = $child_element['#chart_type'] ? $this->getType($child_element['#chart_type'], $element['#polar'] ?? FALSE) : $type;
if (!in_array($type, ['scatter', 'bubble'])) {
$columns[$columns_key_start][] = $series_title;
foreach ($child_element['#data'] as $datum) {
if (gettype($datum) === 'array') {
if ($type === 'gauge') {
array_shift($datum);
}
$columns[$columns_key_start][] = array_map(function ($item) {
return isset($item) ? strip_tags($item) : NULL;
}, $datum);
}
else {
$columns[$columns_key_start][] = isset($datum) ? strip_tags($datum) : NULL;
}
}
}
else {
$row = [];
$row[$series_title][0] = $series_title;
$row[$series_title . '_x'][0] = $series_title . '_x';
foreach ($child_element['#data'] as $datum) {
$row[$series_title][] = $datum[0];
$row[$series_title . '_x'][] = $datum[1];
}
$chart_definition['data']['xs'][$series_title] = $series_title . '_x';
foreach ($row as $value) {
$columns[] = $value;
}
$columns = array_values($columns);
}
}
else {
foreach ($child_element['#data'] as $datum_index => $datum) {
if (!empty($datum['color'])) {
$chart_definition['color']['pattern'][$datum_index] = $datum['color'];
unset($datum['color']);
$datum = array_values($datum);
}
$columns[] = $datum;
}
// Add colors for each segment.
foreach ($child_element['#grouping_colors'] ?? [] as $key => $colors_array) {
$color = reset($colors_array);
$chart_definition['color']['pattern'][$key] = $color;
}
}
$columns_key_start++;
}
if ($element['#stacking']) {
$chart_definition['data']['groups'] = [array_keys($types)];
}
$chart_definition['data']['types'] = $types;
$chart_definition['data']['columns'] = $columns;
if (count($y_axes) >= 2) {
foreach ($columns as $index => $column) {
if ($index <= 1) {
$axis = ($index + 1) === 2 ? 2 : '';
$chart_definition['data']['axes'][$column[0]] = 'y' . $axis;
}
}
}
return $chart_definition;
}
/**
* Strip tags from each item in an array.
*
* @param array $items
* The array.
*
* @return array
* Return the cleaned array.
*/
private function stripLabelTags(array $items): array {
if (empty($items)) {
return [];
}
$categories = [];
foreach ($items as $item) {
$categories[] = isset($item) ? strip_tags($item) : NULL;
}
return $categories;
}
}

View File

@@ -0,0 +1,9 @@
#Charts Blocks
Use this module if you would like a chart added via Layout Builder or the
"Block layout" page. You will need to enable at least one charting provider
submodule before this module can be used. Data can be added manually or by
uploading a CSV file.
If you create your chart using a View, you do not need this module - just add
a "Block" display to your View.

View File

@@ -0,0 +1,12 @@
name: Charts Blocks
type: module
description: Create Charts blocks without the need for Views.
core_version_requirement: ^10.3 || ^11
package: Charts
dependencies:
- drupal:charts
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains chart_block_example.module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function charts_blocks_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the charts_blocks module.
case 'help.page.charts_blocks':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('Create Charts blocks without the need for Views.') . '</p>';
return $output;
default:
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Drupal\charts_blocks\Plugin\Block;
use Drupal\charts\Element\Chart;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a 'ChartsBlock' block.
*
* @Block(
* id = "charts_block",
* admin_label = @Translation("Charts block"),
* )
*/
class ChartsBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* The UUID service.
*
* @var \Drupal\Component\Uuid\UuidInterface
*/
protected $uuidService;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config_factory, UuidInterface $uuidService) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->setConfiguration($configuration);
$this->configFactory = $config_factory;
$this->uuidService = $uuidService;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('config.factory'),
$container->get('uuid')
);
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
parent::blockForm($form, $form_state);
$chart_block_configurations = !empty($this->configuration['chart']) ? $this->configuration['chart'] : [];
// Merge the charts default settings with this block's configuration.
$charts_settings = $this->configFactory->get('charts.settings');
$charts_default_settings = $charts_settings->get('charts_default_settings') ?? [];
$defaults = NestedArray::mergeDeep($charts_default_settings, $chart_block_configurations);
$form['chart'] = [
'#type' => 'details',
'#title' => $this->t('Chart configurations'),
'#open' => TRUE,
];
$form['chart']['settings'] = [
'#type' => 'charts_settings',
'#used_in' => 'basic_form',
'#required' => TRUE,
'#series' => TRUE,
'#default_value' => $defaults,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['chart'] = $form_state->getValue(['chart', 'settings']);
}
/**
* {@inheritdoc}
*/
public function build() {
$chart_settings = !empty($this->configuration['chart']) ? $this->configuration['chart'] : [];
// Creates a UUID for the chart ID.
$chart_id = 'charts_block__' . $this->configuration['id'];
$id = 'chart-' . $this->uuidService->generate();
$build = Chart::buildElement($chart_settings, $chart_id);
$build['#id'] = $id;
$build['#chart_id'] = $chart_id;
return $build;
}
}

View File

@@ -0,0 +1,137 @@
#Installation Using Composer (recommended)
If you use Composer to manage dependencies, edit your site's "composer.json"
file as follows.
1. Run the following command to ensure that you have the "composer/installers"
package installed. This package facilitates the installation of packages into
directories other than "/vendor" (e.g. "/libraries") using Composer.
composer require --prefer-dist composer/installers
2. Add the following to the "installer-paths" section of "composer.json":
"libraries/{$name}": ["type:drupal-library"],
3. Add the following to the "repositories" section of "composer.json":
{
"type": "package",
"package": {
"name": "c3js/c3",
"version": "v0.7.20",
"type": "drupal-library",
"extra": {
"installer-name": "c3"
},
"dist": {
"url": "https://github.com/c3js/c3/archive/v0.7.20.zip",
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "d3/d3",
"version": "v4.9.1",
"type": "drupal-library",
"extra": {
"installer-name": "d3"
},
"dist": {
"url": "https://github.com/d3/d3/archive/v4.9.1.zip",
"type": "zip"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
}
}
}
4. This and the following step is optional but recommended. The reason for
them is that when installing the C3.js package with Composer,
additional files are added into the library directory. These files are not
necessary and can be potentially harmful to your site, so it's best to remove
them. So: create a new directory in your project root called "scripts".
5. Inside that directory, create a new file called "clean-c3js.sh" and
paste the following into it:
#!/usr/bin/env bash
set -eu
declare -a directories=(
"web/libraries/c3/.circleci"
"web/libraries/c3/.github"
"web/libraries/c3/data"
"web/libraries/c3/design"
"web/libraries/c3/docs"
"web/libraries/c3/extensions"
"web/libraries/c3/htdocs"
"web/libraries/c3/spec"
"web/libraries/c3/src"
)
counter=0
echo "Deleting unneeded directories inside web/libraries/c3"
for directory in "${directories[@]}"
do
if [ -d $directory ]; then
echo "Deleting $directory"
rm -rf $directory
counter=$((counter+1))
fi
done
echo "$counter folders were deleted"
declare -a files=(
"web/libraries/c3/CONTRIBUTING.md"
"web/libraries/c3/README.md"
"web/libraries/c3/LICENSE"
"web/libraries/c3/package.json"
"web/libraries/c3/.bmp.yml"
"web/libraries/c3/.editorconfig"
"web/libraries/c3/.gitignore"
"web/libraries/c3/.jshintrc"
"web/libraries/c3/.prettierrc.json"
"web/libraries/c3/.travis.yml"
"web/libraries/c3/bower.json"
"web/libraries/c3/codecov.yml"
"web/libraries/c3/component.json"
"web/libraries/c3/config.rb"
"web/libraries/c3/Gemfile"
"web/libraries/c3/Gemfile.lock"
"web/libraries/c3/karma.conf.js"
"web/libraries/c3/MAINTAINANCE.md"
"web/libraries/c3/rollup.config.js"
"web/libraries/c3/tsconfig.json"
"web/libraries/c3/yarn.lock"
)
counter=0
echo "Deleting unneeded files inside web/libraries/c3"
for file in "${files[@]}"
do
if [[ -f $file ]]; then
echo "Deleting $file"
rm $file
counter=$((counter+1))
fi
done
echo "$counter files were deleted"
6. Add a "scripts" entry to your "composer.json" file as shown below. If
"scripts" already exists, you will need to do a little more to incorporate
the code below.
"scripts": {
"clean-c3js": "chmod +x scripts/clean-c3js.sh &&
./scripts/clean-c3js.sh",
"post-install-cmd": [
"@clean-c3js"
],
"post-update-cmd": [
"@clean-c3js"
]
}
7. Run the following command; you should find that new directories have been
created under "/libraries".
composer require --prefer-dist c3js/c3:0.7.20 d3/d3:4.9.1

View File

@@ -0,0 +1,12 @@
name: 'C3.js Charts'
type: module
description: 'Charts module integration with C3.js Charts.'
package: Charts
core_version_requirement: ^10.3 || ^11
dependencies:
- 'charts:charts'
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,95 @@
<?php
/**
* @file
* Installation and uninstallation functions.
*/
/**
* Implements hook_requirements().
*/
function charts_c3_requirements($phase) {
$requirements = [];
switch ($phase) {
case 'runtime':
$library_path = charts_c3_find_library();
$config = \Drupal::config('charts.settings');
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
if (!$library_path) {
if ($cdn) {
$requirements['charts_c3_js'] = [
'title' => t('C3.js Library'),
'value' => t('Available through a CDN'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You are using the C3.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_c3 for instructions to install the library.'),
];
}
else {
$requirements['charts_c3_js'] = [
'title' => t('C3.js Library'),
'value' => t('Not Installed'),
'severity' => REQUIREMENT_ERROR,
'description' => t('You are missing the C3.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_c3 for instructions to install the library.'),
];
}
}
else {
// If the library directory contains a demo directory, warn.
if (file_exists($library_path . '/src')) {
$requirements['charts_c3_js'] = [
'title' => t('C3.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You have installed the C3.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_c3.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
];
}
else {
$requirements['charts_c3_js'] = [
'title' => t('C3.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_OK,
'description' => t('You have installed the C3.js library in your Drupal installation directory.'),
];
}
}
break;
}
return $requirements;
}
/**
* Get the location of the C3.js library.
*
* @return string
* The location of the library, or FALSE if the library isn't installed.
*/
function charts_c3_find_library() {
// The following logic is taken from libraries_get_libraries()
$searchdir = [];
// Similar to 'modules' and 'themes' directories inside an installation
// profile, installation profiles may want to place libraries into a
// 'libraries' directory.
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
// Always search libraries.
$searchdir[] = 'libraries';
// Also search sites/<domain>/*.
$container = \Drupal::getContainer();
$site_path = $container->getParameter('site.path');
$site_path = explode('/', $site_path);
$site_name = $site_path[1];
$searchdir[] = $site_name . '/libraries';
foreach ($searchdir as $dir) {
if (file_exists($dir . '/c3/c3.min.js') || file_exists($dir . '/c3/c3.js')) {
return $dir . '/c3';
}
}
return FALSE;
}

View File

@@ -0,0 +1,36 @@
charts_c3:
version: 1.x
js:
js/charts_c3.js: { weight: -1 }
dependencies:
- charts/global
- core/drupal
- core/once
d3:
remote: 'https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js'
version: 5.16.0
license:
name: BSD
url: 'https://en.wikipedia.org/wiki/BSD_licenses'
gpl-compatible: false
cdn:
/libraries/d3/: https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/
js:
/libraries/d3/d3.min.js: { }
c3:
remote: 'https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/c3.min.js'
version: 0.7.20
license:
name: MIT
url: 'https://opensource.org/licenses/MIT'
gpl-compatible: true
cdn:
/libraries/c3/: https://cdnjs.cloudflare.com/ajax/libs/c3/0.7.20/
css:
theme:
/libraries/c3/c3.min.css: { }
js:
/libraries/c3/c3.min.js: { }
dependencies:
- charts_c3/charts_c3
- charts_c3/d3

View File

@@ -0,0 +1,17 @@
<?php
/**
* @file
* Implement module related hooks for the charts_c3 module.
*/
/**
* Implements hook_charts_version3_to_new_settings_structure_alter().
*/
function charts_c3_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
$config_factory = \Drupal::configFactory();
$c3_config = $config_factory->getEditable('charts_c3.settings');
if ($c3_config) {
$c3_config->delete();
}
}

View File

@@ -0,0 +1,49 @@
{
"name": "drupal/charts_c3",
"description": "Charts module integration with C3 Charts.",
"type": "drupal-module",
"homepage": "http://drupal.org/project/charts",
"support": {
"issues": "https://www.drupal.org/project/issues/charts"
},
"license": "GPL-2.0+",
"require": {
"c3js/c3": "0.7.20",
"d3/d3": "4.9.1"
},
"repositories": [
{
"type": "package",
"package": {
"name": "c3js/c3",
"version": "v0.7.20",
"type": "drupal-library",
"extra": {
"installer-name": "c3"
},
"dist": {
"url": "https://github.com/c3js/c3/archive/v0.7.20.zip",
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "d3/d3",
"version": "v4.9.1",
"type": "drupal-library",
"extra": {
"installer-name": "d3"
},
"dist": {
"url": "https://github.com/d3/d3/archive/v4.9.1.zip",
"type": "zip"
},
"require": {
"composer/installers": "^1.0 || ^2.0"
}
}
}
]
}

View File

@@ -0,0 +1,21 @@
/**
* @file
* JavaScript's integration between C3 and Drupal.
*/
(function (Drupal, once) {
'use strict';
Drupal.behaviors.chartsC3 = {
attach: function (context, settings) {
const contents = new Drupal.Charts.Contents();
once('charts-c3', '.charts-c3', context).forEach(function (element) {
const config = contents.getData(element.id);
c3.generate(config);
if (element.nextElementSibling && element.nextElementSibling.hasAttribute('data-charts-debug-container')) {
element.nextElementSibling.querySelector('code').innerText = JSON.stringify(config, null, ' ');
}
});
}
};
}(Drupal, once));

View File

@@ -0,0 +1,454 @@
<?php
namespace Drupal\charts_c3\Plugin\chart\Library;
use Drupal\charts\Plugin\chart\Library\ChartBase;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\ElementInfoManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Define a concrete class for a Chart.
*
* @Chart(
* id = "c3",
* name = @Translation("C3"),
* types = {
* "area",
* "bar",
* "bubble",
* "column",
* "donut",
* "gauge",
* "line",
* "pie",
* "scatter",
* "spline",
* },
* )
*/
class C3 extends ChartBase implements ContainerFactoryPluginInterface {
/**
* The element info manager.
*
* @var \Drupal\Core\Render\ElementInfoManagerInterface
*/
protected $elementInfo;
/**
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info
* The element info manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ElementInfoManagerInterface $element_info) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->elementInfo = $element_info;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('element_info')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['intro_text'] = [
'#markup' => $this->t('<p>This is a placeholder for C3.js-specific library options. If you would like to help build this out, please work from <a href="@issue_link">this issue</a>.</p>', [
'@issue_link' => Url::fromUri('https://www.drupal.org/project/charts/issues/3046982')->toString(),
]),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function preRender(array $element) {
// Populate chart settings.
$chart_definition = [];
$chart_definition = $this->populateOptions($element, $chart_definition);
$chart_definition = $this->populateData($element, $chart_definition);
$chart_definition = $this->populateAxes($element, $chart_definition);
if (!empty($element['#height']) || !empty($element['#width'])) {
$element['#attributes']['style'] = 'height:' . $element['#height'] . $element['#height_units'] . ';width:' . $element['#width'] . $element['#width_units'] . ';';
}
if (!isset($element['#id'])) {
$element['#id'] = Html::getUniqueId('chart-c3');
}
$chart_definition['bindto'] = '#' . $element['#id'];
$element['#attached']['library'][] = 'charts_c3/c3';
$element['#attributes']['class'][] = 'charts-c3';
$element['#chart_definition'] = $chart_definition;
return $element;
}
/**
* The chart type.
*
* @param string $chart_type
* The chart type.
* @param bool $is_polar
* Whether polar is checked.
*
* @return string
* Return the type.
*/
protected function getType($chart_type, $is_polar = FALSE) {
// If Polar is checked, then convert to Radar chart type.
if ($is_polar) {
$type = 'radar';
}
else {
$type = $chart_type == 'column' ? 'bar' : $chart_type;
}
return $type;
}
/**
* Get options by type.
*
* @param string $type
* The chart type.
* @param array $element
* The element.
*
* @return array
* Return options.
*/
protected function getOptionsByType($type, array $element) {
$options = $this->getOptionsByCustomProperty($element, $type);
if ($type === 'bar') {
$options['width'] = $element['#width'];
}
return $options;
}
/**
* Get options by custom property.
*
* @param array $element
* The element.
* @param string $type
* The chart type.
*
* @return array
* Return options.
*/
protected function getOptionsByCustomProperty(array $element, $type) {
$options = [];
$properties = Element::properties($element);
// Remove properties which are not related to this chart type.
$properties = array_filter($properties, function ($property) use ($type) {
$query = '#chart_' . $type . '_';
return substr($property, 0, strlen($query)) === $query;
});
foreach ($properties as $property) {
$query = '#chart_' . $type . '_';
$option_key = substr($property, strlen($query), strlen($property));
$options[$option_key] = $element[$property];
}
return $options;
}
/**
* Populate options.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return the chart definition.
*/
private function populateOptions(array $element, array $chart_definition) {
$type = $this->getType($element['#chart_type']);
$title = $element['#title'] ?? '';
if (!empty($element['#subtitle'])) {
$title .= ': ' . $element['#subtitle'];
}
$chart_definition['title']['text'] = $title;
$chart_definition['legend']['show'] = !empty($element['#legend_position']);
if (!in_array($type, ['scatter', 'bubble'])) {
$chart_definition['axis']['x']['type'] = 'category';
}
$chart_definition['data']['labels'] = (bool) $element['#data_labels'];
if ($type === 'pie' || $type === 'donut') {
}
elseif ($type === 'gauge') {
$chart_definition['gauge']['min'] = $element['#gauge']['min'];
$chart_definition['gauge']['max'] = $element['#gauge']['max'];
$chart_definition['color']['pattern'] = [
'red',
'yellow',
'green',
];
$chart_definition['color']['threshold']['values'] = [
$element['#gauge']['red_from'],
$element['#gauge']['yellow_from'],
$element['#gauge']['green_from'],
];
}
elseif ($type === 'line' || $type === 'spline') {
$chart_definition['point']['show'] = !empty($element['#data_markers']);
}
else {
/*
* Billboard does not use bar, so column must be used. Since 'column'
* is changed
* to 'bar' in getType(), we need to use the value from the element.
*/
if ($element['#chart_type'] === 'bar') {
$chart_definition['axis']['rotated'] = TRUE;
}
elseif ($element['#chart_type'] === 'column') {
$type = 'bar';
$chart_definition['axis']['rotated'] = FALSE;
}
}
$chart_definition['data']['type'] = $type;
// Merge in chart raw options.
if (!empty($element['#raw_options'])) {
$chart_definition = NestedArray::mergeDeepArray([
$chart_definition,
$element['#raw_options'],
]);
}
return $chart_definition;
}
/**
* Populate axes.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return chart definition.
*/
private function populateAxes(array $element, array $chart_definition) {
$children = Element::children($element);
foreach ($children as $child) {
$type = $element[$child]['#type'];
if ($type === 'chart_xaxis') {
$chart_definition['axis']['x']['label'] = $element[$child]['#title'] ?? '';
$chart_type = $this->getType($element['#chart_type']);
$categories = $this->stripLabelTags($element[$child]['#labels']);
if (!in_array($chart_type, ['pie', 'donut'])) {
if ($chart_type === 'scatter' || $chart_type === 'bubble') {
// Scatter is not supported: https://github.com/c3js/c3/issues/1902
}
else {
$chart_definition['data']['columns'][] = ['x'];
$chart_definition['data']['x'] = 'x';
$categories_keys = array_keys($chart_definition['data']['columns']);
$categories_key = end($categories_keys);
foreach ($categories as $category) {
$chart_definition['data']['columns'][$categories_key][] = $category;
}
}
}
else {
$chart_definition['data']['columns'] = array_map(NULL, $categories, $chart_definition['data']['columns']);
}
}
if ($type === 'chart_yaxis') {
if (!empty($element[$child]['#opposite']) && $element[$child]['#opposite'] === TRUE) {
$chart_definition['axis']['y2']['show'] = TRUE;
$this->setLabelMinMax($chart_definition, 'y2', $element[$child]);
}
else {
$this->setLabelMinMax($chart_definition, 'y', $element[$child]);
}
}
}
return $chart_definition;
}
/**
* Set the label, min, and max.
*
* @param array $chart_definition
* The chart definition.
* @param string $axis
* The axis.
* @param array $element
* The element.
*/
private function setLabelMinMax(array &$chart_definition, string $axis, array $element): void {
$chart_definition['axis'][$axis]['label'] = $element['#title'] ?? '';
if (!empty($element['#min'])) {
$chart_definition['axis'][$axis]['min'] = $element['#min'];
}
if (!empty($element['#max'])) {
$chart_definition['axis'][$axis]['max'] = $element['#max'];
}
}
/**
* Populate data.
*
* @param array $element
* The element.
* @param array $chart_definition
* The chart definition.
*
* @return array
* Return the chart definition.
*/
private function populateData(array &$element, array $chart_definition) {
$type = $this->getType($element['#chart_type']);
$types = [];
$children = Element::children($element);
$y_axes = [];
foreach ($children as $child) {
$element_type = $element[$child]['#type'];
if ($element_type === 'chart_yaxis') {
$y_axes[] = $child;
}
}
$data_elements = array_filter($children, function ($child) use ($element) {
return $element[$child]['#type'] === 'chart_data';
});
$columns = $chart_definition['data']['columns'] ?? [];
$column_keys = array_keys($columns);
$columns_key_start = $columns ? end($column_keys) + 1 : 0;
foreach ($data_elements as $key) {
$child_element = $element[$key];
// Make sure defaults are loaded.
if (empty($child_element['#defaults_loaded'])) {
$child_element += $this->elementInfo->getInfo($child_element['#type']);
}
if ($child_element['#color'] && $type !== 'gauge') {
$chart_definition['color']['pattern'][] = $child_element['#color'];
}
if (!in_array($type, ['pie', 'donut'])) {
$series_title = isset($child_element['#title']) ? strip_tags($child_element['#title']) : '';
$types[$series_title] = $child_element['#chart_type'] ? $this->getType($child_element['#chart_type']) : $type;
if (!in_array($type, ['scatter', 'bubble'])) {
$columns[$columns_key_start][] = $series_title;
foreach ($child_element['#data'] as $datum) {
if (gettype($datum) === 'array') {
if ($type === 'gauge') {
array_shift($datum);
}
$columns[$columns_key_start][] = array_map(function ($item) {
return isset($item) ? strip_tags($item) : NULL;
}, $datum);
}
else {
$columns[$columns_key_start][] = isset($datum) ? strip_tags($datum) : NULL;
}
}
}
else {
$row = [];
$row[$series_title][0] = $series_title;
$row[$series_title . '_x'][0] = $series_title . '_x';
foreach ($child_element['#data'] as $datum) {
$row[$series_title][] = $datum[0];
$row[$series_title . '_x'][] = $datum[1];
}
$chart_definition['data']['xs'][$series_title] = $series_title . '_x';
foreach ($row as $value) {
$columns[] = $value;
}
$columns = array_values($columns);
}
}
else {
foreach ($child_element['#data'] as $datum_index => $datum) {
if (!empty($datum['color'])) {
$chart_definition['color']['pattern'][$datum_index] = $datum['color'];
unset($datum['color']);
$datum = array_values($datum);
}
$columns[] = $datum;
}
// Add colors for each segment.
foreach ($child_element['#grouping_colors'] ?? [] as $key => $colors_array) {
$color = reset($colors_array);
$chart_definition['color']['pattern'][$key] = $color;
}
}
$columns_key_start++;
}
if ($element['#stacking']) {
$chart_definition['data']['groups'] = [array_keys($types)];
}
$chart_definition['data']['types'] = $types;
$chart_definition['data']['columns'] = $columns;
if (count($y_axes) >= 2) {
foreach ($columns as $index => $column) {
if ($index <= 1) {
$axis = ($index + 1) === 2 ? 2 : '';
$chart_definition['data']['axes'][$column[0]] = 'y' . $axis;
}
}
}
return $chart_definition;
}
/**
* Strip tags from each item in an array.
*
* @param array $items
* The array.
*
* @return array
* Return the cleaned array.
*/
private function stripLabelTags(array $items): array {
if (empty($items)) {
return [];
}
$categories = [];
foreach ($items as $item) {
$categories[] = isset($item) ? strip_tags($item) : NULL;
}
return $categories;
}
}

View File

@@ -0,0 +1,158 @@
#Installation Using Composer (recommended)
If you use Composer to manage dependencies, edit your site's "composer.json"
file as follows.
1. Add the asset-packagist composer repository to "composer.json".
This allows installing packages (like Chart.js) that are published on NPM.
"asset-packagist": {
"type": "composer",
"url": "https://asset-packagist.org"
},
You may need to add it in your composer.json file like this (second item in
the array):
"repositories": [
{
"type": "composer",
"url": "https://packages.drupal.org/8"
},
{
"type": "composer",
"url": "https://asset-packagist.org"
},
],
2. Run the following command to ensure that you have the
"oomphinc/composer-installers-extender" package installed. This package
facilitates the installation of any package into directories other than the
default "/vendor" (e.g. "/libraries") using Composer.
composer require --prefer-dist oomphinc/composer-installers-extender
3. Configure composer to install the Chart.js dependencies into "/libraries"
by adding the following "installer-types" and "installer-paths" to the "extra"
section of "composer.json". If you are not using the "web" directory, then
remove "web/" from the lines below:
"extra": {
"installer-types": ["npm-asset"],
"installer-paths": {
"web/libraries/chart.js": ["npm-asset/chart.js"],
"web/libraries/chartjs-adapter-date-fns": [
"npm-asset/chartjs-adapter-date-fns"
],
"web/libraries/chartjs-plugin-datalabels": [
"npm-asset/chartjs-plugin-datalabels"
],
},
}
NOTE: If this isn't working, try instead adding:
"extra": {
"installer-types": ["npm-asset"],
"installer-paths": {
...
"web/libraries/{$name}": ["type:drupal-library", "vendor:npm-asset"]
},
}
4. This and the following step is optional but recommended. The reason for
them is that when installing the Chart.js package with Composer,
additional files are added into the library directory. These files are not
necessary and can be potentially harmful to your site, so it's best to remove
them. So: create a new directory in your project root called "scripts".
5. Inside that directory, create a new file called "clean-chartjs.sh" and
paste the following into it:
#!/usr/bin/env bash
set -eu
declare -a directories=(
"web/libraries/chart.js/auto"
"web/libraries/chart.js/helpers"
"web/libraries/chart.js/types"
"web/libraries/chart.js/dist/chunks"
"web/libraries/chart.js/dist/docs"
"web/libraries/chart.js/dist/controllers"
"web/libraries/chart.js/dist/core"
"web/libraries/chart.js/dist/elements"
"web/libraries/chart.js/dist/helpers"
"web/libraries/chart.js/dist/platform"
"web/libraries/chart.js/dist/plugins"
"web/libraries/chart.js/dist/scales"
"web/libraries/chart.js/dist/types"
"web/libraries/chartjs-plugin-datalabels/types"
)
counter=0
echo "Deleting unneeded directories inside web/libraries/chartjs"
for directory in "${directories[@]}"
do
if [ -d $directory ]; then
echo "Deleting $directory"
rm -rf $directory
counter=$((counter+1))
fi
done
echo "$counter folders were deleted"
declare -a files=(
"web/libraries/chart.js/README.md"
"web/libraries/chart.js/LICENSE.md"
"web/libraries/chart.js/package.json"
"web/libraries/chart.js/dist/helpers.esm.js"
"web/libraries/chart.js/dist/helpers.mjs"
"web/libraries/chart.js/dist/chart.mjs"
"web/libraries/chart.js/dist/chart.esm.js"
"web/libraries/chart.js/dist/chart.cjs"
"web/libraries/chart.js/dist/chart.cjs.map"
"web/libraries/chart.js/dist/helpers.js"
"web/libraries/chart.js/dist/helpers.js.map"
"web/libraries/chart.js/dist/helpers.cjs"
"web/libraries/chart.js/dist/helpers.cjs.map"
"web/libraries/chart.js/dist/index.d.ts"
"web/libraries/chart.js/dist/index.umd.d.ts"
"web/libraries/chart.js/dist/types.d.ts"
"web/libraries/chartjs-adapter-date-fns/README.md"
"web/libraries/chartjs-adapter-date-fns/LICENSE.md"
"web/libraries/chartjs-adapter-date-fns/package.json"
"web/libraries/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.esm.js"
"web/libraries/chartjs-plugin-datalabels/README.md"
"web/libraries/chartjs-plugin-datalabels/LICENSE.md"
"web/libraries/chartjs-plugin-datalabels/package.json"
"web/libraries/chartjs-plugin-datalabels/bower.json"
)
counter=0
echo "Deleting unneeded files inside web/libraries/chartjs"
for file in "${files[@]}"
do
if [[ -f $file ]]; then
echo "Deleting $file"
rm $file
counter=$((counter+1))
fi
done
echo "$counter files were deleted"
6. Add a "scripts" entry to your "composer.json" file as shown below. If
"scripts" already exists, you will need to do a little more to incorporate
the code below.
"scripts": {
"clean-chartjs": "chmod +x scripts/clean-chartjs.sh &&
./scripts/clean-chartjs.sh",
"post-install-cmd": [
"@clean-chartjs"
],
"post-update-cmd": [
"@clean-chartjs"
]
}
7. Run the following command; you should find that new directories have been
created under "/libraries".
composer require --prefer-dist npm-asset/chart.js:^4.4
npm-asset/chartjs-adapter-date-fns:^3.0
npm-asset/chartjs-plugin-datalabels:^2.0

View File

@@ -0,0 +1,5 @@
polarArea:
label: 'Polar Area'
axis: 'y_only'
axis_inverted: false
stacking: false

View File

@@ -0,0 +1,12 @@
name: 'Chart.js Charts'
type: module
description: 'Charts module integration with Chart.js.'
package: Charts
core_version_requirement: ^10.3 || ^11
dependencies:
- 'charts:charts'
# Information added by Drupal.org packaging script on 2024-12-02
version: '5.1.1'
project: 'charts'
datestamp: 1733170633

View File

@@ -0,0 +1,95 @@
<?php
/**
* @file
* Installation and uninstallation functions.
*/
/**
* Implements hook_requirements().
*/
function charts_chartjs_requirements($phase) {
$requirements = [];
switch ($phase) {
case 'runtime':
$library_path = charts_chartjs_find_library();
$config = \Drupal::config('charts.settings');
$cdn = $config->get('advanced.requirements.cdn') ?? FALSE;
if (!$library_path) {
if ($cdn) {
$requirements['charts_chartjs_js'] = [
'title' => t('Chart.js Library'),
'value' => t('Available through a CDN'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You are using the Chart.js library via a content delivery network. It is generally considered better practice to install the library files locally. Please see the README file inside charts_chartjs for instructions to install the library.'),
];
}
else {
$requirements['charts_chartjs_js'] = [
'title' => t('Chart.js Library'),
'value' => t('Not Installed'),
'severity' => REQUIREMENT_ERROR,
'description' => t('You are missing the Chart.js library in your Drupal installation directory and you have opted not to use a CDN. Please either enable use of the CDN in the Chart Settings under the Advanced tab or see the README file inside charts_chartjs for instructions to install the library.'),
];
}
}
else {
// If the library directory contains "auto", provide a warning.
if (file_exists($library_path . '/auto')) {
$requirements['charts_chartjs_js'] = [
'title' => t('Chart.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_WARNING,
'description' => t('You have installed the Chart.js library in your Drupal installation directory, but it contains additional files that are not required and may be harmful. Please delete everything except the files listed in charts_chartjs.libraries.yml. We have included a suggested post-install/post-update script in the README file.'),
];
}
else {
$requirements['charts_chartjs_js'] = [
'title' => t('Chart.js Library'),
'value' => t('Installed'),
'severity' => REQUIREMENT_OK,
'description' => t('You have installed the Chart.js library in your Drupal installation directory.'),
];
}
}
break;
}
return $requirements;
}
/**
* Get the location of the Chart.js library.
*
* @return string
* The location of the library, or FALSE if the library isn't installed.
*/
function charts_chartjs_find_library() {
// The following logic is taken from libraries_get_libraries()
$searchdir = [];
// Similar to 'modules' and 'themes' directories inside an installation
// profile, installation profiles may want to place libraries into a
// 'libraries' directory.
$searchdir[] = 'profiles/' . \Drupal::installProfile() . '/libraries';
// Always search libraries.
$searchdir[] = 'libraries';
// Also search sites/<domain>/*.
$container = \Drupal::getContainer();
$site_path = $container->getParameter('site.path');
$site_path = explode('/', $site_path);
$site_name = $site_path[1];
$searchdir[] = $site_name . '/libraries';
foreach ($searchdir as $dir) {
if (file_exists($dir . '/chart.js/dist/chart.js') || file_exists($dir . '/chart.js/dist/chart.umd.js')) {
return $dir . '/chartjs';
}
}
return FALSE;
}

View File

@@ -0,0 +1,26 @@
charts_chartjs:
remote: 'https://github.com/chartjs/Chart.js'
version: VERSION
license:
name: 'Apache 2.0'
url: 'https://www.apache.org/licenses/LICENSE-2.0'
gpl-compatible: false
# @todo
cdn:
/libraries/chart.js/: https://unpkg.com/chart.js@4.4.0/
/libraries/chartjs-adapter-date-fns/: https://unpkg.com/chartjs-adapter-date-fns@3.0.0/
/libraries/chartjs-plugin-datalabels/: https://unpkg.com/chartjs-plugin-datalabels@2.0.0/
js:
/libraries/chart.js/dist/chart.umd.js: { }
/libraries/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.js: { }
/libraries/chartjs-plugin-datalabels/dist/chartjs-plugin-datalabels.js: { }
dependencies:
- core/drupal
- core/once
chartjs:
version: 1.x
js:
js/charts_chartjs.js: { weight: -1 }
dependencies:
- charts/global
- charts_chartjs/charts_chartjs

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Implement module related hooks for the charts_chartjs module.
*/
/**
* Implements hook_charts_version3_to_new_settings_structure_alter().
*/
function charts_chartjs_charts_version3_to_new_settings_structure_alter(array &$new_settings, string $for) {
$config_factory = \Drupal::configFactory();
$is_config = $for === 'config';
$chartjs_config = $is_config ? $config_factory->getEditable('charts_chartjs.settings') : NULL;
if (!$is_config || !$chartjs_config || empty($new_settings['library']) || $new_settings['library'] !== 'chartjs') {
if ($chartjs_config) {
$chartjs_config->delete();
}
return;
}
$new_settings['library_config'] = [
'xaxis' => [
'autoskip' => TRUE,
'horizontal_axis_title_align' => 'start',
],
'yaxis' => [
'vertical_axis_title_align' => 'start',
],
];
$chartjs_config->delete();
}

Some files were not shown because too many files have changed in this diff Show More