├── .gitignore ├── .travis.yml ├── LISCENSES.txt ├── MANIFEST.in ├── README.md ├── chartjs_engine ├── __init__.py ├── admin.py ├── apps.py ├── models.py ├── templates │ └── chartjs_engine │ │ └── chart.html ├── tests.py ├── urls.py └── views │ ├── __init__.py │ ├── bar.py │ ├── base.py │ ├── engine.py │ ├── line.py │ └── pie_doughnut.py ├── manage.py ├── project ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── requirements.txt └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | dist/* 3 | django_chartjs_engine.egg-info/* 4 | db.sqlite3 5 | 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | 5 | before_script: 6 | - pip install python-coveralls 7 | 8 | script: 9 | - coverage run --source=chartjs_engine manage.py test chartjs_engine 10 | 11 | after_success: 12 | - coveralls 13 | -------------------------------------------------------------------------------- /LISCENSES.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 Jeff Willette (jrwillette88@gmail.com) 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include chartjs_engine/* 2 | include chartjs_engine/templates/chartjs_engine/* 3 | include chartjs_engine/views/* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/deltaskelta/django-chartjs-engine.svg?branch=master)](https://travis-ci.org/deltaskelta/django-chartjs-engine) 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/deltaskelta/django-chartjs-engine/badge.svg?branch=master)](https://coveralls.io/github/deltaskelta/django-chartjs-engine?branch=master) 4 | 5 | ## Chartjs_engine 6 | 7 | `chartjs_engine` aims to provide a django app that will produce [chartjs](http://www.chartjs.org/) 8 | charts by instantiating a chart engine which renders the chart to a string of html and `' 36 | ``` 37 | 38 | You must also add the `chartjs` script to your html file somewhere. It has been 39 | excluded from the template in order to allow it to be loaded exactly once. (Each 40 | page may have multiple charts, and if it was included in the template it would load 41 | once for every chart) 42 | 43 | In order to include it in the response, it is best to put the script source in settings and then 44 | add it to the response later. 45 | 46 | ## Method 1: Pass data to the Engine And Return Chart as Response 47 | 48 | ```python 49 | from django.http import HttpResponse 50 | from chartjs_engine.views.engine import Engine 51 | from django.conf import settings 52 | 53 | 54 | def chart_view(request): 55 | """construct data form a source and send it to the chart engine""" 56 | 57 | chart_setup = { 58 | 'chart_name': 'testchart', 59 | 'chart_type': 'line', 60 | 'chart_labels': ['the', 'labels'], 61 | 'options': 'options', 62 | 'datasets': { 63 | 'data1': [1, 2], 64 | 'data2': [3, 4], 65 | } 66 | } 67 | 68 | engine = ChartEngine(**chart_setup) 69 | chart = engine.make_chart() 70 | return HttpResponse(settings.CHARTJS_SCRIPT + chart) 71 | ``` 72 | 73 | ## Method 2: Creating Custom Markup on a Database Object 74 | 75 | If you have a database object which is returned in a response (like a blog post) 76 | you can make some custom markup which can be substituted for chart html when the 77 | view is loaded. 78 | 79 | #### Example: 80 | 81 | ##### settings.py 82 | ```python 83 | CHARTJS_REGEX = re.compile(r'(\[chartjs\].*?\[/chartjs\])', re.DOTALL) 84 | ``` 85 | 86 | The above regular expression will capture everything in between and including 87 | `[chartjs]` and `[/chartjs]` tags in the database. 88 | 89 | Then, with the following model method, you could turn some markup (like the example 90 | below into a chartjs javascript chart... 91 | 92 | ``` 93 | [chartjs] 94 | name: testchart 95 | type: line 96 | labels: the, labels 97 | data-foo: 1, 2 98 | data-bar: 3, 4 99 | [/chartjs] 100 | ``` 101 | 102 | ##### models.py 103 | ```python 104 | class BlogPost(models.Model): 105 | """A model that has everything needed for a blogpost""" 106 | 107 | # All of the model fields would be here 108 | ... 109 | 110 | def insert_chart(self): 111 | """ 112 | Finds the chartjs data by regex in a post and calls the function to 113 | replace markup with chart javascript in the post. 114 | """ 115 | chart_data = {} 116 | # re.DOTALL makes "." accept all characters. chart_data var is list of matches. 117 | pattern = settings.CHARTJS_REGEX 118 | #Finding all the chartjs markup and iterating to parse each one 119 | markup_data = re.findall(pattern, self.post) 120 | if markup_data: 121 | # Adding the chartjs javascript library (exactly once) to the html 122 | self.post = settings.CHARTJS_SCRIPT + self.post 123 | for data in markup_data: 124 | # Regex captures the "[chartsjs]" tags, omitting them... 125 | data = data.split('\r\n')[1:-1] 126 | # name and type are in list as "name: the-name" so split/strip whitespace 127 | chart_data['chart_name'] = data[0].split(':')[1].strip() 128 | chart_data['chart_type'] = data[1].split(':')[1].strip() 129 | 130 | # Split label by item delimited by "," stripped of whitespace. 131 | chart_data['chart_labels'] = [item.strip() for item in \ 132 | data[2].split(':')[1].split(',')] 133 | 134 | chart_data['datasets'] = {} 135 | # data[3:] is going to be chart data so split/strip and convert to json. 136 | for data_set in data[3:]: 137 | # split by ':', then [0] index will be the dataset title 138 | d = data_set.split(':') 139 | chart_data['datasets'][d[0]] = [item.strip() for item in \ 140 | d[1].split(',')] 141 | 142 | # Left for future use, this may cause errors with chart specific 143 | # options in views if changed 144 | chart_data['options'] = 'options' 145 | 146 | # Instantiate the ChartEngine and make the chart 147 | engine = ChartEngine(**chart_data) 148 | # replace 1 match in the post with the chart javascript 149 | self.post = re.sub(pattern, engine.make_chart(), self.post, count=1) 150 | ``` 151 | -------------------------------------------------------------------------------- /chartjs_engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffwillette/django-chartjs-engine/2658d7840e1e92ef4a5170490de0688baa6b8695/chartjs_engine/__init__.py -------------------------------------------------------------------------------- /chartjs_engine/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /chartjs_engine/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class ChartsConfig(AppConfig): 7 | name = 'chartjs_engine' 8 | -------------------------------------------------------------------------------- /chartjs_engine/models.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import models 4 | 5 | # Create your models here. 6 | -------------------------------------------------------------------------------- /chartjs_engine/templates/chartjs_engine/chart.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chartjs_engine/tests.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from django.test import TestCase, Client 3 | from django.template.loader import render_to_string 4 | from .views.engine import * 5 | 6 | # Create your tests here. 7 | class ChartTests(TestCase): 8 | """Tests for the blog views""" 9 | 10 | def setUp(self): 11 | """Making tests for the charting app. Uses another chartjs app installed to virtualenv""" 12 | self.chart = Chart( 13 | chart_type='line', 14 | chart_name='this', 15 | options={'options': 'options'}, 16 | chart_labels=['the', 'labels'], 17 | datasets={'data': (1, 2), 'data2': (3, 4)}) 18 | 19 | def test_chart_class(self): 20 | """Testing that the chart class instantiates""" 21 | self.assertEqual(self.chart.chart_type, 'line') 22 | 23 | def test_random_color(self): 24 | """Testing that the random color generator returns a tuple of length 2""" 25 | colors = self.chart.random_color() 26 | self.assertEqual(len(colors), 2) 27 | 28 | def test_make_js(self): 29 | """ 30 | Testing that the exception is raised when I call the Chart class make_js 31 | method 32 | """ 33 | with self.assertRaises(Exception): 34 | self.chart.render_template() 35 | 36 | def test_exception_when_kwargs_missing(self): 37 | """Testing that there is an exception rasied when all kwargs not present""" 38 | with self.assertRaises(Exception): 39 | chart = Chart() 40 | 41 | 42 | 43 | class LineChartTests(TestCase): 44 | """Tests for the different chart class types""" 45 | 46 | def setUp(self): 47 | """Setting up what needs to be done for the line charts""" 48 | self.chart = LineChart( 49 | chart_type='line', 50 | chart_name='the_chart', 51 | chart_labels=['the', 'labels'], 52 | datasets={'data': (1, 2), 'data2': (3, 4)}) 53 | 54 | def test_get_options(self): 55 | """Testing the get options method""" 56 | self.assertFalse(self.chart.options) 57 | options = self.chart.get_options() 58 | self.assertTrue(options) 59 | 60 | def test_get_data(self): 61 | """Testing that get data returns everything equal to the input""" 62 | data = self.chart.get_data() 63 | keys = ['label', 'backgroundColor', 'borderColor', 'data'] 64 | for k in keys: 65 | for d in data['datasets']: 66 | self.assertTrue(d[k]) 67 | 68 | def test_make_context(self): 69 | """Testing that context returns""" 70 | context = self.chart.make_context() 71 | keys = ['chart_type', 'chart_name', 'data', 'options'] 72 | for k in keys: 73 | self.assertTrue(context[k]) 74 | 75 | @patch('chartjs_engine.views.line.render_to_string') 76 | def test_render_template_method(self, mock_rts): 77 | """Testing that everything has rendered correctly 'rts' is render_to_string""" 78 | chart = self.chart.to_string() 79 | mock_rts.assert_called() 80 | 81 | 82 | 83 | class BarChartTests(TestCase): 84 | """Testing the bar chart class""" 85 | 86 | def setUp(self): 87 | """Setting up the options for a bar chart""" 88 | 89 | self.chart_type = 'bar' 90 | self.chart_name = 'the_chart' 91 | self.chart_labels = ['the', 'labels'] 92 | self.datasets = {'data': (1, 2), 'data2': (3, 4)} 93 | # For testing bar chart behavior with single dataset 94 | self.datasets2 = {'data': (1, 2)} 95 | 96 | self.chart = BarChart( 97 | chart_type=self.chart_type, 98 | chart_name=self.chart_name, 99 | chart_labels=self.chart_labels, 100 | datasets=self.datasets) 101 | 102 | self.single_dataset_chart = BarChart( 103 | chart_type=self.chart_type, 104 | chart_name=self.chart_name, 105 | chart_labels=self.chart_labels, 106 | datasets=self.datasets2) 107 | 108 | def test_get_options(self): 109 | """Testing the get options method""" 110 | self.assertFalse(self.chart.options) 111 | options = self.chart.get_options() 112 | self.assertTrue(options) 113 | 114 | def test_get_data(self): 115 | """Testing that get data returns everything equal to the input""" 116 | data = self.chart.get_data() 117 | keys = ['label', 'backgroundColor', 'borderColor', 'borderWidth', 'data'] 118 | for k in keys: 119 | for d in data['datasets']: 120 | self.assertTrue(d[k]) 121 | 122 | def test_make_context(self): 123 | """Testing that context returns""" 124 | context = self.chart.make_context() 125 | keys = ['chart_type', 'chart_name', 'data', 'options'] 126 | for k in keys: 127 | self.assertTrue(context[k]) 128 | 129 | @patch('chartjs_engine.views.bar.render_to_string') 130 | def test_to_string_method(self, mock_rts): 131 | """Testing rendering the bar chart to a template""" 132 | chart = self.chart.to_string() 133 | mock_rts.assert_called() 134 | 135 | def test_one_color_if_multiple_datasets(self): 136 | """ 137 | Testing that there will be one color per dataset when multiple datasets 138 | are present 139 | """ 140 | chart = self.chart.to_string() 141 | for ds in self.chart.data['datasets']: 142 | self.assertEqual(ds['backgroundColor'][0], ds['backgroundColor'][1]) 143 | self.assertEqual(ds['borderColor'][0], ds['borderColor'][1]) 144 | 145 | def test_multiple_colors_if_one_dataset(self): 146 | """Testing that there will be multiple colors if there is only one dataset""" 147 | chart = self.single_dataset_chart.to_string() 148 | for ds in self.single_dataset_chart.data['datasets']: 149 | self.assertNotEqual(ds['backgroundColor'][0], ds['backgroundColor'][1]) 150 | self.assertNotEqual(ds['borderColor'][0], ds['borderColor'][1]) 151 | 152 | 153 | 154 | class PieDoughnutChartTests(TestCase): 155 | """Testing that Pie and Doughnut Charts render correctly""" 156 | 157 | def setUp(self): 158 | """Setting up multiple and single datasets""" 159 | self.chart = PieDoughnutChart( 160 | chart_type='pie', 161 | chart_name='the_chart', 162 | chart_labels=['the', 'labels'], 163 | datasets={'data': (1, 2), 'data2': (3, 4)}) 164 | 165 | self.doughnut_chart = PieDoughnutChart( 166 | chart_type='doughnut', 167 | chart_name='the_chart', 168 | chart_labels=['the', 'labels'], 169 | datasets={'data': (1, 2)}) 170 | 171 | self.single_dataset_chart = PieDoughnutChart( 172 | chart_type='pie', 173 | chart_name='the_chart', 174 | chart_labels=['the', 'labels'], 175 | datasets={'data': (1, 2)}) 176 | 177 | def test_get_options(self): 178 | """Testing the get options method""" 179 | self.assertFalse(self.chart.options) 180 | options = self.chart.get_options() 181 | self.assertFalse(options) 182 | 183 | def test_get_data(self): 184 | """Testing that get data returns everything equal to the input""" 185 | data = self.single_dataset_chart.get_data() 186 | keys = ['label', 'backgroundColor', 'hoverBackgroundColor', 'data'] 187 | for k in keys: 188 | for d in data['datasets']: 189 | self.assertTrue(d[k]) 190 | 191 | def test_make_context(self): 192 | """Testing that context returns""" 193 | context = self.chart.make_context() 194 | keys = ['chart_type', 'chart_name', 'data', 'options'] 195 | for k in keys: 196 | self.assertTrue(context[k]) 197 | 198 | @patch('chartjs_engine.views.pie_doughnut.render_to_string') 199 | def test_one_dataset_returns_chart(self, mock_rts): 200 | """Testing that one dataset will return a chart""" 201 | chart = self.single_dataset_chart.to_string() 202 | mock_rts.assert_called() 203 | 204 | @patch('chartjs_engine.views.pie_doughnut.render_to_string') 205 | def test_doughnut_chart_returns_chart(self, mock_rts): 206 | """Testing that passing in settings for doughnut chart returns a chart""" 207 | chart = self.doughnut_chart.to_string() 208 | mock_rts.assert_called() 209 | 210 | def test_two_dataset_returns_error_as_string(self): 211 | """Testing that two datasets will return an error as a string.""" 212 | with self.assertRaises(Exception): 213 | self.chart.to_string() 214 | 215 | 216 | class ChartEngineTests(TestCase): 217 | """Testing that the chart engine works with proper input""" 218 | 219 | def setUp(self): 220 | """Setting up the data that will be plugged into the charts.""" 221 | 222 | self.chart_setup = { 223 | 'chart_name': 'testchart', 224 | 'chart_type': 'line', 225 | 'chart_labels': ['the', 'labels'], 226 | 'options': 'options', 227 | 'datasets': { 228 | 'data1': [1, 2], 229 | 'data2': [3, 4], 230 | } 231 | } 232 | 233 | def test_make_line_chart(self): 234 | """Testing that the engine delivers the charts as expected""" 235 | self.engine = ChartEngine(**self.chart_setup) 236 | self.assertIn('line', self.engine.make_chart()) 237 | 238 | def test_make_bar_chart(self): 239 | """Testing that the engine delivers charts with "bar" as the title""" 240 | self.chart_setup['chart_type'] = 'bar' 241 | self.engine = ChartEngine(**self.chart_setup) 242 | self.assertIn('bar', self.engine.make_chart()) 243 | 244 | def test_make_pie_chart(self): 245 | """Testing that the engine delivers charts with pie as the type""" 246 | self.chart_setup['chart_type'] = 'pie' 247 | self.chart_setup['datasets'] = {'data1': [1, 2]} 248 | self.engine = ChartEngine(**self.chart_setup) 249 | self.assertIn('pie', self.engine.make_chart()) 250 | 251 | def test_make_doughnut_chart(self): 252 | """Testing that the engine delivers charts with dughnut as the type""" 253 | self.chart_setup['chart_type'] = 'doughnut' 254 | self.chart_setup['datasets'] = {'data1': [1, 2]} 255 | self.engine = ChartEngine(**self.chart_setup) 256 | self.assertIn('doughnut', self.engine.make_chart()) 257 | -------------------------------------------------------------------------------- /chartjs_engine/urls.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffwillette/django-chartjs-engine/2658d7840e1e92ef4a5170490de0688baa6b8695/chartjs_engine/urls.py -------------------------------------------------------------------------------- /chartjs_engine/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffwillette/django-chartjs-engine/2658d7840e1e92ef4a5170490de0688baa6b8695/chartjs_engine/views/__init__.py -------------------------------------------------------------------------------- /chartjs_engine/views/bar.py: -------------------------------------------------------------------------------- 1 | """ 2 | The bar chart class 3 | """ 4 | from .base import Chart 5 | from django.template.loader import render_to_string 6 | import json 7 | 8 | 9 | class BarChart(Chart): 10 | """ 11 | Making the JSON data necessary for a bar chart 12 | DOCS: http://www.chartjs.org/docs/#bar-chart-introduction 13 | """ 14 | 15 | def get_options(self): 16 | """Gets the options for the chart""" 17 | self.options = { 18 | 'scales': { 19 | 'yAxes': [{ 20 | 'ticks': { 21 | 'beginAtZero': True 22 | } 23 | }] 24 | } 25 | } 26 | return self.options 27 | 28 | def get_data(self): 29 | """Populating self.data with self.datasets""" 30 | 31 | # self.data['labels'] already set in the Chart base class 32 | self.data['datasets'] = [] 33 | 34 | for i, name in enumerate(self.datasets): 35 | 36 | if len(self.datasets) == 1: 37 | # If there is one dataset, each label will have a different color 38 | self.colors = [self.random_color() for d in self.datasets[name]] 39 | else: 40 | # If there is more than one dataset, each dataset will have its own 41 | # color which stays the same throughout the chart. 42 | self.rand_color = self.random_color() 43 | self.colors = [self.rand_color for d in self.datasets[name]] 44 | 45 | self.data['datasets'].append({ 46 | 'label': name, 47 | 'backgroundColor': [color[0] for color in self.colors], 48 | 'borderColor': [color[1] for color in self.colors], 49 | 'borderWidth': 3, 50 | 'data': self.datasets[name], 51 | }) 52 | return self.data 53 | 54 | def make_context(self): 55 | """Making the context to be returned to the render functions""" 56 | self.context = { 57 | 'chart_type': self.chart_type, 58 | 'chart_name': self.chart_name, 59 | 'data': json.dumps(self.data), 60 | 'options': json.dumps(self.options) 61 | } 62 | return self.context 63 | 64 | def to_string(self): 65 | """Rendering bar chart data to a template, returning string""" 66 | self.get_options() 67 | self.get_data() 68 | self.make_context() 69 | return render_to_string('chartjs_engine/chart.html', self.context) 70 | -------------------------------------------------------------------------------- /chartjs_engine/views/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | The base chart class that all the chart type cublcasses inherits from 3 | 4 | TODO: 5 | 6 | 1. Construct the data in JSON and make it accpet all types of charts. 7 | 2. Move [chartjs] markup splitting to the model so chart engine can be agnostic, 8 | and just take chart settings as input 9 | 3. split each chart type class into smaller methods 10 | """ 11 | import random 12 | 13 | 14 | 15 | class Chart(object): 16 | """A base class for charts, init will store all the data needed for subclasses""" 17 | 18 | def __init__(self, chart_type=None, chart_name=None, options=None, \ 19 | chart_labels=None, datasets=None): 20 | """ 21 | Setting all of the settings that will be needed in the charts subclasses 22 | """ 23 | self.chart_type = chart_type 24 | # datasets will be put into self.data in each charts get_data method 25 | self.datasets = datasets 26 | self.chart_name = chart_name 27 | self.options = options 28 | self.data = {'labels': chart_labels,} 29 | # Figure out how to access the kwargs as a list and make sure none of them 30 | # are None. Raise exception if they are and test. 31 | if not all([self.chart_type, self.chart_name, self.data['labels'], \ 32 | self.datasets]): 33 | raise Exception( 34 | "Chart class needs to have all keyword arguments specified") 35 | 36 | 37 | def random_color(self): 38 | """Generates a random javascript valid rgba color for each set in datasets 39 | return: tuple of javascript rgba color strings.""" 40 | 41 | red, green, blue = random.randint(0, 255), \ 42 | random.randint(0, 255), random.randint(0, 150) 43 | 44 | return ("rgba(%s, %s, %s, .4)" % (red, green, blue), \ 45 | "rgba(%s, %s, %s, 1)" % (red, green, blue)) 46 | 47 | 48 | def to_string(self): 49 | """ 50 | This method is meant to be overridden in the child chart type classes 51 | """ 52 | raise Exception("to_string method has not been overridden") 53 | 54 | 55 | ''' 56 | class RadarChart(Chart): 57 | """ 58 | Making the JSON data necessary for a radar chart 59 | DOCS: http://www.chartjs.org/docs/#radar-chart-introduction 60 | """ 61 | pass 62 | 63 | 64 | class PolarAreaChart(Chart): 65 | """ 66 | Making JSON data necessary for a polar area chart 67 | DOCS: http://www.chartjs.org/docs/#polar-area-chart-introduction""" 68 | pass 69 | 70 | 71 | class BubbleChart(Chart): 72 | """ 73 | Making the JSON necessary for a bubble chart 74 | DOCS: http://www.chartjs.org/docs/#bubble-chart-introduction 75 | """ 76 | pass 77 | ''' -------------------------------------------------------------------------------- /chartjs_engine/views/engine.py: -------------------------------------------------------------------------------- 1 | """ 2 | Charting engine which takes the input and routes it to the proper Chart sublcass. 3 | """ 4 | from .line import LineChart 5 | from .bar import BarChart 6 | from .pie_doughnut import PieDoughnutChart 7 | from .base import Chart 8 | 9 | 10 | 11 | class ChartEngine(object): 12 | """An engine to make all of the charts necessary""" 13 | 14 | def __init__(self, **kwargs): 15 | """take in chart options and decide what kind of chart to make""" 16 | charts = { 17 | 'line': LineChart, 18 | 'bar': BarChart, 19 | 'pie': PieDoughnutChart, 20 | 'doughnut': PieDoughnutChart, 21 | } 22 | 23 | self.chart = charts[kwargs['chart_type']]( 24 | chart_name=kwargs['chart_name'], 25 | chart_type=kwargs['chart_type'], 26 | chart_labels=kwargs['chart_labels'], 27 | # Options is not used but left for possible future use 28 | options=kwargs['options'], 29 | datasets=kwargs['datasets']) 30 | 31 | 32 | def make_chart(self): 33 | """Render the proper chart from the given""" 34 | return self.chart.to_string() 35 | -------------------------------------------------------------------------------- /chartjs_engine/views/line.py: -------------------------------------------------------------------------------- 1 | """ 2 | The line chart class 3 | """ 4 | from .base import Chart 5 | from django.template.loader import render_to_string 6 | import json 7 | 8 | 9 | class LineChart(Chart): 10 | """ 11 | Making the JSON data necessary for line charts. 12 | DOCS: http://www.chartjs.org/docs/#line-chart-introduction 13 | """ 14 | 15 | def get_options(self): 16 | """Gets the options for the chart""" 17 | self.options = { 18 | 'scales': { 19 | 'yAxes': [{ 20 | 'ticks': { 21 | 'beginAtZero': True 22 | } 23 | }] 24 | } 25 | } 26 | return self.options 27 | 28 | def get_data(self): 29 | """Populating self.data with self.datasets""" 30 | 31 | # self.data['labels'] already set in the Chart base class 32 | self.data['datasets'] = [] 33 | self.colors = [self.random_color() for sets in self.datasets] 34 | 35 | for i, name in enumerate(self.datasets): 36 | self.data['datasets'].append({ 37 | 'label': name, 38 | 'fill': False, 39 | 'lineTension': 0.1, 40 | 'borderCapStyle': 'butt', 41 | 'borderDash': [], 42 | 'borderDashOffset': 0.0, 43 | 'borderJoinStyle': 'miter', 44 | 'pointBorderColor': "rgba(75,192,192,1)", 45 | 'pointBackgroundColor': "#fff", 46 | 'pointBorderWidth': 1, 47 | 'pointHoverRadius': 5, 48 | 'pointHoverBackgroundColor': "rgba(75,192,192,1)", 49 | 'pointHoverBorderColor': "rgba(220,220,220,1)", 50 | 'pointHoverBorderWidth': 2, 51 | 'pointRadius': 1, 52 | 'pointHitRadius': 10, 53 | 'spanGaps': False, 54 | 'backgroundColor': self.colors[i][0], 55 | 'borderColor': self.colors[i][1], 56 | 'data': self.datasets[name], 57 | }) 58 | return self.data 59 | 60 | def make_context(self): 61 | """Making the context to be returned to the render functions""" 62 | self.context = { 63 | 'chart_type': self.chart_type, 64 | 'chart_name': self.chart_name, 65 | 'data': json.dumps(self.data), 66 | 'options': json.dumps(self.options) 67 | } 68 | return self.context 69 | 70 | 71 | def to_string(self): 72 | """Generating the javascript needed for a line chart.""" 73 | 74 | self.get_options() 75 | self.get_data() 76 | self.make_context() 77 | return render_to_string('chartjs_engine/chart.html', self.context) 78 | -------------------------------------------------------------------------------- /chartjs_engine/views/pie_doughnut.py: -------------------------------------------------------------------------------- 1 | """ 2 | The pie/doughnut chart class 3 | """ 4 | from .base import Chart 5 | from django.template.loader import render_to_string 6 | import json 7 | 8 | 9 | 10 | class PieDoughnutChart(Chart): 11 | """ 12 | Making the JSON necessary for a pie or doughnut chart 13 | DOCS: http://www.chartjs.org/docs/#doughnut-pie-chart-introduction 14 | """ 15 | 16 | def get_options(self): 17 | """Gets the options for the chart""" 18 | self.options = {} 19 | return self.options 20 | 21 | def get_data(self): 22 | """Populating self.data with self.datasets""" 23 | 24 | # self.data['labels'] already set in the Chart base class 25 | self.data['datasets'] = [] 26 | 27 | for i, name in enumerate(self.datasets): 28 | 29 | if len(self.datasets) == 1: 30 | self.colors = [self.random_color() for d in self.datasets[name]] 31 | else: 32 | raise Exception( 33 | 'Pie/Doughnut charts only support one dataset at this time') 34 | 35 | self.data['datasets'].append({ 36 | # Labels in a pie chart have no function as of now 37 | 'label': name, 38 | 'backgroundColor': [color[0] for color in self.colors], 39 | 'hoverBackgroundColor': [color[1] for color in self.colors], 40 | 'borderWidth': 3, 41 | # For future support of multiple datasets 42 | 'data': self.datasets[name], 43 | }) 44 | 45 | return self.data 46 | 47 | def make_context(self): 48 | """Making the context to be returned to the render functions""" 49 | self.context = { 50 | 'chart_type': self.chart_type, 51 | 'chart_name': self.chart_name, 52 | 'data': json.dumps(self.data), 53 | 'options': json.dumps(self.options) 54 | } 55 | return self.context 56 | 57 | def to_string(self): 58 | """Rendering pie or doughnut chart data to a template""" 59 | self.get_options() 60 | self.get_data() 61 | self.make_context() 62 | return render_to_string('chartjs_engine/chart.html', self.context) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /project/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeffwillette/django-chartjs-engine/2658d7840e1e92ef4a5170490de0688baa6b8695/project/__init__.py -------------------------------------------------------------------------------- /project/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for chartjsx project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.4. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '_j#%z=pk-1p6ycuh@$#ja1y*11$mq7v%*$m-g=_6c4))6r0q5w' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'chartjs_engine', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'project.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'project.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_L10N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/1.10/howto/static-files/ 120 | 121 | STATIC_URL = '/static/' 122 | -------------------------------------------------------------------------------- /project/urls.py: -------------------------------------------------------------------------------- 1 | """chartjsx URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/1.10/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.conf.urls import url, include 14 | 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import url 17 | from django.contrib import admin 18 | 19 | urlpatterns = [ 20 | url(r'^admin/', admin.site.urls), 21 | ] 22 | -------------------------------------------------------------------------------- /project/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for chartjsx project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "chartjsx.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.10.4 2 | funcsigs==1.0.2 3 | mock==2.0.0 4 | pbr==1.10.0 5 | six==1.10.0 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='django-chartjs-engine', 4 | version='0.0.3', 5 | description='Django app to build chartjs javascript charts', 6 | url='https://github.com/deltaskelta/django-chartjs-engine', 7 | author='Jeff Willette', 8 | author_email='jrwillette88@gmail.com', 9 | keywords = ['django', 'chartjs', 'javascript', 'charts'], 10 | packages = ['chartjs_engine'], 11 | include_package_data = True, 12 | ) 13 | 14 | # The command to upload: python setup.py sdist upload -r pypi 15 | --------------------------------------------------------------------------------