diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml new file mode 100644 index 0000000..88865f7 --- /dev/null +++ b/.github/workflows/build-release.yml @@ -0,0 +1,47 @@ +name: Build Release + +on: + push: + branches: + - 'release/*' + +jobs: + autocommit: + name: Build Release + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:latest + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + + - name: Install PHP dependencies + run: composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + + - name: Update composer.json + run: >- + composer config --unset version && php -r ' + $f = __DIR__ . "/composer.json"; + $data = json_decode(file_get_contents($f), true); + foreach ($data as $k => $v) { + if (preg_match("~^(.+)-release$~", $k, $matches)) { + $data[$matches[1]] = $data[$k]; unset($data[$k]); + } + } + $str = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n"; + echo $str; + file_put_contents($f, $str); + ' + + - name: Commit + run: | + git config --global user.name "$(git show -s --format='%an')" + git config --global user.email "$(git show -s --format='%ae')" + git add . -N && (git diff --exit-code || git commit -a -m "Branch for stable release") + + - name: Push + uses: ad-m/github-push-action@master + with: + branch: ${{ github.ref }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/bundler.yml b/.github/workflows/bundler.yml deleted file mode 100644 index 953db85..0000000 --- a/.github/workflows/bundler.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Bundler - -on: create - -jobs: - autocommit: - name: Update to stable dependencies - if: startsWith(github.ref, 'refs/heads/release/') - runs-on: ubuntu-latest - container: - image: atk4/image:latest # https://github.com/atk4/image - steps: - - uses: actions/checkout@v2 - with: - ref: ${{ github.ref }} - - run: echo ${{ github.ref }} - - name: Update to stable dependencies - run: | - # replaces X keys with X-release keys - jq '. as $in | reduce (keys_unsorted[] | select(endswith("-release")|not)) as $k ({}; . + {($k) : (($k + "-release") as $kr | $in | if has($kr) then .[$kr] else .[$k] end) } )' < composer.json > tmp && mv tmp composer.json - v=$(echo ${{ github.ref }} | cut -d / -f 4) - echo "::set-env name=version::$v" - - - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Setting release dependencies - - uses: ad-m/github-push-action@master - with: - branch: ${{ github.ref }} - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml deleted file mode 100644 index 1a783cf..0000000 --- a/.github/workflows/release-drafter.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Release Drafter - -on: - push: - # branches to consider in the event; optional, defaults to all - branches: - - develop - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - # Drafts your next Release notes as Pull Requests are merged into "master" - - uses: toolmantim/release-drafter@v5.6.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml new file mode 100644 index 0000000..e3f20b9 --- /dev/null +++ b/.github/workflows/test-unit.yml @@ -0,0 +1,71 @@ +name: Unit + +on: + pull_request: + push: + schedule: + - cron: '0 0/2 * * *' + +jobs: + smoke-test: + name: Smoke + runs-on: ubuntu-latest + container: + image: ghcr.io/mvorisek/image-php:${{ matrix.php }} + strategy: + fail-fast: false + matrix: + php: ['latest'] + type: ['Phpunit'] + include: + - php: 'latest' + type: 'CodingStyle' + - php: 'latest' + type: 'StaticAnalysis' + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Configure PHP + run: | + rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini + php --version + + - name: Setup cache 1/2 + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Setup cache 2/2 + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-smoke-${{ matrix.php }}-${{ matrix.type }}-${{ hashFiles('composer.json') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install PHP dependencies + run: | + if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi + if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi + if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* behat/\* --dev; fi + composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader + + - name: "Run tests: SQLite (only for Phpunit)" + if: startsWith(matrix.type, 'Phpunit') + run: | + echo "not implemented" || vendor/bin/phpunit --exclude-group none --no-coverage -v + + - name: Check Coding Style (only for CodingStyle) + if: matrix.type == 'CodingStyle' + run: | + if [ "$(find demos/ -name '*.php' -print0 | xargs -0 grep -L "namespace Atk4\\\\Chart\\\\Demos[;\\\\]" | tee /dev/fd/2)" ]; then echo 'All demos/ files must have namespace declared' && (exit 1); fi + vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --verbose + composer config --unset version && composer config --unset require-release + composer validate --strict --no-check-lock && composer normalize --dry-run --no-check-lock + + - name: Run Static Analysis (only for StaticAnalysis) + if: matrix.type == 'StaticAnalysis' + run: | + echo "memory_limit = 2G" > /usr/local/etc/php/conf.d/custom-memory-limit.ini + vendor/bin/phpstan analyse diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml deleted file mode 100644 index d7a89d2..0000000 --- a/.github/workflows/unit-tests.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Unit Testing - -on: - pull_request: - branches: '**' - push: - branches: '**' - schedule: - - cron: '0 * * * *' - -jobs: - unit-test: - name: Unit - runs-on: ubuntu-latest - container: - image: atk4/image:${{ matrix.php }} # https://github.com/atk4/image - strategy: - fail-fast: false - matrix: - php: ['7.3', 'latest'] - type: ['Phpunit'] - include: - - php: 'latest' - type: 'CodingStyle' - env: - LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == 'latest' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Configure PHP - run: | - if [ -n "$LOG_COVERAGE" ]; then echo "xdebug.mode=coverage" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; else rm /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini; fi - php --version - - - name: Setup cache 1/2 - id: composer-cache - run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - - name: Setup cache 2/2 - uses: actions/cache@v1 - with: - path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ matrix.php }}-${{ matrix.type }}-${{ hashFiles('composer.json') }} - restore-keys: | - ${{ runner.os }}-composer- - - - name: Install PHP dependencies - run: | - if [ "${{ matrix.type }}" != "Phpunit" ]; then composer remove --no-interaction --no-update phpunit/phpunit phpunit/dbunit phpunit/phpcov --dev ; fi - if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev ; fi - composer install --no-suggest --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader - - - name: Init - run: | - mkdir -p build/logs - - - name: "Run tests: SQLite (only for Phpunit)" - if: matrix.type == 'Phpunit' - run: "vendor/bin/phpunit \"$(if [ -n \"$LOG_COVERAGE\" ]; then echo '--coverage-text'; else echo '--no-coverage'; fi)\" -v" - - - name: Lint / check syntax (only for CodingStyle) - if: matrix.type == 'CodingStyle' - run: find . \( -type d \( -path './vendor/*' \) \) -prune -o ! -type d -name '*.php' -print0 | xargs -0 -n1 php -l - - - name: Check Coding Style (only for CodingStyle) - if: matrix.type == 'CodingStyle' - run: vendor/bin/php-cs-fixer fix --dry-run --using-cache=no --diff --diff-format=udiff --verbose --show-progress=dots - - - name: Upload coverage logs 1/2 (only for "latest" Phpunit) - if: env.LOG_COVERAGE - run: vendor/bin/phpcov merge build/logs/ --clover build/logs/cc.xml - - - name: Upload coverage logs 2/2 (only for "latest" Phpunit) - if: env.LOG_COVERAGE - uses: codecov/codecov-action@v1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: build/logs/cc.xml diff --git a/.gitignore b/.gitignore index ea1efc8..ce729ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ -docs/build -/build +/docs/build +/coverage /vendor /composer.lock .idea nbproject +.vscode .DS_Store local @@ -14,4 +15,3 @@ cache *.cache.* /phpunit.xml -/phpunit-*.xml diff --git a/.php_cs.dist b/.php-cs-fixer.dist.php similarity index 67% rename from .php_cs.dist rename to .php-cs-fixer.dist.php index 5e91a35..9dfc26b 100644 --- a/.php_cs.dist +++ b/.php-cs-fixer.dist.php @@ -8,12 +8,13 @@ 'vendor', ]); -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setRiskyAllowed(true) ->setRules([ '@PhpCsFixer' => true, - '@PhpCsFixer:risky' =>true, - '@PHP71Migration:risky' => true, + '@PhpCsFixer:risky' => true, + '@PHP74Migration:risky' => true, + '@PHP74Migration' => true, // required by PSR-12 'concat_space' => [ @@ -21,6 +22,11 @@ ], // disable some too strict rules + 'phpdoc_types' => [ + // keep enabled, but without "alias" group to not fix + // "Callback" to "callback" in phpdoc + 'groups' => ['simple', 'meta'], + ], 'phpdoc_types_order' => [ 'null_adjustment' => 'always_last', 'sort_algorithm' => 'none', @@ -30,11 +36,12 @@ 'equal' => false, 'identical' => false, ], + 'native_constant_invocation' => true, 'native_function_invocation' => false, - 'non_printable_character' => [ - 'use_escape_sequences_in_strings' => true, - ], 'void_return' => false, + 'blank_line_before_statement' => [ + 'statements' => ['break', 'continue', 'declare', 'return', 'throw', 'exit'], + ], 'combine_consecutive_issets' => false, 'combine_consecutive_unsets' => false, 'multiline_whitespace_before_semicolons' => false, @@ -54,6 +61,10 @@ 'nullable_type_declaration_for_default_null_value' => [ 'use_nullable_type_declaration' => false, ], + + // fn => without curly brackets is less readable, + // also prevent bounding of unwanted variables for GC + 'use_arrow_functions' => false, ]) ->setFinder($finder) - ->setCacheFile(__DIR__ . '/.php_cs.cache'); + ->setCacheFile(sys_get_temp_dir() . '/php-cs-fixer.' . md5(__DIR__) . '.cache'); diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 865320b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# Change Log - -## [1.0.0](https://github.com/atk4/chart/tree/1.0.0) (2019-03-01) - -[Full Changelog](https://github.com/atk4/chart/compare/09fd17a39a808f84b05f75a0222814eb82c0b05e...1.0.0) - -**Closed issues:** - -- Composer didn't update to 4b593ed34af271af3dea054f2a2dc2dc69592383 [\#5](https://github.com/atk4/chart/issues/5) - -**Merged pull requests:** - -- show "no data" in label if there is no data [\#4](https://github.com/atk4/chart/pull/4) ([DarkSide666](https://github.com/DarkSide666)) -- fix options [\#3](https://github.com/atk4/chart/pull/3) ([DarkSide666](https://github.com/DarkSide666)) -- bugfix [\#2](https://github.com/atk4/chart/pull/2) ([DarkSide666](https://github.com/DarkSide666)) -- add descriptions, add withCurrencyY [\#1](https://github.com/atk4/chart/pull/1) ([DarkSide666](https://github.com/DarkSide666)) - - - -\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file diff --git a/codecov.yml b/codecov.yml index edca4fe..1f1d4d4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,11 +1,12 @@ ignore: - - demo + - docs comment: false coverage: status: project: default: target: auto - threshold: 0.1 + threshold: 0.025 patch: false changes: false + diff --git a/composer.json b/composer.json index fd3e72f..2eab6b8 100644 --- a/composer.json +++ b/composer.json @@ -1,47 +1,58 @@ { - "name": "atk4/chart", - "type": "library", - "description": "ChartJS for Agile UI", - "keywords": [ - "agile", - "data", - "framework", - "chart", - "chartjs" - ], - "homepage": "https://github.com/atk4/chart", - "license": "MIT", - "authors": [ - { - "name": "Romans Malinovskis", - "email": "romans@agiletoolkit.org", - "homepage": "https://nearly.guru/" + "name": "atk4/chart", + "description": "ChartJS for Agile UI", + "license": "MIT", + "type": "library", + "keywords": [ + "agile", + "data", + "framework", + "chart", + "chartjs" + ], + "version": "dev-develop", + "authors": [ + { + "name": "Romans Malinovskis", + "email": "romans@agiletoolkit.org", + "homepage": "https://nearly.guru/" + } + ], + "homepage": "https://github.com/atk4/chart", + "require": { + "php": ">=7.4 <8.2", + "atk4/ui": "dev-develop" + }, + "require-release": { + "php": ">=7.4 <8.2", + "atk4/ui": "~4.0.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.13", + "friendsofphp/php-cs-fixer": "^3.0", + "johnkary/phpunit-speedtrap": "^3.3", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpunit/phpunit": "^9.5.5" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "autoload": { + "psr-4": { + "Atk4\\Chart\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Atk4\\Chart\\Tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true, + "phpstan/extension-installer": true + }, + "sort-packages": true } - ], - "minimum-stability": "dev", - "prefer-stable": true, - "config": { - "sort-packages": true - }, - "require": { - "atk4/ui": "dev-develop" - }, - "require-release": { - "atk4/ui": "~2.3.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.16", - "phpunit/phpcov": "*", - "phpunit/phpunit": "*" - }, - "autoload": { - "psr-4": { - "Atk4\\Chart\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "Atk4\\Chart\\Tests\\": "tests/" - } - } } diff --git a/demo/index.php b/demos/index.php similarity index 55% rename from demo/index.php rename to demos/index.php index ea22262..1844d00 100644 --- a/demo/index.php +++ b/demos/index.php @@ -2,39 +2,44 @@ declare(strict_types=1); -require '../vendor/autoload.php'; +namespace Atk4\Chart\Demos; use Atk4\Chart\BarChart; use Atk4\Chart\ChartBox; use Atk4\Chart\PieChart; use Atk4\Data\Model; -use Atk4\Data\Persistence\Array_; +use Atk4\Data\Persistence; use Atk4\Ui\App; use Atk4\Ui\Columns; use Atk4\Ui\Layout; -$p = ['t' => [ - ['name' => 'January', 'sales' => 20000, 'purchases' => 10000], - ['name' => 'February', 'sales' => 23000, 'purchases' => 12000], - ['name' => 'March', 'sales' => 16000, 'purchases' => 11000], - ['name' => 'April', 'sales' => 14000, 'purchases' => 13000], -]]; -$m = new Model(new Array_($p), 't'); +require '../vendor/autoload.php'; + +$t = [ + 1 => ['name' => 'January', 'sales' => 20000, 'purchases' => 10000], + 2 => ['name' => 'February', 'sales' => 23000, 'purchases' => 12000], + 3 => ['name' => 'March', 'sales' => 16000, 'purchases' => 11000], + 4 => ['name' => 'April', 'sales' => 14000, 'purchases' => 13000], +]; + +$m = new Model(new Persistence\Array_($t)); $m->addFields(['name', 'sales', 'purchases', 'profit']); -$m->onHook($m::HOOK_AFTER_LOAD, function ($m) { $m->set('profit', $m->get('sales') - $m->get('purchases')); }); -$app = new App('Chart Demo'); +$m->onHook($m::HOOK_AFTER_LOAD, function ($m) { + $m->set('profit', $m->get('sales') - $m->get('purchases')); +}); +$app = new App(['title' => 'Chart Demo']); $app->initLayout([Layout\Centered::class]); // split in columns $columns = Columns::addTo($app->layout); -// Lets put your chart into a box: +// lets put your chart into a box $cb = ChartBox::addTo($columns->addColumn(8), ['label' => ['Demo Bar Chart', 'icon' => 'book']]); $chart = BarChart::addTo($cb); $chart->setModel($m, ['name', 'sales', 'purchases', 'profit']); $chart->withCurrency('$'); -// Tweak our chart to support currencies better +// tweak our chart to support currencies better $cb = ChartBox::addTo($columns->addColumn(8), ['label' => ['Demo Pie Chart', 'icon' => 'book']]); $chart = PieChart::addTo($cb); $chart->setModel($m, ['name', 'profit']); diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..09b8f29 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,29 @@ +parameters: + level: 6 + paths: + - ./ + excludePaths: + - cache/ + - build/ + - vendor/ + + # TODO review once we drop PHP 7.x support + treatPhpDocTypesAsCertain: false + + # some extra rules + checkAlwaysTrueCheckTypeFunctionCall: true + checkAlwaysTrueInstanceof: true + checkAlwaysTrueStrictComparison: true + checkExplicitMixedMissingReturn: true + checkFunctionNameCase: true + # TODO checkMissingClosureNativeReturnTypehintRule: true + reportMaybesInMethodSignatures: true + reportStaticMethodSignatures: true + checkTooWideReturnTypesInProtectedAndPublicMethods: true + checkMissingIterableValueType: false # TODO + + ignoreErrors: + #- '~^Unsafe usage of new static\(\)\.$~' + + # for src/Chart.php + - '~^Call to an undefined method Atk4\\Data\\Model::expr\(\)\.$~' diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 2beed80..0000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - src - - - - - tests - - - - - - diff --git a/src/Chart.php b/src/Chart.php index 84d5403..867da12 100644 --- a/src/Chart.php +++ b/src/Chart.php @@ -9,9 +9,6 @@ use Atk4\Ui\JsExpression; use Atk4\Ui\View; -/** - * Implement basic logic for ChartJS. - */ class Chart extends View { /** @var string HTML element type */ @@ -40,23 +37,17 @@ class Chart extends View protected $labels; /** @var array Datasets. Fills with setModel(). */ - protected $dataSets; + protected $datasets; - /** - * Initialization. - */ protected function init(): void { parent::init(); if ($this->js_include) { - $this->getApp()->requireJS('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js'); + $this->getApp()->requireJs('https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js'); } } - /** - * Renders chart view. - */ public function renderView(): void { $this->js(true, new JsExpression('new Chart([], []);', [$this->name, $this->getConfig()])); @@ -64,53 +55,39 @@ public function renderView(): void parent::renderView(); } - /** - * Builds configuration for a chart. - */ public function getConfig(): array { return [ 'type' => $this->type, 'data' => [ 'labels' => $this->getLabels(), - 'datasets' => $this->getDataSets(), + 'datasets' => $this->getDatasets(), ], 'options' => $this->getOptions(), ]; } - /** - * Return labels. - */ public function getLabels(): array { return $this->labels; } - /** - * Return datasets. - */ - public function getDataSets(): array + public function getDatasets(): array { - return array_values($this->dataSets); + return array_values($this->datasets); } - /** - * Return options. - */ public function getOptions(): array { return $this->options; } /** - * Set options. - * * @return $this */ public function setOptions(array $options) { - // Important: use replace not merge here to preserve numeric keys !!! + // IMPORTANT: use replace not merge here to preserve numeric keys !!! $this->options = array_replace_recursive($this->options, $options); return $this; @@ -124,25 +101,25 @@ public function setOptions(array $options) * This component will automatically figure out name of the chart, * series titles based on column captions etc. */ - public function setModel(Model $model, array $columns = []): Model + public function setModel(Model $model, array $columns = []): void { if (!$columns) { throw new Exception('Second argument must be specified to Chart::setModel()'); } - $this->dataSets = []; + $this->datasets = []; - // Initialize data-sets + // initialize data-sets foreach ($columns as $key => $column) { if ($key === 0) { - $title_column = $column; + $titleColumn = $column; continue; // skipping labels } $colors = array_shift($this->nice_colors); - $this->dataSets[$column] = [ + $this->datasets[$column] = [ 'label' => $model->getField($column)->getCaption(), 'backgroundColor' => $colors[0], 'borderColor' => $colors[1], @@ -151,15 +128,13 @@ public function setModel(Model $model, array $columns = []): Model ]; } - // Prepopulate data-sets + // prepopulate data-sets foreach ($model as $row) { - $this->labels[] = $row->get($title_column); - foreach ($this->dataSets as $key => &$dataset) { + $this->labels[] = $row->get($titleColumn); // @phpstan-ignore-line + foreach ($this->datasets as $key => &$dataset) { $dataset['data'][] = $row->get($key); } } - - return $model; } /** @@ -219,20 +194,22 @@ public function withCurrencyY(string $char = '€') * Example: * * // Pie or Bar chart - * $chart->summarize($users, ['by'=>'status', 'fx'=>'count']); - * $chart->summarize($users, ['by'=>'status', 'fx'=>'sum', 'field'=>'total_net']); + * $chart->summarize($users, ['by' => 'status', 'fx' => 'count']); + * $chart->summarize($users, ['by' => 'status', 'fx' => 'sum', 'field' => 'total_net']); * * or * * // Bar chart * $orders = $clients->ref('Orders'); * $chart->summarize($orders, [ - * 'by'=>$orders->expr('year([date])'), - * 'fields'=>[ - * 'purchase'=>$orders->expr('sum(if([is_purchase], [amount], 0)'), - * 'sale'=>$orders->expr('sum(if([is_purchase], 0, [amount])'), - * ], + * 'by'=>$orders->expr('year([date])'), + * 'fields'=>[ + * 'purchase' => $orders->expr('sum(if([is_purchase], [amount], 0)'), + * 'sale' => $orders->expr('sum(if([is_purchase], 0, [amount])'), + * ], * ])->withCurrency('$'); + * + * @return $this */ public function summarize(Model $model, array $options = []) { @@ -265,8 +242,8 @@ public function summarize(Model $model, array $options = []) $qq = $model->action('fx', [$fx, $options['field'] ?? $model->expr('*'), 'alias' => $fx]); $fields[] = $fx; } else { - $qq = $model->action('select', [[$model->title_field]]); - $fields[] = $model->title_field; + $qq = $model->action('select', [[$model->titleField]]); + $fields[] = $model->titleField; } } @@ -279,11 +256,11 @@ public function summarize(Model $model, array $options = []) $qq->field($field, 'by'); $qq->group('by'); } else { - $qq->field($model->getField($model->title_field), 'by'); + $qq->field($model->getField($model->titleField), 'by'); } // and then set it as chart source - $this->setSource($qq->get(), $fields); + $this->setSource($qq->getRows(), $fields); return $this; } diff --git a/src/ChartBox.php b/src/ChartBox.php index 8cee983..463d31b 100644 --- a/src/ChartBox.php +++ b/src/ChartBox.php @@ -13,6 +13,8 @@ class ChartBox extends View { public $ui = 'segment'; + + /** @var string */ public $label = 'Chart Box'; protected function init(): void @@ -20,6 +22,6 @@ protected function init(): void $this->defaultTemplate = dirname(__DIR__) . '/template/chartbox.html'; parent::init(); - $this->add(new Label($this->label), 'Label')->addClass('top attached'); + Label::addTo($this, [$this->label], ['Label'])->addClass('top attached'); } } diff --git a/src/PieChart.php b/src/PieChart.php index f1f309a..d8a2e51 100644 --- a/src/PieChart.php +++ b/src/PieChart.php @@ -21,46 +21,44 @@ class PieChart extends Chart * This component will automatically figure out name of the chart, * series titles based on column captions etc. */ - public function setModel(Model $model, array $columns = []): Model + public function setModel(Model $model, array $columns = []): void { if (!$columns) { throw new Exception('Second argument must be specified to Chart::setModel()'); } - $this->dataSets = []; + $this->datasets = []; $colors = []; - // Initialize data-sets + // initialize data-sets foreach ($columns as $key => $column) { $colors[$column] = $this->nice_colors; if ($key === 0) { - $title_column = $column; + $titleColumn = $column; continue; // skipping labels } - $this->dataSets[$column] = [ - //'label' => $model->getField($column)->getCaption(), + $this->datasets[$column] = [ + // 'label' => $model->getField($column)->getCaption(), 'data' => [], - 'backgroundColor' => [], //$colors[0], - //'borderColor' => [], //$colors[1], - //'borderWidth' => 1, + 'backgroundColor' => [], // $colors[0], + // 'borderColor' => [], // $colors[1], + // 'borderWidth' => 1, ]; } - // Prepopulate data-sets + // prepopulate data-sets foreach ($model as $row) { - $this->labels[] = $row->get($title_column); - foreach ($this->dataSets as $key => &$dataset) { + $this->labels[] = $row->get($titleColumn); // @phpstan-ignore-line + foreach ($this->datasets as $key => &$dataset) { $dataset['data'][] = $row->get($key); $color = array_shift($colors[$key]); $dataset['backgroundColor'][] = $color[0]; $dataset['borderColor'][] = $color[1]; } } - - return $model; } /** @@ -74,12 +72,13 @@ public function setModel(Model $model, array $columns = []): Model public function withCurrency(string $char = '€', string $axis = 'y') { $options['tooltips'] = [ - //'enabled' => true, - //'mode' => 'single', + // 'enabled' => true, + // 'mode' => 'single', 'callbacks' => [ 'label' => new JsExpression('{}', [ 'function(item, data, bb) { var val = data.datasets[item.datasetIndex].data[item.index]; + return "' . $char . '" + val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }', ]), diff --git a/template/chartbox.html b/template/chartbox.html index 7303c65..653aeae 100644 --- a/template/chartbox.html +++ b/template/chartbox.html @@ -1 +1 @@ -<{_element}div{/} id="{$_id}" class="{_ui}ui{/} {$class} {_class}{/}" style="{$style}" {$attributes}>{$Label}
{$Content}
\ No newline at end of file +<{_element}div{/} id="{$_id}" class="{_ui}ui{/} {$class} {_class}{/}" style="{$style}" {$attributes}>{$Label}
{$Content}
diff --git a/tests/.keep b/tests/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/BasicTest.php b/tests/BasicTest.php deleted file mode 100644 index ba27383..0000000 --- a/tests/BasicTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue(true); - } -}