dataviz: add support for loop, warn if there are three dimensions (#35698)
This commit is contained in:
parent
d2a2dd6c91
commit
3b2df2f522
|
@ -30,6 +30,10 @@ from combo.data.library import register_cell_class
|
||||||
from combo.utils import get_templated_url, requests
|
from combo.utils import get_templated_url, requests
|
||||||
|
|
||||||
|
|
||||||
|
class UnsupportedDataSet(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@register_cell_class
|
@register_cell_class
|
||||||
class Gauge(CellBase):
|
class Gauge(CellBase):
|
||||||
title = models.CharField(_('Title'), max_length=150, blank=True, null=True)
|
title = models.CharField(_('Title'), max_length=150, blank=True, null=True)
|
||||||
|
@ -157,11 +161,15 @@ class ChartNgCell(CellBase):
|
||||||
def get_cell_extra_context(self, context):
|
def get_cell_extra_context(self, context):
|
||||||
ctx = super(ChartNgCell, self).get_cell_extra_context(context)
|
ctx = super(ChartNgCell, self).get_cell_extra_context(context)
|
||||||
if self.chart_type == 'table':
|
if self.chart_type == 'table':
|
||||||
chart = self.get_chart(raise_if_not_cached=not(context.get('synchronous')))
|
try:
|
||||||
ctx['table'] = chart.render_table(
|
chart = self.get_chart(raise_if_not_cached=not(context.get('synchronous')))
|
||||||
transpose=bool(chart.axis_count == 2),
|
except UnsupportedDataSet:
|
||||||
)
|
ctx['table'] = '<p>%s</p>' % _('Unsupported dataset.')
|
||||||
ctx['table'] = ctx['table'].replace('<table>', '<table class="main">')
|
else:
|
||||||
|
ctx['table'] = chart.render_table(
|
||||||
|
transpose=bool(chart.axis_count == 2),
|
||||||
|
)
|
||||||
|
ctx['table'] = ctx['table'].replace('<table>', '<table class="main">')
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_chart(self, width=None, height=None, raise_if_not_cached=False):
|
def get_chart(self, width=None, height=None, raise_if_not_cached=False):
|
||||||
|
@ -186,9 +194,18 @@ class ChartNgCell(CellBase):
|
||||||
|
|
||||||
# normalize axis to have a fake axis when there are no dimensions and
|
# normalize axis to have a fake axis when there are no dimensions and
|
||||||
# always a x axis when there is a single dimension.
|
# always a x axis when there is a single dimension.
|
||||||
|
data = response['data']
|
||||||
|
loop_labels = response['axis'].get('loop') or []
|
||||||
x_labels = response['axis'].get('x_labels') or []
|
x_labels = response['axis'].get('x_labels') or []
|
||||||
y_labels = response['axis'].get('y_labels') or []
|
y_labels = response['axis'].get('y_labels') or []
|
||||||
data = response['data']
|
if loop_labels:
|
||||||
|
if x_labels and y_labels:
|
||||||
|
# no support for three dimensions
|
||||||
|
raise UnsupportedDataSet()
|
||||||
|
if not y_labels:
|
||||||
|
y_labels = loop_labels
|
||||||
|
else:
|
||||||
|
x_labels, y_labels = y_labels, loop_labels
|
||||||
if not x_labels and not y_labels: # unidata
|
if not x_labels and not y_labels: # unidata
|
||||||
x_labels = ['']
|
x_labels = ['']
|
||||||
y_labels = ['']
|
y_labels = ['']
|
||||||
|
|
|
@ -16,9 +16,10 @@
|
||||||
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from combo.utils import get_templated_url, requests
|
from combo.utils import get_templated_url, requests
|
||||||
from .models import Gauge, ChartNgCell
|
from .models import Gauge, ChartNgCell, UnsupportedDataSet
|
||||||
|
|
||||||
|
|
||||||
def ajax_gauge_count(request, *args, **kwargs):
|
def ajax_gauge_count(request, *args, **kwargs):
|
||||||
|
@ -33,8 +34,21 @@ def dataviz_graph(request, *args, **kwargs):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
if not cell.is_visible(request.user):
|
if not cell.is_visible(request.user):
|
||||||
raise PermissionDenied()
|
raise PermissionDenied()
|
||||||
chart = cell.get_chart(
|
try:
|
||||||
width=int(request.GET.get('width', 0)) or None,
|
chart = cell.get_chart(
|
||||||
height=int(request.GET.get('height', 0)) or int(cell.height)
|
width=int(request.GET.get('width', 0)) or None,
|
||||||
)
|
height=int(request.GET.get('height', 0)) or int(cell.height)
|
||||||
return HttpResponse(chart.render(), content_type='image/svg+xml')
|
)
|
||||||
|
except UnsupportedDataSet:
|
||||||
|
svg = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
|
||||||
|
viewBox="0 0 %(width)s 30" width="%(width)s" height="30">
|
||||||
|
<text
|
||||||
|
y="20"
|
||||||
|
x="10"
|
||||||
|
style="font-family: sans-serif; font-size: 16px; fill:#000000;">%(text)s</text>
|
||||||
|
</svg>""" % {'width': request.GET.get('width', 200),
|
||||||
|
'text': _('Unsupported dataset.')}
|
||||||
|
else:
|
||||||
|
svg = chart.render()
|
||||||
|
return HttpResponse(svg, content_type='image/svg+xml')
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.contrib.auth.models import User, Group
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
|
|
||||||
from combo.data.models import Page
|
from combo.data.models import Page
|
||||||
from combo.apps.dataviz.models import Gauge, ChartNgCell
|
from combo.apps.dataviz.models import Gauge, ChartNgCell, UnsupportedDataSet
|
||||||
|
|
||||||
from .test_public import login, normal_user
|
from .test_public import login, normal_user
|
||||||
|
|
||||||
|
@ -70,6 +70,25 @@ VISUALIZATION_JSON = [
|
||||||
'name': 'fourth visualization (no axis)',
|
'name': 'fourth visualization (no axis)',
|
||||||
'slug': 'fourth',
|
'slug': 'fourth',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'data-url': 'https://bijoe.example.com/visualization/5/json/',
|
||||||
|
'path': 'https://bijoe.example.com/visualization/5/iframe/?signature=123',
|
||||||
|
'name': 'fifth visualization (loop/X)',
|
||||||
|
'slug': 'fifth',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'data-url': 'https://bijoe.example.com/visualization/6/json/',
|
||||||
|
'path': 'https://bijoe.example.com/visualization/6/iframe/?signature=123',
|
||||||
|
'name': 'sixth visualization (loop/Y)',
|
||||||
|
'slug': 'sixth',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'data-url': 'https://bijoe.example.com/visualization/7/json/',
|
||||||
|
'path': 'https://bijoe.example.com/visualization/7/iframe/?signature=123',
|
||||||
|
'name': 'seventh visualization (loop/X/Y)',
|
||||||
|
'slug': 'seventh',
|
||||||
|
},
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -114,6 +133,48 @@ def bijoe_mock(url, request):
|
||||||
'axis': {}
|
'axis': {}
|
||||||
}
|
}
|
||||||
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||||
|
if url.path == '/visualization/5/json/':
|
||||||
|
response = {
|
||||||
|
'format': '1',
|
||||||
|
'data': [
|
||||||
|
[222, 134, 53],
|
||||||
|
[122, 114, 33],
|
||||||
|
],
|
||||||
|
'axis': {
|
||||||
|
'x_labels': ['web', 'mail', 'email'],
|
||||||
|
'loop': ['foo', 'bar'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||||
|
if url.path == '/visualization/6/json/':
|
||||||
|
response = {
|
||||||
|
'format': '1',
|
||||||
|
'data': [
|
||||||
|
[222, 134, 53],
|
||||||
|
[122, 114, 33],
|
||||||
|
],
|
||||||
|
'axis': {
|
||||||
|
'y_labels': ['web', 'mail', 'email'],
|
||||||
|
'loop': ['foo', 'bar'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||||
|
if url.path == '/visualization/7/json/':
|
||||||
|
response = {
|
||||||
|
'format': '1',
|
||||||
|
'data': [
|
||||||
|
[[222, 134, 53], [122, 114, 33]],
|
||||||
|
[[222, 134, 53], [122, 114, 33]],
|
||||||
|
[[222, 134, 53], [122, 114, 33]],
|
||||||
|
[[222, 134, 53], [122, 114, 33]],
|
||||||
|
],
|
||||||
|
'axis': {
|
||||||
|
'x_labels': ['foo', 'bar'],
|
||||||
|
'y_labels': ['web', 'mail', 'email'],
|
||||||
|
'loop': ['a', 'b', 'c', 'd'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||||
|
|
||||||
|
|
||||||
def test_chartng_cell(app):
|
def test_chartng_cell(app):
|
||||||
|
@ -186,6 +247,33 @@ def test_chartng_cell(app):
|
||||||
assert chart.x_labels == ['']
|
assert chart.x_labels == ['']
|
||||||
assert chart.raw_series == [([222], {'title': ''})]
|
assert chart.raw_series == [([222], {'title': ''})]
|
||||||
|
|
||||||
|
# loop/X
|
||||||
|
cell.data_reference = 'plop:fifth'
|
||||||
|
cell.save()
|
||||||
|
chart = cell.get_chart()
|
||||||
|
assert chart.x_labels == ['web', 'mail', 'email']
|
||||||
|
assert chart.raw_series == [
|
||||||
|
([222, 134, 53], {'title': u'foo'}),
|
||||||
|
([122, 114, 33], {'title': u'bar'}),
|
||||||
|
]
|
||||||
|
|
||||||
|
# loop/Y
|
||||||
|
cell.data_reference = 'plop:sixth'
|
||||||
|
cell.save()
|
||||||
|
chart = cell.get_chart()
|
||||||
|
assert chart.x_labels == ['web', 'mail', 'email']
|
||||||
|
assert chart.raw_series == [
|
||||||
|
([222, 134, 53], {'title': u'foo'}),
|
||||||
|
([122, 114, 33], {'title': u'bar'}),
|
||||||
|
]
|
||||||
|
|
||||||
|
# loop/X/Y
|
||||||
|
cell.data_reference = 'plop:seventh'
|
||||||
|
cell.save()
|
||||||
|
with pytest.raises(UnsupportedDataSet):
|
||||||
|
chart = cell.get_chart()
|
||||||
|
|
||||||
|
|
||||||
def test_chartng_cell_view(app, normal_user):
|
def test_chartng_cell_view(app, normal_user):
|
||||||
page = Page(title='One', slug='index')
|
page = Page(title='One', slug='index')
|
||||||
page.save()
|
page.save()
|
||||||
|
@ -230,6 +318,17 @@ def test_chartng_cell_view(app, normal_user):
|
||||||
resp = app.get('/')
|
resp = app.get('/')
|
||||||
assert '<td>222</td>' in resp.text
|
assert '<td>222</td>' in resp.text
|
||||||
|
|
||||||
|
# unsupported dataset
|
||||||
|
cell.data_reference = 'plop:seventh'
|
||||||
|
cell.save()
|
||||||
|
resp = app.get('/')
|
||||||
|
assert 'Unsupported dataset' in resp.text
|
||||||
|
|
||||||
|
cell.chart_type = 'bar'
|
||||||
|
cell.save()
|
||||||
|
resp = app.get('/api/dataviz/graph/1/?width=400', status=200)
|
||||||
|
assert 'Unsupported dataset' in resp.text
|
||||||
|
|
||||||
|
|
||||||
def test_chartng_cell_manager(app, admin_user):
|
def test_chartng_cell_manager(app, admin_user):
|
||||||
page = Page(title='One', slug='index')
|
page = Page(title='One', slug='index')
|
||||||
|
@ -247,7 +346,10 @@ def test_chartng_cell_manager(app, admin_user):
|
||||||
resp = app.get('/manage/pages/%s/' % page.id)
|
resp = app.get('/manage/pages/%s/' % page.id)
|
||||||
assert resp.form['cdataviz_chartngcell-%s-data_reference' % cell.id].options == [
|
assert resp.form['cdataviz_chartngcell-%s-data_reference' % cell.id].options == [
|
||||||
(u'plop:example', True, u'example visualization (X)'),
|
(u'plop:example', True, u'example visualization (X)'),
|
||||||
|
(u'plop:fifth', False, u'fifth visualization (loop/X)'),
|
||||||
(u'plop:fourth', False, u'fourth visualization (no axis)'),
|
(u'plop:fourth', False, u'fourth visualization (no axis)'),
|
||||||
(u'plop:second', False, u'second visualization (Y)'),
|
(u'plop:second', False, u'second visualization (Y)'),
|
||||||
|
(u'plop:seventh', False, u'seventh visualization (loop/X/Y)'),
|
||||||
|
(u'plop:sixth', False, u'sixth visualization (loop/Y)'),
|
||||||
(u'plop:third', False, u'third visualization (X/Y)'),
|
(u'plop:third', False, u'third visualization (X/Y)'),
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue