dataviz: adapt chart's height to content (#62282) #54
|
@ -360,7 +360,6 @@ class ChartNgCell(CellBase):
|
|||
if self.statistic.service_slug == 'bijoe':
|
||||
x_labels, y_labels, data = self.parse_response(response, chart)
|
||||
chart.x_labels = x_labels
|
||||
self.prepare_chart(chart, width, height)
|
||||
|
||||
if chart.axis_count == 1:
|
||||
data = self.process_one_dimensional_data(chart, data)
|
||||
|
||||
|
@ -379,7 +378,6 @@ class ChartNgCell(CellBase):
|
|||
|
||||
chart.x_labels = data['x_labels']
|
||||
chart.axis_count = min(len(data['series']), 2)
|
||||
self.prepare_chart(chart, width, height)
|
||||
|
||||
if self.statistic.data_type:
|
||||
chart.config.value_formatter = self.get_value_formatter(self.statistic.data_type)
|
||||
|
@ -401,7 +399,7 @@ class ChartNgCell(CellBase):
|
|||
|
||||
for serie in data['series']:
|
||||
chart.add(serie['label'], serie['data'])
|
||||
|
||||
self.configure_chart(chart, width, height)
|
||||
return chart
|
||||
|
||||
def get_filter_params(self):
|
||||
|
@ -531,14 +529,37 @@ class ChartNgCell(CellBase):
|
|||
|
||||
return x_labels, y_labels, data
|
||||
|
||||
def prepare_chart(self, chart, width, height):
|
||||
def configure_dot_chart(self, chart, width, height):
|
||||
chart.show_legend = False
|
||||
# use a single colour for dots
|
||||
chart.config.style.colors = ('#1f77b4',) * max(len(chart.x_labels), 1)
|
||||
|
||||
def configure_horizontal_bar_chart(self, chart, width, height):
|
||||
vdeniaud
commented
Il faut garder la condition Il faut garder la condition `width < 500`, le fait que ça passe sans traduit juste un manque de test.
smihai
commented
Bien vu. Bien vu.
J'ai remis la condition `width < 500` et rajouté un test.
|
||||
if width and width < 500:
|
||||
# truncate labels
|
||||
chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels]
|
||||
|
||||
def configure_pie_chart(self, chart, width, height):
|
||||
chart.show_legend = True
|
||||
if width and height:
|
||||
# pies are as tall as wide, reserve the appropriate space and distribute
|
||||
# the rest for the legend.
|
||||
chart.truncate_legend = (width - height) // 10
|
||||
elif width:
|
||||
chart.truncate_legend = width // 20
|
||||
|
||||
def configure_chart(self, chart, width, height):
|
||||
auto_height_scale = pygal.style.DefaultStyle.legend_font_size * 1.75
|
||||
chart.config.margin = 0
|
||||
if width:
|
||||
chart.config.width = width
|
||||
if height:
|
||||
chart.config.height = height
|
||||
height = height or int(self.height)
|
||||
vdeniaud
commented
J'aime bien ce changement, par soucis de cohérence ça serait cool d'avoir la même chose pour width genre juste en remplaçant le bloc J'aime bien ce changement, par soucis de cohérence ça serait cool d'avoir la même chose pour width genre juste en remplaçant le bloc `if width:` du dessus par `width = width or int(self.width)` (en plus de la modif à DatavizGraphView). Idéalement ce changement irait dans un commit séparé, mais pas très important.
smihai
commented
Fait un commit séparé pour Fait un commit séparé pour `height`. En revanche `self.width` n'existe pas.
vdeniaud
commented
Ah oui, désolé pour l'embrouille Ah oui, désolé pour l'embrouille
|
||||
# adapt chart's height to legend length
|
||||
chart.config.height = max(height, auto_height_scale * len(chart.raw_series))
|
||||
|
||||
if width or height:
|
||||
chart.config.explicit_size = True
|
||||
|
||||
chart.config.js = [os.path.join(settings.STATIC_URL, 'js/pygal-tooltips.js')]
|
||||
|
||||
vdeniaud
commented
À quoi sert cette ligne ? À quoi sert cette ligne ?
smihai
commented
A garder la hauteur définie de la cellule lorsque
A garder la hauteur définie de la cellule lorsque
```
chart.legend_at_bottom = True
```
vdeniaud
commented
Ah oui désolé j'aurais pu m'en apercevoir... Ça serait bien d'avoir un test si tu te sens. Et c'est aussi un endroit où on perd la valeur du paramètre Ah oui désolé j'aurais pu m'en apercevoir... Ça serait bien d'avoir un test si tu te sens.
Et c'est aussi un endroit où on perd la valeur du paramètre `height` passé dans la requête, il ne faudrait pas.
vdeniaud
commented
Cool je vois le nouveau test, par contre la remarque sur conserver le paramètre Cool je vois le nouveau test, par contre la remarque sur conserver le paramètre `height` me paraît toujours importante (tu peux voir dans le `/manage/` que ça semi-casse le rendu du graphe).
|
||||
chart.show_legend = bool(chart.axis_count > 1)
|
||||
vdeniaud
commented
Mais le graphe de type pie peut également voir sa légende déborder en hauteur, non ? (peut-être que je me plante) Si c'est le cas il doit bénéficier de la correction du patch, et donc ce serait plutôt se placer sous ce bloc et faire
Mais le graphe de type pie peut également voir sa légende déborder en hauteur, non ? (peut-être que je me plante)
Si c'est le cas il doit bénéficier de la correction du patch, et donc ce serait plutôt se placer sous ce bloc et faire
```
if not chart.legend_at_bottom:
# adapt height
height = max(...
```
smihai
commented
En effet, il restait ce cas. Pas repris ta suggestion d'implémentation car > Mais le graphe de type pie peut également voir sa légende déborder en hauteur, non ? (peut-être que je me plante)
>
En effet, il restait ce cas.
Je l'ai pris en compte et rajouté un test.
Pas repris ta suggestion d'implémentation car `chart.legend_at_bottom` lève un `AttributeError`.
|
||||
|
@ -557,25 +578,15 @@ class ChartNgCell(CellBase):
|
|||
'#17becf',
|
||||
)
|
||||
|
||||
if self.chart_type == 'dot':
|
||||
chart.show_legend = False
|
||||
# use a single colour for dots
|
||||
chart.config.style.colors = ('#1f77b4',) * max(len(chart.x_labels), 1)
|
||||
custom_configure_method_name = 'configure_%s_chart' % self.chart_type.replace('-', '_')
|
||||
if hasattr(self, custom_configure_method_name):
|
||||
getattr(self, custom_configure_method_name)(chart, width, height)
|
||||
|
||||
if self.chart_type != 'pie':
|
||||
if width and width < 500:
|
||||
vdeniaud
commented
C'est sur cette ligne que portait mon unique réserve sur le patch précédent, je ne sais pas pourquoi elle est encore là ? En plus avant c'était Encore une fois le bug c'est qu'en rendu BO ça va produire un graphe trop haut. Il faut que cette ligne restaure la valeur originale de height avant correction (self.height ou la valeur transmise en paramètre, selon les cas). Ça serait bien aussi d'ajouter un test là dessus. C'est sur cette ligne que portait mon unique réserve sur le patch précédent, je ne sais pas pourquoi elle est encore là ?
En plus avant c'était `chart.config.height = int(self.height)`, maintenant le `chart.config` a disparu donc la ligne ne fait plus rien.
Encore une fois le bug c'est qu'en rendu BO ça va produire un graphe trop haut. Il faut que cette ligne restaure la valeur originale de height avant correction (self.height ou la valeur transmise en paramètre, selon les cas). Ça serait bien aussi d'ajouter un test là dessus.
smihai
commented
Je me plantais à chaque fois ici, fatigue. Je me plantais à chaque fois ici, fatigue.
|
||||
chart.legend_at_bottom = True
|
||||
if self.chart_type == 'horizontal-bar':
|
||||
# truncate labels
|
||||
chart.x_labels = [pygal.util.truncate(x, 15) for x in chart.x_labels]
|
||||
else:
|
||||
chart.show_legend = True
|
||||
if width and height:
|
||||
# pies are as tall as wide, reserve the appropriate space and distribute
|
||||
# the rest for the legend.
|
||||
chart.truncate_legend = (width - height) // 10
|
||||
elif width:
|
||||
chart.truncate_legend = width // 20
|
||||
# restore demanded chart's height
|
||||
chart.config.height = height
|
||||
|
||||
def process_one_dimensional_data(self, chart, data):
|
||||
if self.hide_null_values:
|
||||
|
|
|
@ -73,7 +73,7 @@ class DatavizGraphView(DetailView):
|
|||
try:
|
||||
chart = form.instance.get_chart(
|
||||
width=int(request.GET['width']) if request.GET.get('width') else None,
|
||||
height=int(request.GET['height']) if request.GET.get('height') else int(self.cell.height),
|
||||
height=int(request.GET['height']) if request.GET.get('height') else None,
|
||||
)
|
||||
except UnsupportedDataSet:
|
||||
return self.error(_('Unsupported dataset.'))
|
||||
|
|
|
@ -4,6 +4,7 @@ import json
|
|||
import urllib.parse
|
||||
from unittest import mock
|
||||
|
||||
import lxml
|
||||
import pytest
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import Group
|
||||
|
@ -149,6 +150,12 @@ VISUALIZATION_JSON = [
|
|||
'name': 'sixteenth visualization (numerical labels)',
|
||||
'slug': 'sixteenth',
|
||||
},
|
||||
{
|
||||
'data-url': 'https://bijoe.example.com/visualization/17/json/',
|
||||
'path': 'https://bijoe.example.com/visualization/17/iframe/?signature=123',
|
||||
'name': 'seventeenth visualization (string labels)',
|
||||
'slug': 'seventeenth',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
@ -324,6 +331,83 @@ def bijoe_mock(url, request):
|
|||
'measure': 'integer',
|
||||
}
|
||||
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||
if url.path == '/visualization/17/json/':
|
||||
response = {
|
||||
'data': [
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2],
|
||||
],
|
||||
'axis': {
|
||||
'x_labels': [
|
||||
'Mois de %s' % m
|
||||
for m in [
|
||||
'janvier',
|
||||
'février',
|
||||
'mars',
|
||||
'avril',
|
||||
'mai',
|
||||
'juin',
|
||||
'juillet',
|
||||
'août',
|
||||
'septembre',
|
||||
'octobre',
|
||||
'novembre',
|
||||
'décembre',
|
||||
]
|
||||
],
|
||||
'y_labels': ['Label %s' % i for i in range(0, 9)],
|
||||
},
|
||||
'format': '1',
|
||||
'unit': None,
|
||||
'measure': 'integer',
|
||||
}
|
||||
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||
|
||||
if url.path == '/visualization/17/json/':
|
||||
response = {
|
||||
'data': [
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2],
|
||||
],
|
||||
'axis': {
|
||||
'x_labels': [
|
||||
'Mois de %s' % m
|
||||
for m in [
|
||||
'janvier',
|
||||
'février',
|
||||
'mars',
|
||||
'avril',
|
||||
'mai',
|
||||
'juin',
|
||||
'juillet',
|
||||
'août',
|
||||
'septembre',
|
||||
'octobre',
|
||||
'novembre',
|
||||
'décembre',
|
||||
]
|
||||
],
|
||||
'y_labels': ['Label %s' % i for i in range(0, 9)],
|
||||
},
|
||||
'format': '1',
|
||||
'unit': None,
|
||||
'measure': 'integer',
|
||||
}
|
||||
return {'content': json.dumps(response), 'request': request, 'status_code': 200}
|
||||
|
||||
|
||||
STATISTICS_LIST = {
|
||||
|
@ -1345,6 +1429,69 @@ def test_chartng_cell_view(app, normal_user, statistics):
|
|||
assert 'not found' in resp.text
|
||||
|
||||
|
||||
@with_httmock(bijoe_mock)
|
||||
def test_chartng_cell_with_truncated_labels(app, normal_user, statistics):
|
||||
page = Page(title='One', slug='index')
|
||||
page.save()
|
||||
|
||||
cell = ChartNgCell(page=page, order=1, placeholder='content', chart_type='horizontal-bar')
|
||||
cell.statistic = Statistic.objects.get(slug='seventeenth')
|
||||
cell.save()
|
||||
location = '/api/dataviz/graph/%s/' % cell.id
|
||||
resp = app.get(location + '?width=499')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
for label in svg.xpath(
|
||||
"//ns:g[@class='legends']//ns:text", namespaces={'ns': 'http://www.w3.org/2000/svg'}
|
||||
):
|
||||
assert len(label) <= 15
|
||||
|
||||
|
||||
@with_httmock(bijoe_mock)
|
||||
def test_chartng_cell_viewbox(app, normal_user, statistics):
|
||||
page = Page(title='One', slug='index')
|
||||
page.save()
|
||||
|
||||
cell = ChartNgCell(page=page, order=1, placeholder='content')
|
||||
cell.statistic = Statistic.objects.get(slug='eighth')
|
||||
cell.save()
|
||||
location = '/api/dataviz/graph/%s/' % cell.id
|
||||
resp = app.get(location + '?width=600')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
assert svg.attrib['width'] == '600'
|
||||
assert svg.attrib['height'] == '250'
|
||||
|
||||
cell.statistic = Statistic.objects.get(slug='seventeenth')
|
||||
cell.save()
|
||||
|
||||
resp = app.get(location + '?width=800&height=600')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
assert svg.attrib['width'] == '800'
|
||||
assert svg.attrib['height'] == '600'
|
||||
|
||||
# simulate chart preview from backoffice
|
||||
resp = app.get(location + '?width=300&height=150')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
assert svg.attrib['width'] == '300'
|
||||
assert svg.attrib['height'] == '150'
|
||||
|
||||
# reduce cell's height
|
||||
cell.height = '150'
|
||||
cell.save()
|
||||
|
||||
resp = app.get(location + '?width=600&height=200')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
# rendered cell should be higher than defined height
|
||||
assert svg.attrib['height'] == '220.5'
|
||||
|
||||
# test pie chart rendering
|
||||
cell.chart_type = 'pie'
|
||||
cell.save()
|
||||
resp = app.get(location + '?width=600&height=200')
|
||||
svg = lxml.etree.fromstring(resp.text.encode())
|
||||
# rendered cell should be higher than defined height
|
||||
assert svg.attrib['height'] == '220.5'
|
||||
|
||||
|
||||
@with_httmock(new_api_mock)
|
||||
def test_chartng_cell_view_new_api(app, normal_user, new_api_statistics):
|
||||
page = Page.objects.create(title='One', slug='index')
|
||||
|
|
Depuis les nouvelles versions du patch cette condition semble superflue.