Adding nvd3 support

This commit is contained in:
Maxime Beauchemin 2015-09-26 15:53:37 -07:00
parent f1a64c0988
commit 89e92904e1
8 changed files with 872 additions and 2 deletions

View File

View File

@ -1,5 +1,6 @@
from wtforms import (
Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField)
Field, Form, SelectMultipleField, SelectField, TextField, TextAreaField,
BooleanField)
from copy import copy
@ -104,6 +105,21 @@ def form_factory(viz):
"Font Size To",
default="150",
description="Font size for the biggest value in the list"),
'show_brush': BooleanField(
"Range Selector", default=True,
description="Whether to display the time range interactive selector"),
'show_legend': BooleanField(
"Legend", default=True,
description="Whether to display the legend (toggles)"),
'rich_tooltip': BooleanField(
"Rich Tooltip", default=True,
description="The rich tooltip shows a list of all series for that point in time"),
'y_axis_zero': BooleanField(
"Y Axis Zero", default=False,
description="Force the Y axis to start at 0 instead of the minimum value"),
'y_log_scale': BooleanField(
"Y Log", default=False,
description="Use a log scale for the Y axis"),
}
field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()}
select2 = [
@ -114,6 +130,8 @@ def form_factory(viz):
field_css_classes['since'] += ['select2_free_since']
field_css_classes['until'] += ['select2_free_until']
field_css_classes['granularity'] += ['select2_free_granularity']
for field in ('show_brush', 'show_legend', 'rich_tooltip'):
field_css_classes[field] += ['input-sm']
for field in select2:
field_css_classes[field] += ['select2']

643
panoramix/static/nv.d3.css Normal file
View File

@ -0,0 +1,643 @@
/* nvd3 version 1.8.1 (https://github.com/novus/nvd3) 2015-06-15 */
.nvd3 .nv-axis {
pointer-events:none;
opacity: 1;
}
.nvd3 .nv-axis path {
fill: none;
stroke: #000;
stroke-opacity: .75;
shape-rendering: crispEdges;
}
.nvd3 .nv-axis path.domain {
stroke-opacity: .75;
}
.nvd3 .nv-axis.nv-x path.domain {
stroke-opacity: 0;
}
.nvd3 .nv-axis line {
fill: none;
stroke: #e5e5e5;
shape-rendering: crispEdges;
}
.nvd3 .nv-axis .zero line,
/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero {
stroke-opacity: .75;
}
.nvd3 .nv-axis .nv-axisMaxMin text {
font-weight: bold;
}
.nvd3 .x .nv-axis .nv-axisMaxMin text,
.nvd3 .x2 .nv-axis .nv-axisMaxMin text,
.nvd3 .x3 .nv-axis .nv-axisMaxMin text {
text-anchor: middle
}
.nvd3 .nv-axis.nv-disabled {
opacity: 0;
}
.nvd3 .nv-bars rect {
fill-opacity: .75;
transition: fill-opacity 250ms linear;
-moz-transition: fill-opacity 250ms linear;
-webkit-transition: fill-opacity 250ms linear;
}
.nvd3 .nv-bars rect.hover {
fill-opacity: 1;
}
.nvd3 .nv-bars .hover rect {
fill: lightblue;
}
.nvd3 .nv-bars text {
fill: rgba(0,0,0,0);
}
.nvd3 .nv-bars .hover text {
fill: rgba(0,0,0,1);
}
.nvd3 .nv-multibar .nv-groups rect,
.nvd3 .nv-multibarHorizontal .nv-groups rect,
.nvd3 .nv-discretebar .nv-groups rect {
stroke-opacity: 0;
transition: fill-opacity 250ms linear;
-moz-transition: fill-opacity 250ms linear;
-webkit-transition: fill-opacity 250ms linear;
}
.nvd3 .nv-multibar .nv-groups rect:hover,
.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,
.nvd3 .nv-candlestickBar .nv-ticks rect:hover,
.nvd3 .nv-discretebar .nv-groups rect:hover {
fill-opacity: 1;
}
.nvd3 .nv-discretebar .nv-groups text,
.nvd3 .nv-multibarHorizontal .nv-groups text {
font-weight: bold;
fill: rgba(0,0,0,1);
stroke: rgba(0,0,0,0);
}
/* boxplot CSS */
.nvd3 .nv-boxplot circle {
fill-opacity: 0.5;
}
.nvd3 .nv-boxplot circle:hover {
stroke: #AAA;
}
.nvd3 .nv-boxplot rect:hover {
fill-opacity: 1;
}
.nvd3 line.nv-boxplot-median {
stroke: black;
}
.nv-boxplot-tick:hover {
stroke-width: 2.5px;
}
/* bullet */
.nvd3.nv-bullet { font: 10px sans-serif; }
.nvd3.nv-bullet .nv-measure { fill-opacity: .8; }
.nvd3.nv-bullet .nv-measure:hover { fill-opacity: 1; }
.nvd3.nv-bullet .nv-marker { stroke: #000; stroke-width: 2px; }
.nvd3.nv-bullet .nv-markerTriangle { stroke: #000; fill: #fff; stroke-width: 1.5px; }
.nvd3.nv-bullet .nv-tick line { stroke: #666; stroke-width: .5px; }
.nvd3.nv-bullet .nv-range.nv-s0 { fill: #eee; }
.nvd3.nv-bullet .nv-range.nv-s1 { fill: #ddd; }
.nvd3.nv-bullet .nv-range.nv-s2 { fill: #ccc; }
.nvd3.nv-bullet .nv-title { font-size: 14px; font-weight: bold; }
.nvd3.nv-bullet .nv-subtitle { fill: #999; }
.nvd3.nv-bullet .nv-range {
fill: #bababa;
fill-opacity: .4;
}
.nvd3.nv-bullet .nv-range:hover {
fill-opacity: .7;
}
.nvd3.nv-candlestickBar .nv-ticks .nv-tick {
stroke-width: 1px;
}
.nvd3.nv-candlestickBar .nv-ticks .nv-tick.hover {
stroke-width: 2px;
}
.nvd3.nv-candlestickBar .nv-ticks .nv-tick.positive rect {
stroke: #2ca02c;
fill: #2ca02c;
}
.nvd3.nv-candlestickBar .nv-ticks .nv-tick.negative rect {
stroke: #d62728;
fill: #d62728;
}
.with-transitions .nv-candlestickBar .nv-ticks .nv-tick {
transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
-moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
-webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
}
.nvd3.nv-candlestickBar .nv-ticks line {
stroke: #333;
}
.nvd3 .nv-legend .nv-disabled rect {
/*fill-opacity: 0;*/
}
.nvd3 .nv-check-box .nv-box {
fill-opacity:0;
stroke-width:2;
}
.nvd3 .nv-check-box .nv-check {
fill-opacity:0;
stroke-width:4;
}
.nvd3 .nv-series.nv-disabled .nv-check-box .nv-check {
fill-opacity:0;
stroke-opacity:0;
}
.nvd3 .nv-controlsWrap .nv-legend .nv-check-box .nv-check {
opacity: 0;
}
/* line plus bar */
.nvd3.nv-linePlusBar .nv-bar rect {
fill-opacity: .75;
}
.nvd3.nv-linePlusBar .nv-bar rect:hover {
fill-opacity: 1;
}
.nvd3 .nv-groups path.nv-line {
fill: none;
}
.nvd3 .nv-groups path.nv-area {
stroke: none;
}
.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point {
fill-opacity: 0;
stroke-opacity: 0;
}
.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point {
fill-opacity: .5 !important;
stroke-opacity: .5 !important;
}
.with-transitions .nvd3 .nv-groups .nv-point {
transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
-moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
-webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear;
}
.nvd3.nv-scatter .nv-groups .nv-point.hover,
.nvd3 .nv-groups .nv-point.hover {
stroke-width: 7px;
fill-opacity: .95 !important;
stroke-opacity: .95 !important;
}
.nvd3 .nv-point-paths path {
stroke: #aaa;
stroke-opacity: 0;
fill: #eee;
fill-opacity: 0;
}
.nvd3 .nv-indexLine {
cursor: ew-resize;
}
/********************
* SVG CSS
*/
/********************
Default CSS for an svg element nvd3 used
*/
svg.nvd3-svg {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
-moz-user-select: none;
user-select: none;
display: block;
width:100%;
height:100%;
}
/********************
Box shadow and border radius styling
*/
.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip {
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
}
.nvd3 text {
font: normal 12px Arial;
}
.nvd3 .title {
font: bold 14px Arial;
}
.nvd3 .nv-background {
fill: white;
fill-opacity: 0;
}
.nvd3.nv-noData {
font-size: 18px;
font-weight: bold;
}
/**********
* Brush
*/
.nv-brush .extent {
fill-opacity: .125;
shape-rendering: crispEdges;
}
.nv-brush .resize path {
fill: #eee;
stroke: #666;
}
/**********
* Legend
*/
.nvd3 .nv-legend .nv-series {
cursor: pointer;
}
.nvd3 .nv-legend .nv-disabled circle {
fill-opacity: 0;
}
/* focus */
.nvd3 .nv-brush .extent {
fill-opacity: 0 !important;
}
.nvd3 .nv-brushBackground rect {
stroke: #000;
stroke-width: .4;
fill: #fff;
fill-opacity: .7;
}
.nvd3.nv-ohlcBar .nv-ticks .nv-tick {
stroke-width: 1px;
}
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover {
stroke-width: 2px;
}
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive {
stroke: #2ca02c;
}
.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative {
stroke: #d62728;
}
.nvd3 .background path {
fill: none;
stroke: #EEE;
stroke-opacity: .4;
shape-rendering: crispEdges;
}
.nvd3 .foreground path {
fill: none;
stroke-opacity: .7;
}
.nvd3 .nv-parallelCoordinates-brush .extent
{
fill: #fff;
fill-opacity: .6;
stroke: gray;
shape-rendering: crispEdges;
}
.nvd3 .nv-parallelCoordinates .hover {
fill-opacity: 1;
stroke-width: 3px;
}
.nvd3 .missingValuesline line {
fill: none;
stroke: black;
stroke-width: 1;
stroke-opacity: 1;
stroke-dasharray: 5, 5;
}
.nvd3.nv-pie path {
stroke-opacity: 0;
transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
-moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
-webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear;
}
.nvd3.nv-pie .nv-pie-title {
font-size: 24px;
fill: rgba(19, 196, 249, 0.59);
}
.nvd3.nv-pie .nv-slice text {
stroke: #000;
stroke-width: 0;
}
.nvd3.nv-pie path {
stroke: #fff;
stroke-width: 1px;
stroke-opacity: 1;
}
.nvd3.nv-pie .hover path {
fill-opacity: .7;
}
.nvd3.nv-pie .nv-label {
pointer-events: none;
}
.nvd3.nv-pie .nv-label rect {
fill-opacity: 0;
stroke-opacity: 0;
}
/* scatter */
.nvd3 .nv-groups .nv-point.hover {
stroke-width: 20px;
stroke-opacity: .5;
}
.nvd3 .nv-scatter .nv-point.hover {
fill-opacity: 1;
}
.nv-noninteractive {
pointer-events: none;
}
.nv-distx, .nv-disty {
pointer-events: none;
}
/* sparkline */
.nvd3.nv-sparkline path {
fill: none;
}
.nvd3.nv-sparklineplus g.nv-hoverValue {
pointer-events: none;
}
.nvd3.nv-sparklineplus .nv-hoverValue line {
stroke: #333;
stroke-width: 1.5px;
}
.nvd3.nv-sparklineplus,
.nvd3.nv-sparklineplus g {
pointer-events: all;
}
.nvd3 .nv-hoverArea {
fill-opacity: 0;
stroke-opacity: 0;
}
.nvd3.nv-sparklineplus .nv-xValue,
.nvd3.nv-sparklineplus .nv-yValue {
stroke-width: 0;
font-size: .9em;
font-weight: normal;
}
.nvd3.nv-sparklineplus .nv-yValue {
stroke: #f66;
}
.nvd3.nv-sparklineplus .nv-maxValue {
stroke: #2ca02c;
fill: #2ca02c;
}
.nvd3.nv-sparklineplus .nv-minValue {
stroke: #d62728;
fill: #d62728;
}
.nvd3.nv-sparklineplus .nv-currentValue {
font-weight: bold;
font-size: 1.1em;
}
/* stacked area */
.nvd3.nv-stackedarea path.nv-area {
fill-opacity: .7;
stroke-opacity: 0;
transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
-moz-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
-webkit-transition: fill-opacity 250ms linear, stroke-opacity 250ms linear;
}
.nvd3.nv-stackedarea path.nv-area.hover {
fill-opacity: .9;
}
.nvd3.nv-stackedarea .nv-groups .nv-point {
stroke-opacity: 0;
fill-opacity: 0;
}
.nvtooltip {
position: absolute;
background-color: rgba(255,255,255,1.0);
color: rgba(0,0,0,1.0);
padding: 1px;
border: 1px solid rgba(0,0,0,.2);
z-index: 10000;
display: block;
font-family: Arial;
font-size: 11px;
text-align: left;
pointer-events: none;
white-space: nowrap;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.nvtooltip {
background: rgba(255,255,255, 0.8);
border: 1px solid rgba(0,0,0,0.5);
border-radius: 4px;
}
/*Give tooltips that old fade in transition by
putting a "with-transitions" class on the container div.
*/
.nvtooltip.with-transitions, .with-transitions .nvtooltip {
transition: opacity 50ms linear;
-moz-transition: opacity 50ms linear;
-webkit-transition: opacity 50ms linear;
transition-delay: 200ms;
-moz-transition-delay: 200ms;
-webkit-transition-delay: 200ms;
}
.nvtooltip.x-nvtooltip,
.nvtooltip.y-nvtooltip {
padding: 8px;
}
.nvtooltip h3 {
margin: 0;
padding: 4px 14px;
line-height: 18px;
font-weight: normal;
background-color: rgba(247,247,247,0.75);
color: rgba(0,0,0,1.0);
text-align: center;
border-bottom: 1px solid #ebebeb;
-webkit-border-radius: 5px 5px 0 0;
-moz-border-radius: 5px 5px 0 0;
border-radius: 5px 5px 0 0;
}
.nvtooltip p {
margin: 0;
padding: 5px 14px;
text-align: center;
}
.nvtooltip span {
display: inline-block;
margin: 2px 0;
}
.nvtooltip table {
margin: 6px;
border-spacing:0;
}
.nvtooltip table td {
padding: 2px 9px 2px 0;
vertical-align: middle;
}
.nvtooltip table td.key {
font-weight:normal;
}
.nvtooltip table td.value {
text-align: right;
font-weight: bold;
}
.nvtooltip table tr.highlight td {
padding: 1px 9px 1px 0;
border-bottom-style: solid;
border-bottom-width: 4px;
border-top-style: solid;
border-top-width: 4px;
font-weight: bold;
font-size: 15px;
}
.nvtooltip table td.legend-color-guide div {
width: 8px;
height: 8px;
vertical-align: middle;
}
.nvtooltip table td.legend-color-guide div {
width: 12px;
height: 12px;
border: 1px solid #999;
}
.nvtooltip .footer {
padding: 3px;
text-align: center;
}
.nvtooltip-pending-removal {
pointer-events: none;
display: none;
}
/****
Interactive Layer
*/
.nvd3 .nv-interactiveGuideLine {
pointer-events:none;
}
.nvd3 line.nv-guideline {
stroke: #ccc;
}

8
panoramix/static/nv.d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -123,7 +123,7 @@
{% endif %}
</h3>
<hr/>
<div class="viz" style="height: 600px;">
<div class="viz" style="height: 700px;">
{% block viz_html %}
{% if viz.error_msg %}
<div class="alert alert-danger">{{ viz.error_msg }}</div>

View File

@ -0,0 +1,99 @@
{% macro viz_html(viz) %}
<div id="{{ viz.token }}" style="height:100%; width: 100%">
<img src="{{ url_for("static", filename="loading.gif") }}" class="loading">
<div class="chart" style="height:100%; width: 100%"></div>
</div>
{% endmacro %}
{% macro viz_js(viz) %}
<script>
$( document ).ready(function() {
function UTC(dttm){
return v = new Date(dttm.getUTCFullYear(), dttm.getUTCMonth(), dttm.getUTCDate(), dttm.getUTCHours(), dttm.getUTCMinutes(), dttm.getUTCSeconds());
}
var tickMultiFormat = d3.time.format.multi([
[".%L", function(d) { return d.getMilliseconds(); }],
[":%S", function(d) { return d.getSeconds(); }],
["%I:%M", function(d) { return d.getMinutes(); }],
["%I %p", function(d) { return d.getHours(); }],
["%a %d", function(d) { return d.getDay() && d.getDate() != 1; }],
["%b %d", function(d) { return d.getDate() != 1; }],
["%B", function(d) { return d.getMonth(); }],
["%Y", function() { return true; }]
]);
colors = [
"#FF5A5F", "#007A87", "#7B0051", "#00D1C1", "#8CE071", "#FFB400",
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
];
var token = d3.select("#{{ viz.token }}");
var jtoken = $("#{{ viz.token }}");
var loading = $("#{{ viz.token }}").find("img.loading");
var chart = $("#{{ viz.token }}").find("div.chart");
var refresh = function(){
chart.hide();
loading.show();
var url = "{{ viz.get_url(json="true")|safe }}";
$.getJSON(url, function(data){
nv.addGraph(function() {
{% if viz.chart_type == 'nvd3_line' %}
{% if viz.args.show_brush == 'y' %}
var chart = nv.models.lineWithFocusChart()
var xext = chart.xAxis.scale().domain();
chart
.x2Axis
.tickFormat(function (d) {return tickMultiFormat(UTC(new Date(d))); })
.tickValues([]);
{% elif viz.chart_type == 'nvd3_line' %}
var chart = nv.models.lineChart()
{% endif %}
chart.xScale(d3.time.scale());
chart.xAxis
.showMaxMin(false)
.tickFormat(function (d) {return tickMultiFormat(new Date(d)); });
chart.showLegend({{ "{}".format(viz.args.show_legend=='y')|lower }});
{% elif viz.chart_type == 'nvd3_bar' %}
var chart = nv.models.multiBarChart()
.showControls(true) //Allow user to switch between 'Grouped' and 'Stacked' mode.
.groupSpacing(0.1); //Distance between each group of bars.
chart.xAxis
.showMaxMin(false)
.tickFormat(function (d) {return tickMultiFormat(UTC(new Date(d))); });
{% endif %}
chart.yAxis.tickFormat(d3.format('.3s'));
{% if viz.chart_type == "nvd3_line" and viz.args.rich_tooltip == 'y' %}
chart.useInteractiveGuideline(true);
{% endif %}
{% if viz.args.y_axis_zero == 'y' %}
chart.forceY([0, 1]);
{% elif viz.args.y_log_scale == 'y' %}
chart.yScale(d3.scale.log());
{% endif %}
token.select('.chart').append("svg")
.datum(data)
.transition().duration(500)
.call(chart);
return chart;
});
chart.show();
loading.hide();
}).fail(function(xhr) {
var err = '<div class="alert alert-danger">' + xhr.responseText + '</div>';
loading.hide();
chart.show();
chart.html(err);
});
};
refresh();
jtoken.parent().find("a.refresh").click(refresh);
});
</script>
{% endmacro %}
{% macro viz_css(viz) %}
{% endmacro %}

View File

@ -1,4 +1,5 @@
from datetime import datetime
import hashlib
from sqlalchemy.types import TypeDecorator, TEXT
import json
import parsedatetime
@ -79,3 +80,18 @@ class JSONEncodedDict(TypeDecorator):
if value is not None:
value = json.loads(value)
return value
def color(s):
"""
Get a consistent color from the same string using a hash function
>>> color("foo")
'#FF5A5F'
"""
colors = [
"#FF5A5F", "#007A87", "#7B0051", "#00D1C1", "#8CE071", "#FFB400",
"#FFAA91", "#B4A76C", "#9CA299", "#565A5C"
]
h = hashlib.md5(s)
i = int(h.hexdigest(), 16)
return colors[i % len(colors)]

View File

@ -5,6 +5,7 @@ import uuid
from flask import flash
from markdown import markdown
from pandas.io.json import dumps
from werkzeug.datastructures import MultiDict
from werkzeug.urls import Href
import numpy as np
@ -209,6 +210,14 @@ class WordCloudViz(BaseViz):
return df.to_json(orient="records")
class NVD3Viz(BaseViz):
verbose_name = "Base NVD3 Viz"
template = 'panoramix/viz_nvd3.html'
chart_kind = 'line'
js_files = ['nv.d3.min.js']
css_files = ['nv.d3.css']
class HighchartsViz(BaseViz):
verbose_name = "Base Highcharts Viz"
template = 'panoramix/viz_highcharts.html'
@ -347,6 +356,81 @@ class TimeSeriesViz(HighchartsViz):
return chart.json
class NVD3TimeSeriesViz(NVD3Viz):
verbose_name = "NVD3 - Time Series - Line Chart"
chart_type = "nvd3_line"
form_fields = [
'viz_type',
'granularity', ('since', 'until'),
'metrics',
'groupby', 'limit',
('rolling_type', 'rolling_periods'),
('show_brush', 'show_legend'),
('rich_tooltip', 'y_axis_zero'),
'y_log_scale',
]
def get_df(self):
args = self.args
df = super(NVD3TimeSeriesViz, self).get_df()
metrics = self.metrics
df = df.pivot_table(
index="timestamp",
columns=self.groupby,
values=metrics,)
rolling_periods = args.get("rolling_periods")
rolling_type = args.get("rolling_type")
if rolling_periods and rolling_type:
if rolling_type == 'mean':
df = pd.rolling_mean(df, int(rolling_periods))
elif rolling_type == 'std':
df = pd.rolling_std(df, int(rolling_periods))
elif rolling_type == 'sum':
df = pd.rolling_sum(df, int(rolling_periods))
return df
def get_json(self):
df = self.get_df()
series = df.to_dict('series')
datas = []
for name, ys in series.items():
if df[name].dtype.kind not in "biufc":
continue
df.tz_localize(None)
df.index.tz_localize(None)
df['timestamp'] = pd.to_datetime(df.index, utc=False)
if isinstance(name, basestring):
series_title = name
elif len(self.metrics) > 1:
series_title = ", ".join(name)
else:
series_title = ", ".join(name[1:])
d = {
"key": series_title,
"color": utils.color(series_title),
"values": [
{'x': ds, 'y': ys[i]}
for i, ds in enumerate(df.timestamp)]
}
datas.append(d)
return dumps(datas)
class NVD3TimeSeriesBarViz(NVD3TimeSeriesViz):
verbose_name = "NVD3 - Time Series - Bar Chart"
chart_type = "nvd3_bar"
form_fields = [
'viz_type',
'granularity', ('since', 'until'),
'metrics',
'groupby', 'limit',
('rolling_type', 'rolling_periods'),
'show_legend',
]
class TimeSeriesCompareViz(TimeSeriesViz):
verbose_name = "Time Series - Percent Change"
compare = 'percent'
@ -413,6 +497,8 @@ class DistributionBarViz(DistributionPieViz):
viz_types = OrderedDict([
['table', TableViz],
['nvd3_line', NVD3TimeSeriesViz],
['nvd3_bar', NVD3TimeSeriesBarViz],
['line', TimeSeriesViz],
['big_number', BigNumberViz],
['compare', TimeSeriesCompareViz],