pax_global_header 0000666 0000000 0000000 00000000064 14135072577 0014525 g ustar 00root root 0000000 0000000 52 comment=09a8f4e8ad0418cb8e13b314fac4af8b447a4b55
chartkick.js-4.1.0/ 0000775 0000000 0000000 00000000000 14135072577 0014105 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/.editorconfig 0000664 0000000 0000000 00000000246 14135072577 0016564 0 ustar 00root root 0000000 0000000 # 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.json 0000664 0000000 0000000 00000000402 14135072577 0016675 0 ustar 00root root 0000000 0000000 {
"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/ 0000775 0000000 0000000 00000000000 14135072577 0015445 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/ 0000775 0000000 0000000 00000000000 14135072577 0017630 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/.github/ISSUE_TEMPLATE/bug_report.md 0000664 0000000 0000000 00000002003 14135072577 0022315 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000000234 14135072577 0021617 0 ustar 00root root 0000000 0000000 blank_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.md 0000664 0000000 0000000 00000000625 14135072577 0023360 0 ustar 00root root 0000000 0000000 ---
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.yml 0000664 0000000 0000000 00000001240 14135072577 0017275 0 ustar 00root root 0000000 0000000 # 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/ 0000775 0000000 0000000 00000000000 14135072577 0017502 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/.github/workflows/build.yml 0000664 0000000 0000000 00000000447 14135072577 0021331 0 ustar 00root root 0000000 0000000 name: 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/.gitignore 0000664 0000000 0000000 00000000117 14135072577 0016074 0 ustar 00root root 0000000 0000000 node_modules/
dist/
yarn.lock
examples/Chart.bundle.js
*.log
package-lock.json
chartkick.js-4.1.0/CHANGELOG.md 0000664 0000000 0000000 00000015554 14135072577 0015730 0 ustar 00root root 0000000 0000000 ## 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.txt 0000664 0000000 0000000 00000002061 14135072577 0015727 0 ustar 00root root 0000000 0000000 Copyright (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.md 0000664 0000000 0000000 00000027013 14135072577 0015367 0 ustar 00root root 0000000 0000000 # 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)
[](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/ 0000775 0000000 0000000 00000000000 14135072577 0015621 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/chart.js/chart.esm.js 0000664 0000000 0000000 00000000174 14135072577 0020045 0 ustar 00root root 0000000 0000000 import 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.json 0000664 0000000 0000000 00000000122 14135072577 0020102 0 ustar 00root root 0000000 0000000 {
"name": "chartkick-chart.js",
"private": true,
"module": "chart.esm.js"
}
chartkick.js-4.1.0/examples/ 0000775 0000000 0000000 00000000000 14135072577 0015723 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/examples/index.html 0000664 0000000 0000000 00000216423 14135072577 0017730 0 ustar 00root root 0000000 0000000
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.json 0000664 0000000 0000000 00000001172 14135072577 0020112 0 ustar 00root root 0000000 0000000 {"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.html 0000664 0000000 0000000 00000001734 14135072577 0017745 0 ustar 00root root 0000000 0000000
chartkick.js-4.1.0/highcharts/ 0000775 0000000 0000000 00000000000 14135072577 0016231 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/highcharts/highcharts.esm.js 0000664 0000000 0000000 00000000141 14135072577 0021472 0 ustar 00root root 0000000 0000000 import Chartkick from "chartkick"
import Highcharts from "highcharts"
Chartkick.use(Highcharts)
chartkick.js-4.1.0/highcharts/package.json 0000664 0000000 0000000 00000000131 14135072577 0020512 0 ustar 00root root 0000000 0000000 {
"name": "chartkick-highcharts",
"private": true,
"module": "highcharts.esm.js"
}
chartkick.js-4.1.0/package.json 0000664 0000000 0000000 00000001623 14135072577 0016375 0 ustar 00root root 0000000 0000000 {
"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.js 0000664 0000000 0000000 00000002324 14135072577 0017225 0 ustar 00root root 0000000 0000000 import 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/ 0000775 0000000 0000000 00000000000 14135072577 0014674 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/src/adapters/ 0000775 0000000 0000000 00000000000 14135072577 0016477 5 ustar 00root root 0000000 0000000 chartkick.js-4.1.0/src/adapters/chartjs.js 0000664 0000000 0000000 00000044011 14135072577 0020473 0 ustar 00root root 0000000 0000000 import { 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.js 0000664 0000000 0000000 00000024476 14135072577 0020326 0 ustar 00root root 0000000 0000000 import { 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.js 0000664 0000000 0000000 00000015530 14135072577 0021165 0 ustar 00root root 0000000 0000000 import { 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.js 0000664 0000000 0000000 00000006305 14135072577 0016147 0 ustar 00root root 0000000 0000000 import { 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.js 0000664 0000000 0000000 00000006204 14135072577 0017043 0 ustar 00root root 0000000 0000000 function 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.js 0000664 0000000 0000000 00000015641 14135072577 0016703 0 ustar 00root root 0000000 0000000 function 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.js 0000664 0000000 0000000 00000025010 14135072577 0016337 0 ustar 00root root 0000000 0000000 import 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.js 0000664 0000000 0000000 00000002525 14135072577 0020050 0 ustar 00root root 0000000 0000000 let 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 };