mirror of
https://github.com/valitydev/redash.git
synced 2024-11-08 09:53:59 +00:00
Merge pull request #86 from EverythingMe/fix_viz
Fixes and improvements (most related to visualizations)
This commit is contained in:
commit
e8c7f728a2
@ -65,4 +65,8 @@ def drop_tables():
|
||||
manager.add_command("database", database_manager)
|
||||
|
||||
if __name__ == '__main__':
|
||||
channel = logging.StreamHandler()
|
||||
logging.getLogger().addHandler(channel)
|
||||
logging.getLogger().setLevel(settings.LOG_LEVEL)
|
||||
|
||||
manager.run()
|
@ -4,7 +4,7 @@ from redash import db
|
||||
from redash import models
|
||||
|
||||
if __name__ == '__main__':
|
||||
default_options = {"series": {"type": "bar"}}
|
||||
default_options = {"series": {"type": "column"}}
|
||||
|
||||
db.connect_db()
|
||||
|
||||
|
@ -112,6 +112,9 @@
|
||||
} else {
|
||||
// TODO: replace this with a safer method
|
||||
$location.path($location.path().replace(oldId, q.id)).replace();
|
||||
|
||||
// Reset visualizations tab to table after duplicating a query:
|
||||
$location.hash('table');
|
||||
}
|
||||
}
|
||||
}, function(httpResponse) {
|
||||
|
@ -62,9 +62,10 @@
|
||||
'Cohort': Visualization.prototype.TYPES.COHORT
|
||||
};
|
||||
scope.seriesTypes = {
|
||||
'Line': Visualization.prototype.SERIES_TYPES.LINE,
|
||||
'Bar': Visualization.prototype.SERIES_TYPES.BAR,
|
||||
'Area': Visualization.prototype.SERIES_TYPES.AREA
|
||||
'Line': 'line',
|
||||
'Column': 'column',
|
||||
'Area': 'area',
|
||||
'Scatter': 'scatter'
|
||||
};
|
||||
|
||||
if (!scope.vis) {
|
||||
@ -77,7 +78,7 @@
|
||||
'query_id': q.id,
|
||||
'type': Visualization.prototype.TYPES.CHART,
|
||||
'name': '',
|
||||
'description': q.description,
|
||||
'description': q.description || '',
|
||||
'options': newOptions()
|
||||
};
|
||||
}
|
||||
@ -93,7 +94,7 @@
|
||||
// Chart
|
||||
return {
|
||||
'series': {
|
||||
'type': Visualization.prototype.SERIES_TYPES.LINE
|
||||
'type': scope.seriesTypes[0]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,19 +1,31 @@
|
||||
(function(){
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var defaultOptions = {
|
||||
title: {
|
||||
"text": null
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
valueDecimals: 2,
|
||||
formatter: function () {
|
||||
if (!this.points) {
|
||||
this.points = [this.point];
|
||||
};
|
||||
|
||||
if (moment.isMoment(this.x)) {
|
||||
var s = '<b>' + moment(this.x).format("DD/MM/YY HH:mm") + '</b>',
|
||||
pointsCount = this.points.length;
|
||||
|
||||
$.each(this.points, function (i, point) {
|
||||
s += '<br/><span style="color:'+point.series.color+'">' + point.series.name + '</span>: ' +
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' +
|
||||
Highcharts.numberFormat(point.y);
|
||||
|
||||
if (pointsCount > 1 && point.percentage) {
|
||||
@ -23,7 +35,7 @@
|
||||
} else {
|
||||
var s = "<b>" + this.points[0].key + "</b>";
|
||||
$.each(this.points, function (i, point) {
|
||||
s+= '<br/><span style="color:'+point.series.color+'">' + point.series.name + '</span>: ' +
|
||||
s += '<br/><span style="color:' + point.series.color + '">' + point.series.name + '</span>: ' +
|
||||
Highcharts.numberFormat(point.y);
|
||||
});
|
||||
}
|
||||
@ -32,14 +44,6 @@
|
||||
},
|
||||
shared: true
|
||||
},
|
||||
xAxis: {
|
||||
type: 'datetime'
|
||||
},
|
||||
yAxis: {
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
},
|
||||
exporting: {
|
||||
chartOptions: {
|
||||
title: {
|
||||
@ -70,12 +74,50 @@
|
||||
enabled: false
|
||||
},
|
||||
plotOptions: {
|
||||
"column": {
|
||||
"stacking": "normal",
|
||||
"pointPadding": 0,
|
||||
"borderWidth": 1,
|
||||
"groupPadding": 0,
|
||||
"shadow": false
|
||||
area: {
|
||||
marker: {
|
||||
enabled: false,
|
||||
symbol: 'circle',
|
||||
radius: 2,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
column: {
|
||||
stacking: "normal",
|
||||
pointPadding: 0,
|
||||
borderWidth: 1,
|
||||
groupPadding: 0,
|
||||
shadow: false
|
||||
},
|
||||
line: {
|
||||
marker: {
|
||||
radius: 3,
|
||||
},
|
||||
lineWidth: 1,
|
||||
states: {
|
||||
hover: {
|
||||
lineWidth: 2
|
||||
}
|
||||
}
|
||||
},
|
||||
scatter: {
|
||||
marker: {
|
||||
radius: 5,
|
||||
states: {
|
||||
hover: {
|
||||
enabled: true,
|
||||
lineColor: 'rgb(100,100,100)'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
headerFormat: '<b>{series.name}</b><br>',
|
||||
pointFormat: '{point.x}, {point.y}'
|
||||
}
|
||||
}
|
||||
},
|
||||
series: []
|
||||
@ -105,26 +147,34 @@
|
||||
|
||||
var chartOptions = $.extend(true, {}, defaultOptions, chartsDefaults);
|
||||
|
||||
// Update when options change
|
||||
scope.$watch('options', function(newOptions) {
|
||||
initChart(newOptions);
|
||||
}, true);
|
||||
// $timeout makes sure that this function invoked after the DOM ready. When draw/init
|
||||
// invoked after the DOM is ready, we see first an empty HighCharts objects and later
|
||||
// they get filled up. Which gives the feeling that the charts loading faster (otherwise
|
||||
// we stare at an empty screen until the HighCharts object is ready).
|
||||
$timeout(function(){
|
||||
// Update when options change
|
||||
scope.$watch('options', function (newOptions) {
|
||||
initChart(newOptions);
|
||||
}, true);
|
||||
|
||||
//Update when charts data changes
|
||||
scope.$watch(function () {
|
||||
return (scope.series && scope.series.length) || 0;
|
||||
}, function (length) {
|
||||
if (!length || length == 0) {
|
||||
scope.chart.showLoading();
|
||||
} else {
|
||||
drawChart();
|
||||
};
|
||||
}, true);
|
||||
//Update when charts data changes
|
||||
scope.$watch(function () {
|
||||
// TODO: this might be an issue in case the series change, but they stay
|
||||
// with the same length
|
||||
return (scope.series && scope.series.length) || 0;
|
||||
}, function (length) {
|
||||
if (!length || length == 0) {
|
||||
scope.chart.showLoading();
|
||||
} else {
|
||||
drawChart();
|
||||
};
|
||||
}, true);
|
||||
});
|
||||
|
||||
function initChart(options) {
|
||||
if (scope.chart) {
|
||||
scope.chart.destroy();
|
||||
}
|
||||
scope.chart.destroy();
|
||||
};
|
||||
|
||||
$.extend(true, chartOptions, options);
|
||||
|
||||
@ -133,23 +183,25 @@
|
||||
}
|
||||
|
||||
function drawChart() {
|
||||
while(scope.chart.series.length > 0) {
|
||||
scope.chart.series[0].remove(true);
|
||||
}
|
||||
while (scope.chart.series.length > 0) {
|
||||
scope.chart.series[0].remove(false);
|
||||
};
|
||||
|
||||
// todo series.type
|
||||
|
||||
if (_.some(scope.series[0].data, function(p) { return angular.isString(p.x) })) {
|
||||
if (_.some(scope.series[0].data, function (p) {
|
||||
return angular.isString(p.x)
|
||||
})) {
|
||||
scope.chart.xAxis[0].update({type: 'category'});
|
||||
|
||||
// We need to make sure that for each category, each series has a value.
|
||||
var categories = _.union.apply(this, _.map(scope.series, function(s) { return _.pluck(s.data,'x')}));
|
||||
var categories = _.union.apply(this, _.map(scope.series, function (s) {
|
||||
return _.pluck(s.data, 'x')
|
||||
}));
|
||||
|
||||
_.each(scope.series, function(s) {
|
||||
_.each(scope.series, function (s) {
|
||||
// TODO: move this logic to Query#getChartData
|
||||
var yValues = _.groupBy(s.data, 'x');
|
||||
|
||||
var newData = _.sortBy(_.map(categories, function(category) {
|
||||
var newData = _.sortBy(_.map(categories, function (category) {
|
||||
return {
|
||||
name: category,
|
||||
y: yValues[category] && yValues[category][0].y
|
||||
@ -164,11 +216,27 @@
|
||||
|
||||
scope.chart.counters.color = 0;
|
||||
|
||||
_.each(scope.series, function(s) {
|
||||
_.each(scope.series, function (s) {
|
||||
// here we override the series with the visualization config
|
||||
var _s = $.extend(true, {}, s, chartOptions['series']);
|
||||
scope.chart.addSeries(_s);
|
||||
})
|
||||
s = _.extend(s, chartOptions['series']);
|
||||
|
||||
if (s.type == 'area') {
|
||||
_.each(s.data, function (p) {
|
||||
// This is an insane hack: somewhere deep in HighChart's code,
|
||||
// when you stack areas, it tries to convert the string representation
|
||||
// of point's x into a number. With the default implementation of toString
|
||||
// it fails....
|
||||
|
||||
if (moment.isMoment(p.x)) {
|
||||
p.x.toString = function () {
|
||||
return String(this.toDate().getTime());
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
scope.chart.addSeries(s, false);
|
||||
});
|
||||
|
||||
scope.chart.redraw();
|
||||
scope.chart.hideLoading();
|
||||
|
@ -291,11 +291,6 @@
|
||||
'CHART': 'CHART',
|
||||
'COHORT': 'COHORT',
|
||||
'TABLE': 'TABLE'
|
||||
},
|
||||
SERIES_TYPES: {
|
||||
'LINE': 'line',
|
||||
'BAR': 'bar',
|
||||
'AREA': 'area'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -209,7 +209,7 @@ class QueryAPI(BaseResource):
|
||||
|
||||
query = models.Query.get_by_id(query_id)
|
||||
|
||||
return query.to_dict(with_result=False)
|
||||
return query.to_dict(with_result=False, with_visualizations=True)
|
||||
|
||||
def get(self, query_id):
|
||||
q = models.Query.get(models.Query.id == query_id)
|
||||
|
@ -64,7 +64,7 @@ class Query(BaseModel):
|
||||
|
||||
def create_default_visualizations(self):
|
||||
table_visualization = Visualization(query=self, name="Table",
|
||||
description=self.description,
|
||||
description='',
|
||||
type="TABLE", options="{}")
|
||||
table_visualization.save()
|
||||
|
||||
@ -185,7 +185,7 @@ class Visualization(BaseModel):
|
||||
type = peewee.CharField(max_length=100)
|
||||
query = peewee.ForeignKeyField(Query, related_name='visualizations')
|
||||
name = peewee.CharField(max_length=255)
|
||||
description = peewee.CharField(max_length=4096)
|
||||
description = peewee.CharField(max_length=4096, null=True)
|
||||
options = peewee.TextField()
|
||||
|
||||
class Meta:
|
||||
|
Loading…
Reference in New Issue
Block a user