├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── composer.json ├── dist ├── js │ ├── chart-js-integration.js │ └── chart-js-integration.js.LICENSE.txt └── mix-manifest.json ├── package-lock.json ├── package.json ├── resources ├── .DS_Store └── js │ ├── bar-chart.vue │ ├── chart-js-integration.js │ ├── components │ ├── BarChart.vue │ ├── DoughnutChart.vue │ ├── Icons │ │ ├── IconExternalLink.vue │ │ └── IconRefresh.vue │ ├── PieChart.vue │ ├── PolarAreaChart.vue │ ├── ScatterChart.vue │ ├── StackedChart.vue │ └── StripeChart.vue │ ├── doughnut-chart.vue │ ├── mixins │ └── HandlesDataLabelsPlugin.js │ ├── pie-chart.vue │ ├── polar-area-chart.vue │ ├── scatter-chart.vue │ └── stripe-chart.vue ├── routes └── api.php ├── spellcheck.yaml ├── src ├── .DS_Store ├── AreaChart.php ├── BarChart.php ├── CardServiceProvider.php ├── DoughnutChart.php ├── LineChart.php ├── PieChart.php ├── PolarAreaChart.php ├── ScatterChart.php ├── StackedChart.php └── api │ ├── ThrowError.php │ ├── TotalCircleController.php │ └── TotalRecordsController.php └── webpack.mix.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['https://www.buymeacoffee.com/coroowicaksono'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Dependencies Version (please complete the following information):** 27 | - Laravel Version: [e.g. 7.27.0] 28 | - Nova Version: [e.g. v3.8.4] 29 | - Nova-ChartJS Version: [e.g. v0.3.2] 30 | 31 | **Desktop (please complete the following information):** 32 | - OS: [e.g. iOS] 33 | - Browser [e.g. chrome, safari] 34 | - Version [e.g. 22] 35 | 36 | **Smartphone (please complete the following information):** 37 | - Device: [e.g. iPhone6] 38 | - OS: [e.g. iOS8.1] 39 | - Browser [e.g. stock browser, safari] 40 | - Version [e.g. 22] 41 | 42 | **Additional context** 43 | Add any other context about the problem here. 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: ci 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the master branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 23 | - uses: actions/checkout@v2 24 | 25 | # Runs a check spelling for vue (get type from spellcheck.yaml) 26 | - name: Check Vue Spelling 27 | uses: UnicornGlobal/spellcheck-github-actions@master 28 | 29 | # Runs a check spelling for php 30 | - name: Check PHP Spelling 31 | uses: michaelw90/PHP-Lint@master 32 | 33 | # Runs a set of commands for build 34 | - name: Build Application 35 | run: | 36 | npm install 37 | npm update --depth 5 @babel/compat-data 38 | npm run dev 39 | npm run prod 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | vendor/ 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ``` 4 | # create tag & push 5 | git tag -a v0.4.2 -m "Release version 0.4.2 to support laravel nova to 4.31" 6 | git push origin v0.4.2 7 | ``` 8 | 9 | All notable changes to `chart-js-integration` will be documented in this file 10 | 11 | ## v0.4.2 - 2023-11-21 12 | 13 | - :sparkles: add new support for nova v4.31.x 14 | 15 | ## v0.4.1 - 2023-09-16 16 | 17 | - :sparkles: add new support for laravel version 10.x 18 | 19 | ## v0.4.0 - 2022-05-15 20 | 21 | - :sparkles: add new support for nova v4.x 22 | 23 | ## v0.3.4 - 2021-02-11 24 | 25 | - :sparkles: add new feature: custom x-axis 26 | 27 | ## v0.3.3 - 2021-02-08 28 | 29 | - :wrench: change api request to /nova-vendor/ 30 | 31 | ## v0.3.2 - 2020-09-17 32 | 33 | - :wrench: fix bug for line chart using model 34 | 35 | ## v0.3.1 - 2020-09-15 36 | 37 | - :wrench: fix error for psql database connection 38 | - Add `hour` as UOM 39 | - ⬆️ Bump http-proxy from 1.18.0 to 1.18.1 dependencies 40 | 41 | ## v0.3.0 - 2020-08-11 42 | 43 | - :angel: new baby born 44 | - add plugin datalabels 45 | - add roadmap for plugin 46 | 47 | ## v0.2.7 - 2020-08-10 48 | 49 | - support Laravel DB connection table prefixes for models 50 | 51 | ## v0.2.6 - 2020-06-10 52 | 53 | - update dependencies in security vulnerability: 54 | - Bump websocket-extensions from 0.1.3 to 0.1.4 55 | - 8efd0cd Bump version to 0.1.4 56 | - 3dad4ad Remove ReDoS vulnerability in the Sec-WebSocket-Extensions header parser 57 | - 4a76c75 Add Node versions 13 and 14 on Travis 58 | - 44a677a Formatting change: {...} should have spaces inside the braces 59 | - f6c50ab Let npm reformat package.json 60 | - 2d211f3 Change markdown formatting of docs. 61 | - 0b62083 Update Travis target versions. 62 | - 729a465 Switch license to Apache 2.0. 63 | 64 | ## v0.2.5 - 2020-06-04 65 | 66 | - update for some bugs: 67 | - #41 Problems getting charts to display 68 | - #45 Unable to set fill for linechart 69 | - #46 Areachart does not support RGBA color values 70 | 71 | ## v0.2.4 - 2020-04-14 72 | 73 | - add new feature: tooltips 74 | - :fire: remove unused fontawesome 75 | - update documentation 76 | 77 | ## v0.2.3 - 2020-03-31 78 | 79 | - add new chart: polar area chart 80 | - add new feature: reload button 81 | - update documentation 82 | 83 | ## v0.2.2 - 2020-03-27 84 | 85 | - add default button filter metrics. available for: 86 | - doughnut 87 | - pie 88 | - bar 89 | - stacked 90 | - line 91 | - area 92 | - update documentation for filter metrics button 93 | 94 | ## v0.2.1 - 2020-03-25 95 | 96 | - new feature: button filter metrics. available for: 97 | - doughnut 98 | - pie 99 | - bar 100 | - stacked 101 | - line 102 | - area 103 | - add documentation for filter metrics button 104 | - update documentation 105 | 106 | ## v0.2.0 - 2020-03-18 107 | 108 | - major update: add new feature for clickable chart with sweetalert2 109 | - doughnut 110 | - pie 111 | - bar 112 | - stacked 113 | - line 114 | - area 115 | - add new feature: go to external link 116 | - add documentation for external link button 117 | - add documentation for clickable chart with sweetalert2 118 | - update documentation 119 | 120 | ## v0.1.5 - 2020-03-11 121 | 122 | - add workaround for change border color for circle chart 123 | - update documentation 124 | 125 | ## v0.1.4 - 2020-02-21 126 | 127 | - add new feature: join table 128 | - change documentation url if any error notification 129 | - add multiple condition in `case when` for laravel model 130 | - update documentation 131 | 132 | ## v0.1.3 - 2020-02-25 133 | 134 | - fixing bug related pie and doughnut chart with API error 135 | 136 | ## v0.1.2 - 2020-02-21 137 | 138 | - add layout configuration 139 | - update documentation 140 | 141 | ## v0.1.1 - 2020-02-20 142 | 143 | - major update: change options behavior 144 | - add legend configuration 145 | - set legend 146 | - hide legend 147 | - update documentation 148 | 149 | ## v0.0.17 - 2020-02-06 150 | 151 | - add scatter-chart feature 152 | - update documentation 153 | 154 | ## v0.0.16 - 2020-01-13 155 | 156 | - add refresh button feature 157 | - update documentation 158 | 159 | ## v0.0.15 - 2020-01-11 160 | 161 | - fix bug for not showing label when hover to stacked chart 162 | 163 | ## v0.0.14 - 2020-01-09 164 | 165 | - add daily view feature 166 | - move documentation to [github page](https://coroo.github.io/chart-js-integration/) 167 | 168 | ## v0.0.13 - 2020-01-06 169 | 170 | - fix weekly base view bug in v0.0.12 release 171 | - add start week option 172 | - update documentation 173 | 174 | ## v0.0.12 - 2020-01-02 175 | 176 | - add weekly base view 177 | - update documentation 178 | 179 | ## v0.0.11 - 2019-12-30 180 | 181 | - add doughnut chart feature 182 | - add pie chart feature 183 | - update documentation 184 | 185 | ## v0.0.10 - 2019-12-27 186 | 187 | - fix issue for bar chart with data after v0.0.9 release 188 | - remove unlisted console log 189 | 190 | ## v0.0.9 - 2019-12-26 191 | 192 | - add line chart feature 193 | - add area chart feature 194 | - update documentation 195 | 196 | ## v0.0.8 - 2019-12-26 197 | 198 | - fix issue style 199 | - https://github.com/coroo/chart-js-integration/issues/5 200 | 201 | ## v0.0.6 - 2019-12-19 202 | 203 | - fix bug for v0.0.5 release 204 | - fix sum calculation for total not changed 205 | - add number format in chart using M for million and K for thousand. 206 | 207 | ## v0.0.5 - 2019-12-19 208 | 209 | - add sum calculation instead of count 210 | - update documentation for chart-js-integration with new feature 211 | - fix bug for v0.0.4 release 212 | - filter data error for IS NULL and IS NOT NULL 213 | 214 | ## v0.0.4 - 2019-12-18 215 | 216 | - add filter data feature 217 | - add latest month feature 218 | - add custom chart color 219 | - hide total feature 220 | - update documentation for chart-js-integration with new feature 221 | 222 | ## v0.0.3 - 2019-12-18 223 | 224 | - update documentation for chart-js-integration with model 225 | - add custom column calculation 226 | 227 | ## v0.0.2 - 2019-12-18 228 | 229 | - add chart js integration with model 230 | 231 | ## v0.0.1 - 2019-12-13 232 | 233 | - initial release 234 | - chart-js-integration with custom data 235 | - bar chart feature 236 | - stacked chart feature 237 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | It's great that you're looking to involve others in your open source project! To encourage collaboration and contributions, you can include a section in your README that invites people to join and contribute. Here's a suggested message: 26 | 27 | ## How to Contribute 28 | 29 | 1. **Fork the repository:** Start by forking this repository to your own GitHub account. 30 | 2. **Clone the repository:** Clone the forked repository to your local machine using `git clone`. 31 | 3. **Create a new branch:** Before making any changes, create a new branch for your feature, bug fix, or enhancement. 32 | 4. **Make your changes:** Work on your contribution in the newly created branch. Be sure to follow the project's coding guidelines. 33 | 5. **Test your changes:** Run tests if available and ensure that your changes don't introduce new issues. 34 | 6. **Submit a pull request:** Once you're satisfied with your changes, submit a pull request from your branch to this repository. 35 | 7. **Join the discussion:** Participate in the pull request discussion, addressing any feedback or questions. 36 | 37 | ## What Can I Contribute? 38 | 39 | - **Code:** Implement new features, fix bugs, improve performance, or refactor code. 40 | - **Documentation:** Enhance the project's documentation, including adding usage examples or clarifying explanations. 41 | - **Design:** Improve the user interface, create icons, or suggest visual enhancements. 42 | - **Testing:** Write unit tests, integration tests, or provide test coverage. 43 | - **Ideas:** Share your thoughts and ideas in the issues section. We're open to new concepts and improvements! 44 | 45 | ### Getting Help 46 | 47 | If you need assistance, have questions, or want to discuss your contribution ideas, don't hesitate to open an issue or reach out to us. 48 | 49 | **Happy coding**! 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Coroo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nova ChartJS - Laravel Nova Package 2 | 3 | A Laravel Nova Dashboard with Chart JS | See [:blue_book:Documentation Page](https://coroo.github.io/nova-chartjs/) 4 | 5 | > [!NOTE] 6 | > 👋 Welcome to Nova-ChartJS! We believe that great software is built through collaboration, and we invite you to be a part of it. Whether you're a developer, designer, tester, or just someone passionate about the project, there are many ways to contribute. 7 | > 8 | > [Click here to contribute!](https://github.com/coroo/nova-chartjs/blob/master/CONTRIBUTING.md#how-to-contribute) 9 | 10 | ![Chart JS Integration in Action](https://raw.githubusercontent.com/coroo/chart-js-integration/gh-pages/assets/img/chart-js-integration.gif) 11 | 12 | ![Continues Integration](https://github.com/coroo/nova-chartjs/workflows/ci/badge.svg?branch=master) 13 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/coroowicaksono/chart-js-integration)](https://packagist.org/packages/coroowicaksono/chart-js-integration) 14 | [![Total Downloads](https://img.shields.io/packagist/dt/coroowicaksono/chart-js-integration)](https://packagist.org/packages/coroowicaksono/chart-js-integration) 15 | [![License](https://img.shields.io/github/languages/top/coroo/nova-chartjs)](https://packagist.org/packages/coroowicaksono/chart-js-integration) 16 | [![State Status](https://img.shields.io/github/deployments/coroo/nova-chartjs/github-pages)](https://packagist.org/packages/coroowicaksono/chart-js-integration) 17 | [![License](https://img.shields.io/packagist/l/coroowicaksono/chart-js-integration)](https://github.com/coroo/chart-js-integration/blob/master/LICENSE) 18 | 19 | [![Listed in Awesome ChartJS](https://camo.githubusercontent.com/13c4e50d88df7178ae1882a203ed57b641674f94/68747470733a2f2f63646e2e7261776769742e636f6d2f73696e647265736f726875732f617765736f6d652f643733303566333864323966656437386661383536353265336136336531353464643865383832392f6d656469612f62616467652e737667)](https://github.com/chartjs/awesome#integrations) 20 | [![License](https://img.shields.io/github/stars/coroo/nova-chartjs?style=social)](https://github.com/coroo/nova-chartjs/stargazers) 21 | 22 | ## Installation & Documentation 23 | 24 | :mortar_board: For better experiences, we moved documentation to : __https://coroo.github.io/nova-chartjs/__ 25 | 26 | ## ChangeLog 27 | 28 | Please see [CHANGELOG](https://github.com/coroo/chart-js-integration/blob/master/CHANGELOG.md) for more information on what has changed recently. 29 | 30 | ## License 31 | 32 | The MIT License (MIT). Please see [License File](https://github.com/coroo/chart-js-integration/blob/master/LICENSE) for more information. 33 | 34 | 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coroowicaksono/chart-js-integration", 3 | "description": "A Simple Dashboard Chart in Laravel Nova using Chart JS. Starting create your own dashboard with Chart JS Integration can save your time and help you maintain consistency across standard elements such as Bar, Stacked, Line, Area, Doughnut and Pie Chart.", 4 | "keywords": [ 5 | "laravel", 6 | "nova", 7 | "stacked chart", 8 | "chart js" 9 | ], 10 | "license": "MIT", 11 | "homepage": "https://github.com/coroo", 12 | "authors": [ 13 | { 14 | "name": "Kuncoro Wicaksono", 15 | "email": "coroo.wicaksono@gmail.com", 16 | "role": "Software Engineer" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=7.1.0", 21 | "laravel/nova": "^4.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Coroowicaksono\\ChartJsIntegration\\": "src/", 26 | "Coroowicaksono\\ChartJsIntegration\\Api\\": "src/api" 27 | } 28 | }, 29 | "extra": { 30 | "laravel": { 31 | "providers": [ 32 | "Coroowicaksono\\ChartJsIntegration\\CardServiceProvider" 33 | ] 34 | } 35 | }, 36 | "config": { 37 | "sort-packages": true 38 | }, 39 | "minimum-stability": "dev", 40 | "prefer-stable": true, 41 | "repositories": [ 42 | { 43 | "type": "composer", 44 | "url": "https://nova.laravel.com" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /dist/js/chart-js-integration.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! 2 | * Chart.js v3.7.1 3 | * https://www.chartjs.org 4 | * (c) 2022 Chart.js Contributors 5 | * Released under the MIT License 6 | */ 7 | -------------------------------------------------------------------------------- /dist/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/chart-js-integration.js": "/js/chart-js-integration.js" 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "mix", 5 | "watch": "mix watch", 6 | "watch-poll": "mix watch -- --watch-options-poll=1000", 7 | "hot": "mix watch --hot", 8 | "prod": "mix --production" 9 | }, 10 | "devDependencies": { 11 | "laravel-mix": "^6.0.43", 12 | "vue-loader": "^16.8.3", 13 | "vue-template-compiler": "^2.6.14" 14 | }, 15 | "dependencies": { 16 | "chart.js": "^3.7.0", 17 | "chartjs-plugin-datalabels": "^2.0.0", 18 | "serialize-javascript": "^4.0.0", 19 | "sweetalert2": "^9.17.1", 20 | "vue": "^3.2.31", 21 | "vue-chartjs": "^4.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coroo/nova-chartjs/49627b5646eb08d1411c25f2be7b9e510fb8175c/resources/.DS_Store -------------------------------------------------------------------------------- /resources/js/bar-chart.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /resources/js/chart-js-integration.js: -------------------------------------------------------------------------------- 1 | import ChartDataLabels from 'chartjs-plugin-datalabels'; 2 | import { Chart, registerables } from 'chart.js'; 3 | 4 | import StackedChart from './components/StackedChart' 5 | import BarChart from './components/BarChart' 6 | import LineChart from './components/StripeChart' 7 | import DoughnutChart from './components/DoughnutChart' 8 | import PieChart from './components/PieChart' 9 | import PolarAreaChart from './components/PolarAreaChart' 10 | import ScatterChart from './components/ScatterChart' 11 | 12 | Nova.booting((Vue) => { 13 | const textColor = getComputedStyle(document.documentElement) 14 | .getPropertyValue('--colors-gray-400'); 15 | 16 | Chart.unregister(ChartDataLabels); 17 | Chart.register(...registerables); 18 | Chart.defaults.color = `rgba(${textColor}, 1)`; 19 | 20 | Vue.component('stacked-chart', StackedChart); 21 | Vue.component('bar-chart', BarChart); 22 | Vue.component('stripe-chart', LineChart); 23 | Vue.component('doughnut-chart', DoughnutChart); 24 | Vue.component('pie-chart', PieChart); 25 | Vue.component('polar-area-chart', PolarAreaChart); 26 | Vue.component('scatter-chart', ScatterChart); 27 | }) 28 | 29 | -------------------------------------------------------------------------------- /resources/js/components/BarChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 291 | -------------------------------------------------------------------------------- /resources/js/components/DoughnutChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 297 | -------------------------------------------------------------------------------- /resources/js/components/Icons/IconExternalLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /resources/js/components/Icons/IconRefresh.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /resources/js/components/PieChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 297 | -------------------------------------------------------------------------------- /resources/js/components/PolarAreaChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 296 | -------------------------------------------------------------------------------- /resources/js/components/ScatterChart.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 168 | -------------------------------------------------------------------------------- /resources/js/components/StackedChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 293 | -------------------------------------------------------------------------------- /resources/js/components/StripeChart.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 293 | -------------------------------------------------------------------------------- /resources/js/doughnut-chart.vue: -------------------------------------------------------------------------------- 1 | 9 | 24 | -------------------------------------------------------------------------------- /resources/js/mixins/HandlesDataLabelsPlugin.js: -------------------------------------------------------------------------------- 1 | import ChartDataLabels from 'chartjs-plugin-datalabels'; 2 | import { Chart } from 'chart.js'; 3 | 4 | export default { 5 | props: { 6 | chartData: { 7 | type: Object, 8 | required: true 9 | }, 10 | options:{ 11 | type: Object, 12 | required: true 13 | }, 14 | }, 15 | data() { 16 | return { 17 | plugins: [], 18 | } 19 | }, 20 | watch: { 21 | chartData () { 22 | if(this.options.plugins !== undefined){ 23 | if(this.options.plugins.datalabels !== undefined){ 24 | if(this.options.plugins.datalabels !== false){ 25 | this.plugins.push(ChartDataLabels); 26 | } 27 | } 28 | } 29 | }, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/js/pie-chart.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /resources/js/polar-area-chart.vue: -------------------------------------------------------------------------------- 1 | 9 | 24 | -------------------------------------------------------------------------------- /resources/js/scatter-chart.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | -------------------------------------------------------------------------------- /resources/js/stripe-chart.vue: -------------------------------------------------------------------------------- 1 | 9 | 24 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | $data) { 35 | if (isset($data['backgroundColor'])) { 36 | if (empty($data['borderColor'])) { 37 | $series[$key]['borderColor'] = $this->adjustBrightness($data['backgroundColor'], '-40'); 38 | } 39 | } 40 | } 41 | return $this->withMeta(['series' => $series]); 42 | } 43 | 44 | public function type(string $type): self 45 | { 46 | return $this->withMeta(['type' => $type]); 47 | } 48 | 49 | public function options(array $options): self 50 | { 51 | return $this->withMeta(['options' => (object) $options]); 52 | } 53 | 54 | public function animations(array $animations): self 55 | { 56 | return $this->withMeta(['animations' => $animations]); 57 | } 58 | 59 | public function title(string $title): self 60 | { 61 | return $this->withMeta(['title' => $title]); 62 | } 63 | 64 | public function model(string $model): self 65 | { 66 | return $this->withMeta(['model' => $model]); 67 | } 68 | 69 | public function col_xaxis(string $col_xaxis): self 70 | { 71 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 72 | } 73 | 74 | public function uriKey(string $uriKey) 75 | { 76 | return $this->withMeta(['uriKey' => $uriKey]); 77 | } 78 | 79 | private function adjustBrightness($hex, $steps) 80 | { 81 | // Steps should be between -255 and 255. Negative = darker, positive = lighter 82 | $steps = max(-255, min(255, $steps)); 83 | 84 | // Normalize into a six character long hex string 85 | $hex = str_replace('#', '', $hex); 86 | if (strlen($hex) == 3) { 87 | $hex = str_repeat(substr($hex, 0, 1), 2) . str_repeat(substr($hex, 1, 1), 2) . str_repeat(substr($hex, 2, 1), 2); 88 | } 89 | 90 | // Split into three parts: R, G and B 91 | $color_parts = str_split($hex, 2); 92 | $return = '#'; 93 | 94 | foreach ($color_parts as $color) { 95 | $color = hexdec($color); // Convert to decimal 96 | $color = max(0, min(255, $color + $steps)); // Adjust color 97 | $return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code 98 | } 99 | 100 | return $return; 101 | } 102 | 103 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 104 | { 105 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/BarChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 35 | } 36 | 37 | public function type(string $type): self 38 | { 39 | return $this->withMeta(['type' => $type]); 40 | } 41 | 42 | public function options(array $options): self 43 | { 44 | return $this->withMeta(['options' => (object) $options]); 45 | } 46 | 47 | public function animations(array $animations): self 48 | { 49 | return $this->withMeta(['animations' => $animations]); 50 | } 51 | 52 | public function title(string $title): self 53 | { 54 | return $this->withMeta(['title' => $title]); 55 | } 56 | 57 | public function model(string $model): self 58 | { 59 | return $this->withMeta(['model' => $model]); 60 | } 61 | 62 | public function col_xaxis(string $col_xaxis): self 63 | { 64 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 65 | } 66 | 67 | public function uriKey(string $uriKey) 68 | { 69 | return $this->withMeta(['uriKey' => $uriKey]); 70 | } 71 | 72 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 73 | { 74 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/CardServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->booted(function () { 20 | $this->routes(); 21 | }); 22 | 23 | Nova::serving(function (ServingNova $event) { 24 | Nova::script('nova-apex-chart', __DIR__ . '/../dist/js/chart-js-integration.js'); 25 | }); 26 | } 27 | 28 | /** 29 | * Register the card's routes. 30 | * 31 | * @return void 32 | */ 33 | protected function routes() 34 | { 35 | Route::middleware(['nova']) 36 | ->prefix('/nova-vendor/coroowicaksono/check-data') 37 | ->group(__DIR__.'/../routes/api.php'); 38 | } 39 | 40 | /** 41 | * Register any application services. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/DoughnutChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 35 | } 36 | 37 | public function type(string $type): self 38 | { 39 | return $this->withMeta(['type' => $type]); 40 | } 41 | 42 | public function options(array $options): self 43 | { 44 | return $this->withMeta(['options' => (object) $options]); 45 | } 46 | 47 | public function animations(array $animations): self 48 | { 49 | return $this->withMeta(['animations' => $animations]); 50 | } 51 | 52 | public function title(string $title): self 53 | { 54 | return $this->withMeta(['title' => $title]); 55 | } 56 | 57 | public function model(string $model): self 58 | { 59 | return $this->withMeta(['model' => $model]); 60 | } 61 | 62 | public function col_xaxis(string $col_xaxis): self 63 | { 64 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 65 | } 66 | 67 | public function uriKey(string $uriKey) 68 | { 69 | return $this->withMeta(['uriKey' => $uriKey]); 70 | } 71 | 72 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 73 | { 74 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/LineChart.php: -------------------------------------------------------------------------------- 1 | $data) { 35 | $series[$key]['fill'] = false; 36 | } 37 | return $this->withMeta(['series' => $series]); 38 | } 39 | 40 | public function type(string $type): self 41 | { 42 | return $this->withMeta(['type' => $type]); 43 | } 44 | 45 | public function options(array $options): self 46 | { 47 | return $this->withMeta(['options' => (object) $options]); 48 | } 49 | 50 | public function animations(array $animations): self 51 | { 52 | return $this->withMeta(['animations' => $animations]); 53 | } 54 | 55 | public function title(string $title): self 56 | { 57 | return $this->withMeta(['title' => $title]); 58 | } 59 | 60 | public function model(string $model): self 61 | { 62 | return $this->withMeta(['model' => $model]); 63 | } 64 | 65 | public function col_xaxis(string $col_xaxis): self 66 | { 67 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 68 | } 69 | 70 | public function uriKey(string $uriKey) 71 | { 72 | return $this->withMeta(['uriKey' => $uriKey]); 73 | } 74 | 75 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 76 | { 77 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/PieChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 35 | } 36 | 37 | public function type(string $type): self 38 | { 39 | return $this->withMeta(['type' => $type]); 40 | } 41 | 42 | public function options(array $options): self 43 | { 44 | return $this->withMeta(['options' => (object) $options]); 45 | } 46 | 47 | public function animations(array $animations): self 48 | { 49 | return $this->withMeta(['animations' => $animations]); 50 | } 51 | 52 | public function title(string $title): self 53 | { 54 | return $this->withMeta(['title' => $title]); 55 | } 56 | 57 | public function model(string $model): self 58 | { 59 | return $this->withMeta(['model' => $model]); 60 | } 61 | 62 | public function col_xaxis(string $col_xaxis): self 63 | { 64 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 65 | } 66 | 67 | public function uriKey(string $uriKey) 68 | { 69 | return $this->withMeta(['uriKey' => $uriKey]); 70 | } 71 | 72 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 73 | { 74 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/PolarAreaChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 35 | } 36 | 37 | public function type(string $type): self 38 | { 39 | return $this->withMeta(['type' => $type]); 40 | } 41 | 42 | public function options(array $options): self 43 | { 44 | return $this->withMeta(['options' => (object) $options]); 45 | } 46 | 47 | public function animations(array $animations): self 48 | { 49 | return $this->withMeta(['animations' => $animations]); 50 | } 51 | 52 | public function title(string $title): self 53 | { 54 | return $this->withMeta(['title' => $title]); 55 | } 56 | 57 | public function model(string $model): self 58 | { 59 | return $this->withMeta(['model' => $model]); 60 | } 61 | 62 | public function col_xaxis(string $col_xaxis): self 63 | { 64 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 65 | } 66 | 67 | public function uriKey(string $uriKey) 68 | { 69 | return $this->withMeta(['uriKey' => $uriKey]); 70 | } 71 | 72 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 73 | { 74 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ScatterChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 35 | } 36 | 37 | public function type(string $type): self 38 | { 39 | return $this->withMeta(['type' => $type]); 40 | } 41 | 42 | public function options(array $options): self 43 | { 44 | return $this->withMeta(['options' => (object) $options]); 45 | } 46 | 47 | public function animations(array $animations): self 48 | { 49 | return $this->withMeta(['animations' => $animations]); 50 | } 51 | 52 | public function title(string $title): self 53 | { 54 | return $this->withMeta(['title' => $title]); 55 | } 56 | 57 | public function model(string $model): self 58 | { 59 | return $this->withMeta(['model' => $model]); 60 | } 61 | 62 | public function col_xaxis(string $col_xaxis): self 63 | { 64 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 65 | } 66 | 67 | public function uriKey(string $uriKey) 68 | { 69 | return $this->withMeta(['uriKey' => $uriKey]); 70 | } 71 | 72 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 73 | { 74 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/StackedChart.php: -------------------------------------------------------------------------------- 1 | withMeta(['series' => $series]); 36 | } 37 | 38 | public function type(string $type): self 39 | { 40 | return $this->withMeta(['type' => $type]); 41 | } 42 | 43 | public function options(array $options): self 44 | { 45 | return $this->withMeta(['options' => (object) $options]); 46 | } 47 | 48 | public function animations(array $animations): self 49 | { 50 | return $this->withMeta(['animations' => $animations]); 51 | } 52 | 53 | public function title(string $title): self 54 | { 55 | return $this->withMeta(['title' => $title, 'uriKey' => Str::slug($title)]); 56 | } 57 | 58 | public function model(string $model): self 59 | { 60 | return $this->withMeta(['model' => $model]); 61 | } 62 | 63 | public function col_xaxis(string $col_xaxis): self 64 | { 65 | return $this->withMeta(['col_xaxis' => $col_xaxis]); 66 | } 67 | 68 | public function uriKey(string $uriKey) 69 | { 70 | return $this->withMeta(['uriKey' => $uriKey]); 71 | } 72 | 73 | public function join(string $joinTable, string $joinColumnFirst, string $joinEqual, string $joinColumnSecond): self 74 | { 75 | return $this->withMeta(['join' => ['joinTable' => $joinTable, 'joinColumnFirst' => $joinColumnFirst, 'joinEqual' => $joinEqual, 'joinColumnSecond' => $joinColumnSecond]]); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/api/ThrowError.php: -------------------------------------------------------------------------------- 1 | input('model')) { 23 | $request->merge(['model' => urldecode($request->input('model'))]); 24 | } 25 | 26 | $options = is_string($request->options) ? json_decode($request->options, true) : $request->input('options', []); 27 | $join = is_string($request->join) ? json_decode($request->join, true) : $request->input('join', []); 28 | 29 | $advanceFilterSelected = $options['advanceFilterSelected'] ?? false; 30 | $dataForLast = $options['latestData'] ?? 3; 31 | $calculation = $options['sum'] ?? 1; 32 | $request->validate(['model' => ['bail', 'required', 'min:1', 'string']]); 33 | $model = $request->input('model'); 34 | $modelInstance = new $model; 35 | $tableName = $modelInstance->getConnection()->getTablePrefix() . $modelInstance->getTable(); 36 | $xAxisColumn = $request->input('col_xaxis') ?? $tableName.'.created_at'; 37 | $cacheKey = hash('md4', $model . (int)(bool)$request->input('expires')); 38 | $dataSet = Cache::get($cacheKey); 39 | if (!$dataSet) { 40 | $labelList = []; 41 | $xAxis = []; 42 | $yAxis = []; 43 | $seriesSql = ""; 44 | $defaultColor = array("#ffcc5c","#91e8e1","#ff6f69","#88d8b0","#b088d8","#d8b088", "#88b0d8", "#6f69ff","#7cb5ec","#434348","#90ed7d","#8085e9","#f7a35c","#f15c80","#e4d354","#2b908f","#f45b5b","#91e8e1","#E27D60","#85DCB","#E8A87C","#C38D9E","#41B3A3","#67c4a7","#992667","#ff4040","#ff7373","#d2d2d2"); 45 | if(isset($request->series)){ 46 | foreach($request->series as $seriesKey => $serieslist){ 47 | $seriesData = (object) $serieslist; 48 | $filter = (object) $seriesData->filter; 49 | $labelList[$seriesKey] = $seriesData->label; 50 | if(empty($filter->value)&&isset($filter->operator)&&($filter->operator=='IS NULL' || $filter->operator=='IS NOT NULL')) { 51 | $seriesSql .= ", SUM(CASE WHEN ".$filter->key." ".$filter->operator." then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 52 | } else if(empty($filter->value)){ 53 | $seriesSql .= ", SUM(CASE WHEN "; 54 | $countFilter = count((array) $filter); 55 | foreach($filter as $keyFilter => $listFilter){ 56 | $listFilter = (object) $listFilter; 57 | $seriesSql .= " ".$listFilter->key." ".($listFilter->operator ?? "=")." '".$listFilter->value."' "; 58 | $seriesSql .= $countFilter-1 != $keyFilter ? " AND " : ""; 59 | } 60 | $seriesSql .= "then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 61 | } else { 62 | $seriesSql .= ", SUM(CASE WHEN ".$filter->key." ".($filter->operator ?? "=")." '".$filter->value."' then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 63 | } 64 | } 65 | } 66 | if(count($join)){ 67 | $joinInformation = $join; 68 | $query = $model::selectRaw('SUM('.$calculation.') counted'.$seriesSql) 69 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 70 | } else { 71 | $query = $model::selectRaw('SUM('.$calculation.') counted'.$seriesSql); 72 | } 73 | 74 | if(is_numeric($advanceFilterSelected)){ 75 | $query->where($xAxisColumn, '>=', Carbon::now()->subDays($advanceFilterSelected)); 76 | } 77 | else if($advanceFilterSelected=='YTD'){ 78 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfYear()->startOfDay(), Carbon::now()]); 79 | } 80 | else if($advanceFilterSelected=='QTD'){ 81 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfQuarter()->startOfDay(), Carbon::now()]); 82 | } 83 | else if($advanceFilterSelected=='MTD'){ 84 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfMonth()->startOfDay(), Carbon::now()]); 85 | } 86 | else if($dataForLast != '*') { 87 | $query->where($xAxisColumn, '>=', Carbon::now()->firstOfMonth()->subMonth($dataForLast-1)); 88 | } 89 | 90 | if($options['queryFilter'] ?? false){ 91 | $queryFilter = $options['queryFilter']; 92 | foreach($queryFilter as $qF){ 93 | if(isset($qF['value']) && !is_array($qF['value'])){ 94 | if(isset($qF['operator'])){ 95 | $query->where($qF['key'], $qF['operator'], $qF['value']); 96 | } else { 97 | $query->where($qF['key'], $qF['value']); 98 | } 99 | } else { 100 | if($qF['operator']=='IS NULL'){ 101 | $query->whereNull($qF['key']); 102 | } else if($qF['operator']=='IS NOT NULL'){ 103 | $query->whereNotNull($qF['key']); 104 | } else if($qF['operator']=='IN'){ 105 | $query->whereIn($qF['key'], $qF['value']); 106 | } else if($qF['operator']=='NOT IN'){ 107 | $query->whereIn($qF['key'], $qF['value']); 108 | } else if($qF['operator']=='BETWEEN') { 109 | $query->whereBetween($qF['key'], $qF['value']); 110 | } else if($qF['operator']=='NOT BETWEEN') { 111 | $query->whereNotBetween($qF['key'], $qF['value']); 112 | } 113 | } 114 | } 115 | } 116 | $dataSet = $query->get(); 117 | $xAxis = collect($labelList); 118 | if(isset($request->series)){ 119 | $countKey = 0; 120 | foreach($request->series as $sKey => $sData){ 121 | $dataSeries = (object) $sData; 122 | foreach($dataSet as $dataDetail){ 123 | $yAxis[0]['backgroundColor'][$sKey] = $dataSeries->backgroundColor ?? $defaultColor[$sKey]; 124 | $yAxis[0]['borderColor'][$sKey] = $dataSeries->borderColor ?? '#FFF'; 125 | $yAxis[0]['data'][$sKey] = $dataDetail[$dataSeries->label]; 126 | } 127 | $countKey++; 128 | } 129 | } else { 130 | throw new ThrowError('You need to have at least 1 series parameters for this type of chart.
Check documentation: https://github.com/coroo/nova-chartjs'); 131 | } 132 | if ($request->input('expires')) { 133 | Cache::put($cacheKey, $dataSet, Carbon::parse($request->input('expires'))); 134 | } 135 | } 136 | return response()->json( 137 | ['dataset' => [ 138 | 'xAxis' => $xAxis, 139 | 'yAxis' => $yAxis 140 | ] 141 | ]); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/api/TotalRecordsController.php: -------------------------------------------------------------------------------- 1 | input('model')) { 24 | $request->merge(['model' => urldecode($request->input('model'))]); 25 | } 26 | 27 | $options = is_string($request->options) ? json_decode($request->options, true) : $request->input('options', []); 28 | $join = is_string($request->join) ? json_decode($request->join, true) : $request->input('join', []); 29 | 30 | $showTotal = (bool) json_decode($options['showTotal'] ?? true); 31 | $totalLabel = $options['totalLabel'] ?? 'Total'; 32 | $chartType = $request->type ?? 'bar'; 33 | $advanceFilterSelected = $options['advanceFilterSelected'] ?? false; 34 | $dataForLast = $options['latestData'] ?? 3; 35 | $unitOfMeasurement = $options['uom'] ?? 'month'; 36 | $startWeek = $options['startWeek'] ?? '1'; 37 | if(!in_array($unitOfMeasurement, ['day', 'week', 'month', 'hour'])){ 38 | throw new ThrowError('UOM not defined correctly.
Check documentation: https://github.com/coroo/nova-chartjs'); 39 | } 40 | $calculation = $options['sum'] ?? 1; 41 | $request->validate(['model' => ['bail', 'required', 'min:1', 'string']]); 42 | $model = $request->input('model'); 43 | $modelInstance = new $model; 44 | $connectionName = $modelInstance->getConnection()->getDriverName(); 45 | $tableName = $modelInstance->getConnection()->getTablePrefix() . $modelInstance->getTable(); 46 | $xAxisColumn = $request->input('col_xaxis') ?? $tableName.'.created_at'; 47 | $cacheKey = hash('md4', $model . (int)(bool)$request->input('expires')); 48 | $dataSet = Cache::get($cacheKey); 49 | if (!$dataSet) { 50 | $labelList = []; 51 | $xAxis = []; 52 | $yAxis = []; 53 | $seriesSql = ""; 54 | $brandColor = config('nova.brand.colors.500') ?: '14,165,233'; 55 | $defaultColor = array("rgba($brandColor, 1)", "#ffcc5c","#91e8e1","#ff6f69","#88d8b0","#b088d8","#d8b088", "#88b0d8", "#6f69ff","#7cb5ec","#434348","#90ed7d","#8085e9","#f7a35c","#f15c80","#e4d354","#2b908f","#f45b5b","#91e8e1","#E27D60","#85DCB","#E8A87C","#C38D9E","#41B3A3","#67c4a7","#992667","#ff4040","#ff7373","#d2d2d2"); 56 | if(isset($request->series)){ 57 | foreach($request->series as $seriesKey => $serieslist){ 58 | $seriesData = (object) $serieslist; 59 | $filter = (object) $seriesData->filter; 60 | $labelList[$seriesKey] = $seriesData->label; 61 | if(empty($filter->value)&&isset($filter->operator)&&($filter->operator=='IS NULL' || $filter->operator=='IS NOT NULL')) { 62 | $seriesSql .= ", SUM(CASE WHEN ".$filter->key." ".$filter->operator." then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 63 | } else if(empty($filter->value)){ 64 | $seriesSql .= ", SUM(CASE WHEN "; 65 | $countFilter = count((array) $filter); 66 | foreach($filter as $keyFilter => $listFilter){ 67 | $listFilter = (object) $listFilter; 68 | $seriesSql .= " ".$listFilter->key." ".($listFilter->operator ?? "=")." '".$listFilter->value."' "; 69 | $seriesSql .= $countFilter-1 != $keyFilter ? " AND " : ""; 70 | } 71 | $seriesSql .= "then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 72 | } else { 73 | $seriesSql .= ", SUM(CASE WHEN ".$filter->key." ".($filter->operator ?? "=")." '".$filter->value."' then ".$calculation." else 0 end) as \"".$labelList[$seriesKey]."\""; 74 | } 75 | } 76 | } 77 | if($unitOfMeasurement=='day'){ 78 | if(count($join)){ 79 | $joinInformation = $join; 80 | $query = $model::selectRaw('DATE('.$xAxisColumn.') AS cat, DATE('.$xAxisColumn.') AS catorder, sum('.$calculation.') counted'.$seriesSql) 81 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 82 | } else { 83 | $query = $model::selectRaw('DATE('.$xAxisColumn.') AS cat, DATE('.$xAxisColumn.') AS catorder, sum('.$calculation.') counted'.$seriesSql); 84 | } 85 | 86 | if(is_numeric($advanceFilterSelected)){ 87 | $query->where($xAxisColumn, '>=', Carbon::now()->subDays($advanceFilterSelected)); 88 | } 89 | else if($advanceFilterSelected=='YTD'){ 90 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfYear()->startOfDay(), Carbon::now()]); 91 | } 92 | else if($advanceFilterSelected=='QTD'){ 93 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfQuarter()->startOfDay(), Carbon::now()]); 94 | } 95 | else if($advanceFilterSelected=='MTD'){ 96 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfMonth()->startOfDay(), Carbon::now()]); 97 | } 98 | else if($dataForLast != '*') { 99 | $query->where($xAxisColumn, '>=', Carbon::now()->subDay($dataForLast+1)); 100 | } 101 | $query->groupBy('catorder', 'cat') 102 | ->orderBy('catorder', 'asc'); 103 | } else if($unitOfMeasurement=='week'){ 104 | if(count($join)){ 105 | $joinInformation = $join; 106 | if($connectionName == 'pgsql'){ 107 | $query = $model::selectRaw("to_char(DATE_TRUNC('week', ".$xAxisColumn."), 'YYYYWW') AS cat, to_char(DATE_TRUNC('week', ".$xAxisColumn."), 'YYYYWW') AS catorder, sum(".$calculation.") counted".$seriesSql) 108 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 109 | } else { 110 | $query = $model::selectRaw('YEARWEEK('.$xAxisColumn.', '.$startWeek.') AS cat, YEARWEEK('.$xAxisColumn.', '.$startWeek.') AS catorder, sum('.$calculation.') counted'.$seriesSql) 111 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 112 | } 113 | } else { 114 | if($connectionName == 'pgsql'){ 115 | $query = $model::selectRaw("to_char(DATE_TRUNC('week', ".$xAxisColumn."), 'YYYYWW') AS cat, to_char(DATE_TRUNC('week', ".$xAxisColumn."), 'YYYYWW') AS catorder, sum(".$calculation.") counted".$seriesSql); 116 | } else { 117 | $query = $model::selectRaw('YEARWEEK('.$xAxisColumn.', '.$startWeek.') AS cat, YEARWEEK('.$xAxisColumn.', '.$startWeek.') AS catorder, sum('.$calculation.') counted'.$seriesSql); 118 | } 119 | } 120 | 121 | if(is_numeric($advanceFilterSelected)){ 122 | $query->where($xAxisColumn, '>=', Carbon::now()->subDays($advanceFilterSelected)); 123 | } 124 | else if($advanceFilterSelected=='YTD'){ 125 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfYear()->startOfDay(), Carbon::now()]); 126 | } 127 | else if($advanceFilterSelected=='QTD'){ 128 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfQuarter()->startOfDay(), Carbon::now()]); 129 | } 130 | else if($advanceFilterSelected=='MTD'){ 131 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfMonth()->startOfDay(), Carbon::now()]); 132 | } 133 | else if($dataForLast != '*') { 134 | $query->where($xAxisColumn, '>=', Carbon::now()->startOfWeek()->subWeek($dataForLast)); 135 | } 136 | $query->groupBy('catorder', 'cat') 137 | ->orderBy('catorder', 'asc'); 138 | } else if($unitOfMeasurement=='hour'){ 139 | if(count($join)){ 140 | $joinInformation = $join; 141 | $query = $model::selectRaw('HOUR('.$xAxisColumn.') AS cat, HOUR('.$xAxisColumn.') AS catorder, sum('.$calculation.') counted'.$seriesSql) 142 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 143 | } else { 144 | $query = $model::selectRaw('HOUR('.$xAxisColumn.') AS cat, HOUR('.$xAxisColumn.') AS catorder, sum('.$calculation.') counted'.$seriesSql); 145 | } 146 | 147 | if(is_numeric($advanceFilterSelected)){ 148 | $query->where($xAxisColumn, '>=', Carbon::now()->subDays($advanceFilterSelected)); 149 | } 150 | else if($advanceFilterSelected=='YTD'){ 151 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfYear()->startOfDay(), Carbon::now()]); 152 | } 153 | else if($advanceFilterSelected=='QTD'){ 154 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfQuarter()->startOfDay(), Carbon::now()]); 155 | } 156 | else if($advanceFilterSelected=='MTD'){ 157 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfMonth()->startOfDay(), Carbon::now()]); 158 | } 159 | else if($dataForLast != '*') { 160 | $query->where($xAxisColumn, '>=', Carbon::now()->startOfDay()); 161 | } 162 | $query->groupBy('catorder', 'cat') 163 | ->orderBy('catorder', 'asc'); 164 | 165 | } else { 166 | if(count($join)){ 167 | $joinInformation = $join; 168 | if($connectionName == 'pgsql'){ 169 | $query = $model::selectRaw("to_char(".$xAxisColumn.", 'Mon YYYY') AS cat, to_char(".$xAxisColumn.", 'YYYY-MM') AS catorder, sum(".$calculation.") counted".$seriesSql) 170 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 171 | } else { 172 | $query = $model::selectRaw('DATE_FORMAT('.$xAxisColumn.', "%b %Y") AS cat, DATE_FORMAT('.$xAxisColumn.', "%Y-%m") AS catorder, sum('.$calculation.') counted'.$seriesSql) 173 | ->join($joinInformation['joinTable'], $joinInformation['joinColumnFirst'], $joinInformation['joinEqual'], $joinInformation['joinColumnSecond']); 174 | } 175 | } else { 176 | if($connectionName == 'pgsql'){ 177 | $query = $model::selectRaw("to_char(".$xAxisColumn.", 'Mon YYYY') AS cat, to_char(".$xAxisColumn.", 'YYYY-MM') AS catorder, sum(".$calculation.") counted".$seriesSql); 178 | } else { 179 | $query = $model::selectRaw('DATE_FORMAT('.$xAxisColumn.', "%b %Y") AS cat, DATE_FORMAT('.$xAxisColumn.', "%Y-%m") AS catorder, sum('.$calculation.') counted'.$seriesSql); 180 | } 181 | } 182 | 183 | if(is_numeric($advanceFilterSelected)){ 184 | $query->where($xAxisColumn, '>=', Carbon::now()->subDays($advanceFilterSelected)); 185 | } 186 | else if($advanceFilterSelected=='YTD'){ 187 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfYear()->startOfDay(), Carbon::now()]); 188 | } 189 | else if($advanceFilterSelected=='QTD'){ 190 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfQuarter()->startOfDay(), Carbon::now()]); 191 | } 192 | else if($advanceFilterSelected=='MTD'){ 193 | $query->whereBetween($xAxisColumn, [Carbon::now()->firstOfMonth()->startOfDay(), Carbon::now()]); 194 | } 195 | else if($dataForLast != '*') { 196 | $query->where($xAxisColumn, '>=', Carbon::now()->firstOfMonth()->subMonth($dataForLast-1)); 197 | } 198 | $query->groupBy('catorder', 'cat') 199 | ->orderBy('catorder', 'asc'); 200 | } 201 | 202 | if($options['queryFilter'] ?? false){ 203 | $queryFilter = $options['queryFilter']; 204 | foreach($queryFilter as $qF){ 205 | if(isset($qF['value']) && !is_array($qF['value'])){ 206 | if(isset($qF['operator'])){ 207 | $query->where($qF['key'], $qF['operator'], $qF['value']); 208 | } else { 209 | $query->where($qF['key'], $qF['value']); 210 | } 211 | } else { 212 | if($qF['operator']=='IS NULL'){ 213 | $query->whereNull($qF['key']); 214 | } else if($qF['operator']=='IS NOT NULL'){ 215 | $query->whereNotNull($qF['key']); 216 | } else if($qF['operator']=='IN'){ 217 | $query->whereIn($qF['key'], $qF['value']); 218 | } else if($qF['operator']=='NOT IN'){ 219 | $query->whereIn($qF['key'], $qF['value']); 220 | } else if($qF['operator']=='BETWEEN') { 221 | $query->whereBetween($qF['key'], $qF['value']); 222 | } else if($qF['operator']=='NOT BETWEEN') { 223 | $query->whereNotBetween($qF['key'], $qF['value']); 224 | } 225 | } 226 | } 227 | } 228 | $dataSet = $query->get(); 229 | $xAxis = collect($dataSet)->map(function ($item, $key) use ($unitOfMeasurement){ 230 | if($unitOfMeasurement=='week'){ 231 | $splitCat = str_split($item->only(['cat'])['cat'], 4); 232 | $cat = "W".$splitCat[1]." ".$splitCat[0]; 233 | } else { 234 | $cat = $item->only(['cat'])['cat']; 235 | } 236 | return $cat; 237 | }); 238 | if(isset($request->series)){ 239 | $countKey = 0; 240 | foreach($request->series as $sKey => $sData){ 241 | $dataSeries = (object) $sData; 242 | $filter = (object) $dataSeries->filter; 243 | $yAxis[$sKey]['label'] = $dataSeries->label; 244 | if(isset($dataSeries->fill)){ 245 | if($dataSeries->fill==false){ 246 | $yAxis[$sKey]['borderColor'] = $dataSeries->backgroundColor ?? $defaultColor[$sKey]; 247 | $yAxis[$sKey]['fill'] = false; 248 | } else { 249 | $yAxis[$sKey]['backgroundColor'] = $dataSeries->backgroundColor ?? $defaultColor[$sKey]; 250 | } 251 | } else { 252 | $yAxis[$sKey]['backgroundColor'] = $dataSeries->backgroundColor ?? $defaultColor[$sKey]; 253 | } 254 | $yAxis[$sKey]['data'] = collect($dataSet)->map(function ($item, $key) use ($dataSeries){ 255 | return $item->only([$dataSeries->label])[$dataSeries->label]; 256 | }); 257 | $countKey++; 258 | } 259 | if($showTotal){ 260 | $yAxis[$countKey] = $this->counted($dataSet, $defaultColor[$countKey], 'line', $totalLabel); 261 | } 262 | } else { 263 | $yAxis[0] = $this->counted($dataSet, $defaultColor[0], $chartType, $totalLabel); 264 | } 265 | if ($request->input('expires')) { 266 | Cache::put($cacheKey, $dataSet, Carbon::parse($request->input('expires'))); 267 | } 268 | } 269 | return response()->json( 270 | ['dataset' => [ 271 | 'xAxis' => $xAxis, 272 | 'yAxis' => $yAxis 273 | ] 274 | ]); 275 | } 276 | 277 | private function counted($dataSet, $bgColor = "#111", $type = "bar", $label = "Total") 278 | { 279 | $yAxis = [ 280 | 'type' => $type, 281 | 'label' => $label, 282 | 'data' => collect($dataSet)->map(function ($item, $key) { 283 | return $item->only(['counted'])['counted']; 284 | }) 285 | ]; 286 | if($type=="line"){ 287 | $yAxis['fill'] = false; 288 | $yAxis['borderColor'] = $bgColor; 289 | } else { 290 | $yAxis['backgroundColor'] = $bgColor; 291 | } 292 | return $yAxis; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix') 2 | 3 | mix 4 | .setPublicPath('dist') 5 | .js('resources/js/chart-js-integration.js', 'js') 6 | .vue({ version: 3 }) 7 | .webpackConfig({ 8 | externals: { 9 | vue: 'Vue', 10 | chartjs: 'Chart', 11 | }, 12 | output: { 13 | uniqueName: 'coroowicaksono/chart-js-integration', 14 | }, 15 | }) 16 | --------------------------------------------------------------------------------