├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Procfile ├── README.md ├── demo_project ├── chart ├── demo │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── charts.py │ ├── context_processors.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── static │ │ ├── favicon.ico │ │ └── style.css │ ├── templates │ │ ├── base.html │ │ ├── code-examples │ │ │ ├── bar_chart.html │ │ │ ├── bubble_chart.html │ │ │ ├── pie_chart.html │ │ │ ├── polar_chart.html │ │ │ ├── radar_chart.html │ │ │ ├── scatter_line_chart.html │ │ │ └── time_series_chart.html │ │ ├── home.html │ │ └── sub │ │ │ ├── docs.html │ │ │ ├── examples-async.html │ │ │ ├── examples.html │ │ │ └── nav.html │ ├── tests.py │ └── views.py ├── demo_project │ ├── __init__.py │ ├── settings │ │ ├── __init__.py │ │ ├── base.py │ │ ├── local.py │ │ └── prod.py │ ├── urls.py │ └── wsgi.py ├── jchart └── manage.py ├── jchart ├── __init__.py ├── apps.py ├── config.py ├── models.py ├── templates │ └── charting │ │ ├── async │ │ ├── chart.html │ │ ├── html.html │ │ └── js.html │ │ ├── chart.html │ │ ├── html.html │ │ └── js.html ├── templatetags │ ├── __init__.py │ └── jchart.py ├── tests.py └── views │ ├── __init__.py │ └── mixins.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | # db 92 | db.sqlite3 93 | 94 | #staticfiles 95 | staticfiles 96 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - 2.7 5 | - 3.4 6 | - 3.5 7 | env: 8 | - DJANGO="django>=1.10.2" 9 | - DJANGO="django>=1.9.3,<1.10.0" 10 | - DJANGO="django>=1.8.10,<1.9.0" 11 | - DJANGO="django==1.7.11" 12 | - DJANGO="django==1.6.11" 13 | - DJANGO="django==1.5.12" 14 | matrix: 15 | exclude: 16 | - python: 3.5 17 | env: DJANGO="django==1.7.11" 18 | - python: 3.5 19 | env: DJANGO="django==1.6.11" 20 | - python: 3.5 21 | env: DJANGO="django==1.5.12" 22 | install: 23 | - travis_retry pip install pep8==1.7.0 24 | - travis_retry pip install selenium 25 | - travis_retry pip install python-coveralls 26 | - travis_retry pip install $DJANGO 27 | script: 28 | - coverage run --source=jchart --omit=**/apps.py demo_project/manage.py test jchart demo 29 | after_script: 30 | # ensure we validate against pep standards 31 | - "pep8 --exclude=migrations --ignore=E501,E225,W293,E126,E123,E121 jchart" 32 | after_success: 33 | - travis_retry coveralls -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | ### Version 0.5.0 4 | 5 | * Better Django backwards compatibility 6 | * Improved sizing of the chart container html element 7 | 8 | ### Version 0.4.0 9 | 10 | * Added custom chart configuration options through the use of the `options` class field 11 | 12 | ### Version 0.3.2 13 | 14 | * `get_datasets` method is type checked. When an instance of the `Chart` class returns somtehing different than a list this results in a ValueError 15 | 16 | ### Version 0.3.1 17 | 18 | * Added support for several bar chart specific Axes config properties (e.g. barPercentage) 19 | 20 | ### Version 0.3.0 21 | 22 | * Added support for non async chart parameterization with `render_chart` template tag 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Matthisk Heimensen 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | recursive-include jchart/templates * -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn demo_project.wsgi --pythonpath demo_project --log-file - -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # django-jchart 2 | 3 | [![Build Status](https://travis-ci.org/matthisk/django-jchart.svg?branch=master)](https://travis-ci.org/matthisk/django-jchart) [![Coverage Status](https://coveralls.io/repos/github/matthisk/django-jchart/badge.svg?branch=master)](https://coveralls.io/github/matthisk/django-jchart?branch=master) 4 | [![PyPI version](https://badge.fury.io/py/django-jchart.svg)](https://badge.fury.io/py/django-jchart) 5 | 6 | This Django app enables you to configure and render Chart.JS charts directly from your Django codebase. Charts can than either be rendered directly into your Django template or served asynchronously to the webbrowser. 7 | 8 | - Authors: Matthisk Heimensen 9 | - Licence: BSD 10 | - Compatibility: Django 1.5+, python2.7 up to python3.5 11 | - Project URL: https://github.com/matthisk/django-jchart 12 | 13 | ### Getting Started 14 | 15 | install ``django-jchart`` 16 | 17 | ``` 18 | pip install django-jchart 19 | ``` 20 | 21 | Add ``django-jchart`` to your installed apps. 22 | 23 | ``` 24 | INSTALLED_APPS = ( 25 | '...', 26 | 'jchart', 27 | ) 28 | ``` 29 | 30 |

31 | Enable template loading from app folders by adding the following property to your TEMPLATES django configuration: 32 |

33 | 34 |
TEMPLATES = [
 35 |     {
 36 |         'BACKEND': 'django.template.backends.django.DjangoTemplates',
 37 |         'APP_DIRS': True,
 38 |         # ...
 39 |     }]
 40 | 
41 | 42 |

43 | 44 | Frontend Dependencies 45 | 46 |

47 | 48 |

49 | For the charts to be rendered inside the browser you will 50 | need to include the Chart.JS library. Add the following 51 | HTML before your closing </body> tag: 52 |

53 | 54 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.min.js"></script>
55 | 56 |

57 | If you want to make use of asynchronous loading charts 58 | you will also need to include jQuery: 59 |

60 | 61 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
62 | 63 |

64 | 65 | The Chart Class 66 | 67 |

68 | 69 |

70 | At the heart of this charting library lies the Chart class. This class describes a chart and defines which data it should display. The chart's 'class fields' map to Chart.JS options which describe how the chart should render and behave. By overriding the get_datasets method on your Chart instance you can define which data should be displayed. 71 |

72 | 73 |

74 | To define which type of chart you want to render (e.g. a line or bar chart), your chart class should set its class field chart_type to one of "line", "bar", "radar", "polarArea", "pie", or "bubble". A chart class without this field is invalid and initialization will result in an ImproperlyConfigured exception. 75 |

76 | 77 |
from jchart import Chart
 78 | 
 79 | class LineChart(Chart):
 80 |     chart_type = 'line'
81 | 82 |
83 | 84 | get_datasets 85 | 86 |
87 | 88 |

89 | The get_datasets method should return a list of datasets this chart should display. Where a dataset is a dictionary with key/value configuration pairs (see the Chart.JS documentation). 90 |

91 | 92 |
from jchart import Chart
 93 | 
 94 | class LineChart(Chart):
 95 |     chart_type = 'line'
 96 | 
 97 |     def get_datasets(self, **kwargs):
 98 |         return [{
 99 |             'label': "My Dataset",
100 |             'data': [69, 30, 45, 60, 55]
101 |         }]
102 | 103 |
104 | 105 | get_labels 106 | 107 |
108 | 109 |

110 | This method allows you to set the Chart.JS data.labels parameter. Which allows you to configure categorical axes. For an example on how to use this feature see this pie chart. 111 |

112 | 113 |
from jchart import Chart
114 | 
115 | class PieChart(Chart):
116 |     chart_type = 'pie'
117 | 
118 |     def get_labels(self, **kwargs):
119 |         return ['Red', 'Blue']
120 | 121 |

122 | 123 | Configuring Charts 124 | 125 |

126 | 127 |

128 | A chart can be configured through the following class fields: 129 |

130 | 131 |

132 | scales 133 | layout 134 | title 135 | legend 136 | tooltips 137 | hover 138 | animation 139 | elements 140 | responsive 141 |

142 | 143 |

144 | All of these fields map to the same key in the Chart.JS 'options' object. For instance, if you wanted to create a chart that does not render responsively you would set the responsive class field to false: 145 |

146 | 147 |
from jchart import Chart
148 | 
149 | class UnresponsiveLineChart(Chart):
150 |     chart_type = 'line'
151 |     responsive = False
152 |     # ...
153 | 154 |

155 | Most of these class fields require either a list of dicitonaries or a dictionary. With the exception of responsive which should be a boolean value. Be sure to read the Chart.JS documentation on how to use these configuration options. 156 |

157 | 158 |

159 | For your convenience there are some methods located in jchart.config which can be used to produce correct dictionaries to configure Chart.JS properties. Most of these methods only serve as a validation step for your input configuration but some can also transform their input. Let's take a look at an example, how would you configure the X-Axis so it is not to be displayed: 160 |

161 | 162 |
from jchart import Chart
163 | from jchart.config import Axes
164 | 
165 | class LineChart(Chart):
166 |     chart_type = 'line'
167 |     scales = {
168 |         'xAxes': [Axes(display=False)],
169 |     }
170 | 171 |

172 | jchart.config also contains a method to create dataset configuration dictionaries. One of the advantages of using this method is that it includes a special property color which can be used to automatically set the values for: 'backgroundColor', 'pointBackgroundColor', 'borderColor', 'pointBorderColor', and 'pointStrokeColor'. 173 |

174 | 175 |
from jchart import Chart
176 | from jchart.config import Axes
177 | 
178 | class LineChart(Chart):
179 |     chart_type = 'line'
180 |     
181 |     def get_datasets(self, **kwargs):
182 |         return [DataSet(color=(255, 255, 255), data=[])]
183 | 184 |

185 | The jchart.config module contains methods for the properties listed below. Keep in mind that you are in no way obligated to use these methods, you could also supply Python dictionaries in the place of these method calls. 186 | 187 |

Available configuration methods:
188 | Axes, ScaleLabel, Tick, DataSet, Tooltips, Legend, LegendLabel, Title, Hover, InteractionModes, Animation, Element, ElementArc, ElementLine, ElementPoint, ElementRectangle 189 |

190 | 191 |

192 |

Custom configuration options
193 | There is another special class field named options this has to be set to a dictionary and can be used to set any other Chart.JS configuration values that are not configurable through a predefined class field (e.g. maintainAspectRatio). The class fields have precedence over any configuration applied through the options dictionary. 194 |

195 | 196 |
from jchart import Chart
197 | 
198 | class OptionsChart(Chart):
199 |     chart_type = 'line'
200 |     options = {
201 |         'maintainAspectRatio': True
202 |     }
203 |     # ...
204 | 
205 | 206 |

207 | 208 | Rendering Charts 209 | 210 |

211 | 212 |

213 | Chart instances can be passed to your Django template context. 214 | Inside the template you can invoke the method `as_html` on the 215 | chart instance to render the chart. 216 |

217 | 218 |
# LineChart is a class inheriting from jchart.Chart
219 | 
220 | def some_view(request):
221 |     render(request, 'template.html', {
222 |         'line_chart': LineChart(),
223 |     })
224 | 225 |

226 | The following code is an example of how to render this line chart 227 | inside your html template: 228 |

229 | 230 |
{{ line_chart.as_html }}
231 | 232 |

233 | 234 | Asynchronous Charts 235 | 236 |

237 | 238 |

239 | When rendering the chart directly into your HTML template, all the data needed for the chart is transmitted on the page's HTTP request. It is also possible to load the chart (and its required data) asynchronous. 240 |

241 | 242 |

243 | To do this we need to setup a url endpoint from which to load the chart's data. There is a classmethod available on jchart.views.ChartView to automatically create a view which exposes the chart's configuration data as JSON on a HTTP get request: 244 |

245 | 246 |
from jchart.views import ChartView
247 | 
248 | # LineChart is a class inheriting from jchart.Chart
249 | line_chart = LineChart()
250 | 
251 | urlpatterns = [
252 |     url(r'^charts/line_chart/$', ChartView.from_chart(line_chart), name='line_chart'),
253 | ]
254 | 255 |

256 | You can use a custom templatetag inside your Django template to load this chart asynchronously. The custom tag behaves like the Django url templatetag and any positional or keyword arguments supplied to it are used to resolve the url for the given url name. In this example the url does not require any url parameters 257 | to be resolved: 258 |

259 | 260 |
{% load jchart %}
261 | 
262 | {% render_chart 'line_chart' %}
263 | 
264 | 265 |

266 | This tag will be expanded into the following JS/HTML code: 267 |

268 | 269 |
<canvas class="chart" id="unique-chart-id">
270 | </canvas>
271 | 
272 | <script type="text/javascript">
273 | window.addEventListener("DOMContentLoaded", function() {
274 |     $.get('/charts/line_chart/', function(configuration) {
275 |         var ctx = document.getElementById("unique-chart-id").getContext("2d");    
276 | 
277 |         new Chart(ctx, configuration);
278 |     });
279 | });
280 | </script>
281 | 282 |

283 | 284 | Chart Parameterization 285 | 286 |

287 | 288 |

289 | It can often be useful to reuse the same chart for different datasets. This can either be done by subclassing an existing chart class and overriding its get_datasets method. But there is another way to do this. Any arguments given to the as_html method are supplied to your get_datasets method. This makes it possible to parameterize the output of get_datasets 290 |

291 | 292 |

293 | Let's have a look at an example. Imagine we have price point data stored in a model called Price and this model has a field called currency_type. We could render the chart for different currency types by accepting the value for this field as a parameter to get_datasets. 294 |

295 | 296 |
from jchart import Chart
297 | from core.models import Price
298 | 
299 | class PriceChart(Chart):
300 |     chart_type = 'line'
301 | 
302 |     def get_datasets(self, currency_type):
303 |         prices = Price.objects.filter(currency_type=currency_type)
304 | 
305 |         data = [{'x': price.date, 'y': price.point} for price in prices]
306 | 
307 |         return [DataSet(data=data)]
308 | 309 |

310 | If we supply an instance of this chart to the context of our template, we could use this to render two different charts. This is done by using the render_chart template tag to supply additional parameters to the get_datasets method: 311 |

312 | 313 |
{% render_chart price_chart 'euro' %}
314 | 
315 | {% render_chart price_chart 'dollar' %}
316 | 317 |

318 | For asynchronous charts any url parameters are passed to the get_datasets method. 319 |

320 | 321 |
from jchart.views import ChartView
322 | from .charts import PriceChart
323 | 
324 | price_chart = PriceChart()
325 | 
326 | urlpatterns = [
327 |     url(r'^currency_chart/(?P<>\w+)/$',
328 |         ChartView.from_chart(price_chart))
329 | ]
330 | 331 |

332 | To render this chart asynchronously we have to supply the url parameter as a second argument to the render_chart template tag, like so: 333 |

334 | 335 |
{% load jchart %}
336 | 
337 | {% render_chart 'price_chart' 'euro' %}
338 | 
339 | {% render_chart 'price_chart' 'dollar' %}
340 | 341 | 342 | ### ToDO 343 | 344 | * Composable datasources (instead of having to rely on inheritance) 345 | * Compare django-jchart to other Django chartig libraries (in the readme) 346 | 347 | 348 | ### Contributing 349 | 350 | #### Releasing 351 | 352 | * To release update the version of the package in `setup.py`. 353 | * Add release to `CHANGELOG.md`. 354 | * Run commands: 355 | 356 | ``` 357 | python setup.py sdist bdist_wheel --universal 358 | twine upload dist/* 359 | ``` 360 | 361 | * Add git tag to commit -------------------------------------------------------------------------------- /demo_project/chart: -------------------------------------------------------------------------------- 1 | ../chart -------------------------------------------------------------------------------- /demo_project/demo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthisk/django-jchart/2e224f061cdb5804814a6031c4d23899408d62e4/demo_project/demo/__init__.py -------------------------------------------------------------------------------- /demo_project/demo/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /demo_project/demo/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class DemoConfig(AppConfig): 7 | name = 'demo' 8 | -------------------------------------------------------------------------------- /demo_project/demo/charts.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from datetime import datetime, timedelta 3 | 4 | from jchart import Chart 5 | from jchart.config import Axes, DataSet, rgba 6 | 7 | 8 | class TimeSeriesChart(Chart): 9 | chart_type = 'line' 10 | scales = { 11 | 'xAxes': [Axes(type='time', position='bottom')], 12 | } 13 | 14 | def get_datasets(self, *args, **kwargs): 15 | data = [{'y': 0, 'x': '2017-01-02T00:00:00'}, {'y': 1, 'x': '2017-01-03T00:00:00'}, {'y': 4, 'x': '2017-01-04T00:00:00'}, {'y': 9, 'x': '2017-01-05T00:00:00'}, {'y': 16, 'x': '2017-01-06T00:00:00'}, {'y': 25, 'x': '2017-01-07T00:00:00'}, {'y': 36, 'x': '2017-01-08T00:00:00'}, {'y': 49, 'x': '2017-01-09T00:00:00'}, {'y': 64, 'x': '2017-01-10T00:00:00'}, {'y': 81, 'x': '2017-01-11T00:00:00'}, {'y': 100, 'x': '2017-01-12T00:00:00'}, {'y': 121, 'x': '2017-01-13T00:00:00'}, {'y': 144, 'x': '2017-01-14T00:00:00'}, {'y': 169, 'x': '2017-01-15T00:00:00'}, {'y': 196, 'x': '2017-01-16T00:00:00'}, {'y': 225, 'x': '2017-01-17T00:00:00'}, {'y': 256, 'x': '2017-01-18T00:00:00'}, {'y': 289, 'x': '2017-01-19T00:00:00'}, {'y': 324, 'x': '2017-01-20T00:00:00'}, {'y': 361, 'x': '2017-01-21T00:00:00'}, {'y': 400, 'x': '2017-01-22T00:00:00'}, {'y': 441, 'x': '2017-01-23T00:00:00'}, {'y': 484, 'x': '2017-01-24T00:00:00'}, {'y': 529, 'x': '2017-01-25T00:00:00'}, {'y': 576, 'x': '2017-01-26T00:00:00'}, {'y': 625, 'x': '2017-01-27T00:00:00'}, {'y': 676, 'x': '2017-01-28T00:00:00'}, {'y': 729, 'x': '2017-01-29T00:00:00'}, {'y': 784, 'x': '2017-01-30T00:00:00'}, {'y': 841, 'x': '2017-01-31T00:00:00'}, {'y': 900, 'x': '2017-02-01T00:00:00'}] 16 | 17 | return [DataSet( 18 | type='line', 19 | label='Time Series', 20 | data=data, 21 | )] 22 | 23 | 24 | class ScatterLineChart(Chart): 25 | chart_type = 'line' 26 | scales = { 27 | 'xAxes': [Axes(type='time', position='bottom')], 28 | } 29 | 30 | def get_datasets(self, **kwargs): 31 | data_scatter = [{'y': 24, 'x': '2017-01-01T21:00:00'}, {'y': 1, 'x': '2017-01-02T03:00:00'}, {'y': 7, 'x': '2017-01-02T14:00:00'}, {'y': 7, 'x': '2017-01-03T08:00:00'}, {'y': 13, 'x': '2017-01-04T00:00:00'}, {'y': 7, 'x': '2017-01-04T07:00:00'}, {'y': 19, 'x': '2017-01-05T01:00:00'}, {'y': 18, 'x': '2017-01-05T15:00:00'}, {'y': 14, 'x': '2017-01-06T00:00:00'}, {'y': 2, 'x': '2017-01-06T07:00:00'}, {'y': 18, 'x': '2017-01-07T06:00:00'}, {'y': 4, 'x': '2017-01-07T07:00:00'}, {'y': 21, 'x': '2017-01-07T21:00:00'}, {'y': 5, 'x': '2017-01-08T00:00:00'}, {'y': 16, 'x': '2017-01-08T07:00:00'}, {'y': 14, 'x': '2017-01-08T11:00:00'}, {'y': 21, 'x': '2017-01-09T04:00:00'}, {'y': 25, 'x': '2017-01-09T20:00:00'}, {'y': 9, 'x': '2017-01-10T15:00:00'}, {'y': 25, 'x': '2017-01-11T10:00:00'}, {'y': 17, 'x': '2017-01-11T17:00:00'}, {'y': 10, 'x': '2017-01-12T11:00:00'}, {'y': 7, 'x': '2017-01-12T17:00:00'}, {'y': 11, 'x': '2017-01-12T22:00:00'}, {'y': 2, 'x': '2017-01-13T04:00:00'}, {'y': 13, 'x': '2017-01-13T12:00:00'}, {'y': 12, 'x': '2017-01-14T12:00:00'}, {'y': 16, 'x': '2017-01-15T10:00:00'}, {'y': 15, 'x': '2017-01-16T00:00:00'}, {'y': 23, 'x': '2017-01-16T17:00:00'}, {'y': 15, 'x': '2017-01-17T02:00:00'}, {'y': 22, 'x': '2017-01-17T12:00:00'}, {'y': 18, 'x': '2017-01-17T15:00:00'}, {'y': 16, 'x': '2017-01-18T14:00:00'}, {'y': 7, 'x': '2017-01-19T09:00:00'}, {'y': 10, 'x': '2017-01-20T02:00:00'}, {'y': 7, 'x': '2017-01-20T13:00:00'}, {'y': 5, 'x': '2017-01-20T17:00:00'}, {'y': 15, 'x': '2017-01-20T20:00:00'}, {'y': 5, 'x': '2017-01-21T06:00:00'}, {'y': 13, 'x': '2017-01-21T18:00:00'}, {'y': 20, 'x': '2017-01-22T13:00:00'}, {'y': 20, 'x': '2017-01-22T16:00:00'}, {'y': 23, 'x': '2017-01-23T15:00:00'}, {'y': 3, 'x': '2017-01-23T20:00:00'}, {'y': 20, 'x': '2017-01-24T15:00:00'}, {'y': 19, 'x': '2017-01-24T16:00:00'}, {'y': 1, 'x': '2017-01-25T00:00:00'}, {'y': 3, 'x': '2017-01-25T02:00:00'}, {'y': 22, 'x': '2017-01-25T23:00:00'}, {'y': 6, 'x': '2017-01-26T19:00:00'}, {'y': 17, 'x': '2017-01-27T10:00:00'}, {'y': 7, 'x': '2017-01-28T09:00:00'}, {'y': 23, 'x': '2017-01-29T05:00:00'}, {'y': 19, 'x': '2017-01-29T17:00:00'}, {'y': 16, 'x': '2017-01-30T08:00:00'}, {'y': 19, 'x': '2017-01-30T09:00:00'}, {'y': 23, 'x': '2017-01-31T06:00:00'}, {'y': 18, 'x': '2017-02-01T05:00:00'}] 32 | data_line = [{'y': 20, 'x': '2017-01-02T00:00:00'}, {'y': 3, 'x': '2017-01-03T00:00:00'}, {'y': 2, 'x': '2017-01-04T00:00:00'}, {'y': 18, 'x': '2017-01-05T00:00:00'}, {'y': 19, 'x': '2017-01-06T00:00:00'}, {'y': 20, 'x': '2017-01-07T00:00:00'}, {'y': 5, 'x': '2017-01-08T00:00:00'}, {'y': 23, 'x': '2017-01-09T00:00:00'}, {'y': 18, 'x': '2017-01-10T00:00:00'}, {'y': 5, 'x': '2017-01-11T00:00:00'}, {'y': 6, 'x': '2017-01-12T00:00:00'}, {'y': 2, 'x': '2017-01-13T00:00:00'}, {'y': 23, 'x': '2017-01-14T00:00:00'}, {'y': 3, 'x': '2017-01-15T00:00:00'}, {'y': 24, 'x': '2017-01-16T00:00:00'}, {'y': 10, 'x': '2017-01-17T00:00:00'}, {'y': 9, 'x': '2017-01-18T00:00:00'}, {'y': 11, 'x': '2017-01-19T00:00:00'}, {'y': 10, 'x': '2017-01-20T00:00:00'}, {'y': 2, 'x': '2017-01-21T00:00:00'}, {'y': 16, 'x': '2017-01-22T00:00:00'}, {'y': 24, 'x': '2017-01-23T00:00:00'}, {'y': 3, 'x': '2017-01-24T00:00:00'}, {'y': 13, 'x': '2017-01-25T00:00:00'}, {'y': 7, 'x': '2017-01-26T00:00:00'}, {'y': 10, 'x': '2017-01-27T00:00:00'}, {'y': 7, 'x': '2017-01-28T00:00:00'}, {'y': 13, 'x': '2017-01-29T00:00:00'}, {'y': 1, 'x': '2017-01-30T00:00:00'}, {'y': 10, 'x': '2017-01-31T00:00:00'}, {'y': 7, 'x': '2017-02-01T00:00:00'}] 33 | 34 | return [ 35 | DataSet(type='line', 36 | label='Scatter', 37 | showLine=False, 38 | data=data_scatter), 39 | DataSet(type='line', 40 | label='Line', 41 | borderColor='red', 42 | data=data_line) 43 | ] 44 | 45 | 46 | class BarChart(Chart): 47 | chart_type = 'bar' 48 | 49 | def get_labels(self, **kwargs): 50 | return ["January", "February", "March", "April", 51 | "May", "June", "July"] 52 | 53 | def get_datasets(self, **kwargs): 54 | data = [10, 15, 29, 30, 5, 10, 22] 55 | colors = [ 56 | rgba(255, 99, 132, 0.2), 57 | rgba(54, 162, 235, 0.2), 58 | rgba(255, 206, 86, 0.2), 59 | rgba(75, 192, 192, 0.2), 60 | rgba(153, 102, 255, 0.2), 61 | rgba(255, 159, 64, 0.2) 62 | ] 63 | 64 | return [DataSet(label='Bar Chart', 65 | data=data, 66 | borderWidth=1, 67 | backgroundColor=colors, 68 | borderColor=colors)] 69 | 70 | 71 | class RadarChart(Chart): 72 | chart_type = 'radar' 73 | 74 | def get_labels(self): 75 | return ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"] 76 | 77 | def get_datasets(self, **kwargs): 78 | return [DataSet(label="My First dataset", 79 | color=(179, 181, 198), 80 | data=[65, 59, 90, 81, 56, 55, 40]), 81 | DataSet(label="My Second dataset", 82 | color=(255, 99, 132), 83 | data=[28, 48, 40, 19, 96, 27, 100]) 84 | ] 85 | 86 | 87 | class PolarChart(Chart): 88 | chart_type = 'polarArea' 89 | 90 | def get_labels(self, **kwargs): 91 | return ["Red", "Green", "Yellow", "Grey", "Blue"] 92 | 93 | def get_datasets(self, **kwargs): 94 | return [DataSet(label="My DataSet", 95 | data=[11, 16, 7, 3, 14], 96 | backgroundColor=[ 97 | "#FF6384", 98 | "#4BC0C0", 99 | "#FFCE56", 100 | "#E7E9ED", 101 | "#36A2EB" 102 | ]) 103 | ] 104 | 105 | 106 | class PieChart(Chart): 107 | chart_type = 'pie' 108 | 109 | def get_labels(self, **kwargs): 110 | return ["Red", "Blue", "Yellow"] 111 | 112 | def get_datasets(self, **kwargs): 113 | data = [300, 50, 100] 114 | colors = [ 115 | "#FF6384", 116 | "#36A2EB", 117 | "#FFCE56" 118 | ] 119 | return [DataSet(data=data, 120 | label="My Pie Data", 121 | backgroundColor=colors, 122 | hoverBackgroundColor=colors)] 123 | 124 | 125 | class BubbleChart(Chart): 126 | chart_type = 'bubble' 127 | 128 | def get_datasets(self, **kwargs): 129 | data = [{ 130 | 'x': randint(1, 10), 131 | 'y': randint(1, 25), 132 | 'r': randint(1, 10), 133 | } for i in range(25)] 134 | 135 | return [DataSet(label="First DataSet", 136 | data=data, 137 | backgroundColor='#FF6384', 138 | hoverBackgroundColor='#FF6384')] 139 | -------------------------------------------------------------------------------- /demo_project/demo/context_processors.py: -------------------------------------------------------------------------------- 1 | def url_name(request): 2 | return dict(url_name=request.resolver_match.url_name) 3 | -------------------------------------------------------------------------------- /demo_project/demo/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthisk/django-jchart/2e224f061cdb5804814a6031c4d23899408d62e4/demo_project/demo/migrations/__init__.py -------------------------------------------------------------------------------- /demo_project/demo/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /demo_project/demo/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthisk/django-jchart/2e224f061cdb5804814a6031c4d23899408d62e4/demo_project/demo/static/favicon.ico -------------------------------------------------------------------------------- /demo_project/demo/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | position: relative; 3 | } 4 | 5 | p { 6 | margin: 1em 0; 7 | } 8 | 9 | h4, h5 { 10 | margin-top: 1.5em; 11 | } 12 | 13 | .nav-title { 14 | padding-right: 17px; 15 | text-align: right; 16 | } 17 | 18 | canvas { 19 | margin-bottom: 20px; 20 | } 21 | 22 | .section-title.first { 23 | margin-top: 10px; 24 | } 25 | 26 | .fragment-link { 27 | color: #333538; 28 | position: relative; 29 | } 30 | 31 | .fragment-link:hover, 32 | .fragment-link:active, 33 | .fragment-link:focus { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | .fragment-link:hover:before { 39 | color: #333538; 40 | } 41 | 42 | .fragment-link:before { 43 | content: '#'; 44 | color: rgba(51, 53, 56, 0.1); 45 | transition: color 100ms ease-out; 46 | position: absolute; 47 | left: -1.4rem; 48 | } 49 | 50 | 51 | /* sidebar */ 52 | .bs-docs-sidebar { 53 | text-align: right; 54 | } 55 | 56 | /* all links */ 57 | .bs-docs-sidebar .nav>li>a { 58 | font-size: 14px; 59 | font-weight: 500; 60 | color: black; 61 | border-right: 2px solid rgba(255, 255, 255, 0); 62 | } 63 | 64 | /* nested links */ 65 | .bs-docs-sidebar .nav .nav>li>a { 66 | font-size: 12px; 67 | font-weight: 400; 68 | padding: 4px 15px 4px 0; 69 | font-size: 13px; 70 | } 71 | 72 | /* active & hover links */ 73 | .bs-docs-sidebar .nav>.active>a, 74 | .bs-docs-sidebar .nav>li>a:hover, 75 | .bs-docs-sidebar .nav>li>a:focus { 76 | background-color: transparent; 77 | color: rgba(255, 99, 132, 1.0); 78 | } 79 | /* all active links */ 80 | .bs-docs-sidebar .nav>.active>a, 81 | .bs-docs-sidebar .nav>.active:hover>a, 82 | .bs-docs-sidebar .nav>.active:focus>a { 83 | font-weight: 700; 84 | color: rgba(255, 99, 132, 1.0); 85 | border-right: 2px solid rgb(255, 99, 132); 86 | } 87 | /* nested active links */ 88 | .bs-docs-sidebar .nav .nav>.active>a, 89 | .bs-docs-sidebar .nav .nav>.active:hover>a, 90 | .bs-docs-sidebar .nav .nav>.active:focus>a { 91 | font-weight: 500; 92 | } 93 | 94 | /* hide inactive nested list */ 95 | .bs-docs-sidebar .nav ul.nav { 96 | display: none; 97 | } 98 | /* show active nested list */ 99 | .bs-docs-sidebar .nav>.active>ul.nav { 100 | display: block; 101 | } -------------------------------------------------------------------------------- /demo_project/demo/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | Django-JChart - Django Chart.js charting library 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% if not debug %} 18 | 27 | {% endif %} 28 | 29 | 30 |
31 |
32 |
33 | 36 |
37 | 38 |
39 | {% block content %} 40 | {% endblock %} 41 |
42 |
43 |
44 | 45 | Fork me on GitHub 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/bar_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class BarChart(Chart):
 6 |     chart_type = 'bar'
 7 | 
 8 |     def get_labels(self, **kwargs):
 9 |         return ["January", "February", "March", "April",
10 |                 "May", "June", "July"]
11 | 
12 |     def get_datasets(self, **kwargs):
13 |         data = [10, 15, 29, 30, 5, 10, 22]
14 |         colors = [
15 |             rgba(255, 99, 132, 0.2),
16 |             rgba(54, 162, 235, 0.2),
17 |             rgba(255, 206, 86, 0.2),
18 |             rgba(75, 192, 192, 0.2),
19 |             rgba(153, 102, 255, 0.2),
20 |             rgba(255, 159, 64, 0.2)
21 |         ]
22 | 
23 |         return [DataSet(label='Bar Chart',
24 |                         data=data,
25 |                         borderWidth=1,
26 |                         backgroundColor=colors,
27 |                         borderColor=colors)]{% if async %}
28 | 
29 | # urls.py
30 | from demo import charts
31 | from jchart.views import ChartView
32 | 
33 | urlpatterns = [
34 |     url(r'^charts/bar_chart/$',
35 |         ChartView.from_chart(charts.BarChart()),
36 |         name='bar_chart'),
37 | ]
38 | 
39 | # Template.html
40 | {% render_chart 'bar_chart' %}
41 | {% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/bubble_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class BubbleChart(Chart):
 6 |     chart_type = 'bubble'
 7 | 
 8 |     def get_datasets(self, **kwargs):
 9 |         data = [{
10 |             'x': randint(1, 10),
11 |             'y': randint(1, 25),
12 |             'r': randint(1, 10),
13 |         } for i in range(25)]
14 | 
15 |         return [DataSet(label="First DataSet",
16 |                         data=data,
17 |                         backgroundColor='#FF6384',
18 |                         hoverBackgroundColor='#FF6384')]{% if async %}
19 | 
20 | # urls.py
21 | from demo import charts
22 | from jchart.views import ChartView
23 | 
24 | urlpatterns = [
25 |     url(r'^charts/bubble_chart/$',
26 |         ChartView.from_chart(charts.BubbleChart()),
27 |         name='bubble_chart'),
28 | ]
29 | 
30 | # Template.html
31 | {% render_chart 'bubble_chart' %}
32 | {% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/pie_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class PieChart(Chart):
 6 |     chart_type = 'pie'
 7 | 
 8 |     def get_labels(self, **kwargs):
 9 |         return ["Red", "Blue", "Yellow"]
10 | 
11 |     def get_datasets(self, **kwargs):
12 |         data = [300, 50, 100]
13 |         colors = [
14 |             "#FF6384",
15 |             "#36A2EB",
16 |             "#FFCE56"
17 |         ]
18 |         return [DataSet(data=data,
19 |                         label="My Pie Data",
20 |                         backgroundColor=colors,
21 |                         hoverBackgroundColor=colors)]{% if async %}
22 | 
23 | # urls.py
24 | from demo import charts
25 | from jchart.views import ChartView
26 | 
27 | urlpatterns = [
28 |     url(r'^charts/pie_chart/$',
29 |         ChartView.from_chart(charts.PieChart()),
30 |         name='pie_chart'),
31 | ]
32 | 
33 | # Template.html
34 | {% render_chart 'pie_chart' %}
35 | {% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/polar_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class PolarChart(Chart):
 6 |     chart_type = 'polarArea'
 7 | 
 8 |     def get_labels(self, **kwargs):
 9 |         return ["Red", "Green", "Yellow", "Grey", "Blue"]
10 | 
11 |     def get_datasets(self, **kwargs):
12 |         return [DataSet(label="My DataSet",
13 |                         data=[11, 16, 7, 3, 14],
14 |                         backgroundColor=[
15 |                             "#FF6384",
16 |                             "#4BC0C0",
17 |                             "#FFCE56",
18 |                             "#E7E9ED",
19 |                             "#36A2EB"
20 |                         ])
21 |                 ]{% if async %}
22 | 
23 | # urls.py 
24 | from demo import charts
25 | from jchart.views import ChartView
26 | 
27 | urlpatterns = [
28 |     url(r'^charts/polar_chart/$',
29 |         ChartView.from_chart(charts.PolarChart()),
30 |         name='polar_chart')
31 | ]
32 | 
33 | # Template.html
34 | {% render_chart 'polar_chart' %}
35 | {% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/radar_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class RadarChart(Chart):
 6 |     chart_type = 'radar'
 7 | 
 8 |     def get_labels(self):
 9 |         return ["Eating", "Drinking", "Sleeping", "Designing", "Coding", "Cycling", "Running"]
10 | 
11 |     def get_datasets(self, **kwargs):
12 |         return [DataSet(label="My First dataset",
13 |                         color=(179, 181, 198),
14 |                         data=[65, 59, 90, 81, 56, 55, 40]),
15 |                 DataSet(label="My Second dataset",
16 |                         color=(255, 99, 132),
17 |                         data=[28, 48, 40, 19, 96, 27, 100])
18 |                 ]{% if async %}
19 | 
20 | # urls.py
21 | from demo import charts
22 | from jchart.views import ChartView
23 | 
24 | urlpatterns = [
25 |     url(r'^charts/radar_chart/$',
26 |         ChartView.from_chart(charts.RadarChart()),
27 |         name='radar_chart')
28 | ]
29 | 
30 | # Template.html
31 | {% render_chart 'radar_chart' %}
32 | {% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/scatter_line_chart.html: -------------------------------------------------------------------------------- 1 |
# charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class ScatterLineChart(Chart):
 6 |     chart_type = 'line'
 7 |     scales = {
 8 |         'xAxes': [Axes(type='time', position='bottom')],
 9 |     }
10 | 
11 |     def get_datasets(self, **kwargs):
12 |         data_scatter = [{'y': 24, 'x': '2017-01-01T21:00:00'}, {'y': 1, 'x': '2017-01-02T03:00:00'}, {'y': 7, 'x': '2017-01-02T14:00:00'}, {'y': 7, 'x': '2017-01-03T08:00:00'}, {'y': 13, 'x': '2017-01-04T00:00:00'}, {'y': 7, 'x': '2017-01-04T07:00:00'}, {'y': 19, 'x': '2017-01-05T01:00:00'}, {'y': 18, 'x': '2017-01-05T15:00:00'}, {'y': 14, 'x': '2017-01-06T00:00:00'}, {'y': 2, 'x': '2017-01-06T07:00:00'}, {'y': 18, 'x': '2017-01-07T06:00:00'}, {'y': 4, 'x': '2017-01-07T07:00:00'}, {'y': 21, 'x': '2017-01-07T21:00:00'}, {'y': 5, 'x': '2017-01-08T00:00:00'}, {'y': 16, 'x': '2017-01-08T07:00:00'}, {'y': 14, 'x': '2017-01-08T11:00:00'}, {'y': 21, 'x': '2017-01-09T04:00:00'}, {'y': 25, 'x': '2017-01-09T20:00:00'}, {'y': 9, 'x': '2017-01-10T15:00:00'}, {'y': 25, 'x': '2017-01-11T10:00:00'}, {'y': 17, 'x': '2017-01-11T17:00:00'}, {'y': 10, 'x': '2017-01-12T11:00:00'}, {'y': 7, 'x': '2017-01-12T17:00:00'}, {'y': 11, 'x': '2017-01-12T22:00:00'}, {'y': 2, 'x': '2017-01-13T04:00:00'}, {'y': 13, 'x': '2017-01-13T12:00:00'}, {'y': 12, 'x': '2017-01-14T12:00:00'}, {'y': 16, 'x': '2017-01-15T10:00:00'}, {'y': 15, 'x': '2017-01-16T00:00:00'}, {'y': 23, 'x': '2017-01-16T17:00:00'}, {'y': 15, 'x': '2017-01-17T02:00:00'}, {'y': 22, 'x': '2017-01-17T12:00:00'}, {'y': 18, 'x': '2017-01-17T15:00:00'}, {'y': 16, 'x': '2017-01-18T14:00:00'}, {'y': 7, 'x': '2017-01-19T09:00:00'}, {'y': 10, 'x': '2017-01-20T02:00:00'}, {'y': 7, 'x': '2017-01-20T13:00:00'}, {'y': 5, 'x': '2017-01-20T17:00:00'}, {'y': 15, 'x': '2017-01-20T20:00:00'}, {'y': 5, 'x': '2017-01-21T06:00:00'}, {'y': 13, 'x': '2017-01-21T18:00:00'}, {'y': 20, 'x': '2017-01-22T13:00:00'}, {'y': 20, 'x': '2017-01-22T16:00:00'}, {'y': 23, 'x': '2017-01-23T15:00:00'}, {'y': 3, 'x': '2017-01-23T20:00:00'}, {'y': 20, 'x': '2017-01-24T15:00:00'}, {'y': 19, 'x': '2017-01-24T16:00:00'}, {'y': 1, 'x': '2017-01-25T00:00:00'}, {'y': 3, 'x': '2017-01-25T02:00:00'}, {'y': 22, 'x': '2017-01-25T23:00:00'}, {'y': 6, 'x': '2017-01-26T19:00:00'}, {'y': 17, 'x': '2017-01-27T10:00:00'}, {'y': 7, 'x': '2017-01-28T09:00:00'}, {'y': 23, 'x': '2017-01-29T05:00:00'}, {'y': 19, 'x': '2017-01-29T17:00:00'}, {'y': 16, 'x': '2017-01-30T08:00:00'}, {'y': 19, 'x': '2017-01-30T09:00:00'}, {'y': 23, 'x': '2017-01-31T06:00:00'}, {'y': 18, 'x': '2017-02-01T05:00:00'}]
13 |         data_line = [{'y': 20, 'x': '2017-01-02T00:00:00'}, {'y': 3, 'x': '2017-01-03T00:00:00'}, {'y': 2, 'x': '2017-01-04T00:00:00'}, {'y': 18, 'x': '2017-01-05T00:00:00'}, {'y': 19, 'x': '2017-01-06T00:00:00'}, {'y': 20, 'x': '2017-01-07T00:00:00'}, {'y': 5, 'x': '2017-01-08T00:00:00'}, {'y': 23, 'x': '2017-01-09T00:00:00'}, {'y': 18, 'x': '2017-01-10T00:00:00'}, {'y': 5, 'x': '2017-01-11T00:00:00'}, {'y': 6, 'x': '2017-01-12T00:00:00'}, {'y': 2, 'x': '2017-01-13T00:00:00'}, {'y': 23, 'x': '2017-01-14T00:00:00'}, {'y': 3, 'x': '2017-01-15T00:00:00'}, {'y': 24, 'x': '2017-01-16T00:00:00'}, {'y': 10, 'x': '2017-01-17T00:00:00'}, {'y': 9, 'x': '2017-01-18T00:00:00'}, {'y': 11, 'x': '2017-01-19T00:00:00'}, {'y': 10, 'x': '2017-01-20T00:00:00'}, {'y': 2, 'x': '2017-01-21T00:00:00'}, {'y': 16, 'x': '2017-01-22T00:00:00'}, {'y': 24, 'x': '2017-01-23T00:00:00'}, {'y': 3, 'x': '2017-01-24T00:00:00'}, {'y': 13, 'x': '2017-01-25T00:00:00'}, {'y': 7, 'x': '2017-01-26T00:00:00'}, {'y': 10, 'x': '2017-01-27T00:00:00'}, {'y': 7, 'x': '2017-01-28T00:00:00'}, {'y': 13, 'x': '2017-01-29T00:00:00'}, {'y': 1, 'x': '2017-01-30T00:00:00'}, {'y': 10, 'x': '2017-01-31T00:00:00'}, {'y': 7, 'x': '2017-02-01T00:00:00'}]
14 | 
15 |         return [
16 |             DataSet(type='line',
17 |                     label='Scatter',
18 |                     showLine=False,
19 |                     data=data_scatter),
20 |             DataSet(type='line',
21 |                     label='Line',
22 |                     borderColor='red',
23 |                     data=data_line)
24 |         ]{% if async %}
25 | 
26 | # urls.py
27 | from demo import charts
28 | from jchart.views import ChartView
29 | 
30 | urlpatterns = [
31 |     url(r'^charts/scatter_line_chart/$',
32 |         ChartView.from_chart(charts.ScatterLineChart()),
33 |         name='scatter_line_chart')
34 | ]
35 | 
36 | # Template.html
37 | {% render_chart 'scatter_line_chart' %}{% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/code-examples/time_series_chart.html: -------------------------------------------------------------------------------- 1 |
#charts.py
 2 | from jchart import Chart
 3 | from jchart.config import Axes, DataSet, rgba
 4 | 
 5 | class TimeSeriesChart(Chart):
 6 |     chart_type = 'line'
 7 |     scales = {
 8 |         'xAxes': [Axes(type='time', position='bottom')],
 9 |     }
10 | 
11 |     def get_datasets(self, **kwargs):
12 |         data = [{'y': 0, 'x': '2017-01-02T00:00:00'}, {'y': 1, 'x': '2017-01-03T00:00:00'}, {'y': 4, 'x': '2017-01-04T00:00:00'}, {'y': 9, 'x': '2017-01-05T00:00:00'}, {'y': 16, 'x': '2017-01-06T00:00:00'}, {'y': 25, 'x': '2017-01-07T00:00:00'}, {'y': 36, 'x': '2017-01-08T00:00:00'}, {'y': 49, 'x': '2017-01-09T00:00:00'}, {'y': 64, 'x': '2017-01-10T00:00:00'}, {'y': 81, 'x': '2017-01-11T00:00:00'}, {'y': 100, 'x': '2017-01-12T00:00:00'}, {'y': 121, 'x': '2017-01-13T00:00:00'}, {'y': 144, 'x': '2017-01-14T00:00:00'}, {'y': 169, 'x': '2017-01-15T00:00:00'}, {'y': 196, 'x': '2017-01-16T00:00:00'}, {'y': 225, 'x': '2017-01-17T00:00:00'}, {'y': 256, 'x': '2017-01-18T00:00:00'}, {'y': 289, 'x': '2017-01-19T00:00:00'}, {'y': 324, 'x': '2017-01-20T00:00:00'}, {'y': 361, 'x': '2017-01-21T00:00:00'}, {'y': 400, 'x': '2017-01-22T00:00:00'}, {'y': 441, 'x': '2017-01-23T00:00:00'}, {'y': 484, 'x': '2017-01-24T00:00:00'}, {'y': 529, 'x': '2017-01-25T00:00:00'}, {'y': 576, 'x': '2017-01-26T00:00:00'}, {'y': 625, 'x': '2017-01-27T00:00:00'}, {'y': 676, 'x': '2017-01-28T00:00:00'}, {'y': 729, 'x': '2017-01-29T00:00:00'}, {'y': 784, 'x': '2017-01-30T00:00:00'}, {'y': 841, 'x': '2017-01-31T00:00:00'}, {'y': 900, 'x': '2017-02-01T00:00:00'}]
13 | 
14 |         return [DataSet(
15 |             type='line',
16 |             label='Time Series',
17 |             data=data,
18 |         )]{% if async %}
19 | 
20 | # urls.py
21 | from demo import charts
22 | from jchart.views import ChartView
23 | 
24 | urlpatterns = [
25 |     url(r'^charts/time_series_chart/$',
26 |         ChartView.from_chart(charts.TimeSeriesChart()),
27 |         name='time_series_chart')
28 | ]
29 | 
30 | # Template.html
31 | {% render_chart 'time_series_chart' %}{% endif %}
-------------------------------------------------------------------------------- /demo_project/demo/templates/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block content %} 4 | {% include "sub/docs.html" %} 5 | 6 | {% include "sub/examples.html" %} 7 | 8 | {% include "sub/examples-async.html" %} 9 | {% endblock %} -------------------------------------------------------------------------------- /demo_project/demo/templates/sub/docs.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | Documentation 4 | 5 |

6 | 7 |

8 | This Django app enables you to configure and render Chart.JS charts directly from your Django codebase. These charts can be rendered directly in your html templates, or served asynchronously on a Django url. 9 |

10 | 11 |

12 | 13 | Installation 14 | 15 |

16 | 17 |

18 | install django-jchart 19 |

20 | 21 |
pip install django-jchart
22 | 23 |

24 | Add django-chart to your installed apps. 25 |

26 | 27 |
INSTALLED_APPS = (
 28 |     '...',
 29 |     'jchart',
 30 | )
31 | 32 |

33 | Enable template loading from app folders by adding the following property to your TEMPLATES django configuration: 34 |

35 | 36 |
TEMPLATES = [
 37 |     {
 38 |         'BACKEND': 'django.template.backends.django.DjangoTemplates',
 39 |         'APP_DIRS': True,
 40 |         # ...
 41 |     }]
 42 | 
43 | 44 |

45 | 46 | Frontend Dependencies 47 | 48 |

49 | 50 |

51 | For the charts to be rendered inside the browser you will 52 | need to include the Chart.JS library. Add the following 53 | HTML before your closing </body> tag: 54 |

55 | 56 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.min.js"></script>
57 | 58 |

59 | If you want to make use of asynchronous loading charts 60 | you will also need to include jQuery: 61 |

62 | 63 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
64 | 65 |

66 | 67 | The Chart Class 68 | 69 |

70 | 71 |

72 | At the heart of this charting library lies the Chart class. This class describes a chart and defines which data it should display. The chart's 'class fields' map to Chart.JS options which describe how the chart should render and behave. By overriding the get_datasets method on your Chart instance you can define which data should be displayed. 73 |

74 | 75 |

76 | To define which type of chart you want to render (e.g. a line or bar chart), your chart class should set its class field chart_type to one of "line", "bar", "radar", "polarArea", "pie", or "bubble". A chart class without this field is invalid and initialization will result in an ImproperlyConfigured exception. 77 |

78 | 79 |
from jchart import Chart
 80 | 
 81 | class LineChart(Chart):
 82 |     chart_type = 'line'
83 | 84 |
85 | 86 | get_datasets 87 | 88 |
89 | 90 |

91 | The get_datasets method should return a list of datasets this chart should display. Where a dataset is a dictionary with key/value configuration pairs (see the Chart.JS documentation). 92 |

93 | 94 |
from jchart import Chart
 95 | 
 96 | class LineChart(Chart):
 97 |     chart_type = 'line'
 98 | 
 99 |     def get_datasets(self, **kwargs):
100 |         return [{
101 |             'label': "My Dataset",
102 |             'data': [69, 30, 45, 60, 55]
103 |         }]
104 | 105 |
106 | 107 | get_labels 108 | 109 |
110 | 111 |

112 | This method allows you to set the Chart.JS data.labels parameter. Which allows you to configure categorical axes. For an example on how to use this feature see this pie chart. 113 |

114 | 115 |
from jchart import Chart
116 | 
117 | class PieChart(Chart):
118 |     chart_type = 'pie'
119 | 
120 |     def get_labels(self, **kwargs):
121 |         return ['Red', 'Blue']
122 | 123 |

124 | 125 | Configuring Charts 126 | 127 |

128 | 129 |

130 | A chart can be configured through the following class fields: 131 |

132 | 133 |

134 | scales 135 | layout 136 | title 137 | legend 138 | tooltips 139 | hover 140 | animation 141 | elements 142 | responsive 143 |

144 | 145 |

146 | All of these fields map to the same key in the Chart.JS 'options' object. For instance, if you wanted to create a chart that does not render responsively you would set the responsive class field to false: 147 |

148 | 149 |
from jchart import Chart
150 | 
151 | class UnresponsiveLineChart(Chart):
152 |     chart_type = 'line'
153 |     responsive = False
154 |     # ...
155 | 156 |

157 | Most of these class fields require either a list of dicitonaries or a dictionary. With the exception of responsive which should be a boolean value. Be sure to read the Chart.JS documentation on how to use these configuration options. 158 |

159 | 160 |

161 | For your convenience there are some methods located in jchart.config which can be used to produce correct dictionaries to configure Chart.JS properties. Most of these methods only serve as a validation step for your input configuration but some can also transform their input. Let's take a look at an example, how would you configure the X-Axis so it is not to be displayed: 162 |

163 | 164 |
from jchart import Chart
165 | from jchart.config import Axes
166 | 
167 | class LineChart(Chart):
168 |     chart_type = 'line'
169 |     scales = {
170 |         'xAxes': [Axes(display=False)],
171 |     }
172 | 173 |

174 | jchart.config also contains a method to create dataset configuration dictionaries. One of the advantages of using this method is that it includes a special property color which can be used to automatically set the values for: 'backgroundColor', 'pointBackgroundColor', 'borderColor', 'pointBorderColor', and 'pointStrokeColor'. 175 |

176 | 177 |
from jchart import Chart
178 | from jchart.config import Axes
179 | 
180 | class LineChart(Chart):
181 |     chart_type = 'line'
182 |     
183 |     def get_datasets(self, **kwargs):
184 |         return [DataSet(color=(255, 255, 255), data=[])]
185 | 186 |

187 | The jchart.config module contains methods for the properties listed below. Keep in mind that you are in no way obligated to use these methods, you could also supply Python dictionaries in the place of these method calls. 188 | 189 |

Available configuration methods:
190 | Axes, ScaleLabel, Tick, DataSet, Tooltips, Legend, LegendLabel, Title, Hover, InteractionModes, Animation, Element, ElementArc, ElementLine, ElementPoint, ElementRectangle 191 |

192 | 193 |

194 |

Custom configuration options
195 | There is another special class field named options this has to be set to a dictionary and can be used to set any other Chart.JS configuration values that are not configurable through a predefined class field (e.g. maintainAspectRatio). The class fields have precedence over any configuration applied through the options dictionary. 196 |

197 | 198 |
from jchart import Chart
199 | 
200 | class OptionsChart(Chart):
201 |     chart_type = 'line'
202 |     options = {
203 |         'maintainAspectRatio': True
204 |     }
205 |     # ...
206 | 
207 | 208 |

209 | 210 | Rendering Charts 211 | 212 |

213 | 214 |

215 | Chart instances can be passed to your Django template context. 216 | Inside the template you can invoke the method `as_html` on the 217 | chart instance to render the chart. 218 |

219 | 220 |
# LineChart is a class inheriting from jchart.Chart
221 | 
222 | def some_view(request):
223 |     render(request, 'template.html', {
224 |         'line_chart': LineChart(),
225 |     })
226 | 227 |

228 | The following code is an example of how to render this line chart 229 | inside your html template: 230 |

231 | 232 |
{{ line_chart.as_html }}
233 | 234 |

235 | 236 | Asynchronous Charts 237 | 238 |

239 | 240 |

241 | When rendering the chart directly into your HTML template, all the data needed for the chart is transmitted on the page's HTTP request. It is also possible to load the chart (and its required data) asynchronous. 242 |

243 | 244 |

245 | To do this we need to setup a url endpoint from which to load the chart's data. There is a classmethod available on jchart.views.ChartView to automatically create a view which exposes the chart's configuration data as JSON on a HTTP get request: 246 |

247 | 248 |
from jchart.views import ChartView
249 | 
250 | # LineChart is a class inheriting from jchart.Chart
251 | line_chart = LineChart()
252 | 
253 | urlpatterns = [
254 |     url(r'^charts/line_chart/$', ChartView.from_chart(line_chart), name='line_chart'),
255 | ]
256 | 257 |

258 | You can use a custom templatetag inside your Django template to load this chart asynchronously. The custom tag behaves like the Django url templatetag and any positional or keyword arguments supplied to it are used to resolve the url for the given url name. In this example the url does not require any url parameters 259 | to be resolved: 260 |

261 | 262 |
{% load jchart %}
263 | 
264 | {% render_chart 'line_chart' %}
265 | 
266 | 267 |

268 | This tag will be expanded into the following JS/HTML code: 269 |

270 | 271 |
<canvas class="chart" id="unique-chart-id">
272 | </canvas>
273 | 
274 | <script type="text/javascript">
275 | window.addEventListener("DOMContentLoaded", function() {
276 |     $.get('/charts/line_chart/', function(configuration) {
277 |         var ctx = document.getElementById("unique-chart-id").getContext("2d");    
278 | 
279 |         new Chart(ctx, configuration);
280 |     });
281 | });
282 | </script>
283 | 284 |

285 | 286 | Chart Parameterization 287 | 288 |

289 | 290 |

291 | It can often be useful to reuse the same chart for different datasets. This can either be done by subclassing an existing chart class and overriding its get_datasets method. But there is another way to do this. Any arguments given to the as_html method are supplied to your get_datasets method. This makes it possible to parameterize the output of get_datasets 292 |

293 | 294 |

295 | Let's have a look at an example. Imagine we have price point data stored in a model called Price and this model has a field called currency_type. We could render the chart for different currency types by accepting the value for this field as a parameter to get_datasets. 296 |

297 | 298 |
from jchart import Chart
299 | from core.models import Price
300 | 
301 | class PriceChart(Chart):
302 |     chart_type = 'line'
303 | 
304 |     def get_datasets(self, currency_type):
305 |         prices = Price.objects.filter(currency_type=currency_type)
306 | 
307 |         data = [{'x': price.date, 'y': price.point} for price in prices]
308 | 
309 |         return [DataSet(data=data)]
310 | 311 |

312 | If we supply an instance of this chart to the context of our template, we could use this to render two different charts. This is done by using the render_chart template tag to supply additional parameters to the get_datasets method: 313 |

314 | 315 |
{% render_chart price_chart 'euro' %}
316 | 
317 | {% render_chart price_chart 'dollar' %}
318 | 319 |

320 | For asynchronous charts any url parameters are passed to the get_datasets method. 321 |

322 | 323 |
from jchart.views import ChartView
324 | from .charts import PriceChart
325 | 
326 | price_chart = PriceChart()
327 | 
328 | urlpatterns = [
329 |     url(r'^currency_chart/(?P<>\w+)/$',
330 |         ChartView.from_chart(price_chart))
331 | ]
332 | 333 |

334 | To render this chart asynchronously we have to supply the url parameter as a second argument to the render_chart template tag, like so: 335 |

336 | 337 |
{% load jchart %}
338 | 
339 | {% render_chart 'price_chart' 'euro' %}
340 | 
341 | {% render_chart 'price_chart' 'dollar' %}
342 | -------------------------------------------------------------------------------- /demo_project/demo/templates/sub/examples-async.html: -------------------------------------------------------------------------------- 1 | {% load jchart %} 2 | 3 |

4 | 5 | Example Charts Async 6 | 7 |

8 | 9 |

10 | 11 | Time Series Chart 12 | 13 |

14 | {% render_chart 'time_series_chart' %} 15 | {% include "code-examples/time_series_chart.html" with async=True %} 16 | 17 |

18 | 19 | Scatter & Line Chart 20 | 21 |

22 | {% render_chart 'scatter_line_chart' %} 23 | {% include "code-examples/scatter_line_chart.html" with async=True %} 24 | 25 |

26 | 27 | Bar Chart 28 | 29 |

30 | {% render_chart 'bar_chart' %} 31 | {% include "code-examples/bar_chart.html" with async=True %} 32 | 33 |

34 | 35 | Radar Chart 36 | 37 |

38 | {% render_chart 'radar_chart' %} 39 | {% include "code-examples/radar_chart.html" with async=True %} 40 | 41 |

42 | 43 | Polar Chart 44 | 45 |

46 | {% render_chart 'polar_chart' %} 47 | {% include "code-examples/polar_chart.html" with async=True %} 48 | 49 | 50 |

51 | 52 | Pie Chart 53 | 54 |

55 | {% render_chart 'pie_chart' %} 56 | {% include "code-examples/pie_chart.html" with async=True %} 57 | 58 |

59 | 60 | Bubble Chart 61 | 62 |

63 | {% render_chart 'bubble_chart' %} 64 | {% include "code-examples/bubble_chart.html" with async=True %} -------------------------------------------------------------------------------- /demo_project/demo/templates/sub/examples.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | Example Charts 4 | 5 |

6 |

7 | 8 | Time Series Chart 9 | 10 |

11 |

12 | An example of a plot using time series data, where the X axis represents the time and the Y axis is any arbitrary value. 13 |

14 | {{ time_series_chart.as_html }} 15 | 16 |

Code used for this example:

17 | {% include "code-examples/time_series_chart.html" with async=False %} 18 | 19 |

20 | 21 | Scatter & Line Chart 22 | 23 |

24 |

25 | An example of a scatter plot combined with a line chart. Such a plot could for instance be used to visualize the moving average of scattered data. 26 |

27 | {{ scatter_line_chart.as_html }} 28 |

29 | Code: 30 |

31 | {% include "code-examples/scatter_line_chart.html" with async=False %} 32 | 33 |

34 | 35 | Bar Chart 36 | 37 |

38 |

39 | A bar chart using one dataset. 40 |

41 | {{ bar_chart.as_html }} 42 |

43 | Code: 44 |

45 | {% include "code-examples/bar_chart.html" with async=False %} 46 | 47 |

48 | 49 | Radar Chart 50 | 51 |

52 |

53 | A line chart is a way of plotting data points on a line. Often, it is used to show trend data, and the comparison of two data sets. 54 |

55 | {{ radar_chart.as_html }} 56 |

57 | Code: 58 |

59 | {% include "code-examples/radar_chart.html" with async=False %} 60 |

61 | 62 | Polar Chart 63 | 64 |

65 |

66 | A line chart is a way of plotting data points on a line. Often, it is used to show trend data, and the comparison of two data sets. 67 |

68 | {{ polar_chart.as_html }} 69 |

70 | Code: 71 |

72 | {% include "code-examples/polar_chart.html" with async=False %} 73 | 74 |

75 | 76 | Pie Chart 77 | 78 |

79 |

80 | A line chart is a way of plotting data points on a line. Often, it is used to show trend data, and the comparison of two data sets. 81 |

82 | {{ pie_chart.as_html }} 83 |

84 | Code: 85 |

86 | {% include "code-examples/pie_chart.html" with async=False %} 87 | 88 |

89 | 90 | Bubble Chart 91 | 92 |

93 |

94 | A line chart is a way of plotting data points on a line. Often, it is used to show trend data, and the comparison of two data sets. 95 |

96 | {{ bubble_chart.as_html }} 97 |

98 | Code: 99 |

100 | {% include "code-examples/bubble_chart.html" with async=False %} 101 | -------------------------------------------------------------------------------- /demo_project/demo/templates/sub/nav.html: -------------------------------------------------------------------------------- 1 | 102 | -------------------------------------------------------------------------------- /demo_project/demo/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from django.test import TestCase, Client 4 | 5 | from selenium.webdriver.chrome.webdriver import WebDriver 6 | 7 | 8 | class HomePageTestCase(TestCase): 9 | 10 | def setUp(self): 11 | self.client = Client() 12 | 13 | def test_home(self): 14 | response = self.client.get('/') 15 | self.assertContains(response, 'Django-JChart') 16 | self.assertContains(response, ' 0: 5 | msg = 'Use of illegal keyword arguments for {}: {}' 6 | msg = msg.format(name, remainder_keys) 7 | raise ValueError(msg) 8 | 9 | 10 | def assert_color(colors): 11 | if len(colors) != 3: 12 | raise ValueError('Expected colors tuple of length 3') 13 | 14 | 15 | def Axes(**kwargs): 16 | allowed_keys = {'type', 'display', 'position', 'id', 'gridLines', 'scaleLabel', 'ticks', 17 | 'barPercentage', 'categoryPercentage', 'barThickness', 'maxBarThickness'} 18 | 19 | assert_keys('Axes', allowed_keys, kwargs) 20 | 21 | return dict(**kwargs) 22 | 23 | 24 | def ScaleLabel(**kwargs): 25 | allowed_keys = {'display', 'labelString', 'fontColor', 'fontFamily', 'fontSize', 26 | 'fontStyle'} 27 | 28 | assert_keys('ScaleLabel', allowed_keys, kwargs) 29 | 30 | return dict(**kwargs) 31 | 32 | 33 | def Tick(**kwargs): 34 | allowed_keys = {'autoSkip', 'autoSkipPadding', 'callback', 'display', 'fontColor', 35 | 'fontFamily', 'fontSize', 'fontStyle', 'labelOffset', 'maxRotation', 36 | 'minRotation', 'mirror', 'padding', 'reverse'} 37 | 38 | assert_keys('Tick', allowed_keys, kwargs) 39 | 40 | return dict(**kwargs) 41 | 42 | 43 | def DataSet(**kwargs): 44 | allowed_keys = {'type', 'data', 'label', 'xAxisID', 'yAxisID', 'fill', 45 | 'cubicInterpolationMode', 'lineTension', 'backgroundColor', 46 | 'borderWidth', 'borderColor', 'borderCapStyle', 'borderDash', 47 | 'borderDashOffset', 'borderJoinStyle', 'pointBorderColor', 48 | 'pointBackgroundColor', 'pointBorderWidth', 'pointRadius', 49 | 'pointHoverRadius', 'pointHitRadius', 'pointHoverBackgroundColor', 50 | 'pointHoverBorderColor', 'pointHoverBorderWidth', 'pointStyle', 51 | 'showLine', 'spanGaps', 'steppedLine', 'color', 52 | 'data', 'label', 'fill', 'lineTension', 'backgroundColor', 53 | 'borderWidth', 'borderColor', 'borderCapStyle', 'borderDash', 54 | 'borderDashOffset', 'borderJoinStyle', 'pointBorderColor', 55 | 'pointBackgroundColor', 'pointBorderWidth', 'pointRadius', 56 | 'pointHoverRadius', 'hitRadius', 'pointHoverBackgroundColor', 57 | 'pointHoverBorderColor', 'pointHoverBorderWidth', 'pointStyle', 58 | 'data', 'label', 'backgroundColor', 'borderColor', 'borderWidth', 59 | 'hoverBackgroundColor', 'hoverBorderColor', 'hoverBorderWidth', 60 | 'hoverRadius'} 61 | 62 | assert_keys('DataSet', allowed_keys, kwargs) 63 | 64 | result = dict(**kwargs) 65 | 66 | if 'color' in kwargs: 67 | color = kwargs['color'] 68 | 69 | assert_color(color) 70 | 71 | set_colors = dict( 72 | backgroundColor=rgba(*color, a=0.2), 73 | pointBackgroundColor=rgba(*color, a=0.5), 74 | borderColor=rgba(*color, a=0.5), 75 | pointBorderColor=rgba(*color, a=1), 76 | pointStrokeColor=rgba(*color, a=1), 77 | ) 78 | result.update(**set_colors) 79 | 80 | return result 81 | 82 | 83 | def Tooltips(**kwargs): 84 | allowed_keys = {'enabled', 'custom', 'mode', 'intersect', 'position', 'itemSort', 'filter', 85 | 'backgroundColor', 'titleFontFamily', 'titleFontSize', 'titleFontStyle', 86 | 'titleFontColor', 'titleSpacing', 'titleMarginBottom', 'bodyFontFamily', 87 | 'bodyFontSize', 'bodyFontStyle', 'bodyFontColor', 'bodySpacing', 88 | 'footerFontFamily', 'footerFontSize', 'footerFontStyle', 'footerFontColor', 89 | 'footerSpacing', 'footerMarginTop', 'xPadding', 'yPadding', 'caretSize', 90 | 'cornerRadius', 'multiKeyBackground', 'displayColors'} 91 | 92 | assert_keys('Tooltips', allowed_keys, kwargs) 93 | 94 | return dict(**kwargs) 95 | 96 | 97 | def Legend(**kwargs): 98 | allowed_keys = {'display', 'position', 'fullWidth', 'labels', 'reverse'} 99 | 100 | assert_keys('Legend', allowed_keys, kwargs) 101 | 102 | return dict(**kwargs) 103 | 104 | 105 | def LegendLabel(**kwargs): 106 | allowed_keys = {'boxWidth', 'fontSize', 'fontStyle', 'fontColor', 'fontFamily', 107 | 'padding', 'generateLabels', 'usePointStyle'} 108 | 109 | assert_keys('LegendLabel', allowed_keys, kwargs) 110 | 111 | return dict(**kwargs) 112 | 113 | 114 | def Title(**kwargs): 115 | allowed_keys = {'display', 'position', 'fullWidth', 'fontSize', 'fontFamily', 116 | 'fontColor', 'fontStyle', 'padding', 'text'} 117 | 118 | assert_keys('Title', allowed_keys, kwargs) 119 | 120 | return dict(**kwargs) 121 | 122 | 123 | def Hover(**kwargs): 124 | allowed_keys = {'mode', 'intersect', 'animationDuration'} 125 | 126 | assert_keys('Hover', allowed_keys, kwargs) 127 | 128 | return dict(**kwargs) 129 | 130 | 131 | def InteractionModes(**kwargs): 132 | allowed_keys = {'point', 'nearest', 'single', 'label', 'index', 133 | 'x-axis', 'dataset', 'x', 'y'} 134 | 135 | assert_keys('InteractionModes', allowed_keys, kwargs) 136 | 137 | return dict(**kwargs) 138 | 139 | 140 | def Animation(**kwargs): 141 | allowed_keys = {'duration', 'easing'} 142 | 143 | assert_keys('Animation', allowed_keys, kwargs) 144 | 145 | return dict(**kwargs) 146 | 147 | 148 | def Element(**kwargs): 149 | allowed_keys = {'arc', 'line', 'point', 'rectangle'} 150 | 151 | assert_keys('Element', allowed_keys, kwargs) 152 | 153 | return dict(**kwargs) 154 | 155 | 156 | def ElementArc(**kwargs): 157 | allowed_keys = {'backgroundColor', 'borderColor', 'borderWidth'} 158 | 159 | assert_keys('Element', allowed_keys, kwargs) 160 | 161 | return dict(**kwargs) 162 | 163 | 164 | def ElementLine(**kwargs): 165 | allowed_keys = {'tension', 'backgroundColor', 'borderWidth', 'borderColor', 166 | 'borderCapStyle', 'borderDash', 'borderDashOffset', 'borderJoinStyle', 167 | 'capBezierPoints', 'fill', 'stepped'} 168 | 169 | assert_keys('ElementLine', allowed_keys, kwargs) 170 | 171 | return dict(**kwargs) 172 | 173 | 174 | def ElementPoint(**kwargs): 175 | allowed_keys = {'radius', 'pointStyle', 'backgroundColor', 'borderWidth', 176 | 'borderColor', 'hitRadius', 'hoverRadius', 'hoverBorderWidth'} 177 | 178 | assert_keys('ElementPoint', allowed_keys, kwargs) 179 | 180 | return dict(**kwargs) 181 | 182 | 183 | def ElementRectangle(**kwargs): 184 | allowed_keys = {'backgroundColor', 'borderWidth', 'borderColor', 'borderSkipped'} 185 | 186 | assert_keys('ElementRectangle', allowed_keys, kwargs) 187 | 188 | return dict(**kwargs) 189 | 190 | 191 | def rgba(r, g, b, a=1.0): 192 | return "rgba({},{},{},{})".format(r, g, b, a) 193 | -------------------------------------------------------------------------------- /jchart/models.py: -------------------------------------------------------------------------------- 1 | # No models for this app 2 | -------------------------------------------------------------------------------- /jchart/templates/charting/async/chart.html: -------------------------------------------------------------------------------- 1 | {{ html }} 2 | {{ js }} -------------------------------------------------------------------------------- /jchart/templates/charting/async/html.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /jchart/templates/charting/async/js.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jchart/templates/charting/chart.html: -------------------------------------------------------------------------------- 1 | {{ html }} 2 | {{ js }} -------------------------------------------------------------------------------- /jchart/templates/charting/html.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /jchart/templates/charting/js.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jchart/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matthisk/django-jchart/2e224f061cdb5804814a6031c4d23899408d62e4/jchart/templatetags/__init__.py -------------------------------------------------------------------------------- /jchart/templatetags/jchart.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django import template 4 | from django.template.loader import render_to_string 5 | try: 6 | from django.urls import reverse 7 | except ImportError: 8 | from django.core.urlresolvers import reverse # maintain backwards compatibility for Django < 1.10 9 | 10 | from .. import Chart 11 | 12 | register = template.Library() 13 | 14 | 15 | def render_html(html_id, url): 16 | context = dict(html_id=html_id, url=url) 17 | return render_to_string('charting/async/html.html', context) 18 | 19 | 20 | def render_js(html_id, url): 21 | context = dict(html_id=html_id, url=url) 22 | return render_to_string('charting/async/js.html', context) 23 | 24 | def render_chart_inline(chart, *args, **kwargs): 25 | return chart.as_html(*args, **kwargs) 26 | 27 | def render_chart_async(url_name, *args, **kwargs): 28 | html_id = 'chart-{}'.format(uuid.uuid4()) 29 | url = reverse(url_name, args=args, kwargs=kwargs) 30 | context = { 31 | 'html': render_html(html_id, url), 32 | 'js': render_js(html_id, url), 33 | } 34 | return render_to_string('charting/chart.html', context) 35 | 36 | @register.simple_tag 37 | def render_chart(chart_or_url_name, *args, **kwargs): 38 | if isinstance(chart_or_url_name, Chart): 39 | return render_chart_inline(chart_or_url_name, *args, **kwargs) 40 | else: 41 | return render_chart_async(chart_or_url_name, *args, **kwargs) 42 | -------------------------------------------------------------------------------- /jchart/tests.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from django.test import TestCase, RequestFactory 4 | from django.utils import six 5 | from django.core.exceptions import ImproperlyConfigured 6 | 7 | from .views import ChartView 8 | from . import Chart 9 | from .config import (Title, Legend, Tooltips, Hover, 10 | InteractionModes, Animation, Element, 11 | ElementArc, Axes, ScaleLabel, Tick, rgba) 12 | 13 | 14 | class LineChart(Chart): 15 | chart_type = 'line' 16 | title = Title(text='Test Title Line') 17 | legend = Legend(display=False) 18 | tooltips = Tooltips(enabled=False) 19 | hover = Hover(mode='default') 20 | animation = Animation(duration=1.0) 21 | scales = { 22 | 'xAxes': [Axes(display=False, type='time', position='bottom')], 23 | 'yAxes': [Axes(type='linear', 24 | position='left', 25 | scaleLabel=ScaleLabel(fontColor='#fff'), 26 | ticks=Tick(fontColor='#fff') 27 | )], 28 | } 29 | 30 | def get_datasets(self, *args, **kwargs): 31 | data = [1, 2, 3, 4, 5, 6, 7, 8, 9] 32 | return [dict(label='Test Line Chart', data=data)] 33 | 34 | 35 | class LineChartParameterized(LineChart): 36 | 37 | def get_datasets(self, currency_type): 38 | eur_data = list(range(10)) 39 | do_data = list(range(10, 20)) 40 | 41 | if currency_type == 'euro': 42 | return [dict(label='Euro Chart', data=eur_data)] 43 | elif currency_type == 'dollar': 44 | return [dict(label='Dollar Chart', data=do_data)] 45 | 46 | raise ValueError('Unkown currency type: {}'.format(currency_type)) 47 | 48 | 49 | class LineChartUnresponsive(LineChart): 50 | responsive = False 51 | 52 | 53 | class BarChart(Chart): 54 | chart_type = 'radar' 55 | title = Title(text='Test Title') 56 | 57 | def get_datasets(self, *args, **kwargs): 58 | data = [] 59 | return [dict(label='Test Radar Chart', data=data)] 60 | 61 | 62 | class PolarChart(Chart): 63 | chart_type = 'polarArea' 64 | title = Title(text='Test Title') 65 | 66 | def get_datasets(self, *args, **kwargs): 67 | data = [] 68 | return [dict(label='Test Polar Chart', data=data)] 69 | 70 | 71 | class RadarChart(Chart): 72 | chart_type = 'bar' 73 | title = Title(text='Test Title') 74 | 75 | def get_datasets(self, *args, **kwargs): 76 | data = [] 77 | return [dict(label='Test Line Chart', data=data)] 78 | 79 | 80 | class PieChart(Chart): 81 | chart_type = 'pie' 82 | title = Title(text='Test Title') 83 | 84 | def get_datasets(self, *args, **kwargs): 85 | data = [] 86 | return [dict(label='Test Pie Chart', data=data)] 87 | 88 | 89 | class BubbleChart(Chart): 90 | chart_type = 'bubble' 91 | title = Title(text='Test Title') 92 | 93 | def get_datasets(self, *args, **kwargs): 94 | data = [] 95 | return [dict(label='Test Bubble Chart', data=data)] 96 | 97 | 98 | class OptionsChart(Chart): 99 | chart_type = 'line' 100 | title = Title(text='Precendence') 101 | options = { 102 | 'title': Title(text='Overriden'), 103 | 'responsive': True, 104 | 'maintainAspectRatio': True, 105 | } 106 | 107 | def get_datasets(self, *args, **kwargs): 108 | data = [1, 2, 3, 4, 5, 6, 7, 8, 9] 109 | return [dict(label='Test Line Chart', data=data)] 110 | 111 | 112 | class ChartViewTestToolkit(TestCase): 113 | classes = None 114 | url_kwargs = {} 115 | 116 | @property 117 | def request(self): 118 | request_factory = RequestFactory() 119 | return request_factory.get('/test-url') 120 | 121 | @property 122 | def responses(self): 123 | for klass in self.classes: 124 | yield ChartView.from_chart(klass())(self.request, **self.url_kwargs) 125 | 126 | 127 | class ChartViewTestToolkitSolo(ChartViewTestToolkit): 128 | klass = None 129 | url_kwargs = {} 130 | 131 | @property 132 | def response(self): 133 | return ChartView.from_chart(self.klass())(self.request, **self.url_kwargs) 134 | return self.klass.as_view()(self.request) 135 | 136 | @property 137 | def data(self): 138 | charset = getattr(self.response, 'charset', 'utf-8') 139 | data = self.response.content.decode(charset) 140 | return json.loads(data) 141 | 142 | 143 | class ChartResponseTestCase(ChartViewTestToolkit): 144 | classes = ( 145 | LineChart, 146 | BarChart, 147 | PolarChart, 148 | RadarChart, 149 | PieChart, 150 | BubbleChart, 151 | ) 152 | 153 | def test_status_code(self): 154 | for response in self.responses: 155 | self.assertEquals(response.status_code, 200) 156 | 157 | def test_content_type(self): 158 | for response in self.responses: 159 | self.assertEquals(response.get('content-type'), 'application/json') 160 | 161 | def test_chart_config(self): 162 | for response in self.responses: 163 | charset = getattr(response, 'charset', 'utf-8') 164 | content = response.content.decode(charset) 165 | data = json.loads(content) 166 | self.assertIn('data', data) 167 | self.assertIn('options', data) 168 | self.assertIn('type', data) 169 | 170 | self.assertTrue(isinstance(data['data'], dict)) 171 | self.assertTrue(isinstance(data['options'], dict)) 172 | self.assertTrue(isinstance(data['type'], (six.string_types, six.text_type))) 173 | 174 | self.assertIn(data['type'], ['bar', 'line', 'radar', 'polarArea', 'pie', 'bubble']) 175 | self.assertIn('title', data['options']) 176 | 177 | 178 | class LineChartTestCase(ChartViewTestToolkitSolo): 179 | klass = LineChart 180 | 181 | def test_title(self): 182 | self.assertEquals(self.data['options']['title']['text'], 'Test Title Line') 183 | 184 | def test_legend(self): 185 | self.assertEquals(self.data['options']['legend']['display'], False) 186 | 187 | def test_tooltips(self): 188 | self.assertEquals(self.data['options']['tooltips']['enabled'], False) 189 | 190 | def test_hover(self): 191 | self.assertEquals(self.data['options']['hover']['mode'], 'default') 192 | 193 | def test_animation(self): 194 | self.assertEquals(self.data['options']['animation']['duration'], 1.0) 195 | 196 | def test_dataset(self): 197 | self.assertEquals(len(self.data['data']['datasets']), 1) 198 | self.assertEquals(len(self.data['data']['labels']), 0) 199 | self.assertEquals(self.data['data']['datasets'][0]['data'], list(range(1, 10))) 200 | 201 | 202 | class TestConfigADTS(TestCase): 203 | 204 | def test_rgba(self): 205 | self.assertEquals(rgba(255, 255, 255), 'rgba(255,255,255,1.0)') 206 | self.assertEquals(rgba(255, 255, 255, 0.0), 'rgba(255,255,255,0.0)') 207 | 208 | def test_title(self): 209 | title = Title(text='Hello World') 210 | self.assertTrue(isinstance(title, dict)) 211 | self.assertRaises(ValueError, lambda: Title(nonsense='something')) 212 | 213 | def test_legend(self): 214 | title = Legend(display=False) 215 | self.assertTrue(isinstance(title, dict)) 216 | self.assertRaises(ValueError, lambda: Legend(nonsense='something')) 217 | 218 | def test_tooltips(self): 219 | title = Tooltips(enabled=True) 220 | self.assertTrue(isinstance(title, dict)) 221 | self.assertRaises(ValueError, lambda: Tooltips(nonsense='something')) 222 | 223 | def test_hover(self): 224 | title = Hover(mode='default') 225 | self.assertTrue(isinstance(title, dict)) 226 | self.assertRaises(ValueError, lambda: Hover(nonsense='something')) 227 | 228 | def test_interaction_modes(self): 229 | title = InteractionModes(label='Hello World') 230 | self.assertTrue(isinstance(title, dict)) 231 | self.assertRaises(ValueError, lambda: InteractionModes(nonsense='something')) 232 | 233 | def test_animation(self): 234 | title = Animation(duration=1.0) 235 | self.assertTrue(isinstance(title, dict)) 236 | self.assertRaises(ValueError, lambda: Animation(nonsense='something')) 237 | 238 | def test_element(self): 239 | arc = ElementArc(borderColor=rgba(255, 255, 255, 1)) 240 | title = Element(arc=arc) 241 | self.assertTrue(isinstance(title, dict)) 242 | self.assertRaises(ValueError, lambda: Element(nonsense='something')) 243 | 244 | def test_scales(self): 245 | axes = Axes(type='linear', 246 | position='left', 247 | scaleLabel=ScaleLabel(fontColor='#fff'), 248 | ticks=Tick(fontColor='#fff') 249 | ) 250 | self.assertTrue(isinstance(axes, dict)) 251 | self.assertRaises(ValueError, lambda: Axes(nonsense='something')) 252 | 253 | 254 | class ChartViewTestCase(TestCase): 255 | 256 | def test_chart_view(self): 257 | self.assertTrue(getattr(ChartView, 'from_chart', False)) 258 | self.assertRaises(ImproperlyConfigured, 259 | lambda: ChartView()) 260 | 261 | def test_chart_view_from_chart_classonly(self): 262 | ChartViewSubClass = type('ChartViewSubClass', (ChartView, ), { 263 | 'chart_instance': LineChart() 264 | }) 265 | chart_view = ChartViewSubClass() 266 | self.assertRaises(AttributeError, 267 | lambda: chart_view.from_chart(LineChart())) 268 | 269 | def test_chart_view_from_chart(self): 270 | self.assertRaises(ImproperlyConfigured, 271 | lambda: ChartView.from_chart(dict())) 272 | self.assertRaises(ImproperlyConfigured, 273 | lambda: ChartView.from_chart(LineChart)) 274 | ChartView.from_chart(LineChart()) 275 | 276 | def test_chart_view_get(self): 277 | ChartViewSubClass = type('ChartViewSubClass', (ChartView, ), { 278 | 'chart_instance': LineChart() 279 | }) 280 | chart_view = ChartViewSubClass() 281 | 282 | request_factory = RequestFactory() 283 | request = request_factory.get('/test-url') 284 | response = chart_view.get(request) 285 | 286 | self.assertEquals(response.status_code, 200) 287 | charset = getattr(response, 'charset', 'utf-8') 288 | content = response.content.decode(charset) 289 | data = json.loads(content) 290 | 291 | self.assertIn('data', data) 292 | self.assertIn('options', data) 293 | self.assertIn('type', data) 294 | 295 | self.assertTrue(isinstance(data['data'], dict)) 296 | self.assertTrue(isinstance(data['options'], dict)) 297 | self.assertTrue(isinstance(data['type'], (six.string_types, six.text_type))) 298 | 299 | self.assertIn(data['type'], ['bar', 'line', 'radar', 'polarArea', 'pie', 'bubble']) 300 | self.assertIn('title', data['options']) 301 | 302 | 303 | class ChartTestCase(TestCase): 304 | 305 | def test_chart_dimension(self): 306 | line_chart = LineChartUnresponsive(width=1000, height=500) 307 | self.assertEquals(line_chart.width, 1000) 308 | self.assertEquals(line_chart.height, 500) 309 | 310 | self.assertIn('height: 500px', line_chart.as_html()) 311 | self.assertIn('width: 1000px', line_chart.as_html()) 312 | 313 | def test_chart_no_dimension(self): 314 | line_chart = LineChart() 315 | self.assertEquals(line_chart.width, None) 316 | self.assertEquals(line_chart.height, None) 317 | 318 | self.assertNotIn('height:', line_chart.as_html()) 319 | self.assertNotIn('width:', line_chart.as_html()) 320 | 321 | def test_chart_html_id(self): 322 | line_chart = LineChart(html_id='test-id') 323 | self.assertIn('id="test-id"', line_chart.as_html()) 324 | 325 | def test_chart_render_html(self): 326 | line_chart = LineChart() 327 | 328 | context = { 329 | 'html_id': 'test-id', 330 | 'chart': line_chart, 331 | 'chart_configuration': line_chart.get_configuration(), 332 | } 333 | html = line_chart.render_html(context) 334 | 335 | self.assertNotIn('=1.5', 14 | ] 15 | 16 | extras_require = { 17 | 'test': ['coverage', 'selenium'], 18 | } 19 | 20 | setup( 21 | name='django-jchart', 22 | version='0.4.2', 23 | packages=find_packages(), 24 | include_package_data=True, 25 | install_requires=requirements, 26 | extras_require=extras_require, 27 | license='BSD License', 28 | description='A Django App to plot charts using the excellent Chart.JS library.', 29 | long_description=README, 30 | url='https://github.com/matthisk/django-jchart', 31 | author='Matthisk Heimensen', 32 | author_email='m@tthisk.nl', 33 | classifiers=[ 34 | 'Development Status :: 5 - Production/Stable', 35 | 'Environment :: Web Environment', 36 | 'Framework :: Django', 37 | 'Intended Audience :: Developers', 38 | 'License :: OSI Approved :: BSD License', 39 | 'Operating System :: OS Independent', 40 | 'Programming Language :: Python', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Programming Language :: Python :: 3', 43 | 'Programming Language :: Python :: 3.4', 44 | 'Programming Language :: Python :: 3.5', 45 | 'Topic :: Internet :: WWW/HTTP', 46 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 47 | ], 48 | ) 49 | --------------------------------------------------------------------------------