diff --git a/panoramix/static/widgets/viz_bignumber.css b/panoramix/static/widgets/viz_bignumber.css new file mode 100644 index 0000000000000..56ea4659d8bee --- /dev/null +++ b/panoramix/static/widgets/viz_bignumber.css @@ -0,0 +1,26 @@ +.viz_bignumber g.axis text{ + font-size:10px; + font-weight:normal; + color: gray; + fill: gray; + text-anchor:middle; + alignment-baseline: middle; + font-weight: none; +} + +.viz_bignumber text.big{ + stroke: black; + text-anchor:middle; + fill: black; +} + +.viz_bignumber g.tick line { + stroke-width: 1px; + stroke: grey; +} + +.viz_bignumber .domain { + fill: none; + stroke: black; + stroke-width; 1; +} diff --git a/panoramix/static/widgets/viz_bignumber.js b/panoramix/static/widgets/viz_bignumber.js new file mode 100644 index 0000000000000..5de399505b4f1 --- /dev/null +++ b/panoramix/static/widgets/viz_bignumber.js @@ -0,0 +1,145 @@ +function viz_bignumber(token, json_callback) { + +var div = d3.select('#' + token); +var render = function(){ + d3.json(json_callback, function(error, payload){ + json = payload.data; + div.html(""); + //Define the percentage bounds that define color from red to green + if(error != null){ + var err = '
' + error.responseText + '
'; + div.html(err); + return ''; + } + var color_range = [-1,1]; + var compare_pos = -23 + var target_url = 'd3js.org'; + + var f = d3.format('.3s'); + var fp = d3.format('+.1%'); + var xy = div.node().getBoundingClientRect(); + var width = xy.width; + var height = xy.height-30; + var svg = div.append('svg'); + svg.attr("width", width); + svg.attr("height", height); + data = json.data; + var compare_suffix = ' ' + json.compare_suffix; + var v_compare = null; + var v = data[data.length -1][1]; + if (json.compare_lag >0){ + pos = data.length - (json.compare_lag+1); + if(pos >=0){ + v_compare = (v / data[pos][1])-1; + } + } + var date_ext = d3.extent(data, function(d){return d[0]}); + var value_ext = d3.extent(data, function(d){return d[1]}); + + var margin=20; + var scale_x = d3.time.scale.utc().domain(date_ext).range([margin, width-margin]); + var scale_y = d3.scale.linear().domain(value_ext).range([height-(margin),margin]); + var colorRange = [d3.hsl(0,1,0.3), d3.hsl(120, 1, 0.3)]; + var scale_color = d3.scale + .linear().domain(color_range) + .interpolate(d3.interpolateHsl) + .range(colorRange).clamp(true); + var line = d3.svg.line() + .x(function(d) { return scale_x(d[0])}) + .y(function(d) { return scale_y(d[1])}) + .interpolate("basis"); + + //Drawing trend line + var g = svg.append('g'); + var path = g.append('path') + .attr('d', function(d){return line(data);}) + .attr('stroke-width', 5) + .attr('opacity', 0.5) + .attr('fill', "none") + .attr('stroke-linecap',"round") + .attr('stroke', "grey"); + + var g = svg.append('g') + .attr('class', 'digits') + .attr('opacity', 1); + + var y = height/2; + if(v_compare != null) + y = (height/8) * 3; + + //Printing big number + g.append('text') + .attr('x', width/2) + .attr('y', y) + .attr('class', 'big') + .attr('alignment-baseline', 'middle') + .attr('id', 'bigNumber') + .style('font-weight', 'bold') + .style('cursor', 'pointer') + .text(f(v)) + .style('font-size', d3.min([height, width])/3.5) + .attr('fill','white'); + + var c = scale_color(v_compare); + + //Printing compare % + if(v_compare != null){ + g.append('text') + .attr('x', width/2) + .attr('y', (height/16) *12) + .text(fp(v_compare) + compare_suffix) + .style('font-size', d3.min([height, width])/8) + .style('text-anchor', 'middle') + .attr('fill', c) + .attr('stroke', c); + } + + var g_axis = svg.append('g').attr('class', 'axis').attr('opacity',0); + var g = g_axis.append('g'); + var x_axis = d3.svg.axis() + .scale(scale_x) + .orient('bottom') + //.tickFormat(d3.time.format('%I%p')) + .ticks(4); + g.call(x_axis); + g.attr('transform', 'translate(0,'+ (height-margin) +')'); + + var g = g_axis.append('g').attr('transform', 'translate('+(width-margin)+',0)'); + var y_axis = d3.svg.axis() + .scale(scale_y) + .orient('left') + .tickFormat(d3.format('.3s')) + .tickValues(value_ext); + g.call(y_axis); + g.selectAll('text') + .style('text-anchor','end') + .attr('y','-5') + .attr('x','1'); + + g.selectAll("text") + .style('font-size','10px'); + + /* + g_axis.selectAll('path.domain') + .attr('stroke-width:1px;'); + */ + + div.on('mouseover', function(d){ + var div = d3.select(this); + div.select('path').transition().duration(500).attr('opacity', 1) + .style('stroke-width', '2px'); + div.select('g.digits').transition().duration(500).attr('opacity', 0.1); + div.select('g.axis').transition().duration(500).attr('opacity', 1); + }) + .on('mouseout', function(d){ + var div = d3.select(this); + div.select('path').transition().duration(500).attr('opacity', 0.5) + .style('stroke-width', '5px'); + div.select('g.digits').transition().duration(500).attr('opacity', 1); + div.select('g.axis').transition().duration(500).attr('opacity', 0); + }); + }); + }; + render(); + $(div).parent().find("a.refresh").click(render); +} diff --git a/panoramix/static/widgets/viz_nvd3.js b/panoramix/static/widgets/viz_nvd3.js new file mode 100644 index 0000000000000..0cdb89941b4f6 --- /dev/null +++ b/panoramix/static/widgets/viz_nvd3.js @@ -0,0 +1,141 @@ + +function viz_nvd3(token_name, json_callback) { + 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('#' + token_name); + var jtoken = $('#' + token_name); + var loading = $('#' + token_name).find("img.loading"); + var chart = $('#' + token_name).find("div.chart"); + var refresh = function(){ + chart.hide(); + loading.show(); + $.getJSON(json_callback, function(payload){ + var data = payload.data; + var viz = payload; + var viz_type = viz.form_data.viz_type; + $("#query_container").html(data.query); + nv.addGraph(function() { + if (viz_type === 'line') { + if (viz.form_data.show_brush) { + var chart = nv.models.lineWithFocusChart() + var xext = chart.xAxis.scale().domain(); + chart + .x2Axis + .tickFormat(function (d) {return tickMultiFormat(UTC(new Date(d))); }) + .tickValues([]); + chart.y2Axis.tickFormat(d3.format('.3s')); + } else { + var chart = nv.models.lineChart() + } + chart.xScale(d3.time.scale()); + chart.xAxis + .showMaxMin(false) + .tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); + chart.showLegend(viz.form_data.show_legend); + chart.yAxis.tickFormat(d3.format('.3s')); + + if (viz.form_data.contribution || viz.form_data.num_period_compare) { + chart.yAxis.tickFormat(d3.format('.3p')); + chart.y2Axis.tickFormat(d3.format('.3p')); + } + + } else if (viz_type === 'dist_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))); }); + chart.yAxis.tickFormat(d3.format('.3s')); + + } else if (viz_type === 'pie') { + var chart = nv.models.pieChart() + chart.showLegend(viz.form_data.show_legend); + if (viz.form_data.donut) { + chart.donut(true); + chart.donutLabelsOutside(true); + } + chart.labelsOutside(true); + chart.cornerRadius(true); + + } else if (viz_type === 'column') { + var chart = nv.models.multiBarChart() + .reduceXTicks(false) + .rotateLabels(45) ; + chart.yAxis.tickFormat(d3.format('.3s')); + + } else if (viz_type === 'compare') { + var chart = nv.models.cumulativeLineChart(); + chart.xScale(d3.time.scale()); + chart.xAxis + .showMaxMin(false) + .tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); + chart.showLegend(viz.form_data.show_legend); + chart.yAxis.tickFormat(d3.format('.3p')); + + } else if (viz_type === 'bubble') { + var chart = nv.models.scatterChart(); + chart.xAxis.tickFormat(d3.format('.3s')); + chart.yAxis.tickFormat(d3.format('.3s')); + chart.showLegend(viz.form_data.show_legend); + chart.pointRange([5, 5000]); + + } else if (viz_type === 'stacked') { + var chart = nv.models.stackedAreaChart(); + chart.xScale(d3.time.scale()); + chart.xAxis + .showMaxMin(false) + .tickFormat(function (d) {return tickMultiFormat(new Date(d)); }); + chart.showLegend(viz.form_data.show_legend); + chart.yAxis.tickFormat(d3.format('.3s')); + + } + + if ((viz_type === "line" || viz_type === "stacked") && viz.form_data.rich_tooltip) { + chart.useInteractiveGuideline(true); + } + if (viz.form_data.y_axis_zero) { + chart.forceY([0, 1]); + } + else if (viz.form_data.y_log_scale) { + chart.yScale(d3.scale.log()); + } + + if (viz.form_data.x_log_scale) { + chart.xScale(d3.scale.log()); + } + + token.select('.chart').append("svg") + .datum(data.chart_data) + .transition().duration(500) + .call(chart); + + return chart; + }); + chart.show(); + loading.hide(); + }).fail(function(xhr) { + var err = '
' + xhr.responseText + '
'; + loading.hide(); + chart.show(); + chart.html(err); + }); + }; + refresh(); + jtoken.parent().find("a.refresh").click(refresh); + } diff --git a/panoramix/static/widgets/viz_wordcloud.js b/panoramix/static/widgets/viz_wordcloud.js new file mode 100644 index 0000000000000..1464816ac730c --- /dev/null +++ b/panoramix/static/widgets/viz_wordcloud.js @@ -0,0 +1,66 @@ +function viz_wordcloud(token, json_callback) { + var token = d3.select('#' + token); + function refresh() { + d3.json(json_callback, function(error, json) { + var data = json.data; + var range = [ + json.form_data.size_from, + json.form_data.size_to, + ]; + var rotation = json.form_data.rotation; + if (rotation == "square") { + var f_rotation = function() { return ~~(Math.random() * 2) * 90; }; + } + else if (rotation == "flat") { + var f_rotation = function() { return 0 }; + } + else { + var f_rotation = function() { return (~~(Math.random() * 6) - 3) * 30; }; + } + var box = token.node().getBoundingClientRect(); + var size = [box.width, box.height - 25]; + + if (error != null){ + var err = '
' + error.responseText + '
'; + token.html(err); + return ''; + } + scale = d3.scale.linear() + .range(range) + .domain(d3.extent(data, function(d) { return d.size; })); + var fill = d3.scale.category20(); + var layout = d3.layout.cloud() + .size(size) + .words(data) + .padding(5) + .rotate(f_rotation) + .font("serif") + .fontSize(function(d) { return scale(d.size); }) + .on("end", draw); + layout.start(); + function draw(words) { + token.selectAll("*").remove(); + + token.append("svg") + .attr("width", layout.size()[0]) + .attr("height", layout.size()[1]) + .append("g") + .attr("transform", "translate(" + layout.size()[0] / 2 + "," + layout.size()[1] / 2 + ")") + .selectAll("text") + .data(words) + .enter().append("text") + .style("font-size", function(d) { return d.size + "px"; }) + .style("font-family", "Impact") + .style("fill", function(d, i) { return fill(i); }) + .attr("text-anchor", "middle") + .attr("transform", function(d) { + return "translate(" + [d.x, d.y] + ") rotate(" + d.rotate + ")"; + }) + .text(function(d) { return d.text; }); + } + }); + } + refresh(); + jtoken = $(token); + jtoken.parent().find("a.refresh").click(refresh); +} diff --git a/panoramix/templates/panoramix/viz_bignumber.html b/panoramix/templates/panoramix/viz_bignumber.html index 4dbeada43cf34..3a5610df6be41 100644 --- a/panoramix/templates/panoramix/viz_bignumber.html +++ b/panoramix/templates/panoramix/viz_bignumber.html @@ -1,199 +1,19 @@ {% macro viz_html(viz) %} -
+
{% endmacro %} {% macro viz_js(viz) %} - + {% endmacro %} {% macro viz_css(viz) %} - {% endmacro %} diff --git a/panoramix/templates/panoramix/viz_nvd3.html b/panoramix/templates/panoramix/viz_nvd3.html index d36881fb80349..4e5ef848f7fae 100644 --- a/panoramix/templates/panoramix/viz_nvd3.html +++ b/panoramix/templates/panoramix/viz_nvd3.html @@ -7,144 +7,11 @@ {% macro viz_js(viz) %} {% endmacro %} diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html index 2674019ccbee5..bb1c8795b14a5 100644 --- a/panoramix/templates/panoramix/viz_table.html +++ b/panoramix/templates/panoramix/viz_table.html @@ -4,7 +4,7 @@ - {% for col in df.columns if not col.endswith('__perc') %} + {% for col in df.columns if not col.endswith('__perc') %} {% endfor %} @@ -12,7 +12,7 @@ {% for row in df.to_dict(orient="records") %} - {% for col in df.columns if not col.endswith('__perc') %} + {% for col in df.columns if not col.endswith('__perc') %} {% if col + '__perc' in df.columns %}
{{ col }}
{{ row[col] }} diff --git a/panoramix/templates/panoramix/viz_word_cloud.html b/panoramix/templates/panoramix/viz_word_cloud.html index 3381a6c65a948..3260da7e4a81d 100644 --- a/panoramix/templates/panoramix/viz_word_cloud.html +++ b/panoramix/templates/panoramix/viz_word_cloud.html @@ -6,69 +6,12 @@ {% macro viz_js(viz) %} {% endmacro %} diff --git a/panoramix/viz.py b/panoramix/viz.py index 55e17bb6b14da..e20eaa0af3fa0 100644 --- a/panoramix/viz.py +++ b/panoramix/viz.py @@ -75,6 +75,10 @@ def get_url(self, **kwargs): if 'action' in d: del d['action'] d.update(kwargs) + # Remove unchecked checkboxes because HTML is weird like that + for key in d.keys(): + if d[key] == False: + del d[key] href = Href( '/panoramix/datasource/{self.datasource.type}/' '{self.datasource.id}/'.format(**locals())) @@ -159,6 +163,15 @@ def query_obj(self): } return d + def get_json(self): + payload = { + 'data': json.loads(self.get_json_data()), + 'form_data': self.form_data, + } + return json.dumps(payload) + + def get_json_data(self): + return json.dumps([]) class TableViz(BaseViz): verbose_name = "Table View" @@ -212,7 +225,11 @@ class WordCloudViz(BaseViz): ('size_from', 'size_to'), 'rotation', ] - js_files = ['d3.layout.cloud.js'] + js_files = [ + 'd3.min.js', + 'd3.layout.cloud.js', + 'widgets/viz_wordcloud.js', + ] def query_obj(self): d = super(WordCloudViz, self).query_obj() @@ -224,7 +241,7 @@ def query_obj(self): d['groupby'] = [d['groupby'][0]] return d - def get_json(self): + def get_json_data(self): df = self.get_df() df.columns = ['text', 'size'] return df.to_json(orient="records") @@ -234,7 +251,11 @@ class NVD3Viz(BaseViz): verbose_name = "Base NVD3 Viz" template = 'panoramix/viz_nvd3.html' chart_kind = 'line' - js_files = ['d3.min.js', 'nv.d3.min.js'] + js_files = [ + 'd3.min.js', + 'nv.d3.min.js', + 'widgets/viz_nvd3.js', + ] css_files = ['nv.d3.css'] @@ -283,7 +304,7 @@ def get_df(self): df['group'] = df[[self.series]] return df - def get_json(self): + def get_json_data(self): df = self.get_df() series = defaultdict(list) for row in df.to_dict(orient='records'): @@ -299,7 +320,13 @@ def get_json(self): class BigNumberViz(BaseViz): verbose_name = "Big Number" template = 'panoramix/viz_bignumber.html' - js_files = ['d3.min.js'] + js_files = [ + 'd3.min.js', + 'widgets/viz_bignumber.js', + ] + css_files = [ + 'widgets/viz_bignumber.css', + ] form_fields = [ 'viz_type', 'granularity', @@ -325,7 +352,7 @@ def query_obj(self): self.form_data['metric'] = metric return d - def get_json(self): + def get_json_data(self): form_data = self.form_data df = self.get_df() df = df.sort(columns=df.columns[0]) @@ -394,7 +421,7 @@ def get_df(self): df = pd.rolling_sum(df, int(rolling_periods)) return df - def get_json(self): + def get_json_data(self): df = self.get_df() series = df.to_dict('series') chart_data = [] @@ -491,7 +518,7 @@ def get_df(self): df = df.sort(self.metrics[0], ascending=False) return df - def get_json(self): + def get_json_data(self): df = self.get_df() df = df.reset_index() df.columns = ['x', 'y'] @@ -515,7 +542,7 @@ def get_df(self): df = df.sort(self.metrics[0], ascending=False) return df - def get_json(self): + def get_json_data(self): df = self.get_df() series = df.to_dict('series') chart_data = []