pax_global_header00006660000000000000000000000064141350725770014525gustar00rootroot0000000000000052 comment=09a8f4e8ad0418cb8e13b314fac4af8b447a4b55 chartkick.js-4.1.0/000077500000000000000000000000001413507257700141055ustar00rootroot00000000000000chartkick.js-4.1.0/.editorconfig000066400000000000000000000002461413507257700165640ustar00rootroot00000000000000# editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true chartkick.js-4.1.0/.eslintrc.json000066400000000000000000000004021413507257700166750ustar00rootroot00000000000000{ "extends": "eslint:recommended", "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, "env": { "browser": true }, "rules": { "no-var": "error", "semi": ["error", "always"], "no-prototype-builtins": "off" } } chartkick.js-4.1.0/.github/000077500000000000000000000000001413507257700154455ustar00rootroot00000000000000chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001413507257700176305ustar00rootroot00000000000000chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000020031413507257700223150ustar00rootroot00000000000000--- name: Bug report about: Report a bug title: '' labels: '' assignees: '' --- **First** Search existing issues to see if it’s been reported and make sure you’re on the latest version. **Describe the bug** A clear and concise description of the bug. Include the complete backtrace for exceptions. **To reproduce** Use this code to reproduce: ```html
``` chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000002341413507257700216170ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Help url: https://stackoverflow.com/questions/tagged/chartkick about: Ask and answer questions chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000006251413507257700233600ustar00rootroot00000000000000--- name: Feature request about: Suggest a feature title: '' labels: '' assignees: '' --- **First** Search existing issues to see if it’s been discussed. **Is your feature request related to a problem? Please describe.** A clear and concise description of the problem. **Describe the solution you'd like** A clear and concise description of your idea. **Additional context** Add any other context. chartkick.js-4.1.0/.github/stale.yml000066400000000000000000000012401413507257700172750ustar00rootroot00000000000000# Number of days of inactivity before an issue becomes stale daysUntilStale: 7 # Number of days of inactivity before a stale issue is closed daysUntilClose: 7 # Issues with these labels will never be considered stale exemptLabels: - enhancement # Label to use when marking an issue as stale staleLabel: stale # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: true chartkick.js-4.1.0/.github/workflows/000077500000000000000000000000001413507257700175025ustar00rootroot00000000000000chartkick.js-4.1.0/.github/workflows/build.yml000066400000000000000000000004471413507257700213310ustar00rootroot00000000000000name: build on: [push, pull_request] jobs: build: if: "!contains(github.event.head_commit.message, '[skip ci]')" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 - run: npm install - run: npm run build - run: npm run lint chartkick.js-4.1.0/.gitignore000066400000000000000000000001171413507257700160740ustar00rootroot00000000000000node_modules/ dist/ yarn.lock examples/Chart.bundle.js *.log package-lock.json chartkick.js-4.1.0/CHANGELOG.md000066400000000000000000000155541413507257700157300ustar00rootroot00000000000000## 4.1.0 (2021-10-23) - Added support for Turbo ## 4.0.5 (2021-07-07) - Fixed x-axis type and position `library` options - Fixed `pointFormat` for Highcharts ## 4.0.4 (2021-05-01) - Fixed error with `destroy` function - Fixed error with hidden charts ## 4.0.3 (2021-04-10) - Fixed error with `background` option for downloads - Fixed null values in stacked column and bar charts ## 4.0.2 (2021-04-06) - Fixed error with time parsing ## 4.0.1 (2021-04-06) - Fixed time parsing logic ## 4.0.0 (2021-04-04) - Added support for Chart.js 3 - Added `loading` option - Added `destroyAll` function - Added `chartkick:load` event - Improved `colors` option for single-series column and bar charts - Increased hover radius for line, area, scatter, and bubble charts - Prefer `empty` over `messages: {empty: ...}` - Stopped refresh when chart is destroyed - Fixed gridline orientation for bar charts Breaking changes - Removed support for Chart.js 2 - Charts with no data show message instead of empty chart - Dates are shown in local time instead of UTC for Highcharts to be consistent with other adapters ## 3.2.1 (2020-07-23) - Added support for petabytes and exabytes - Fixed error with `xmin` and `xmax` and empty data - Fixed error with `GeoChart` with latest Google Charts release ## 3.2.0 (2019-11-09) - Fixed prototype pollution - see [#117](https://github.com/ankane/chartkick.js/issues/117) - Added `bytes` option for Chart.js - Added `precision` option - Added `round` option - Added `zeros` option ## 3.1.3 (2019-10-27) - Removed `Error Loading Chart` prefix for callback errors ## 3.1.2 (2019-10-27) - Added support for callbacks as data - Fixed `stacked` option for Highcharts area charts - Fixed error with jQuery slim - Fixed deprecation warning with Chart.js 2.9.0+ ## 3.1.1 (2019-07-15) - Fixed missing bar chart labels with Chart.js 2.8.0 ## 3.1.0 (2019-05-26) - Improved `require` so `default` is no longer needed - Added `use` function - Added `xmin` and `xmax` for Chart.js - Fixed Highcharts tooltip colors - Fixed error when passing images to options ## 3.0.2 (2019-01-03) - Added `Chartkick.setDefaultOptions` function - Added `points` and `curve` options for individual series with Chart.js - Added support for download background color - Raise an error for `refresh` option and non-URL data source ## 3.0.1 (2018-08-13) - Fixed numeric axes for Chart.js column and bar charts ## 3.0.0 (2018-08-08) - Added `code` option - Fixed `dataset` option for scatter charts Breaking changes - Removed `xtype` option - numeric axes are automatically detected - Removed `window.Chartkick = {...}` way to set config - use `Chartkick.configure` instead - Removed support for the Google Charts jsapi loader - use loader.js instead ## 2.3.6 (2018-05-31) - Added `dataset` option for Chart.js - Added `destroy` function ## 2.3.5 (2018-03-26) - Added `addAdapter` function ## 2.3.4 (2018-03-25) - Fixed module build - Fixed `Cannot read property 'config' of undefined` error - Fixed error when switching between data and no data with empty message with Chart.js ## 2.3.3 (2018-02-26) - Fixed error when minified ## 2.3.2 (2018-02-24) - Fixed export for modules - Added more `stacked` options ## 2.3.1 (2018-02-23) - Fixed issues with `thousands` option - Friendlier error messages when charting library not found - Refresh interval now updates when `refresh` option changed - Removed experimental `Chartkick.createChart` function - Stopped pushing updates to Bower ## 2.3.0 (2018-02-21) - Added `prefix` and `suffix` options for Chart.js and Highcharts - Added `thousands` and `decimal` options for Chart.js and Highcharts - Added `messages` option - Fixed boolean labels for column chart - Clean up charts before refresh ## 2.2.4 (2017-05-14) - Added multiple series stacked and grouped charts - *Chart.js and Highcharts* - Fixed `refreshData` after `updateData` - Fixed redraw issue with HighCharts ## 2.2.3 (2017-02-22) - Added `xtype` option - Added `points` option - Added `mapsApiKey` option to `configure` ## 2.2.2 (2017-01-07) - Fixed missing scatter points for Google Charts - Fixed scatter chart for Highcharts - Limit concurrent requests to avoid overloading servers - Added ability to specify color with series for Chart.js and Highcharts - Added bubble chart for Chart.js [experimental] ## 2.2.1 (2016-12-05) - Added `curve` option - Added `legend` option - Added `title` option - Added `getAdapter` function - Added `setOptions` function - Added `redraw` function - Fixed column order for Google Charts and Highcharts ## 2.2.0 (2016-12-03) - Added global options - Added `download` option - *Chart.js only* - Added `updateData` function - Added `refreshData` function - Added `refresh` option - Added `stopRefresh` function - Added `getDataSource` function - Added `donut` option to pie chart - Added `eachChart` function - Remove colors from tooltips for Chart.js pie chart ## 2.1.2 (2016-11-29) - Fix for missing zero values for Chart.js ## 2.1.1 (2016-11-28) - Fix for missing values for multiple series column chart with sparse data - Remove colors from tooltips for Chart.js ## 2.1.0 (2016-09-10) - Added basic support for new Google Charts loader - Added `configure` function - Dropped jQuery and Zepto dependencies for AJAX - Fixed legend colors on scatter chart for Chart.js ## 2.0.1 (2016-08-11) - Added scatter chart for Chart.js - Fixed error with `xtitle` and `ytitle` on column and bar charts - Fixed all zeros with Chart.js - Fixed odd tick spacing with Chart.js ## 2.0.0 (2016-05-30) - Chart.js is now the default adapter - yay open source! - Axis types are automatically detected - no need for `discrete: true` - Better date support - New official API - Fixed min and max for Chart.js bar charts ## 1.5.1 (2016-05-03) - Added bar chart for Chart.js - Added `library` option for series - Better tick selection for time and discrete scales ## 1.5.0 (2016-05-01) - Added Chart.js adapter **beta** - Added `smarterDates` option (temporary until 2.0) - Added `smarterDiscrete` option (temporary until 2.0) - Fixed line height on timeline charts ## 1.4.2 (2016-02-29) - Added `label` option - Better tooltip for dates for Google Charts ## 1.4.1 (2015-09-07) - Fixed regression with `min: null` ## 1.4.0 (2015-08-31) - Added scatter chart - Added axis titles ## 1.3.0 (2014-10-09) - Added timelines - Added `adapter` option ## 1.2.2 (2014-03-29) - Added `colors` option ## 1.2.1 (2014-03-23) - Added `discrete` option ## 1.2.0 (2014-02-23) - Added geo chart - Added `stacked` option ## 1.1.1 (2013-12-08) - Made sure options can be overridden - Added support for Google Charts localization ## 1.1.0 (2013-06-27) - Added bar chart and area chart - Resize charts when window is resized ## 1.0.2 (2013-06-11) - Added library option ## 1.0.1 (2013-05-23) - Added support for Highcharts 2.1+ - Fixed sorting for line chart with multiple series and Google Charts ## 1.0.0 (2013-05-15) - First major release chartkick.js-4.1.0/LICENSE.txt000066400000000000000000000020611413507257700157270ustar00rootroot00000000000000Copyright (c) 2013-2021 Andrew Kane MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. chartkick.js-4.1.0/README.md000066400000000000000000000270131413507257700153670ustar00rootroot00000000000000# Chartkick.js Create beautiful charts with one line of JavaScript [See it in action](https://ankane.github.io/chartkick.js/examples/) **Chartkick.js 4.0 was recently released** - see [how to upgrade](#upgrading) Supports [Chart.js](https://www.chartjs.org/), [Google Charts](https://developers.google.com/chart/), and [Highcharts](https://www.highcharts.com/) Also available for [React](https://github.com/ankane/react-chartkick), [Vue.js](https://github.com/ankane/vue-chartkick), [Ruby](https://github.com/ankane/chartkick), [Python](https://github.com/mher/chartkick.py), [Elixir](https://github.com/buren/chartkick-ex), and [Clojure](https://github.com/yfractal/chartkick) [![Build Status](https://github.com/ankane/chartkick.js/workflows/build/badge.svg?branch=master)](https://github.com/ankane/chartkick.js/actions) ## Quick Start Run ```sh npm install chartkick chart.js ``` And add ```javascript import Chartkick from "chartkick" import "chartkick/chart.js" ``` This sets up Chartkick with Chart.js. For other charting libraries, see [detailed instructions](#installation). ## Charts Create a div for the chart ```html
``` Line chart ```javascript new Chartkick.LineChart("chart-1", {"2021-01-01": 11, "2021-01-02": 6}) ``` Pie chart ```javascript new Chartkick.PieChart("chart-1", [["Blueberry", 44], ["Strawberry", 23]]) ``` Column chart ```javascript new Chartkick.ColumnChart("chart-1", [["Sun", 32], ["Mon", 46], ["Tue", 28]]) ``` Bar chart ```javascript new Chartkick.BarChart("chart-1", [["Work", 32], ["Play", 1492]]) ``` Area chart ```javascript new Chartkick.AreaChart("chart-1", {"2021-01-01": 11, "2021-01-02": 6}) ``` Scatter chart ```javascript new Chartkick.ScatterChart("chart-1", [[174.0, 80.0], [176.5, 82.3], [180.3, 73.6]]) ``` Geo chart - *Google Charts* ```javascript new Chartkick.GeoChart("chart-1", [["United States", 44], ["Germany", 23], ["Brazil", 22]]) ``` Timeline - *Google Charts* ```javascript new Chartkick.Timeline("chart-1", [["Washington", "1789-04-29", "1797-03-03"], ["Adams", "1797-03-03", "1801-03-03"]]) ``` Multiple series ```javascript data = [ {name: "Workout", data: {"2021-01-01": 3, "2021-01-02": 4}}, {name: "Call parents", data: {"2021-01-01": 5, "2021-01-02": 3}} ] new Chartkick.LineChart("chart-1", data) ``` Multiple series stacked and grouped - *Chart.js or Highcharts* ```javascript data = [ {name: "Apple", data: {"Tuesday": 3, "Friday": 4}, stack: "fruit"}, {name: "Pear", data: {"Tuesday": 1, "Friday": 8}, stack: "fruit"}, {name: "Carrot", data: {"Tuesday": 3, "Friday": 4}, stack: "vegetable"}, {name: "Beet", data: {"Tuesday": 1, "Friday": 8}, stack: "vegetable"} ] new Chartkick.BarChart("chart-1", data, {stacked: true}) ``` ## Data Data can be an array, object, callback, or URL. #### Array ```javascript new Chartkick.LineChart("chart-1", [["2021-01-01", 2], ["2021-01-02", 3]]) ``` #### Object ```javascript new Chartkick.LineChart("chart-1", {"2021-01-01": 2, "2021-01-02": 3}) ``` #### Callback ```javascript function fetchData(success, fail) { success({"2021-01-01": 2, "2021-01-02": 3}) // or fail("Data not available") } new Chartkick.LineChart("chart-1", fetchData) ``` #### URL Make your pages load super fast and stop worrying about timeouts. Give each chart its own endpoint. ```javascript new Chartkick.LineChart("chart-1", "/stocks") ``` ## Options Min and max for y-axis ```javascript new Chartkick.LineChart("chart-1", data, {min: 1000, max: 5000}) ``` `min` defaults to 0 for charts with non-negative values. Use `null` to let the charting library decide. Min and max for x-axis - *Chart.js* ```javascript new Chartkick.LineChart("chart-1", data, {xmin: "2021-01-01", xmax: "2022-01-01"}) ``` Colors ```javascript new Chartkick.LineChart("chart-1", data, {colors: ["#b00", "#666"]}) ``` Stacked columns or bars ```javascript new Chartkick.ColumnChart("chart-1", data, {stacked: true}) ``` > You can also set `stacked` to `percent` or `relative` for Google Charts and `percent` for Highcharts Discrete axis ```javascript new Chartkick.LineChart("chart-1", data, {discrete: true}) ``` Label (for single series) ```javascript new Chartkick.LineChart("chart-1", data, {label: "Value"}) ``` Axis titles ```javascript new Chartkick.LineChart("chart-1", data, {xtitle: "Time", ytitle: "Population"}) ``` Straight lines between points instead of a curve ```javascript new Chartkick.LineChart("chart-1", data, {curve: false}) ``` Hide points ```javascript new Chartkick.LineChart("chart-1", data, {points: false}) ``` Show or hide legend ```javascript new Chartkick.LineChart("chart-1", data, {legend: true}) ``` Specify legend position ```javascript new Chartkick.LineChart("chart-1", data, {legend: "bottom"}) ``` Donut chart ```javascript new Chartkick.PieChart("chart-1", data, {donut: true}) ``` Prefix, useful for currency - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {prefix: "$"}) ``` Suffix, useful for percentages - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {suffix: "%"}) ``` Set a thousands separator - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {thousands: ","}) ``` Set a decimal separator - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {decimal: ","}) ``` Set significant digits - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {precision: 3}) ``` Set rounding - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {round: 2}) ``` Show insignificant zeros, useful for currency - *Chart.js, Highcharts* ```javascript new Chartkick.LineChart("chart-1", data, {round: 2, zeros: true}) ``` Friendly byte sizes - *Chart.js* ```javascript new Chartkick.LineChart("chart-1", data, {bytes: true}) ``` Specify the message when the chart is loading ```javascript new Chartkick.LineChart("chart-1", data, {loading: "Loading..."}) ``` Specify the message when data is empty ```javascript new Chartkick.LineChart("chart-1", data, {empty: "No data"}) ``` Refresh data from a remote source every `n` seconds ```javascript new Chartkick.LineChart("chart-1", url, {refresh: 60}) ``` You can pass options directly to the charting library with: ```javascript new Chartkick.LineChart("chart-1", data, {library: {backgroundColor: "pink"}}) ``` See the documentation for [Chart.js](https://www.chartjs.org/docs/), [Google Charts](https://developers.google.com/chart/interactive/docs/gallery), and [Highcharts](https://api.highcharts.com/highcharts) for more info. To customize datasets in Chart.js, use: ```javascript new Chartkick.LineChart("chart-1", data, {dataset: {borderWidth: 10}}) ``` You can pass this option to individual series as well. ### Global Options To set options for all of your charts, use: ```javascript Chartkick.options = { colors: ["#b00", "#666"] } ``` ### Multiple Series You can pass a few options with a series: - `name` - `data` - `color` - `dataset` - *Chart.js only* - `points` - *Chart.js only* - `curve` - *Chart.js only* ### Code If you want to use the charting library directly, get the code with: ```javascript new Chartkick.LineChart("chart-1", data, {code: true}) ``` The code will be logged to the JavaScript console. **Note:** JavaScript functions cannot be logged, so it may not be identical. ### Download Charts *Chart.js only* Give users the ability to download charts. It all happens in the browser - no server-side code needed. ```javascript new Chartkick.LineChart("chart-1", data, {download: true}) ``` Set the filename ```javascript new Chartkick.LineChart("chart-1", data, {download: {filename: "boom"}}) ``` **Note:** Safari will open the image in a new window instead of downloading. Set the background color ```javascript new Chartkick.LineChart("chart-1", data, {download: {background: "#fff"}}) ``` ## Installation ### Chart.js Run ```sh npm install chartkick chart.js ``` And add ```javascript import Chartkick from "chartkick" import "chartkick/chart.js" ``` ### Google Charts Run ```sh npm install chartkick ``` And add ```javascript import Chartkick from "chartkick" ``` And include on the page ```html ``` To specify a language or Google Maps API key, use: ```js Chartkick.configure({language: "de", mapsApiKey: "..."}) ``` before your charts. ### Highcharts Run ```sh npm install chartkick highcharts ``` And add ```javascript import Chartkick from "chartkick" import "chartkick/highcharts" ``` ### No Package Manager Download [chartkick.js](https://unpkg.com/chartkick) directly. For Chart.js (works with 3+), [download it](https://unpkg.com/chart.js@3/dist/chart.js) and the [date-fns adapter bundle](https://unpkg.com/chartjs-adapter-date-fns@2/dist/chartjs-adapter-date-fns.bundle.js) and use: ```html ``` For Google Charts, use: ```html ``` For Highcharts (works with 2.1+), [download it](https://www.highcharts.com/download) and use: ```html ``` ### Multiple Libraries If more than one charting library is loaded, choose between them with: ```javascript new Chartkick.LineChart("chart-1", data, {adapter: "google"}) // or highcharts or chartjs ``` ## API Access a chart with: ```javascript var chart = Chartkick.charts["chart-id"] ``` Get the underlying chart object with: ```javascript chart.getChartObject() ``` You can also use: ```javascript chart.getElement() chart.getData() chart.getOptions() chart.getAdapter() ``` Update the data with: ```javascript chart.updateData(newData) ``` You can also specify new options: ```javascript chart.setOptions(newOptions) // or chart.updateData(newData, newOptions) ``` Refresh the data from a remote source: ```javascript chart.refreshData() ``` Redraw the chart with: ```javascript chart.redraw() ``` Loop over charts with: ```javascript Chartkick.eachChart( function(chart) { // do something }) ``` ## Custom Adapters **Note:** This feature is experimental. Add your own custom adapter with: ```javascript var CustomAdapter = { name: "custom", renderLineChart: function (chart) { chart.getElement().innerHTML = "Hi" } } Chartkick.adapters.unshift(CustomAdapter) ``` ## Examples To run the files in the `examples` directory, you’ll need a web server. Run: ```sh npm install -g serve serve ``` and visit [http://localhost:5000/examples/](http://localhost:5000/examples/) ## Upgrading ### 4.0 Run: ```sh npm install chartkick@latest ``` For Chart.js, also run: ```sh npm install chart.js@latest ``` And change: ```javascript import Chart from "chart.js" Chartkick.use(Chart) ``` to: ```javascript import "chartkick/chart.js" ``` ## History View the [changelog](https://github.com/ankane/chartkick.js/blob/master/CHANGELOG.md) ## Contributing Everyone is encouraged to help improve this project. Here are a few ways you can help: - [Report bugs](https://github.com/ankane/chartkick.js/issues) - Fix bugs and [submit pull requests](https://github.com/ankane/chartkick.js/pulls) - Write, clarify, or fix documentation - Suggest or add new features To get started with development: ```sh git clone https://github.com/ankane/chartkick.js.git cd chartkick.js npm install npm run build # start web server npm install -g serve serve ``` chartkick.js-4.1.0/chart.js/000077500000000000000000000000001413507257700156215ustar00rootroot00000000000000chartkick.js-4.1.0/chart.js/chart.esm.js000066400000000000000000000001741413507257700200450ustar00rootroot00000000000000import Chartkick from "chartkick" import Chart from "chart.js/auto" import "chartjs-adapter-date-fns" Chartkick.use(Chart) chartkick.js-4.1.0/chart.js/package.json000066400000000000000000000001221413507257700201020ustar00rootroot00000000000000{ "name": "chartkick-chart.js", "private": true, "module": "chart.esm.js" } chartkick.js-4.1.0/examples/000077500000000000000000000000001413507257700157235ustar00rootroot00000000000000chartkick.js-4.1.0/examples/index.html000066400000000000000000002164231413507257700177300ustar00rootroot00000000000000 Chartkick.js

Line Chart

Pie Chart

Donut Chart

Column Chart

Bar Chart

Area Chart

Scatter Chart

Bubble Chart

Bubble Chart - Large Values

Timeline

URL

URL Error

Callback Success

Callback Fetch

Callback Error

Callback Error 2

Multiple Series Line

Multiple Series Bar

Multiple Series Bar Stacked

Multiple Series Area Unstacked

Multiple Series Bar Stacked and Grouped - Chart.js 2.5+ and Highcharts

Multiple Series Bar Stacked Percent - Google Charts and Highcharts

Multiple Series Bar Stacked Relative - Google Charts

Multiple Series Column Stacked

Multiple Series Area

Multiple Series Scatter

Remote

One Series

Library Option

Line Chart - Dataset

Pie Chart - Dataset

Scatter Chart - Dataset

Multiple Series - Dataset

Geo Chart

Markers

Discrete Line Chart

Discrete Line Chart Numeric String

Discrete Area Chart

Discrete Multiple Series

Different Data

Min

Max

Min Null

X-Axis Min and Max - Chart.js

X-Axis Min and Max Empty - Chart.js

X-Axis Min and Max Scatter - Chart.js

X-Axis Min and Max Scatter Empty - Chart.js

Axis Titles Line

Axis Titles Bar

Colors

Pie Colors

Column Colors

Column Colors - Array

Inline Color

Multiple Inline Color

Line Label

Pie Label

Line Chart - Times

Line Chart - Single Time

Line Chart - Week Monday

Smarter Discrete

Column Labels

Time Labels

Unordered Data

Weekly Data

Minute Data

One Point

All Zeros

Overlapping Labels - Chart.js

Bad Label - Chart.js

NaN Bug - Chart.js

10 Minutes

30 Minutes

Line Chart - Curve

Line Chart - Legend

Line Chart - Legend Bottom

Line Chart - Title

Points

Line Chart - Prefix

Pie Chart - Prefix

Column Chart - Prefix

Bar Chart - Prefix

Line Chart - Negative Prefix

Scatter - Prefix

Bubble - Prefix

Multiple Series Line - Prefix

Line Chart - Suffix

Thousands

Thousands - Negative Prefix

Decimal

Scatter Same X

Numeric Axis Line

Numeric Axis Area

Numeric Axis Column

Numeric Axis Bar

Boolean Axis Column

Bytes - Line Chart

Bytes - Pie Chart

Bytes - Column Chart

Bytes - Bar Chart

Precision - Large

Precision - Small

Round - Positive

Round - Negative

Round and Precision

Zeros - Precision

Zeros - Round

Null Data

Empty Message

Empty Pie

Custom Chart

Redraw Chart

Update Data

Refresh Data URL

Refresh Data Callback

Refresh Data Error

chartkick.js-4.1.0/examples/remote.json000066400000000000000000000011721413507257700201120ustar00rootroot00000000000000{"2013-02-10 00:00:00 -0800":11,"2013-02-11 00:00:00 -0800":6,"2013-02-12 00:00:00 -0800":3,"2013-02-13 00:00:00 -0800":2,"2013-02-14 00:00:00 -0800":5,"2013-02-15 00:00:00 -0800":3,"2013-02-16 00:00:00 -0800":8,"2013-02-17 00:00:00 -0800":6,"2013-02-18 00:00:00 -0800":6,"2013-02-19 00:00:00 -0800":12,"2013-02-20 00:00:00 -0800":5,"2013-02-21 00:00:00 -0800":5,"2013-02-22 00:00:00 -0800":3,"2013-02-23 00:00:00 -0800":1,"2013-02-24 00:00:00 -0800":10,"2013-02-25 00:00:00 -0800":1,"2013-02-26 00:00:00 -0800":3,"2013-02-27 00:00:00 -0800":2,"2013-02-28 00:00:00 -0800":3,"2013-03-01 00:00:00 -0800":2,"2013-03-02 00:00:00 -0800":8}chartkick.js-4.1.0/examples/repro.html000066400000000000000000000017341413507257700177450ustar00rootroot00000000000000
chartkick.js-4.1.0/highcharts/000077500000000000000000000000001413507257700162315ustar00rootroot00000000000000chartkick.js-4.1.0/highcharts/highcharts.esm.js000066400000000000000000000001411413507257700214720ustar00rootroot00000000000000import Chartkick from "chartkick" import Highcharts from "highcharts" Chartkick.use(Highcharts) chartkick.js-4.1.0/highcharts/package.json000066400000000000000000000001311413507257700205120ustar00rootroot00000000000000{ "name": "chartkick-highcharts", "private": true, "module": "highcharts.esm.js" } chartkick.js-4.1.0/package.json000066400000000000000000000016231413507257700163750ustar00rootroot00000000000000{ "name": "chartkick", "version": "4.1.0", "homepage": "https://github.com/ankane/chartkick.js", "description": "Create beautiful charts with one line of JavaScript", "main": "dist/chartkick.js", "scripts": { "build": "rollup -c", "lint": "eslint src" }, "dependencies": {}, "keywords": [ "charts" ], "authors": [ "ankane" ], "license": "MIT", "repository": { "type": "git", "url": "https://github.com/ankane/chartkick.js" }, "files": [ "dist", "chart.js", "highcharts" ], "devDependencies": { "@rollup/plugin-buble": "^0.21.1", "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-node-resolve": "^11.2.1", "eslint": "^7.23.0", "rollup": "^2.44.0", "rollup-plugin-uglify": "^6.0.2" }, "optionalDependencies": { "chart.js": ">=3.0.2", "chartjs-adapter-date-fns": ">=2.0.0", "date-fns": ">=2.0.0" } } chartkick.js-4.1.0/rollup.config.js000066400000000000000000000023241413507257700172250ustar00rootroot00000000000000import buble from "@rollup/plugin-buble"; import commonjs from "@rollup/plugin-commonjs"; import pkg from "./package.json"; import resolve from "@rollup/plugin-node-resolve"; import { uglify } from "rollup-plugin-uglify"; const input = "src/index.js"; const outputName = "Chartkick"; const banner = `/*! * Chartkick.js * ${pkg.description} * ${pkg.repository.url} * v${pkg.version} * ${pkg.license} License */ `; const minBanner = `/*! Chartkick.js v${pkg.version} | ${pkg.license} License */`; export default [ { input: input, output: { name: outputName, file: pkg.main, format: "umd", banner: banner }, plugins: [ resolve(), commonjs(), buble() ] }, { input: input, output: { name: outputName, file: pkg.main.replace(/\.js$/, ".min.js"), format: "umd", banner: minBanner }, plugins: [ resolve(), commonjs(), buble(), uglify({ output: { comments: /^!/ } }) ] }, { input: input, output: { file: pkg.main.replace(/\.js$/, ".esm.js"), format: "es", banner: banner }, external: [], plugins: [ buble() ] } ]; chartkick.js-4.1.0/src/000077500000000000000000000000001413507257700146745ustar00rootroot00000000000000chartkick.js-4.1.0/src/adapters/000077500000000000000000000000001413507257700164775ustar00rootroot00000000000000chartkick.js-4.1.0/src/adapters/chartjs.js000066400000000000000000000440111413507257700204730ustar00rootroot00000000000000import { formatValue, jsOptionsFunc, merge, isArray, toStr, toFloat, toDate, sortByNumber, isMinute, isHour, isDay, isWeek, isMonth, isYear, seriesOption } from "../helpers"; function allZeros(data) { let i, j, d; for (i = 0; i < data.length; i++) { d = data[i].data; for (j = 0; j < d.length; j++) { if (d[j][1] != 0) { return false; } } } return true; } let baseOptions = { maintainAspectRatio: false, animation: false, plugins: { legend: {}, tooltip: { displayColors: false, callbacks: {} }, title: { font: { size: 20 }, color: "#333" } }, interaction: {} }; let defaultOptions = { scales: { y: { ticks: { maxTicksLimit: 4 }, title: { font: { size: 16 }, color: "#333" }, grid: {} }, x: { grid: { drawOnChartArea: false }, title: { font: { size: 16 }, color: "#333" }, time: {}, ticks: {} } } }; // http://there4.io/2012/05/02/google-chart-color-list/ let defaultColors = [ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6", "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11", "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067" ]; let hideLegend = function (options, legend, hideLegend) { if (legend !== undefined) { options.plugins.legend.display = !!legend; if (legend && legend !== true) { options.plugins.legend.position = legend; } } else if (hideLegend) { options.plugins.legend.display = false; } }; let setTitle = function (options, title) { options.plugins.title.display = true; options.plugins.title.text = title; }; let setMin = function (options, min) { if (min !== null) { options.scales.y.min = toFloat(min); } }; let setMax = function (options, max) { options.scales.y.max = toFloat(max); }; let setBarMin = function (options, min) { if (min !== null) { options.scales.x.min = toFloat(min); } }; let setBarMax = function (options, max) { options.scales.x.max = toFloat(max); }; let setStacked = function (options, stacked) { options.scales.x.stacked = !!stacked; options.scales.y.stacked = !!stacked; }; let setXtitle = function (options, title) { options.scales.x.title.display = true; options.scales.x.title.text = title; }; let setYtitle = function (options, title) { options.scales.y.title.display = true; options.scales.y.title.text = title; }; // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb let addOpacity = function (hex, opacity) { let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex; }; // check if not null or undefined // https://stackoverflow.com/a/27757708/1177228 let notnull = function (x) { return x != null; }; let setLabelSize = function (chart, data, options) { let maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length); if (maxLabelSize > 25) { maxLabelSize = 25; } else if (maxLabelSize < 10) { maxLabelSize = 10; } if (!options.scales.x.ticks.callback) { options.scales.x.ticks.callback = function (value) { value = toStr(this.getLabelForValue(value)); if (value.length > maxLabelSize) { return value.substring(0, maxLabelSize - 2) + "..."; } else { return value; } }; } }; let setFormatOptions = function (chart, options, chartType) { let formatOptions = { prefix: chart.options.prefix, suffix: chart.options.suffix, thousands: chart.options.thousands, decimal: chart.options.decimal, precision: chart.options.precision, round: chart.options.round, zeros: chart.options.zeros }; if (chart.options.bytes) { let series = chart.data; if (chartType === "pie") { series = [{data: series}]; } // calculate max let max = 0; for (let i = 0; i < series.length; i++) { let s = series[i]; for (let j = 0; j < s.data.length; j++) { if (s.data[j][1] > max) { max = s.data[j][1]; } } } // calculate scale let scale = 1; while (max >= 1024) { scale *= 1024; max /= 1024; } // set step size formatOptions.byteScale = scale; } if (chartType !== "pie") { let axis = options.scales.y; if (chartType === "bar") { axis = options.scales.x; } if (formatOptions.byteScale) { if (!axis.ticks.stepSize) { axis.ticks.stepSize = formatOptions.byteScale / 2; } if (!axis.ticks.maxTicksLimit) { axis.ticks.maxTicksLimit = 4; } } if (!axis.ticks.callback) { axis.ticks.callback = function (value) { return formatValue("", value, formatOptions, true); }; } } if (!options.plugins.tooltip.callbacks.label) { if (chartType === "scatter") { options.plugins.tooltip.callbacks.label = function (context) { let label = context.dataset.label || ''; if (label) { label += ': '; } return label + '(' + context.label + ', ' + context.formattedValue + ')'; }; } else if (chartType === "bubble") { options.plugins.tooltip.callbacks.label = function (context) { let label = context.dataset.label || ''; if (label) { label += ': '; } let dataPoint = context.raw; return label + '(' + dataPoint.x + ', ' + dataPoint.y + ', ' + dataPoint.v + ')'; }; } else if (chartType === "pie") { // need to use separate label for pie charts options.plugins.tooltip.callbacks.label = function (context) { let dataLabel = context.label; let value = ': '; if (isArray(dataLabel)) { // show value on first line of multiline label // need to clone because we are changing the value dataLabel = dataLabel.slice(); dataLabel[0] += value; } else { dataLabel += value; } return formatValue(dataLabel, context.parsed, formatOptions); }; } else { let valueLabel = chartType === "bar" ? "x" : "y"; options.plugins.tooltip.callbacks.label = function (context) { // don't show null values for stacked charts if (context.parsed[valueLabel] === null) { return; } let label = context.dataset.label || ''; if (label) { label += ': '; } return formatValue(label, context.parsed[valueLabel], formatOptions); }; } } }; let jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle); let createDataTable = function (chart, options, chartType) { let datasets = []; let labels = []; let colors = chart.options.colors || defaultColors; let day = true; let week = true; let dayOfWeek; let month = true; let year = true; let hour = true; let minute = true; let series = chart.data; let max = 0; if (chartType === "bubble") { for (let i = 0; i < series.length; i++) { let s = series[i]; for (let j = 0; j < s.data.length; j++) { if (s.data[j][2] > max) { max = s.data[j][2]; } } } } let i, j, s, d, key, rows = [], rows2 = []; if (chartType === "bar" || chartType === "column" || (chart.xtype !== "number" && chart.xtype !== "bubble")) { let sortedLabels = []; for (i = 0; i < series.length; i++) { s = series[i]; for (j = 0; j < s.data.length; j++) { d = s.data[j]; key = chart.xtype == "datetime" ? d[0].getTime() : d[0]; if (!rows[key]) { rows[key] = new Array(series.length); } rows[key][i] = toFloat(d[1]); if (sortedLabels.indexOf(key) === -1) { sortedLabels.push(key); } } } if (chart.xtype === "datetime" || chart.xtype === "number") { sortedLabels.sort(sortByNumber); } for (j = 0; j < series.length; j++) { rows2.push([]); } let value; let k; for (k = 0; k < sortedLabels.length; k++) { i = sortedLabels[k]; if (chart.xtype === "datetime") { value = new Date(toFloat(i)); // TODO make this efficient day = day && isDay(value); if (!dayOfWeek) { dayOfWeek = value.getDay(); } week = week && isWeek(value, dayOfWeek); month = month && isMonth(value); year = year && isYear(value); hour = hour && isHour(value); minute = minute && isMinute(value); } else { value = i; } labels.push(value); for (j = 0; j < series.length; j++) { // Chart.js doesn't like undefined rows2[j].push(rows[i][j] === undefined ? null : rows[i][j]); } } } else { for (let i = 0; i < series.length; i++) { let s = series[i]; let d = []; for (let j = 0; j < s.data.length; j++) { let point = { x: toFloat(s.data[j][0]), y: toFloat(s.data[j][1]) }; if (chartType === "bubble") { point.r = toFloat(s.data[j][2]) * 20 / max; // custom attribute, for tooltip point.v = s.data[j][2]; } d.push(point); } rows2.push(d); } } let color; let backgroundColor; for (i = 0; i < series.length; i++) { s = series[i]; // use colors for each bar for single series format if (chart.options.colors && chart.singleSeriesFormat && (chartType === "bar" || chartType === "column") && !s.color && isArray(chart.options.colors) && !isArray(chart.options.colors[0])) { color = colors; backgroundColor = []; for (let j = 0; j < colors.length; j++) { backgroundColor[j] = addOpacity(color[j], 0.5); } } else { color = s.color || colors[i]; backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color; } let dataset = { label: s.name || "", data: rows2[i], fill: chartType === "area", borderColor: color, backgroundColor: backgroundColor, borderWidth: 2 }; let pointChart = chartType === "line" || chartType === "area" || chartType === "scatter" || chartType === "bubble"; if (pointChart) { dataset.pointBackgroundColor = color; dataset.pointHoverBackgroundColor = color; dataset.pointHitRadius = 50; } if (chartType === "bubble") { dataset.pointBackgroundColor = backgroundColor; dataset.pointHoverBackgroundColor = backgroundColor; dataset.pointHoverBorderWidth = 2; } if (s.stack) { dataset.stack = s.stack; } let curve = seriesOption(chart, s, "curve"); if (curve === false) { dataset.tension = 0; } else if (pointChart) { dataset.tension = 0.4; } let points = seriesOption(chart, s, "points"); if (points === false) { dataset.pointRadius = 0; dataset.pointHoverRadius = 0; } dataset = merge(dataset, chart.options.dataset || {}); dataset = merge(dataset, s.library || {}); dataset = merge(dataset, s.dataset || {}); datasets.push(dataset); } let xmin = chart.options.xmin; let xmax = chart.options.xmax; if (chart.xtype === "datetime") { if (notnull(xmin)) { options.scales.x.ticks.min = toDate(xmin).getTime(); } if (notnull(xmax)) { options.scales.x.ticks.max = toDate(xmax).getTime(); } } else if (chart.xtype === "number") { if (notnull(xmin)) { options.scales.x.ticks.min = xmin; } if (notnull(xmax)) { options.scales.x.ticks.max = xmax; } } // for empty datetime chart if (chart.xtype === "datetime" && labels.length === 0) { if (notnull(xmin)) { labels.push(toDate(xmin)); } if (notnull(xmax)) { labels.push(toDate(xmax)); } day = false; week = false; month = false; year = false; hour = false; minute = false; } if (chart.xtype === "datetime" && labels.length > 0) { let minTime = (notnull(xmin) ? toDate(xmin) : labels[0]).getTime(); let maxTime = (notnull(xmax) ? toDate(xmax) : labels[0]).getTime(); for (i = 1; i < labels.length; i++) { let value = labels[i].getTime(); if (value < minTime) { minTime = value; } if (value > maxTime) { maxTime = value; } } let timeDiff = (maxTime - minTime) / (86400 * 1000.0); if (!options.scales.x.time.unit) { let step; if (year || timeDiff > 365 * 10) { options.scales.x.time.unit = "year"; step = 365; } else if (month || timeDiff > 30 * 10) { options.scales.x.time.unit = "month"; step = 30; } else if (day || timeDiff > 10) { options.scales.x.time.unit = "day"; step = 1; } else if (hour || timeDiff > 0.5) { options.scales.x.time.displayFormats = {hour: "MMM d, h a"}; options.scales.x.time.unit = "hour"; step = 1 / 24.0; } else if (minute) { options.scales.x.time.displayFormats = {minute: "h:mm a"}; options.scales.x.time.unit = "minute"; step = 1 / 24.0 / 60.0; } if (step && timeDiff > 0) { // width not available for hidden elements let width = chart.element.offsetWidth; if (width > 0) { let unitStepSize = Math.ceil(timeDiff / step / (width / 100.0)); if (week && step === 1) { unitStepSize = Math.ceil(unitStepSize / 7.0) * 7; } options.scales.x.time.stepSize = unitStepSize; } } } if (!options.scales.x.time.tooltipFormat) { if (day) { options.scales.x.time.tooltipFormat = "PP"; } else if (hour) { options.scales.x.time.tooltipFormat = "MMM d, h a"; } else if (minute) { options.scales.x.time.tooltipFormat = "h:mm a"; } } } let data = { labels: labels, datasets: datasets }; return data; }; export default class { constructor(library) { this.name = "chartjs"; this.library = library; } renderLineChart(chart, chartType) { let chartOptions = {}; if (chartType === "area") { // TODO fix area stacked // chartOptions.stacked = true; } // fix for https://github.com/chartjs/Chart.js/issues/2441 if (!chart.options.max && allZeros(chart.data)) { chartOptions.max = 1; } let options = jsOptions(chart, merge(chartOptions, chart.options)); setFormatOptions(chart, options, chartType); let data = createDataTable(chart, options, chartType || "line"); if (chart.xtype === "number") { options.scales.x.type = options.scales.x.type || "linear"; options.scales.x.position = options.scales.x.position ||"bottom"; } else { options.scales.x.type = chart.xtype === "string" ? "category" : "time"; } this.drawChart(chart, "line", data, options); } renderPieChart(chart) { let options = merge({}, baseOptions); if (chart.options.donut) { options.cutout = "50%"; } if ("legend" in chart.options) { hideLegend(options, chart.options.legend); } if (chart.options.title) { setTitle(options, chart.options.title); } options = merge(options, chart.options.library || {}); setFormatOptions(chart, options, "pie"); let labels = []; let values = []; for (let i = 0; i < chart.data.length; i++) { let point = chart.data[i]; labels.push(point[0]); values.push(point[1]); } let dataset = { data: values, backgroundColor: chart.options.colors || defaultColors }; dataset = merge(dataset, chart.options.dataset || {}); let data = { labels: labels, datasets: [dataset] }; this.drawChart(chart, "pie", data, options); } renderColumnChart(chart, chartType) { let options; if (chartType === "bar") { let barOptions = merge(baseOptions, defaultOptions); barOptions.indexAxis = "y"; // ensure gridlines have proper orientation barOptions.scales.x.grid.drawOnChartArea = true; barOptions.scales.y.grid.drawOnChartArea = false; delete barOptions.scales.y.ticks.maxTicksLimit; options = jsOptionsFunc(barOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options); } else { options = jsOptions(chart, chart.options); } setFormatOptions(chart, options, chartType); let data = createDataTable(chart, options, "column"); if (chartType !== "bar") { setLabelSize(chart, data, options); } this.drawChart(chart, "bar", data, options); } renderAreaChart(chart) { this.renderLineChart(chart, "area"); } renderBarChart(chart) { this.renderColumnChart(chart, "bar"); } renderScatterChart(chart, chartType) { chartType = chartType || "scatter"; let options = jsOptions(chart, chart.options); setFormatOptions(chart, options, chartType); if (!("showLine" in options)) { options.showLine = false; } let data = createDataTable(chart, options, chartType); options.scales.x.type = options.scales.x.type || "linear"; options.scales.x.position = options.scales.x.position || "bottom"; // prevent grouping hover and tooltips if (!("mode" in options.interaction)) { options.interaction.mode = "nearest"; } this.drawChart(chart, chartType, data, options); } renderBubbleChart(chart) { this.renderScatterChart(chart, "bubble"); } destroy(chart) { if (chart.chart) { chart.chart.destroy(); } } drawChart(chart, type, data, options) { this.destroy(chart); if (chart.destroyed) return; let chartOptions = { type: type, data: data, options: options }; if (chart.options.code) { window.console.log("new Chart(ctx, " + JSON.stringify(chartOptions) + ");"); } chart.element.innerHTML = ""; let ctx = chart.element.getElementsByTagName("CANVAS")[0]; chart.chart = new this.library(ctx, chartOptions); } } chartkick.js-4.1.0/src/adapters/google.js000066400000000000000000000244761413507257700203260ustar00rootroot00000000000000import { jsOptionsFunc, merge, toStr, toFloat, sortByTime, sortByNumberSeries, isDay } from "../helpers"; let loaded = {}; let callbacks = []; // Set chart options let defaultOptions = { chartArea: {}, fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif", pointSize: 6, legend: { textStyle: { fontSize: 12, color: "#444" }, alignment: "center", position: "right" }, curveType: "function", hAxis: { textStyle: { color: "#666", fontSize: 12 }, titleTextStyle: {}, gridlines: { color: "transparent" }, baselineColor: "#ccc", viewWindow: {} }, vAxis: { textStyle: { color: "#666", fontSize: 12 }, titleTextStyle: {}, baselineColor: "#ccc", viewWindow: {} }, tooltip: { textStyle: { color: "#666", fontSize: 12 } } }; let hideLegend = function (options, legend, hideLegend) { if (legend !== undefined) { let position; if (!legend) { position = "none"; } else if (legend === true) { position = "right"; } else { position = legend; } options.legend.position = position; } else if (hideLegend) { options.legend.position = "none"; } }; let setTitle = function (options, title) { options.title = title; options.titleTextStyle = {color: "#333", fontSize: "20px"}; }; let setMin = function (options, min) { options.vAxis.viewWindow.min = min; }; let setMax = function (options, max) { options.vAxis.viewWindow.max = max; }; let setBarMin = function (options, min) { options.hAxis.viewWindow.min = min; }; let setBarMax = function (options, max) { options.hAxis.viewWindow.max = max; }; let setStacked = function (options, stacked) { options.isStacked = stacked ? stacked : false; }; let setXtitle = function (options, title) { options.hAxis.title = title; options.hAxis.titleTextStyle.italic = false; }; let setYtitle = function (options, title) { options.vAxis.title = title; options.vAxis.titleTextStyle.italic = false; }; let jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle); let resize = function (callback) { if (window.attachEvent) { window.attachEvent("onresize", callback); } else if (window.addEventListener) { window.addEventListener("resize", callback, true); } callback(); }; export default class { constructor(library) { this.name = "google"; this.library = library; } renderLineChart(chart) { this.waitForLoaded(chart, () => { let chartOptions = {}; if (chart.options.curve === false) { chartOptions.curveType = "none"; } if (chart.options.points === false) { chartOptions.pointSize = 0; } let options = jsOptions(chart, chart.options, chartOptions); let data = this.createDataTable(chart.data, chart.xtype); this.drawChart(chart, "LineChart", data, options); }); } renderPieChart(chart) { this.waitForLoaded(chart, () => { let chartOptions = { chartArea: { top: "10%", height: "80%" }, legend: {} }; if (chart.options.colors) { chartOptions.colors = chart.options.colors; } if (chart.options.donut) { chartOptions.pieHole = 0.5; } if ("legend" in chart.options) { hideLegend(chartOptions, chart.options.legend); } if (chart.options.title) { setTitle(chartOptions, chart.options.title); } let options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); let data = new this.library.visualization.DataTable(); data.addColumn("string", ""); data.addColumn("number", "Value"); data.addRows(chart.data); this.drawChart(chart, "PieChart", data, options); }); } renderColumnChart(chart) { this.waitForLoaded(chart, () => { let options = jsOptions(chart, chart.options); let data = this.createDataTable(chart.data, chart.xtype); this.drawChart(chart, "ColumnChart", data, options); }); } renderBarChart(chart) { this.waitForLoaded(chart, () => { let chartOptions = { hAxis: { gridlines: { color: "#ccc" } } }; let options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions); let data = this.createDataTable(chart.data, chart.xtype); this.drawChart(chart, "BarChart", data, options); }); } renderAreaChart(chart) { this.waitForLoaded(chart, () => { let chartOptions = { isStacked: true, pointSize: 0, areaOpacity: 0.5 }; let options = jsOptions(chart, chart.options, chartOptions); let data = this.createDataTable(chart.data, chart.xtype); this.drawChart(chart, "AreaChart", data, options); }); } renderGeoChart(chart) { this.waitForLoaded(chart, "geochart", () => { let chartOptions = { legend: "none", colorAxis: { colors: chart.options.colors || ["#f6c7b6", "#ce502d"] } }; let options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); let data = new this.library.visualization.DataTable(); data.addColumn("string", ""); data.addColumn("number", chart.options.label || "Value"); data.addRows(chart.data); this.drawChart(chart, "GeoChart", data, options); }); } renderScatterChart(chart) { this.waitForLoaded(chart, () => { let chartOptions = {}; let options = jsOptions(chart, chart.options, chartOptions); let series = chart.data, rows2 = [], i, j, data, d; for (i = 0; i < series.length; i++) { series[i].name = series[i].name || "Value"; d = series[i].data; for (j = 0; j < d.length; j++) { let row = new Array(series.length + 1); row[0] = d[j][0]; row[i + 1] = d[j][1]; rows2.push(row); } } data = new this.library.visualization.DataTable(); data.addColumn("number", ""); for (i = 0; i < series.length; i++) { data.addColumn("number", series[i].name); } data.addRows(rows2); this.drawChart(chart, "ScatterChart", data, options); }); } renderTimeline(chart) { this.waitForLoaded(chart, "timeline", () => { let chartOptions = { legend: "none" }; if (chart.options.colors) { chartOptions.colors = chart.options.colors; } let options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); let data = new this.library.visualization.DataTable(); data.addColumn({type: "string", id: "Name"}); data.addColumn({type: "date", id: "Start"}); data.addColumn({type: "date", id: "End"}); data.addRows(chart.data); chart.element.style.lineHeight = "normal"; this.drawChart(chart, "Timeline", data, options); }); } // TODO remove resize events destroy(chart) { if (chart.chart) { chart.chart.clearChart(); } } drawChart(chart, type, data, options) { this.destroy(chart); if (chart.destroyed) return; if (chart.options.code) { window.console.log("var data = new google.visualization.DataTable(" + data.toJSON() + ");\nvar chart = new google.visualization." + type + "(element);\nchart.draw(data, " + JSON.stringify(options) + ");"); } chart.chart = new this.library.visualization[type](chart.element); resize(function () { chart.chart.draw(data, options); }); } waitForLoaded(chart, pack, callback) { if (!callback) { callback = pack; pack = "corechart"; } callbacks.push({pack: pack, callback: callback}); if (loaded[pack]) { this.runCallbacks(); } else { loaded[pack] = true; // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI let loadOptions = { packages: [pack], callback: () => { this.runCallbacks(); } }; let config = chart.__config(); if (config.language) { loadOptions.language = config.language; } if (pack === "geochart" && config.mapsApiKey) { loadOptions.mapsApiKey = config.mapsApiKey; } this.library.charts.load("current", loadOptions); } } runCallbacks() { let cb, call; for (let i = 0; i < callbacks.length; i++) { cb = callbacks[i]; call = this.library.visualization && ((cb.pack === "corechart" && this.library.visualization.LineChart) || (cb.pack === "timeline" && this.library.visualization.Timeline) || (cb.pack === "geochart" && this.library.visualization.GeoChart)); if (call) { cb.callback(); callbacks.splice(i, 1); i--; } } } // cant use object as key createDataTable(series, columnType) { let i, j, s, d, key, rows = [], sortedLabels = []; for (i = 0; i < series.length; i++) { s = series[i]; series[i].name = series[i].name || "Value"; for (j = 0; j < s.data.length; j++) { d = s.data[j]; key = (columnType === "datetime") ? d[0].getTime() : d[0]; if (!rows[key]) { rows[key] = new Array(series.length); sortedLabels.push(key); } rows[key][i] = toFloat(d[1]); } } let rows2 = []; let day = true; let value; for (j = 0; j < sortedLabels.length; j++) { i = sortedLabels[j]; if (columnType === "datetime") { value = new Date(toFloat(i)); day = day && isDay(value); } else if (columnType === "number") { value = toFloat(i); } else { value = i; } rows2.push([value].concat(rows[i])); } if (columnType === "datetime") { rows2.sort(sortByTime); } else if (columnType === "number") { rows2.sort(sortByNumberSeries); for (i = 0; i < rows2.length; i++) { rows2[i][0] = toStr(rows2[i][0]); } columnType = "string"; } // create datatable let data = new this.library.visualization.DataTable(); columnType = columnType === "datetime" && day ? "date" : columnType; data.addColumn(columnType, ""); for (i = 0; i < series.length; i++) { data.addColumn("number", series[i].name); } data.addRows(rows2); return data; } } chartkick.js-4.1.0/src/adapters/highcharts.js000066400000000000000000000155301413507257700211650ustar00rootroot00000000000000import { formatValue, jsOptionsFunc, merge, sortByNumber } from "../helpers"; let defaultOptions = { chart: {}, xAxis: { title: { text: null }, labels: { style: { fontSize: "12px" } } }, yAxis: { title: { text: null }, labels: { style: { fontSize: "12px" } } }, title: { text: null }, credits: { enabled: false }, legend: { borderWidth: 0 }, tooltip: { style: { fontSize: "12px" } }, plotOptions: { areaspline: {}, area: {}, series: { marker: {} } }, time: { useUTC: false } }; let hideLegend = function (options, legend, hideLegend) { if (legend !== undefined) { options.legend.enabled = !!legend; if (legend && legend !== true) { if (legend === "top" || legend === "bottom") { options.legend.verticalAlign = legend; } else { options.legend.layout = "vertical"; options.legend.verticalAlign = "middle"; options.legend.align = legend; } } } else if (hideLegend) { options.legend.enabled = false; } }; let setTitle = function (options, title) { options.title.text = title; }; let setMin = function (options, min) { options.yAxis.min = min; }; let setMax = function (options, max) { options.yAxis.max = max; }; let setStacked = function (options, stacked) { let stackedValue = stacked ? (stacked === true ? "normal" : stacked) : null; options.plotOptions.series.stacking = stackedValue; options.plotOptions.area.stacking = stackedValue; options.plotOptions.areaspline.stacking = stackedValue; }; let setXtitle = function (options, title) { options.xAxis.title.text = title; }; let setYtitle = function (options, title) { options.yAxis.title.text = title; }; let jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle); let setFormatOptions = function(chart, options, chartType) { let formatOptions = { prefix: chart.options.prefix, suffix: chart.options.suffix, thousands: chart.options.thousands, decimal: chart.options.decimal, precision: chart.options.precision, round: chart.options.round, zeros: chart.options.zeros }; if (chartType !== "pie" && !options.yAxis.labels.formatter) { options.yAxis.labels.formatter = function () { return formatValue("", this.value, formatOptions); }; } if (!options.tooltip.pointFormatter && !options.tooltip.pointFormat) { options.tooltip.pointFormatter = function () { return '\u25CF ' + formatValue(this.series.name + ': ', this.y, formatOptions) + '
'; }; } }; export default class { constructor(library) { this.name = "highcharts"; this.library = library; } renderLineChart(chart, chartType) { chartType = chartType || "spline"; let chartOptions = {}; if (chartType === "areaspline") { chartOptions = { plotOptions: { areaspline: { stacking: "normal" }, area: { stacking: "normal" }, series: { marker: { enabled: false } } } }; } if (chart.options.curve === false) { if (chartType === "areaspline") { chartType = "area"; } else if (chartType === "spline") { chartType = "line"; } } let options = jsOptions(chart, chart.options, chartOptions), data, i, j; if (chart.xtype === "number") { options.xAxis.type = options.xAxis.type || "linear"; } else { options.xAxis.type = chart.xtype === "string" ? "category" : "datetime"; } if (!options.chart.type) { options.chart.type = chartType; } setFormatOptions(chart, options, chartType); let series = chart.data; for (i = 0; i < series.length; i++) { series[i].name = series[i].name || "Value"; data = series[i].data; if (chart.xtype === "datetime") { for (j = 0; j < data.length; j++) { data[j][0] = data[j][0].getTime(); } } series[i].marker = {symbol: "circle"}; if (chart.options.points === false) { series[i].marker.enabled = false; } } this.drawChart(chart, series, options); } renderScatterChart(chart) { let options = jsOptions(chart, chart.options, {}); options.chart.type = "scatter"; this.drawChart(chart, chart.data, options); } renderPieChart(chart) { let chartOptions = merge(defaultOptions, {}); if (chart.options.colors) { chartOptions.colors = chart.options.colors; } if (chart.options.donut) { chartOptions.plotOptions = {pie: {innerSize: "50%"}}; } if ("legend" in chart.options) { hideLegend(chartOptions, chart.options.legend); } if (chart.options.title) { setTitle(chartOptions, chart.options.title); } let options = merge(chartOptions, chart.options.library || {}); setFormatOptions(chart, options, "pie"); let series = [{ type: "pie", name: chart.options.label || "Value", data: chart.data }]; this.drawChart(chart, series, options); } renderColumnChart(chart, chartType) { chartType = chartType || "column"; let series = chart.data; let options = jsOptions(chart, chart.options), i, j, s, d, rows = [], categories = []; options.chart.type = chartType; setFormatOptions(chart, options, chartType); for (i = 0; i < series.length; i++) { s = series[i]; for (j = 0; j < s.data.length; j++) { d = s.data[j]; if (!rows[d[0]]) { rows[d[0]] = new Array(series.length); categories.push(d[0]); } rows[d[0]][i] = d[1]; } } if (chart.xtype === "number") { categories.sort(sortByNumber); } options.xAxis.categories = categories; let newSeries = [], d2; for (i = 0; i < series.length; i++) { d = []; for (j = 0; j < categories.length; j++) { d.push(rows[categories[j]][i] || 0); } d2 = { name: series[i].name || "Value", data: d }; if (series[i].stack) { d2.stack = series[i].stack; } newSeries.push(d2); } this.drawChart(chart, newSeries, options); } renderBarChart(chart) { this.renderColumnChart(chart, "bar"); } renderAreaChart(chart) { this.renderLineChart(chart, "areaspline"); } destroy(chart) { if (chart.chart) { chart.chart.destroy(); } } drawChart(chart, data, options) { this.destroy(chart); if (chart.destroyed) return; options.chart.renderTo = chart.element.id; options.series = data; if (chart.options.code) { window.console.log("new Highcharts.Chart(" + JSON.stringify(options) + ");"); } chart.chart = new this.library.Chart(options); } } chartkick.js-4.1.0/src/data.js000066400000000000000000000063051413507257700161470ustar00rootroot00000000000000import { isArray, toStr, toFloat, toDate, toArr, sortByTime, sortByNumberSeries, isDate, isNumber } from "./helpers"; function formatSeriesData(data, keyType) { let r = [], j, keyFunc; if (keyType === "number") { keyFunc = toFloat; } else if (keyType === "datetime") { keyFunc = toDate; } else { keyFunc = toStr; } if (keyType === "bubble") { for (j = 0; j < data.length; j++) { r.push([toFloat(data[j][0]), toFloat(data[j][1]), toFloat(data[j][2])]); } } else { for (j = 0; j < data.length; j++) { r.push([keyFunc(data[j][0]), toFloat(data[j][1])]); } } if (keyType === "datetime") { r.sort(sortByTime); } else if (keyType === "number") { r.sort(sortByNumberSeries); } return r; } function detectXType(series, noDatetime, options) { if (dataEmpty(series)) { if ((options.xmin || options.xmax) && (!options.xmin || isDate(options.xmin)) && (!options.xmax || isDate(options.xmax))) { return "datetime"; } else { return "number"; } } else if (detectXTypeWithFunction(series, isNumber)) { return "number"; } else if (!noDatetime && detectXTypeWithFunction(series, isDate)) { return "datetime"; } else { return "string"; } } function detectXTypeWithFunction(series, func) { let i, j, data; for (i = 0; i < series.length; i++) { data = toArr(series[i].data); for (j = 0; j < data.length; j++) { if (!func(data[j][0])) { return false; } } } return true; } // creates a shallow copy of each element of the array // elements are expected to be objects function copySeries(series) { let newSeries = [], i, j; for (i = 0; i < series.length; i++) { let copy = {}; for (j in series[i]) { if (series[i].hasOwnProperty(j)) { copy[j] = series[i][j]; } } newSeries.push(copy); } return newSeries; } function processSeries(chart, keyType, noDatetime) { let i; let opts = chart.options; let series = chart.rawData; // see if one series or multiple chart.singleSeriesFormat = (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])); if (chart.singleSeriesFormat) { series = [{name: opts.label, data: series}]; } // convert to array // must come before dataEmpty check series = copySeries(series); for (i = 0; i < series.length; i++) { series[i].data = toArr(series[i].data); } chart.xtype = keyType ? keyType : (opts.discrete ? "string" : detectXType(series, noDatetime, opts)); // right format for (i = 0; i < series.length; i++) { series[i].data = formatSeriesData(series[i].data, chart.xtype); } return series; } function processSimple(chart) { let perfectData = toArr(chart.rawData), i; for (i = 0; i < perfectData.length; i++) { perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])]; } return perfectData; } function dataEmpty(data, chartType) { if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") { return data.length === 0; } else { for (let i = 0; i < data.length; i++) { if (data[i].data.length > 0) { return false; } } return true; } } export { dataEmpty, processSeries, processSimple }; chartkick.js-4.1.0/src/download.js000066400000000000000000000062041413507257700170430ustar00rootroot00000000000000function addDownloadButton(chart) { let element = chart.element; let link = document.createElement("a"); let download = chart.options.download; if (download === true) { download = {}; } else if (typeof download === "string") { download = {filename: download}; } link.download = download.filename || "chart.png"; // https://caniuse.com/download link.style.position = "absolute"; link.style.top = "20px"; link.style.right = "20px"; link.style.zIndex = 1000; link.style.lineHeight = "20px"; link.target = "_blank"; // for safari let image = document.createElement("img"); image.alt = "Download"; image.style.border = "none"; // icon from font-awesome // http://fa2png.io/ image.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABCFBMVEUAAADMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMywEsqxAAAAV3RSTlMAAQIDBggJCgsMDQ4PERQaHB0eISIjJCouLzE0OTo/QUJHSUpLTU5PUllhYmltcHh5foWLjI+SlaCio6atr7S1t7m6vsHHyM7R2tze5Obo7fHz9ff5+/1hlxK2AAAA30lEQVQYGUXBhVYCQQBA0TdYWAt2d3d3YWAHyur7/z9xgD16Lw0DW+XKx+1GgX+FRzM3HWQWrHl5N/oapW5RPe0PkBu+UYeICvozTWZVK23Ao04B79oJrOsJDOoxkZoQPWgX29pHpCZEk7rEvQYiNSFq1UMqvlCjJkRBS1R8hb00Vb/TajtBL7nTHE1X1vyMQF732dQhyF2o6SAwrzP06iUQzvwsArlnzcOdrgBhJyHa1QOgO9U1GsKuvjUTjavliZYQ8nNPapG6sap/3nrIdJ6bOWzmX/fy0XVpfzZP3S8OJT3g9EEiJwAAAABJRU5ErkJggg=="; link.appendChild(image); element.style.position = "relative"; chart.__downloadAttached = true; // mouseenter chart.__enterEvent = addEvent(element, "mouseover", function(e) { let related = e.relatedTarget; // check download option again to ensure it wasn't changed if ((!related || (related !== this && !childOf(this, related))) && chart.options.download) { link.href = chart.toImage(download); element.appendChild(link); } }); // mouseleave chart.__leaveEvent = addEvent(element, "mouseout", function(e) { let related = e.relatedTarget; if (!related || (related !== this && !childOf(this, related))) { if (link.parentNode) { link.parentNode.removeChild(link); } } }); } // https://stackoverflow.com/questions/10149963/adding-event-listener-cross-browser function addEvent(elem, event, fn) { if (elem.addEventListener) { elem.addEventListener(event, fn, false); return fn; } else { let fn2 = function() { // set the this pointer same as addEventListener when fn is called return(fn.call(elem, window.event)); }; elem.attachEvent("on" + event, fn2); return fn2; } } function removeEvent(elem, event, fn) { if (elem.removeEventListener) { elem.removeEventListener(event, fn, false); } else { elem.detachEvent("on" + event, fn); } } // https://gist.github.com/shawnbot/4166283 function childOf(p, c) { if (p === c) return false; while (c && c !== p) c = c.parentNode; return c === p; } export { addDownloadButton, removeEvent }; chartkick.js-4.1.0/src/helpers.js000066400000000000000000000156411413507257700167030ustar00rootroot00000000000000function isArray(variable) { return Object.prototype.toString.call(variable) === "[object Array]"; } function isFunction(variable) { return variable instanceof Function; } function isPlainObject(variable) { // protect against prototype pollution, defense 2 return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object; } // https://github.com/madrobby/zepto/blob/master/src/zepto.js function extend(target, source) { let key; for (key in source) { // protect against prototype pollution, defense 1 if (key === "__proto__") continue; if (isPlainObject(source[key]) || isArray(source[key])) { if (isPlainObject(source[key]) && !isPlainObject(target[key])) { target[key] = {}; } if (isArray(source[key]) && !isArray(target[key])) { target[key] = []; } extend(target[key], source[key]); } else if (source[key] !== undefined) { target[key] = source[key]; } } } function merge(obj1, obj2) { let target = {}; extend(target, obj1); extend(target, obj2); return target; } let DATE_PATTERN = /^(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)$/i; function negativeValues(series) { let i, j, data; for (i = 0; i < series.length; i++) { data = series[i].data; for (j = 0; j < data.length; j++) { if (data[j][1] < 0) { return true; } } } return false; } function toStr(n) { return "" + n; } function toFloat(n) { return parseFloat(n); } function toDate(n) { let matches, year, month, day; if (typeof n !== "object") { if (typeof n === "number") { n = new Date(n * 1000); // ms } else { n = toStr(n); if ((matches = n.match(DATE_PATTERN))) { year = parseInt(matches[1], 10); month = parseInt(matches[3], 10) - 1; day = parseInt(matches[5], 10); return new Date(year, month, day); } else { // try our best to get the str into iso8601 // TODO be smarter about this let str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z"); // Date.parse returns milliseconds if valid and NaN if invalid n = new Date(Date.parse(str) || n); } } } return n; } function toArr(n) { if (!isArray(n)) { let arr = [], i; for (i in n) { if (n.hasOwnProperty(i)) { arr.push([i, n[i]]); } } n = arr; } return n; } function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) { return function (chart, opts, chartOptions) { let series = chart.data; let options = merge({}, defaultOptions); options = merge(options, chartOptions || {}); if (chart.singleSeriesFormat || "legend" in opts) { hideLegend(options, opts.legend, chart.singleSeriesFormat); } if (opts.title) { setTitle(options, opts.title); } // min if ("min" in opts) { setMin(options, opts.min); } else if (!negativeValues(series)) { setMin(options, 0); } // max if (opts.max) { setMax(options, opts.max); } if ("stacked" in opts) { setStacked(options, opts.stacked); } if (opts.colors) { options.colors = opts.colors; } if (opts.xtitle) { setXtitle(options, opts.xtitle); } if (opts.ytitle) { setYtitle(options, opts.ytitle); } // merge library last options = merge(options, opts.library || {}); return options; }; } function sortByTime(a, b) { return a[0].getTime() - b[0].getTime(); } function sortByNumberSeries(a, b) { return a[0] - b[0]; } function sortByNumber(a, b) { return a - b; } function isMinute(d) { return d.getMilliseconds() === 0 && d.getSeconds() === 0; } function isHour(d) { return isMinute(d) && d.getMinutes() === 0; } function isDay(d) { return isHour(d) && d.getHours() === 0; } function isWeek(d, dayOfWeek) { return isDay(d) && d.getDay() === dayOfWeek; } function isMonth(d) { return isDay(d) && d.getDate() === 1; } function isYear(d) { return isMonth(d) && d.getMonth() === 0; } function isDate(obj) { return !isNaN(toDate(obj)) && toStr(obj).length >= 6; } function isNumber(obj) { return typeof obj === "number"; } let byteSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB"]; function formatValue(pre, value, options, axis) { pre = pre || ""; if (options.prefix) { if (value < 0) { value = value * -1; pre += "-"; } pre += options.prefix; } let suffix = options.suffix || ""; let precision = options.precision; let round = options.round; if (options.byteScale) { let suffixIdx; let baseValue = axis ? options.byteScale : value; if (baseValue >= 1152921504606846976) { value /= 1152921504606846976; suffixIdx = 6; } else if (baseValue >= 1125899906842624) { value /= 1125899906842624; suffixIdx = 5; } else if (baseValue >= 1099511627776) { value /= 1099511627776; suffixIdx = 4; } else if (baseValue >= 1073741824) { value /= 1073741824; suffixIdx = 3; } else if (baseValue >= 1048576) { value /= 1048576; suffixIdx = 2; } else if (baseValue >= 1024) { value /= 1024; suffixIdx = 1; } else { suffixIdx = 0; } // TODO handle manual precision case if (precision === undefined && round === undefined) { if (value >= 1023.5) { if (suffixIdx < byteSuffixes.length - 1) { value = 1.0; suffixIdx += 1; } } precision = value >= 1000 ? 4 : 3; } suffix = " " + byteSuffixes[suffixIdx]; } if (precision !== undefined && round !== undefined) { throw Error("Use either round or precision, not both"); } if (!axis) { if (precision !== undefined) { value = value.toPrecision(precision); if (!options.zeros) { value = parseFloat(value); } } if (round !== undefined) { if (round < 0) { let num = Math.pow(10, -1 * round); value = parseInt((1.0 * value / num).toFixed(0)) * num; } else { value = value.toFixed(round); if (!options.zeros) { value = parseFloat(value); } } } } if (options.thousands || options.decimal) { value = toStr(value); let parts = value.split("."); value = parts[0]; if (options.thousands) { value = value.replace(/\B(?=(\d{3})+(?!\d))/g, options.thousands); } if (parts.length > 1) { value += (options.decimal || ".") + parts[1]; } } return pre + value + suffix; } function seriesOption(chart, series, option) { if (option in series) { return series[option]; } else if (option in chart.options) { return chart.options[option]; } return null; } export { formatValue, jsOptionsFunc, merge, isArray, isFunction, toStr, toFloat, toDate, toArr, sortByTime, sortByNumberSeries, sortByNumber, isMinute, isHour, isDay, isWeek, isMonth, isYear, isDate, isNumber, seriesOption }; chartkick.js-4.1.0/src/index.js000066400000000000000000000250101413507257700163370ustar00rootroot00000000000000import ChartjsAdapter from "./adapters/chartjs"; import HighchartsAdapter from "./adapters/highcharts"; import GoogleChartsAdapter from "./adapters/google"; import { dataEmpty, processSeries, processSimple } from "./data"; import { addDownloadButton, removeEvent } from "./download"; import { merge, isFunction, toDate } from "./helpers"; import { pushRequest } from "./request-queue"; let config = {}; let adapters = []; // helpers function setText(element, text) { if (document.body.innerText) { element.innerText = text; } else { element.textContent = text; } } // TODO remove prefix for all messages function chartError(element, message, noPrefix) { if (!noPrefix) { message = "Error Loading Chart: " + message; } setText(element, message); element.style.color = "#ff0000"; } function errorCatcher(chart) { try { chart.__render(); } catch (err) { chartError(chart.element, err.message); throw err; } } function fetchDataSource(chart, dataSource, showLoading) { // only show loading message for urls and callbacks if (showLoading && chart.options.loading && (typeof dataSource === "string" || typeof dataSource === "function")) { setText(chart.element, chart.options.loading); } if (typeof dataSource === "string") { pushRequest(dataSource, function (data) { chart.rawData = data; errorCatcher(chart); }, function (message) { chartError(chart.element, message); }); } else if (typeof dataSource === "function") { try { dataSource(function (data) { chart.rawData = data; errorCatcher(chart); }, function (message) { chartError(chart.element, message, true); }); } catch (err) { chartError(chart.element, err, true); } } else { chart.rawData = dataSource; errorCatcher(chart); } } function getAdapterType(library) { if (library) { if (library.product === "Highcharts") { return HighchartsAdapter; } else if (library.charts) { return GoogleChartsAdapter; } else if (isFunction(library)) { return ChartjsAdapter; } } throw new Error("Unknown adapter"); } function addAdapter(library) { let adapterType = getAdapterType(library); let adapter = new adapterType(library); if (adapters.indexOf(adapter) === -1) { adapters.push(adapter); } } function loadAdapters() { if ("Chart" in window) { addAdapter(window.Chart); } if ("Highcharts" in window) { addAdapter(window.Highcharts); } if (window.google && window.google.charts) { addAdapter(window.google); } } function renderChart(chartType, chart) { if (dataEmpty(chart.data, chartType)) { let message = chart.options.empty || (chart.options.messages && chart.options.messages.empty) || "No data"; setText(chart.element, message); } else { callAdapter(chartType, chart); if (chart.options.download && !chart.__downloadAttached && chart.adapter === "chartjs") { addDownloadButton(chart); } } } // TODO remove chartType if cross-browser way // to get the name of the chart class function callAdapter(chartType, chart) { let i, adapter, fnName, adapterName; fnName = "render" + chartType; adapterName = chart.options.adapter; loadAdapters(); for (i = 0; i < adapters.length; i++) { adapter = adapters[i]; if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) { chart.adapter = adapter.name; chart.__adapterObject = adapter; return adapter[fnName](chart); } } if (adapters.length > 0) { throw new Error("No charting library found for " + chartType); } else { throw new Error("No charting libraries found - be sure to include one before your charts"); } } // define classes class Chart { constructor(element, dataSource, options) { let elementId; if (typeof element === "string") { elementId = element; element = document.getElementById(element); if (!element) { throw new Error("No element with id " + elementId); } } this.element = element; this.options = merge(Chartkick.options, options || {}); this.dataSource = dataSource; Chartkick.charts[element.id] = this; fetchDataSource(this, dataSource, true); if (this.options.refresh) { this.startRefresh(); } } getElement() { return this.element; } getDataSource() { return this.dataSource; } getData() { return this.data; } getOptions() { return this.options; } getChartObject() { return this.chart; } getAdapter() { return this.adapter; } updateData(dataSource, options) { this.dataSource = dataSource; if (options) { this.__updateOptions(options); } fetchDataSource(this, dataSource, true); } setOptions(options) { this.__updateOptions(options); this.redraw(); } redraw() { fetchDataSource(this, this.rawData); } refreshData() { if (typeof this.dataSource === "string") { // prevent browser from caching let sep = this.dataSource.indexOf("?") === -1 ? "?" : "&"; let url = this.dataSource + sep + "_=" + (new Date()).getTime(); fetchDataSource(this, url); } else if (typeof this.dataSource === "function") { fetchDataSource(this, this.dataSource); } } startRefresh() { let refresh = this.options.refresh; if (refresh && typeof this.dataSource !== "string" && typeof this.dataSource !== "function") { throw new Error("Data source must be a URL or callback for refresh"); } if (!this.intervalId) { if (refresh) { this.intervalId = setInterval( () => { this.refreshData(); }, refresh * 1000); } else { throw new Error("No refresh interval"); } } } stopRefresh() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } } toImage(download) { if (this.adapter === "chartjs") { if (download && download.background && download.background !== "transparent") { // https://stackoverflow.com/questions/30464750/chartjs-line-chart-set-background-color let canvas = this.chart.canvas; let ctx = this.chart.ctx; let tmpCanvas = document.createElement("canvas"); let tmpCtx = tmpCanvas.getContext("2d"); tmpCanvas.width = ctx.canvas.width; tmpCanvas.height = ctx.canvas.height; tmpCtx.fillStyle = download.background; tmpCtx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height); tmpCtx.drawImage(canvas, 0, 0); return tmpCanvas.toDataURL("image/png"); } else { return this.chart.toBase64Image(); } } else { throw new Error("Feature only available for Chart.js"); } } destroy() { this.destroyed = true; this.stopRefresh(); if (this.__adapterObject) { this.__adapterObject.destroy(this); } if (this.__enterEvent) { removeEvent(this.element, "mouseover", this.__enterEvent); } if (this.__leaveEvent) { removeEvent(this.element, "mouseout", this.__leaveEvent); } } __updateOptions(options) { let updateRefresh = options.refresh && options.refresh !== this.options.refresh; this.options = merge(Chartkick.options, options); if (updateRefresh) { this.stopRefresh(); this.startRefresh(); } } __render() { this.data = this.__processData(); renderChart(this.__chartName(), this); } __config() { return config; } } class LineChart extends Chart { __processData() { return processSeries(this); } __chartName() { return "LineChart"; } } class PieChart extends Chart { __processData() { return processSimple(this); } __chartName() { return "PieChart"; } } class ColumnChart extends Chart { __processData() { return processSeries(this, null, true); } __chartName() { return "ColumnChart"; } } class BarChart extends Chart { __processData() { return processSeries(this, null, true); } __chartName() { return "BarChart"; } } class AreaChart extends Chart { __processData() { return processSeries(this); } __chartName() { return "AreaChart"; } } class GeoChart extends Chart { __processData() { return processSimple(this); } __chartName() { return "GeoChart"; } } class ScatterChart extends Chart { __processData() { return processSeries(this, "number"); } __chartName() { return "ScatterChart"; } } class BubbleChart extends Chart { __processData() { return processSeries(this, "bubble"); } __chartName() { return "BubbleChart"; } } class Timeline extends Chart { __processData() { let i, data = this.rawData; for (i = 0; i < data.length; i++) { data[i][1] = toDate(data[i][1]); data[i][2] = toDate(data[i][2]); } return data; } __chartName() { return "Timeline"; } } const Chartkick = { LineChart: LineChart, PieChart: PieChart, ColumnChart: ColumnChart, BarChart: BarChart, AreaChart: AreaChart, GeoChart: GeoChart, ScatterChart: ScatterChart, BubbleChart: BubbleChart, Timeline: Timeline, charts: {}, configure: function (options) { for (let key in options) { if (options.hasOwnProperty(key)) { config[key] = options[key]; } } }, setDefaultOptions: function (opts) { Chartkick.options = opts; }, eachChart: function (callback) { for (let chartId in Chartkick.charts) { if (Chartkick.charts.hasOwnProperty(chartId)) { callback(Chartkick.charts[chartId]); } } }, destroyAll: function() { for (let chartId in Chartkick.charts) { if (Chartkick.charts.hasOwnProperty(chartId)) { Chartkick.charts[chartId].destroy(); delete Chartkick.charts[chartId]; } } }, config: config, options: {}, adapters: adapters, addAdapter: addAdapter, use: function(adapter) { addAdapter(adapter); return Chartkick; } }; // not ideal, but allows for simpler integration if (typeof window !== "undefined" && !window.Chartkick) { window.Chartkick = Chartkick; // clean up previous charts before Turbolinks loads new page document.addEventListener("turbolinks:before-render", function() { Chartkick.destroyAll(); }); document.addEventListener("turbo:before-render", function() { Chartkick.destroyAll(); }); // use setTimeout so charting library can come later in same JS file setTimeout(function() { window.dispatchEvent(new Event("chartkick:load")); }, 0); } // backwards compatibility for esm require Chartkick.default = Chartkick; export default Chartkick; chartkick.js-4.1.0/src/request-queue.js000066400000000000000000000025251413507257700200500ustar00rootroot00000000000000let pendingRequests = [], runningRequests = 0, maxRequests = 4; function pushRequest(url, success, error) { pendingRequests.push([url, success, error]); runNext(); } function runNext() { if (runningRequests < maxRequests) { let request = pendingRequests.shift(); if (request) { runningRequests++; getJSON(request[0], request[1], request[2]); runNext(); } } } function requestComplete() { runningRequests--; runNext(); } function getJSON(url, success, error) { ajaxCall(url, success, function (jqXHR, textStatus, errorThrown) { let message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message; error(message); }); } function ajaxCall(url, success, error) { let $ = window.jQuery || window.Zepto || window.$; if ($ && $.ajax) { $.ajax({ dataType: "json", url: url, success: success, error: error, complete: requestComplete }); } else { let xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.setRequestHeader("Content-Type", "application/json"); xhr.onload = function () { requestComplete(); if (xhr.status === 200) { success(JSON.parse(xhr.responseText), xhr.statusText, xhr); } else { error(xhr, "error", xhr.statusText); } }; xhr.send(); } } export { pushRequest };