chartkick-0.5.0/0000755000076500000240000000000012723707103013537 5ustar mherstaff00000000000000chartkick-0.5.0/chartkick/0000755000076500000240000000000012723707103015502 5ustar mherstaff00000000000000chartkick-0.5.0/chartkick/__init__.py0000644000076500000240000000035112723707026017616 0ustar mherstaff00000000000000from __future__ import absolute_import import os VERSION = (0, 5, 0) __version__ = '.'.join(map(str, VERSION)) def js(): "returns home directory of js" return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'js') chartkick-0.5.0/chartkick/ext.py0000644000076500000240000000577112376042102016661 0ustar mherstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import json import itertools import functools from jinja2 import nodes from jinja2.ext import Extension from jinja2.exceptions import TemplateNotFound from .template import CHART_HTML from .options import Options class ChartExtension(Extension): tags = set(['line_chart', 'pie_chart', 'column_chart', 'bar_chart', 'area_chart']) id = itertools.count() _library = None def __init__(self, environment): super(ChartExtension, self).__init__(environment) environment.extend( options=dict(height='300px'), ) for tag in self.tags: setattr(self, tag + '_support', functools.partial(self._chart_support, tag)) def parse(self, parser): # parse chart name chart_tag = next(parser.stream) args = [parser.parse_expression()] # parse 'with' statement if parser.stream.current.type != 'block_end': token = next(parser.stream) if token.value != 'with': parser.fail("expected 'with' statement", token.lineno) # parse options while parser.stream.current.type != 'block_end': lineno = parser.stream.current.lineno target = parser.parse_assign_target() parser.stream.expect('assign') expr = parser.parse_expression() args.append(nodes.Assign(target, expr, lineno=lineno)) support_func = chart_tag.value + '_support' return nodes.CallBlock(self.call_method(support_func, args), [], [], []).set_lineno(chart_tag.lineno) def _chart_support(self, name, data, caller, **kwargs): "template chart support function" id = 'chart-%s' % next(self.id) name = self._chart_class_name(name) options = dict(self.environment.options) options.update(name=name, id=id) # jinja2 prepends 'l_' to keys kwargs = dict((k[2:], v) for (k, v) in kwargs.items()) if self._library is None: self._library = self.load_library() id = kwargs.get('id', '') library = self._library.get(id, {}) # apply options from a tag library.update(kwargs.get('library', {})) # apply options from chartkick.json kwargs.update(library=library) options.update(kwargs) return CHART_HTML.format(data=data, options=json.dumps(kwargs), **options) def _chart_class_name(self, tag_name): "converts chart tag name to javascript class name" return ''.join(map(str.title, tag_name.split('_'))) def load_library(self): "loads configuration options" try: filename = self.environment.get_template('chartkick.json').filename except TemplateNotFound: return {} else: options = Options() options.load(filename) return options charts = ChartExtension chartkick-0.5.0/chartkick/js/0000755000076500000240000000000012723707103016116 5ustar mherstaff00000000000000chartkick-0.5.0/chartkick/js/chartkick.js0000644000076500000240000011574012723706751020437 0ustar mherstaff00000000000000/* * Chartkick.js * Create beautiful JavaScript charts with minimal code * https://github.com/ankane/chartkick.js * v2.0.0 * MIT License */ /*jslint browser: true, indent: 2, plusplus: true, vars: true */ (function (window) { 'use strict'; var config = window.Chartkick || {}; var Chartkick, ISO8601_PATTERN, DECIMAL_SEPARATOR, adapters = []; var DATE_PATTERN = /^(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)$/i; var GoogleChartsAdapter, HighchartsAdapter, ChartjsAdapter; // helpers function isArray(variable) { return Object.prototype.toString.call(variable) === "[object Array]"; } function isFunction(variable) { return variable instanceof Function; } function isPlainObject(variable) { return !isFunction(variable) && variable instanceof Object; } // https://github.com/madrobby/zepto/blob/master/src/zepto.js function extend(target, source) { var key; for (key in source) { if (isPlainObject(source[key]) || isArray(source[key])) { if (isPlainObject(source[key]) && !isPlainObject(target[key])) { target[key] = {}; } if (isArray(source[key]) && !isArray(target[key])) { target[key] = []; } extend(target[key], source[key]); } else if (source[key] !== undefined) { target[key] = source[key]; } } } function merge(obj1, obj2) { var target = {}; extend(target, obj1); extend(target, obj2); return target; } // https://github.com/Do/iso8601.js ISO8601_PATTERN = /(\d\d\d\d)(\-)?(\d\d)(\-)?(\d\d)(T)?(\d\d)(:)?(\d\d)?(:)?(\d\d)?([\.,]\d+)?($|Z|([\+\-])(\d\d)(:)?(\d\d)?)/i; DECIMAL_SEPARATOR = String(1.5).charAt(1); function parseISO8601(input) { var day, hour, matches, milliseconds, minutes, month, offset, result, seconds, type, year; type = Object.prototype.toString.call(input); if (type === '[object Date]') { return input; } if (type !== '[object String]') { return; } matches = input.match(ISO8601_PATTERN); if (matches) { year = parseInt(matches[1], 10); month = parseInt(matches[3], 10) - 1; day = parseInt(matches[5], 10); hour = parseInt(matches[7], 10); minutes = matches[9] ? parseInt(matches[9], 10) : 0; seconds = matches[11] ? parseInt(matches[11], 10) : 0; milliseconds = matches[12] ? parseFloat(DECIMAL_SEPARATOR + matches[12].slice(1)) * 1000 : 0; result = Date.UTC(year, month, day, hour, minutes, seconds, milliseconds); if (matches[13] && matches[14]) { offset = matches[15] * 60; if (matches[17]) { offset += parseInt(matches[17], 10); } offset *= matches[14] === '-' ? -1 : 1; result -= offset * 60 * 1000; } return new Date(result); } } // end iso8601.js function negativeValues(series) { var i, j, data; for (i = 0; i < series.length; i++) { data = series[i].data; for (j = 0; j < data.length; j++) { if (data[j][1] < 0) { return true; } } } return false; } function jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle) { return function (series, opts, chartOptions) { var options = merge({}, defaultOptions); options = merge(options, chartOptions || {}); // hide legend // this is *not* an external option! if (opts.hideLegend) { hideLegend(options); } // min if ("min" in opts) { setMin(options, opts.min); } else if (!negativeValues(series)) { setMin(options, 0); } // max if (opts.max) { setMax(options, opts.max); } if ("stacked" in opts) { setStacked(options, opts.stacked); } if (opts.colors) { options.colors = opts.colors; } if (opts.xtitle) { setXtitle(options, opts.xtitle); } if (opts.ytitle) { setYtitle(options, opts.ytitle); } // merge library last options = merge(options, opts.library || {}); return options; }; } function setText(element, text) { if (document.body.innerText) { element.innerText = text; } else { element.textContent = text; } } function chartError(element, message) { setText(element, "Error Loading Chart: " + message); element.style.color = "#ff0000"; } function getJSON(element, url, success) { var $ = window.jQuery || window.Zepto || window.$; $.ajax({ dataType: "json", url: url, success: success, error: function (jqXHR, textStatus, errorThrown) { var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message; chartError(element, message); } }); } function errorCatcher(chart, callback) { try { callback(chart); } catch (err) { chartError(chart.element, err.message); throw err; } } function fetchDataSource(chart, callback) { if (typeof chart.dataSource === "string") { getJSON(chart.element, chart.dataSource, function (data, textStatus, jqXHR) { chart.data = data; errorCatcher(chart, callback); }); } else { chart.data = chart.dataSource; errorCatcher(chart, callback); } } // type conversions function toStr(n) { return "" + n; } function toFloat(n) { return parseFloat(n); } function toDate(n) { var matches, year, month, day; if (typeof n !== "object") { if (typeof n === "number") { n = new Date(n * 1000); // ms } else if ((matches = n.match(DATE_PATTERN))) { year = parseInt(matches[1], 10); month = parseInt(matches[3], 10) - 1; day = parseInt(matches[5], 10); return new Date(year, month, day); } else { // str // try our best to get the str into iso8601 // TODO be smarter about this var str = n.replace(/ /, "T").replace(" ", "").replace("UTC", "Z"); n = parseISO8601(str) || new Date(n); } } return n; } function toArr(n) { if (!isArray(n)) { var arr = [], i; for (i in n) { if (n.hasOwnProperty(i)) { arr.push([i, n[i]]); } } n = arr; } return n; } function sortByTime(a, b) { return a[0].getTime() - b[0].getTime(); } function sortByNumber(a, b) { return a - b; } function loadAdapters() { if (!HighchartsAdapter && "Highcharts" in window) { HighchartsAdapter = new function () { var Highcharts = window.Highcharts; this.name = "highcharts"; var defaultOptions = { chart: {}, xAxis: { title: { text: null }, labels: { style: { fontSize: "12px" } } }, yAxis: { title: { text: null }, labels: { style: { fontSize: "12px" } } }, title: { text: null }, credits: { enabled: false }, legend: { borderWidth: 0 }, tooltip: { style: { fontSize: "12px" } }, plotOptions: { areaspline: {}, series: { marker: {} } } }; var hideLegend = function (options) { options.legend.enabled = false; }; var setMin = function (options, min) { options.yAxis.min = min; }; var setMax = function (options, max) { options.yAxis.max = max; }; var setStacked = function (options, stacked) { options.plotOptions.series.stacking = stacked ? "normal" : null; }; var setXtitle = function (options, title) { options.xAxis.title.text = title; }; var setYtitle = function (options, title) { options.yAxis.title.text = title; }; var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle); this.renderLineChart = function (chart, chartType) { chartType = chartType || "spline"; var chartOptions = {}; if (chartType === "areaspline") { chartOptions = { plotOptions: { areaspline: { stacking: "normal" }, series: { marker: { enabled: false } } } }; } var options = jsOptions(chart.data, chart.options, chartOptions), data, i, j; options.xAxis.type = chart.options.discrete ? "category" : "datetime"; options.chart.type = chartType; options.chart.renderTo = chart.element.id; var series = chart.data; for (i = 0; i < series.length; i++) { data = series[i].data; if (!chart.options.discrete) { for (j = 0; j < data.length; j++) { data[j][0] = data[j][0].getTime(); } } series[i].marker = {symbol: "circle"}; } options.series = series; new Highcharts.Chart(options); }; this.renderScatterChart = function (chart) { var chartOptions = {}; var options = jsOptions(chart.data, chart.options, chartOptions); options.chart.type = 'scatter'; options.chart.renderTo = chart.element.id; options.series = chart.data; new Highcharts.Chart(options); }; this.renderPieChart = function (chart) { var chartOptions = {}; if (chart.options.colors) { chartOptions.colors = chart.options.colors; } var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); options.chart.renderTo = chart.element.id; options.series = [{ type: "pie", name: chart.options.label || "Value", data: chart.data }]; new Highcharts.Chart(options); }; this.renderColumnChart = function (chart, chartType) { chartType = chartType || "column"; var series = chart.data; var options = jsOptions(series, chart.options), i, j, s, d, rows = []; options.chart.type = chartType; options.chart.renderTo = chart.element.id; for (i = 0; i < series.length; i++) { s = series[i]; for (j = 0; j < s.data.length; j++) { d = s.data[j]; if (!rows[d[0]]) { rows[d[0]] = new Array(series.length); } rows[d[0]][i] = d[1]; } } var categories = []; for (i in rows) { if (rows.hasOwnProperty(i)) { categories.push(i); } } options.xAxis.categories = categories; var newSeries = []; for (i = 0; i < series.length; i++) { d = []; for (j = 0; j < categories.length; j++) { d.push(rows[categories[j]][i] || 0); } newSeries.push({ name: series[i].name, data: d }); } options.series = newSeries; new Highcharts.Chart(options); }; var self = this; this.renderBarChart = function (chart) { self.renderColumnChart(chart, "bar"); }; this.renderAreaChart = function (chart) { self.renderLineChart(chart, "areaspline"); }; }; adapters.push(HighchartsAdapter); } if (!GoogleChartsAdapter && window.google && window.google.setOnLoadCallback) { GoogleChartsAdapter = new function () { var google = window.google; this.name = "google"; var loaded = {}; var callbacks = []; var runCallbacks = function () { var cb, call; for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i]; call = google.visualization && ((cb.pack === "corechart" && google.visualization.LineChart) || (cb.pack === "timeline" && google.visualization.Timeline)); if (call) { cb.callback(); callbacks.splice(i, 1); i--; } } }; var waitForLoaded = function (pack, callback) { if (!callback) { callback = pack; pack = "corechart"; } callbacks.push({pack: pack, callback: callback}); if (loaded[pack]) { runCallbacks(); } else { loaded[pack] = true; // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI var loadOptions = { packages: [pack], callback: runCallbacks }; if (config.language) { loadOptions.language = config.language; } google.load("visualization", "1", loadOptions); } }; // Set chart options var defaultOptions = { chartArea: {}, fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif", pointSize: 6, legend: { textStyle: { fontSize: 12, color: "#444" }, alignment: "center", position: "right" }, curveType: "function", hAxis: { textStyle: { color: "#666", fontSize: 12 }, titleTextStyle: {}, gridlines: { color: "transparent" }, baselineColor: "#ccc", viewWindow: {} }, vAxis: { textStyle: { color: "#666", fontSize: 12 }, titleTextStyle: {}, baselineColor: "#ccc", viewWindow: {} }, tooltip: { textStyle: { color: "#666", fontSize: 12 } } }; var hideLegend = function (options) { options.legend.position = "none"; }; var setMin = function (options, min) { options.vAxis.viewWindow.min = min; }; var setMax = function (options, max) { options.vAxis.viewWindow.max = max; }; var setBarMin = function (options, min) { options.hAxis.viewWindow.min = min; }; var setBarMax = function (options, max) { options.hAxis.viewWindow.max = max; }; var setStacked = function (options, stacked) { options.isStacked = !!stacked; }; var setXtitle = function (options, title) { options.hAxis.title = title; options.hAxis.titleTextStyle.italic = false; }; var setYtitle = function (options, title) { options.vAxis.title = title; options.vAxis.titleTextStyle.italic = false; }; var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle); // cant use object as key var createDataTable = function (series, columnType) { var i, j, s, d, key, rows = []; for (i = 0; i < series.length; i++) { s = series[i]; for (j = 0; j < s.data.length; j++) { d = s.data[j]; key = (columnType === "datetime") ? d[0].getTime() : d[0]; if (!rows[key]) { rows[key] = new Array(series.length); } rows[key][i] = toFloat(d[1]); } } var rows2 = []; var day = true; var value; for (i in rows) { if (rows.hasOwnProperty(i)) { if (columnType === "datetime") { value = new Date(toFloat(i)); day = day && isDay(value); } else if (columnType === "number") { value = toFloat(i); } else { value = i; } rows2.push([value].concat(rows[i])); } } if (columnType === "datetime") { rows2.sort(sortByTime); } // create datatable var data = new google.visualization.DataTable(); columnType = columnType === "datetime" && day ? "date" : columnType; data.addColumn(columnType, ""); for (i = 0; i < series.length; i++) { data.addColumn("number", series[i].name); } data.addRows(rows2); return data; }; var resize = function (callback) { if (window.attachEvent) { window.attachEvent("onresize", callback); } else if (window.addEventListener) { window.addEventListener("resize", callback, true); } callback(); }; this.renderLineChart = function (chart) { waitForLoaded(function () { var options = jsOptions(chart.data, chart.options); var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime"); chart.chart = new google.visualization.LineChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderPieChart = function (chart) { waitForLoaded(function () { var chartOptions = { chartArea: { top: "10%", height: "80%" } }; if (chart.options.colors) { chartOptions.colors = chart.options.colors; } var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); var data = new google.visualization.DataTable(); data.addColumn("string", ""); data.addColumn("number", "Value"); data.addRows(chart.data); chart.chart = new google.visualization.PieChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderColumnChart = function (chart) { waitForLoaded(function () { var options = jsOptions(chart.data, chart.options); var data = createDataTable(chart.data, "string"); chart.chart = new google.visualization.ColumnChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderBarChart = function (chart) { waitForLoaded(function () { var chartOptions = { hAxis: { gridlines: { color: "#ccc" } } }; var options = jsOptionsFunc(defaultOptions, hideLegend, setBarMin, setBarMax, setStacked)(chart.data, chart.options, chartOptions); var data = createDataTable(chart.data, "string"); chart.chart = new google.visualization.BarChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderAreaChart = function (chart) { waitForLoaded(function () { var chartOptions = { isStacked: true, pointSize: 0, areaOpacity: 0.5 }; var options = jsOptions(chart.data, chart.options, chartOptions); var data = createDataTable(chart.data, chart.options.discrete ? "string" : "datetime"); chart.chart = new google.visualization.AreaChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderGeoChart = function (chart) { waitForLoaded(function () { var chartOptions = { legend: "none", colorAxis: { colors: chart.options.colors || ["#f6c7b6", "#ce502d"] } }; var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); var data = new google.visualization.DataTable(); data.addColumn("string", ""); data.addColumn("number", chart.options.label || "Value"); data.addRows(chart.data); chart.chart = new google.visualization.GeoChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderScatterChart = function (chart) { waitForLoaded(function () { var chartOptions = {}; var options = jsOptions(chart.data, chart.options, chartOptions); var data = createDataTable(chart.data, "number"); chart.chart = new google.visualization.ScatterChart(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; this.renderTimeline = function (chart) { waitForLoaded("timeline", function () { var chartOptions = { legend: "none" }; if (chart.options.colors) { chartOptions.colors = chart.options.colors; } var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {}); var data = new google.visualization.DataTable(); data.addColumn({type: "string", id: "Name"}); data.addColumn({type: "date", id: "Start"}); data.addColumn({type: "date", id: "End"}); data.addRows(chart.data); chart.element.style.lineHeight = "normal"; chart.chart = new google.visualization.Timeline(chart.element); resize(function () { chart.chart.draw(data, options); }); }); }; }; adapters.push(GoogleChartsAdapter); } if (!ChartjsAdapter && "Chart" in window) { ChartjsAdapter = new function () { var Chart = window.Chart; this.name = "chartjs"; var baseOptions = { maintainAspectRatio: false, animation: false }; var defaultOptions = { scales: { yAxes: [ { ticks: { maxTicksLimit: 4 }, scaleLabel: { fontSize: 16, // fontStyle: "bold", fontColor: "#333" } } ], xAxes: [ { gridLines: { drawOnChartArea: false }, scaleLabel: { fontSize: 16, // fontStyle: "bold", fontColor: "#333" }, time: {}, ticks: {} } ] }, legend: {} }; // http://there4.io/2012/05/02/google-chart-color-list/ var defaultColors = [ "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6", "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11", "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#3B3EAC" ]; var hideLegend = function (options) { options.legend.display = false; }; var setMin = function (options, min) { if (min !== null) { options.scales.yAxes[0].ticks.min = min; } }; var setMax = function (options, max) { options.scales.yAxes[0].ticks.max = max; }; var setBarMin = function (options, min) { if (min !== null) { options.scales.xAxes[0].ticks.min = min; } }; var setBarMax = function (options, max) { options.scales.xAxes[0].ticks.max = max; }; var setStacked = function (options, stacked) { options.scales.xAxes[0].stacked = !!stacked; options.scales.yAxes[0].stacked = !!stacked; }; var setXtitle = function (options, title) { options.scales.xAxes[0].scaleLabel.display = true; options.scales.xAxes[0].scaleLabel.labelString = title; }; var setYtitle = function (options, title) { options.scales.yAxes[0].scaleLabel.display = true; options.scales.yAxes[0].scaleLabel.labelString = title; }; var drawChart = function(chart, type, data, options) { chart.element.innerHTML = ""; var ctx = chart.element.getElementsByTagName("CANVAS")[0]; chart.chart = new Chart(ctx, { type: type, data: data, options: options }); }; // http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb var addOpacity = function(hex, opacity) { var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex; }; var setLabelSize = function (chart, data, options) { var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length); options.scales.xAxes[0].ticks.callback = function (value) { value = toStr(value); if (value.length > maxLabelSize) { return value.substring(0, maxLabelSize - 2) + "..."; } else { return value; } }; }; var jsOptions = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setMin, setMax, setStacked, setXtitle, setYtitle); var createDataTable = function (chart, options, chartType) { var datasets = []; var labels = []; var colors = chart.options.colors || defaultColors; var day = true; var week = true; var dayOfWeek; var month = true; var year = true; var hour = true; var minute = true; var detectType = (chartType === "line" || chartType === "area") && !chart.options.discrete; var series = chart.data; var sortedLabels = []; var i, j, s, d, key, rows = []; for (i = 0; i < series.length; i++) { s = series[i]; for (j = 0; j < s.data.length; j++) { d = s.data[j]; key = detectType ? d[0].getTime() : d[0]; if (!rows[key]) { rows[key] = new Array(series.length); } rows[key][i] = toFloat(d[1]); if (sortedLabels.indexOf(key) === -1) { sortedLabels.push(key); } } } if (detectType) { sortedLabels.sort(sortByNumber); } var rows2 = []; for (j = 0; j < series.length; j++) { rows2.push([]); } var value; var k; for (k = 0; k < sortedLabels.length; k++) { i = sortedLabels[k]; if (detectType) { value = new Date(toFloat(i)); // TODO make this efficient day = day && isDay(value); if (!dayOfWeek) { dayOfWeek = value.getDay(); } week = week && isWeek(value, dayOfWeek); month = month && isMonth(value); year = year && isYear(value); hour = hour && isHour(value); minute = minute && isMinute(value); } else { value = i; } labels.push(value); for (j = 0; j < series.length; j++) { rows2[j].push(rows[i][j]); } } for (i = 0; i < series.length; i++) { s = series[i]; var backgroundColor = chartType !== "line" ? addOpacity(colors[i], 0.5) : colors[i]; var dataset = { label: s.name, data: rows2[i], fill: chartType === "area", borderColor: colors[i], backgroundColor: backgroundColor, pointBackgroundColor: colors[i], borderWidth: 2 }; datasets.push(merge(dataset, s.library || {})); } if (detectType && labels.length > 0) { var minTime = labels[0].getTime(); var maxTime = labels[0].getTime(); for (i = 1; i < labels.length; i++) { value = labels[i].getTime(); if (value < minTime) { minTime = value; } if (value > maxTime) { maxTime = value; } } var timeDiff = (maxTime - minTime) / (86400 * 1000.0); if (!options.scales.xAxes[0].time.unit) { var step; if (year || timeDiff > 365 * 10) { options.scales.xAxes[0].time.unit = "year"; step = 365; } else if (month || timeDiff > 30 * 10) { options.scales.xAxes[0].time.unit = "month"; step = 30; } else if (day || timeDiff > 10) { options.scales.xAxes[0].time.unit = "day"; step = 1; } else if (hour) { options.scales.xAxes[0].time.unit = "hour"; step = 1 / 24.0; } else if (minute) { options.scales.xAxes[0].time.displayFormats = {minute: "h:mm a"}; options.scales.xAxes[0].time.unit = "minute"; step = 1 / 24.0 / 60.0; } if (step && timeDiff > 0) { var unitStepSize = Math.ceil(timeDiff / step / (chart.element.offsetWidth / 100.0)); if (week) { unitStepSize = Math.ceil(unitStepSize / 7.0) * 7; } options.scales.xAxes[0].time.unitStepSize = unitStepSize; } } if (!options.scales.xAxes[0].time.tooltipFormat) { if (day) { options.scales.xAxes[0].time.tooltipFormat = "ll"; } else if (hour) { options.scales.xAxes[0].time.tooltipFormat = "MMM D, h a"; } else if (minute) { options.scales.xAxes[0].time.tooltipFormat = "h:mm a"; } } } var data = { labels: labels, datasets: datasets }; return data; }; this.renderLineChart = function (chart, chartType) { var areaOptions = {}; if (chartType === "area") { // TODO fix area stacked // areaOptions.stacked = true; } // fix for https://github.com/chartjs/Chart.js/issues/2441 if (!chart.options.max && allZeros(chart.data)) { chart.options.max = 1; } var options = jsOptions(chart.data, merge(areaOptions, chart.options)); var data = createDataTable(chart, options, chartType || "line"); options.scales.xAxes[0].type = chart.options.discrete ? "category" : "time"; drawChart(chart, "line", data, options); }; this.renderPieChart = function (chart) { var options = merge(baseOptions, chart.options.library || {}); var labels = []; var values = []; for (var i = 0; i < chart.data.length; i++) { var point = chart.data[i]; labels.push(point[0]); values.push(point[1]); } var data = { labels: labels, datasets: [ { data: values, backgroundColor: chart.options.colors || defaultColors } ] }; drawChart(chart, "pie", data, options); }; this.renderColumnChart = function (chart, chartType) { var options; if (chartType === "bar") { options = jsOptionsFunc(merge(baseOptions, defaultOptions), hideLegend, setBarMin, setBarMax, setStacked)(chart.data, chart.options); } else { options = jsOptions(chart.data, chart.options); } var data = createDataTable(chart, options, "column"); setLabelSize(chart, data, options); drawChart(chart, (chartType === "bar" ? "horizontalBar" : "bar"), data, options); }; var self = this; this.renderAreaChart = function (chart) { self.renderLineChart(chart, "area"); }; this.renderBarChart = function (chart) { self.renderColumnChart(chart, "bar"); }; }; adapters.unshift(ChartjsAdapter); } } // TODO remove chartType if cross-browser way // to get the name of the chart class function renderChart(chartType, chart) { var i, adapter, fnName, adapterName; fnName = "render" + chartType; adapterName = chart.options.adapter; loadAdapters(); for (i = 0; i < adapters.length; i++) { adapter = adapters[i]; if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) { return adapter[fnName](chart); } } throw new Error("No adapter found"); } // process data var toFormattedKey = function (key, keyType) { if (keyType === "number") { key = toFloat(key); } else if (keyType === "datetime") { key = toDate(key); } else { key = toStr(key); } return key; }; var formatSeriesData = function (data, keyType) { var r = [], key, j; for (j = 0; j < data.length; j++) { key = toFormattedKey(data[j][0], keyType); r.push([key, toFloat(data[j][1])]); } if (keyType === "datetime") { r.sort(sortByTime); } return r; }; function isMinute(d) { return d.getMilliseconds() === 0 && d.getSeconds() === 0; } function isHour(d) { return isMinute(d) && d.getMinutes() === 0; } function isDay(d) { return isHour(d) && d.getHours() === 0; } function isWeek(d, dayOfWeek) { return isDay(d) && d.getDay() === dayOfWeek; } function isMonth(d) { return isDay(d) && d.getDate() === 1; } function isYear(d) { return isMonth(d) && d.getMonth() === 0; } function isDate(obj) { return !isNaN(toDate(obj)) && toStr(obj).length >= 6; } function allZeros(data) { var i, j, d; for (i = 0; i < data.length; i++) { d = data[i].data; for (j = 0; j < d.length; j++) { if (d[j][1] != 0) { return false; } } } return true; } function detectDiscrete(series) { var i, j, data; for (i = 0; i < series.length; i++) { data = toArr(series[i].data); for (j = 0; j < data.length; j++) { if (!isDate(data[j][0])) { return true; } } } return false; } function processSeries(series, opts, keyType) { var i; // see if one series or multiple if (!isArray(series) || typeof series[0] !== "object" || isArray(series[0])) { series = [{name: opts.label || "Value", data: series}]; opts.hideLegend = true; } else { opts.hideLegend = false; } if ((opts.discrete === null || opts.discrete === undefined)) { opts.discrete = detectDiscrete(series); } if (opts.discrete) { keyType = "string"; } // right format for (i = 0; i < series.length; i++) { series[i].data = formatSeriesData(toArr(series[i].data), keyType); } return series; } function processSimple(data) { var perfectData = toArr(data), i; for (i = 0; i < perfectData.length; i++) { perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])]; } return perfectData; } function processTime(data) { var i; for (i = 0; i < data.length; i++) { data[i][1] = toDate(data[i][1]); data[i][2] = toDate(data[i][2]); } return data; } function processLineData(chart) { chart.data = processSeries(chart.data, chart.options, "datetime"); renderChart("LineChart", chart); } function processColumnData(chart) { chart.data = processSeries(chart.data, chart.options, "string"); renderChart("ColumnChart", chart); } function processPieData(chart) { chart.data = processSimple(chart.data); renderChart("PieChart", chart); } function processBarData(chart) { chart.data = processSeries(chart.data, chart.options, "string"); renderChart("BarChart", chart); } function processAreaData(chart) { chart.data = processSeries(chart.data, chart.options, "datetime"); renderChart("AreaChart", chart); } function processGeoData(chart) { chart.data = processSimple(chart.data); renderChart("GeoChart", chart); } function processScatterData(chart) { chart.data = processSeries(chart.data, chart.options, "number"); renderChart("ScatterChart", chart); } function processTimelineData(chart) { chart.data = processTime(chart.data); renderChart("Timeline", chart); } function setElement(chart, element, dataSource, opts, callback) { if (typeof element === "string") { element = document.getElementById(element); } chart.element = element; chart.options = opts || {}; chart.dataSource = dataSource; chart.getElement = function () { return element; }; chart.getData = function () { return chart.data; }; chart.getOptions = function () { return opts || {}; }; chart.getChartObject = function () { return chart.chart; }; Chartkick.charts[element.id] = chart; fetchDataSource(chart, callback); } // define classes Chartkick = { LineChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processLineData); }, PieChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processPieData); }, ColumnChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processColumnData); }, BarChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processBarData); }, AreaChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processAreaData); }, GeoChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processGeoData); }, ScatterChart: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processScatterData); }, Timeline: function (element, dataSource, opts) { setElement(this, element, dataSource, opts, processTimelineData); }, charts: {} }; if (typeof module === "object" && typeof module.exports === "object") { module.exports = Chartkick; } else { window.Chartkick = Chartkick; } }(window)); chartkick-0.5.0/chartkick/options.py0000644000076500000240000000110212376042102017534 0ustar mherstaff00000000000000from __future__ import absolute_import import json import logging class Options(dict): def __init__(self, *args, **kwargs) : dict.__init__(self, *args, **kwargs) def load(self, filename): with open(filename) as jsonfile: options = json.loads(jsonfile.read()) self.clear() for option in options: id = option.get('id', None) if id is None: logging.warning("Missing chart 'id' in %s" % option) continue self.update({id: option}) chartkick-0.5.0/chartkick/template.py0000644000076500000240000000072012376042102017661 0ustar mherstaff00000000000000from __future__ import absolute_import CHART_HTML = """
Loading...
""" chartkick-0.5.0/chartkick/templatetags/0000755000076500000240000000000012723707103020174 5ustar mherstaff00000000000000chartkick-0.5.0/chartkick/templatetags/__init__.py0000644000076500000240000000000012376042102022266 0ustar mherstaff00000000000000chartkick-0.5.0/chartkick/templatetags/chartkick.py0000644000076500000240000000647012723703431022520 0ustar mherstaff00000000000000from __future__ import absolute_import import os import ast import json import functools import itertools from django import template from django.template import Engine from django.template.loaders.filesystem import Loader from ..template import CHART_HTML from ..options import Options register = template.Library() class ChartNode(template.Node): id = itertools.count() _library = None def __init__(self, name, variable, options=None): self.name = name self.variable = template.Variable(variable) self.options = options or {} for name, value in self.options.items(): try: self.options[name] = ast.literal_eval(value) except ValueError: self.options[name] = template.Variable(value) except SyntaxError as e: raise template.TemplateSyntaxError(e) def render(self, context): for name, value in self.options.items(): if isinstance(value, template.Variable): self.options[name] = value.resolve(context) options = dict(id='chart-%s' % next(self.id), height='300px') id = self.options.get('id', None) or options['id'] # apply options from chartkick.json options.update(library=self.library(id)) # apply options from a tag options.update(self.options) data = json.dumps(self.variable.resolve(context)) return CHART_HTML.format(name=self.name, data=data, options=json.dumps(options), **options) @classmethod def library(cls, chart_id): if cls._library is None: loader = Loader(Engine()) for filename in loader.get_template_sources('chartkick.json'): if os.path.exists(filename): oprtions = Options() oprtions.load(filename) cls._library = oprtions break else: cls._library = Options() return cls._library.get(chart_id, {}) def chart(name, parser, token): args = token.split_contents() if len(args) < 2: raise template.TemplateSyntaxError( '%r statement requires at least one argument' % token.split_contents()[0]) options = None if len(args) > 2: if args[2] != 'with': raise template.TemplateSyntaxError("Expected 'with' statement") try: options = parse_options(' '.join(args[3:])) except ValueError: raise template.TemplateSyntaxError('Invalid options') return ChartNode(name=name, variable=args[1], options=options) def parse_options(source): """parses chart tag options""" options = {} tokens = [t.strip() for t in source.split('=')] name = tokens[0] for token in tokens[1:-1]: value, next_name = token.rsplit(' ', 1) options[name.strip()] = value name = next_name options[name.strip()] = tokens[-1].strip() return options register.tag('line_chart', functools.partial(chart, 'LineChart')) register.tag('pie_chart', functools.partial(chart, 'PieChart')) register.tag('column_chart', functools.partial(chart, 'ColumnChart')) register.tag('bar_chart', functools.partial(chart, 'BarChart')) register.tag('area_chart', functools.partial(chart, 'AreaChart')) chartkick-0.5.0/chartkick.egg-info/0000755000076500000240000000000012723707103017174 5ustar mherstaff00000000000000chartkick-0.5.0/chartkick.egg-info/dependency_links.txt0000644000076500000240000000000112723707103023242 0ustar mherstaff00000000000000 chartkick-0.5.0/chartkick.egg-info/PKG-INFO0000644000076500000240000001150612723707103020274 0ustar mherstaff00000000000000Metadata-Version: 1.1 Name: chartkick Version: 0.5.0 Summary: Create beautiful Javascript charts with minimal code Home-page: https://github.com/mher/chartkick.py Author: Mher Movsisyan Author-email: mher.movsisyan@gmail.com License: MIT Description: Chartkick.py ============ .. image:: https://img.shields.io/pypi/v/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://img.shields.io/pypi/dm/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://travis-ci.org/mher/chartkick.py.svg?branch=master :target: https://travis-ci.org/mher/chartkick.py Create beautiful Javascript charts with minimal code. Demo_! Supports `Chart.js`_, `Google Charts`_, and Highcharts_ Works with Django, Flask/Jinja2 and most browsers (including IE 6). Also available in Ruby_ and pure JavaScript_ .. _Chartkick: http://chartkick.com .. _Chart.js: http://www.chartjs.org .. _Google Charts: https://developers.google.com/chart/ .. _Highcharts: http://highcharts.com .. _Demo: http://mher.github.io/chartkick.py/ .. _Ruby: http://chartkick.com .. _Javascript: https://github.com/ankane/chartkick.js Usage ----- Line chart: :: {% line_chart data %} Pie chart: :: {% pie_chart data with id='chart-1' height='400px' %} Column chart: :: {% column_chart data with min=400 max=1000 %} Bar chart: :: {% bar_chart data %} Area chart: :: {% area_chart data %} Data ---- Data can be a dictionary or a list: :: {'Chrome': 52.9, 'Opera': 1.6, 'Firefox': 27.7} [['Chrome', 52.9], ['Firefox', 27.7], ['Opera', 1.6]] For multiple series: :: [{'data': [['2013-04-01 00:00:00 UTC', 52.9], ['2013-05-01 00:00:00 UTC', 50.7]], 'name': 'Chrome'}, {'data': [['2013-04-01 00:00:00 UTC', 27.7], ['2013-05-01 00:00:00 UTC', 25.9]], 'name': 'Firefox'}] Options ------- Charting library options can be passed through the *library* variable: :: {% column_chart data with library={"title":"Super chart","width":"400px"} %} .. Note:: Google Charts and Highcharts have different APIs. You may need to change the value of `library` when you switch from one library to another. Or using *chartkick.json* file. Chartkick tries to locate *chartkick.json* file in template path and match options by id. Installation ------------ Install chartkick: :: $ pip install chartkick - Django: Add chartkick to *INSTALLED_APPS* and *STATICFILES_DIRS*: :: INSTALLED_APPS = ( 'chartkick', ) import chartkick STATICFILES_DIRS = ( chartkick.js(), ) - Flask: Add chartkick to *jinja_env* and *static_folder*: :: ck = Blueprint('ck_page', __name__, static_folder=chartkick.js(), static_url_path='/static') app.register_blueprint(ck, url_prefix='/ck') app.jinja_env.add_extension("chartkick.ext.charts") Load JS scripts: - Chart.js :: - Google Charts :: - Highcharts :: Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development :: Libraries Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Operating System :: OS Independent chartkick-0.5.0/chartkick.egg-info/SOURCES.txt0000644000076500000240000000052612723707103021063 0ustar mherstaff00000000000000MANIFEST.in README.rst setup.py chartkick/__init__.py chartkick/ext.py chartkick/options.py chartkick/template.py chartkick.egg-info/PKG-INFO chartkick.egg-info/SOURCES.txt chartkick.egg-info/dependency_links.txt chartkick.egg-info/top_level.txt chartkick/js/chartkick.js chartkick/templatetags/__init__.py chartkick/templatetags/chartkick.pychartkick-0.5.0/chartkick.egg-info/top_level.txt0000644000076500000240000000001212723707103021717 0ustar mherstaff00000000000000chartkick chartkick-0.5.0/MANIFEST.in0000644000076500000240000000011012376042102015260 0ustar mherstaff00000000000000include MANIFEST.in include README.rst recursive-include chartkick/js * chartkick-0.5.0/PKG-INFO0000644000076500000240000001150612723707103014637 0ustar mherstaff00000000000000Metadata-Version: 1.1 Name: chartkick Version: 0.5.0 Summary: Create beautiful Javascript charts with minimal code Home-page: https://github.com/mher/chartkick.py Author: Mher Movsisyan Author-email: mher.movsisyan@gmail.com License: MIT Description: Chartkick.py ============ .. image:: https://img.shields.io/pypi/v/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://img.shields.io/pypi/dm/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://travis-ci.org/mher/chartkick.py.svg?branch=master :target: https://travis-ci.org/mher/chartkick.py Create beautiful Javascript charts with minimal code. Demo_! Supports `Chart.js`_, `Google Charts`_, and Highcharts_ Works with Django, Flask/Jinja2 and most browsers (including IE 6). Also available in Ruby_ and pure JavaScript_ .. _Chartkick: http://chartkick.com .. _Chart.js: http://www.chartjs.org .. _Google Charts: https://developers.google.com/chart/ .. _Highcharts: http://highcharts.com .. _Demo: http://mher.github.io/chartkick.py/ .. _Ruby: http://chartkick.com .. _Javascript: https://github.com/ankane/chartkick.js Usage ----- Line chart: :: {% line_chart data %} Pie chart: :: {% pie_chart data with id='chart-1' height='400px' %} Column chart: :: {% column_chart data with min=400 max=1000 %} Bar chart: :: {% bar_chart data %} Area chart: :: {% area_chart data %} Data ---- Data can be a dictionary or a list: :: {'Chrome': 52.9, 'Opera': 1.6, 'Firefox': 27.7} [['Chrome', 52.9], ['Firefox', 27.7], ['Opera', 1.6]] For multiple series: :: [{'data': [['2013-04-01 00:00:00 UTC', 52.9], ['2013-05-01 00:00:00 UTC', 50.7]], 'name': 'Chrome'}, {'data': [['2013-04-01 00:00:00 UTC', 27.7], ['2013-05-01 00:00:00 UTC', 25.9]], 'name': 'Firefox'}] Options ------- Charting library options can be passed through the *library* variable: :: {% column_chart data with library={"title":"Super chart","width":"400px"} %} .. Note:: Google Charts and Highcharts have different APIs. You may need to change the value of `library` when you switch from one library to another. Or using *chartkick.json* file. Chartkick tries to locate *chartkick.json* file in template path and match options by id. Installation ------------ Install chartkick: :: $ pip install chartkick - Django: Add chartkick to *INSTALLED_APPS* and *STATICFILES_DIRS*: :: INSTALLED_APPS = ( 'chartkick', ) import chartkick STATICFILES_DIRS = ( chartkick.js(), ) - Flask: Add chartkick to *jinja_env* and *static_folder*: :: ck = Blueprint('ck_page', __name__, static_folder=chartkick.js(), static_url_path='/static') app.register_blueprint(ck, url_prefix='/ck') app.jinja_env.add_extension("chartkick.ext.charts") Load JS scripts: - Chart.js :: - Google Charts :: - Highcharts :: Platform: UNKNOWN Classifier: Development Status :: 3 - Alpha Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development :: Libraries Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Operating System :: OS Independent chartkick-0.5.0/README.rst0000644000076500000240000000611612723706751015242 0ustar mherstaff00000000000000Chartkick.py ============ .. image:: https://img.shields.io/pypi/v/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://img.shields.io/pypi/dm/chartkick.svg :target: https://pypi.python.org/pypi/chartkick .. image:: https://travis-ci.org/mher/chartkick.py.svg?branch=master :target: https://travis-ci.org/mher/chartkick.py Create beautiful Javascript charts with minimal code. Demo_! Supports `Chart.js`_, `Google Charts`_, and Highcharts_ Works with Django, Flask/Jinja2 and most browsers (including IE 6). Also available in Ruby_ and pure JavaScript_ .. _Chartkick: http://chartkick.com .. _Chart.js: http://www.chartjs.org .. _Google Charts: https://developers.google.com/chart/ .. _Highcharts: http://highcharts.com .. _Demo: http://mher.github.io/chartkick.py/ .. _Ruby: http://chartkick.com .. _Javascript: https://github.com/ankane/chartkick.js Usage ----- Line chart: :: {% line_chart data %} Pie chart: :: {% pie_chart data with id='chart-1' height='400px' %} Column chart: :: {% column_chart data with min=400 max=1000 %} Bar chart: :: {% bar_chart data %} Area chart: :: {% area_chart data %} Data ---- Data can be a dictionary or a list: :: {'Chrome': 52.9, 'Opera': 1.6, 'Firefox': 27.7} [['Chrome', 52.9], ['Firefox', 27.7], ['Opera', 1.6]] For multiple series: :: [{'data': [['2013-04-01 00:00:00 UTC', 52.9], ['2013-05-01 00:00:00 UTC', 50.7]], 'name': 'Chrome'}, {'data': [['2013-04-01 00:00:00 UTC', 27.7], ['2013-05-01 00:00:00 UTC', 25.9]], 'name': 'Firefox'}] Options ------- Charting library options can be passed through the *library* variable: :: {% column_chart data with library={"title":"Super chart","width":"400px"} %} .. Note:: Google Charts and Highcharts have different APIs. You may need to change the value of `library` when you switch from one library to another. Or using *chartkick.json* file. Chartkick tries to locate *chartkick.json* file in template path and match options by id. Installation ------------ Install chartkick: :: $ pip install chartkick - Django: Add chartkick to *INSTALLED_APPS* and *STATICFILES_DIRS*: :: INSTALLED_APPS = ( 'chartkick', ) import chartkick STATICFILES_DIRS = ( chartkick.js(), ) - Flask: Add chartkick to *jinja_env* and *static_folder*: :: ck = Blueprint('ck_page', __name__, static_folder=chartkick.js(), static_url_path='/static') app.register_blueprint(ck, url_prefix='/ck') app.jinja_env.add_extension("chartkick.ext.charts") Load JS scripts: - Chart.js :: - Google Charts :: - Highcharts :: chartkick-0.5.0/setup.cfg0000644000076500000240000000007312723707103015360 0ustar mherstaff00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 chartkick-0.5.0/setup.py0000644000076500000240000000275612521244543015263 0ustar mherstaff00000000000000#!/usr/bin/env python import os import re from setuptools import setup, find_packages version = re.compile(r'VERSION\s*=\s*\((.*?)\)') def get_package_version(): "returns package version without importing it" base = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(base, "chartkick/__init__.py")) as initf: for line in initf: m = version.match(line.strip()) if not m: continue return ".".join(m.groups()[0].split(", ")) classes = """ Development Status :: 3 - Alpha Intended Audience :: Developers License :: OSI Approved :: MIT License Topic :: Software Development :: Libraries Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.3 Programming Language :: Python :: 3.4 Programming Language :: Python :: Implementation :: CPython Operating System :: OS Independent """ classifiers = [s.strip() for s in classes.split('\n') if s] setup( name='chartkick', version=get_package_version(), description='Create beautiful Javascript charts with minimal code', long_description=open('README.rst').read(), author='Mher Movsisyan', author_email='mher.movsisyan@gmail.com', url='https://github.com/mher/chartkick.py', license='MIT', classifiers=classifiers, packages=find_packages(exclude=['tests', 'tests.*']), package_data={'chartkick': ['js/*']}, )