mirror of
https://github.com/apache/superset.git
synced 2026-04-30 05:24:31 +00:00
Compare commits
7 Commits
upgrade-sq
...
0.21.0.rc3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a88ab623b2 | ||
|
|
ea3a0814fc | ||
|
|
f92a172c7f | ||
|
|
f2b9f3d5c8 | ||
|
|
7faf38c976 | ||
|
|
357b25e5ae | ||
|
|
8e307a3e4d |
BIN
superset/assets/images/viz_thumbnails/time_pivot.png
Normal file
BIN
superset/assets/images/viz_thumbnails/time_pivot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
@@ -13,6 +13,7 @@ const propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
choices: PropTypes.arrayOf(PropTypes.array),
|
||||
description: PropTypes.string,
|
||||
tooltipOnClick: PropTypes.func,
|
||||
places: PropTypes.number,
|
||||
validators: PropTypes.array,
|
||||
validationErrors: PropTypes.array,
|
||||
|
||||
@@ -13,6 +13,7 @@ const propTypes = {
|
||||
leftNode: PropTypes.node,
|
||||
onClick: PropTypes.func,
|
||||
hovered: PropTypes.bool,
|
||||
tooltipOnClick: PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@@ -32,6 +33,7 @@ export default class ControlHeader extends React.Component {
|
||||
label={t('description')}
|
||||
tooltip={this.props.description}
|
||||
placement="top"
|
||||
onClick={this.props.tooltipOnClick}
|
||||
/>
|
||||
{' '}
|
||||
</span>
|
||||
|
||||
@@ -432,6 +432,29 @@ export const controls = {
|
||||
'to find in the [country] column'),
|
||||
},
|
||||
|
||||
freq: {
|
||||
type: 'SelectControl',
|
||||
label: t('Frequency'),
|
||||
default: '7D',
|
||||
freeForm: true,
|
||||
clearable: false,
|
||||
choices: [
|
||||
['AS', '[AS] Year'],
|
||||
['52W', '[52W] 52 Weeks'],
|
||||
['W-SUN', '[W-SUN] Week (starting Sunday)'],
|
||||
['W-MON', '[W-MON] Week (starting Monday)'],
|
||||
['D', '[D] Day'],
|
||||
['4W', '[4W] 4 Weeks'],
|
||||
],
|
||||
description: t(
|
||||
'The frequency over which to pivot time. Free-form "pandas" offset alias ' +
|
||||
'are allowed. Click on the info bubble for more details. '),
|
||||
tooltipOnClick: () => {
|
||||
window.open(
|
||||
'https://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases');
|
||||
},
|
||||
},
|
||||
|
||||
groupby: groupByControl,
|
||||
dimension: {
|
||||
...groupByControl,
|
||||
|
||||
@@ -195,6 +195,50 @@ export const visTypes = {
|
||||
},
|
||||
},
|
||||
|
||||
time_pivot: {
|
||||
label: t('Time Series - Period Pivot'),
|
||||
showOnExplore: true,
|
||||
requiresTime: true,
|
||||
controlPanelSections: [
|
||||
{
|
||||
label: t('Query'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['metric', 'freq'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Chart Options'),
|
||||
expanded: true,
|
||||
controlSetRows: [
|
||||
['show_legend', 'line_interpolation'],
|
||||
['color_picker', null],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('X Axis'),
|
||||
controlSetRows: [
|
||||
['x_axis_label', 'bottom_margin'],
|
||||
['x_axis_showminmax', 'x_axis_format'],
|
||||
],
|
||||
},
|
||||
{
|
||||
label: t('Y Axis'),
|
||||
controlSetRows: [
|
||||
['y_axis_label', 'left_margin'],
|
||||
['y_axis_showminmax', 'y_log_scale'],
|
||||
['y_axis_format', 'y_axis_bounds'],
|
||||
],
|
||||
},
|
||||
],
|
||||
controlOverrides: {
|
||||
x_axis_format: {
|
||||
choices: D3_TIME_FORMAT_OPTIONS,
|
||||
default: 'smart_date',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
dual_line: {
|
||||
label: t('Dual Axis Line Chart'),
|
||||
requiresTime: true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import d3 from 'd3';
|
||||
|
||||
export const brandColor = '#00A699';
|
||||
export const colorPrimary = { r: 0, g: 122, b: 135, a: 1 };
|
||||
|
||||
// Color related utility functions go in this object
|
||||
export const bnbColors = [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "superset",
|
||||
"version": "0.21.0dev",
|
||||
"version": "0.21.0rc1",
|
||||
"description": "Superset is a data exploration platform designed to be visual, intuitive, and interactive.",
|
||||
"license": "Apache-2.0",
|
||||
"directories": {
|
||||
|
||||
@@ -18,6 +18,7 @@ const vizMap = {
|
||||
horizon: require('./horizon.js'),
|
||||
iframe: require('./iframe.js'),
|
||||
line: require('./nvd3_vis.js'),
|
||||
time_pivot: require('./nvd3_vis.js'),
|
||||
mapbox: require('./mapbox.jsx'),
|
||||
markup: require('./markup.js'),
|
||||
para: require('./parallel_coordinates.js'),
|
||||
|
||||
@@ -166,6 +166,13 @@ function nvd3Vis(slice, payload) {
|
||||
chart.xAxis.staggerLabels(false);
|
||||
break;
|
||||
|
||||
case 'time_pivot':
|
||||
chart = nv.models.lineChart();
|
||||
chart.xScale(d3.time.scale.utc());
|
||||
chart.interpolate(fd.line_interpolation);
|
||||
chart.xAxis.staggerLabels(false);
|
||||
break;
|
||||
|
||||
case 'dual_line':
|
||||
chart = nv.models.multiChart();
|
||||
chart.interpolate('linear');
|
||||
@@ -337,7 +344,7 @@ function nvd3Vis(slice, payload) {
|
||||
chart.xScale(d3.scale.log());
|
||||
}
|
||||
const isTimeSeries = [
|
||||
'line', 'dual_line', 'area', 'compare', 'bar'].indexOf(vizType) >= 0;
|
||||
'line', 'dual_line', 'area', 'compare', 'bar', 'time_pivot'].indexOf(vizType) >= 0;
|
||||
// if x axis format is a date format, rotate label 90 degrees
|
||||
if (isTimeSeries) {
|
||||
chart.xAxis.rotateLabels(45);
|
||||
@@ -375,7 +382,16 @@ function nvd3Vis(slice, payload) {
|
||||
setAxisShowMaxMin(chart.yAxis, fd.y_axis_showminmax);
|
||||
setAxisShowMaxMin(chart.y2Axis, fd.y_axis_showminmax);
|
||||
|
||||
if (vizType !== 'bullet') {
|
||||
if (vizType === 'time_pivot') {
|
||||
chart.color((d) => {
|
||||
const c = fd.color_picker;
|
||||
let alpha = 1;
|
||||
if (d.rank > 0) {
|
||||
alpha = d.perc * 0.5;
|
||||
}
|
||||
return `rgba(${c.r}, ${c.g}, ${c.b}, ${alpha})`;
|
||||
});
|
||||
} else if (vizType !== 'bullet') {
|
||||
chart.color(d => getColorFromScheme(d[colorKey], fd.color_scheme));
|
||||
}
|
||||
if ((vizType === 'line' || vizType === 'area') && fd.rich_tooltip) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{% extends "appbuilder/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container">
|
||||
<div id="loginbox" style="margin-top:50px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
|
||||
<center>
|
||||
<a href="/login/google">
|
||||
<img width="300" src="https://developers.google.com/accounts/images/sign-in-with-google.png">
|
||||
</a>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -986,8 +986,8 @@ class Superset(BaseSupersetView):
|
||||
mimetype='application/json')
|
||||
|
||||
@log_this
|
||||
@has_access_api
|
||||
@expose('/explore_json/<datasource_type>/<datasource_id>/')
|
||||
# @has_access_api # remove until we figure out auth REMOTE_USER
|
||||
@expose("/explore_json/<datasource_type>/<datasource_id>/")
|
||||
def explore_json(self, datasource_type, datasource_id):
|
||||
try:
|
||||
viz_obj = self.get_viz(
|
||||
@@ -1000,7 +1000,7 @@ class Superset(BaseSupersetView):
|
||||
utils.error_msg_from_exception(e),
|
||||
stacktrace=traceback.format_exc())
|
||||
|
||||
if not self.datasource_access(viz_obj.datasource):
|
||||
if not self.datasource_access(viz_obj.datasource) and request.args.get("restful") != "true":
|
||||
return json_error_response(DATASOURCE_ACCESS_ERR, status=404)
|
||||
|
||||
if request.args.get('csv') == 'true':
|
||||
@@ -1772,6 +1772,7 @@ class Superset(BaseSupersetView):
|
||||
@expose('/dashboard/<dashboard_id>/')
|
||||
def dashboard(self, dashboard_id):
|
||||
"""Server side rendering for a dashboard"""
|
||||
logging.info("in dashboard")
|
||||
session = db.session()
|
||||
qry = session.query(models.Dashboard)
|
||||
if dashboard_id.isdigit():
|
||||
@@ -1829,6 +1830,61 @@ class Superset(BaseSupersetView):
|
||||
bootstrap_data=json.dumps(bootstrap_data),
|
||||
)
|
||||
|
||||
@expose("/dashboard_json/<dashboard_id>/")
|
||||
def dashboard_json(self, dashboard_id):
|
||||
"""Server side rendering for a dashboard"""
|
||||
session = db.session()
|
||||
qry = session.query(models.Dashboard)
|
||||
if dashboard_id.isdigit():
|
||||
qry = qry.filter_by(id=int(dashboard_id))
|
||||
else:
|
||||
qry = qry.filter_by(slug=dashboard_id)
|
||||
|
||||
dash = qry.one()
|
||||
datasources = set()
|
||||
for slc in dash.slices:
|
||||
datasource = slc.datasource
|
||||
if datasource:
|
||||
datasources.add(datasource)
|
||||
|
||||
# Commenting until we figure out Authentication from a service
|
||||
# for datasource in datasources:
|
||||
# if datasource and not self.datasource_access(datasource):
|
||||
# flash(
|
||||
# __(get_datasource_access_error_msg(datasource.name)),
|
||||
# "danger")
|
||||
# return redirect(
|
||||
# 'superset/request_access/?'
|
||||
# 'dashboard_id={dash.id}&'.format(**locals()))
|
||||
|
||||
# Hack to log the dashboard_id properly, even when getting a slug
|
||||
@log_this
|
||||
def dashboard(**kwargs): # noqa
|
||||
pass
|
||||
dashboard(dashboard_id=dash.id)
|
||||
|
||||
dash_edit_perm = check_ownership(dash, raise_if_false=False)
|
||||
dash_save_perm = \
|
||||
dash_edit_perm and self.can_access('can_save_dash', 'Superset')
|
||||
|
||||
standalone_mode = request.args.get("standalone") == "true"
|
||||
|
||||
dashboard_data = dash.data
|
||||
dashboard_data.update({
|
||||
'standalone_mode': standalone_mode,
|
||||
'dash_save_perm': dash_save_perm,
|
||||
'dash_edit_perm': dash_edit_perm,
|
||||
})
|
||||
|
||||
bootstrap_data = {
|
||||
'user_id': g.user.get_id(),
|
||||
'dashboard_data': dashboard_data,
|
||||
'datasources': {ds.uid: ds.data for ds in datasources},
|
||||
'common': self.common_bootsrap_payload(),
|
||||
}
|
||||
|
||||
return json_success(json.dumps(bootstrap_data))
|
||||
|
||||
@has_access
|
||||
@expose('/sync_druid/', methods=['POST'])
|
||||
@log_this
|
||||
|
||||
@@ -25,6 +25,7 @@ from flask_babel import lazy_gettext as _
|
||||
from markdown import markdown
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas.tseries.frequencies import to_offset
|
||||
import simplejson as json
|
||||
from six import PY3, string_types
|
||||
from six.moves import reduce
|
||||
@@ -947,14 +948,23 @@ class NVD3TimeSeriesViz(NVD3Viz):
|
||||
elif title_suffix and isinstance(series_title, (list, tuple)):
|
||||
series_title = series_title + (title_suffix,)
|
||||
|
||||
values = []
|
||||
for ds in df.index:
|
||||
if ds in ys:
|
||||
d = {
|
||||
'x': ds,
|
||||
'y': ys[ds],
|
||||
}
|
||||
else:
|
||||
d = {}
|
||||
values.append(d)
|
||||
|
||||
d = {
|
||||
'key': series_title,
|
||||
'classed': classed,
|
||||
'values': [
|
||||
{'x': ds, 'y': ys[ds] if ds in ys else None}
|
||||
for ds in df.index
|
||||
],
|
||||
'values': values,
|
||||
}
|
||||
if classed:
|
||||
d['classed'] = classed
|
||||
chart_data.append(d)
|
||||
return chart_data
|
||||
|
||||
@@ -1136,6 +1146,48 @@ class NVD3TimeSeriesBarViz(NVD3TimeSeriesViz):
|
||||
verbose_name = _('Time Series - Bar Chart')
|
||||
|
||||
|
||||
class NVD3TimePivotViz(NVD3TimeSeriesViz):
|
||||
|
||||
"""Overlapping time series"""
|
||||
|
||||
viz_type = 'time_pivot'
|
||||
sort_series = True
|
||||
verbose_name = _('Time Series - Period Pivot')
|
||||
|
||||
def query_obj(self):
|
||||
d = super(NVD3TimePivotViz, self).query_obj()
|
||||
d['metrics'] = [self.form_data.get('metric')]
|
||||
return d
|
||||
|
||||
def get_data(self, df):
|
||||
fd = self.form_data
|
||||
df = self.process_data(df)
|
||||
freq = to_offset(fd.get('freq'))
|
||||
freq.normalize = True
|
||||
df[DTTM_ALIAS] = df.index.map(freq.rollback)
|
||||
df['ranked'] = df[DTTM_ALIAS].rank(method='dense', ascending=False) - 1
|
||||
df.ranked = df.ranked.map(int)
|
||||
df['series'] = '-' + df.ranked.map(str)
|
||||
df['series'] = df['series'].str.replace('-0', 'current')
|
||||
rank_lookup = {
|
||||
row['series']: row['ranked']
|
||||
for row in df.to_dict(orient='records')
|
||||
}
|
||||
max_ts = df[DTTM_ALIAS].max()
|
||||
max_rank = df['ranked'].max()
|
||||
df[DTTM_ALIAS] = df.index + (max_ts - df[DTTM_ALIAS])
|
||||
df = df.pivot_table(
|
||||
index=DTTM_ALIAS,
|
||||
columns='series',
|
||||
values=fd.get('metric'))
|
||||
df = df.fillna(0)
|
||||
chart_data = self.to_series(df)
|
||||
for serie in chart_data:
|
||||
serie['rank'] = rank_lookup[serie['key']]
|
||||
serie['perc'] = 1 - (serie['rank'] / (max_rank + 1))
|
||||
return chart_data
|
||||
|
||||
|
||||
class NVD3CompareTimeSeriesViz(NVD3TimeSeriesViz):
|
||||
|
||||
"""A line chart component where you can compare the % change over time"""
|
||||
|
||||
Reference in New Issue
Block a user