From a9ffbbc7ca82fc4ebdd493b07a39f373194bf423 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Hennecart?= Date: Tue, 17 Nov 2020 14:37:10 +0000 Subject: [PATCH] [IMP] survey: improve label axis x in chartjs live survey Before when there were long labels, this was leading to an overlapping of the text. Now, we display the whole label but in multiline with a maximum number of characters per line depending on the number of columns. The font size will also be compute depending of the number of characters per line and with the length of the longest word in the labels. task-2382681 closes odoo/odoo#64877 X-original-commit: 58bd6a51041d1c0c011810823caf540995428c2a Signed-off-by: Thibault Delavallee (tde) --- .../static/src/js/survey_session_chart.js | 85 ++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/addons/survey/static/src/js/survey_session_chart.js b/addons/survey/static/src/js/survey_session_chart.js index 2198afcceee..a4e74555193 100644 --- a/addons/survey/static/src/js/survey_session_chart.js +++ b/addons/survey/static/src/js/survey_session_chart.js @@ -166,6 +166,89 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({ } } }, + plugins: [{ + /** + * The way it works is each label is an array of words. + * eg.: if we have a chart label: "this is an example of a label" + * The library will split it as: ["this is an example", "of a label"] + * Each value of the array represents a line of the label. + * So for this example above: it will be displayed as: + * "this is an examble
of a label", breaking the label in 2 parts and put on 2 lines visually. + * + * What we do here is rework the labels with our own algorithm to make them fit better in screen space + * based on breakpoints based on number of columns to display. + * So this example will become: ["this is an", "example of", "a label"] if we have a lot of labels to put in the chart. + * Which will be displayed as "this is an
example of
a label" + * Obviously, the more labels you have, the more columns, and less screen space is available. + * + * We also adapt the font size based on the width available in the chart. + * + * So we counterbalance multiple times: + * - Based on number of columns (i.e. number of survey.question.answer of your current survey.question), + * we split the words of every labels to make them display on more rows. + * - Based on the width of the chart (which is equivalent to screen width), + * we reduce the chart font to be able to fit more characters. + * - Based on the longest word present in the labels, we apply a certain ratio with the width of the chart + * to get a more accurate font size for the space available. + * + * @param {Object} chart + */ + beforeInit: function (chart) { + const nbrCol = chart.data.labels.length; + const minRatio = 0.4; + // Numbers of maximum characters per line to print based on the number of columns and default ratio for the font size + // Between 1 and 2 -> 25, 3 and 4 -> 20, 5 and 6 -> 15, ... + const charPerLineBreakpoints = [ + [1, 2, 25, minRatio], + [3, 4, 20, minRatio], + [5, 6, 15, 0.45], + [7, 8, 10, 0.65], + [9, null, 7, 0.7], + ]; + + let charPerLine; + let fontRatio; + charPerLineBreakpoints.forEach(([lowerBound, upperBound, value, ratio]) => { + if (nbrCol >= lowerBound && (upperBound === null || nbrCol <= upperBound)) { + charPerLine = value; + fontRatio = ratio; + } + }); + + // Adapt font size if the number of characters per line is under the maximum + if (charPerLine < 25) { + const allWords = chart.data.labels.reduce((accumulator, words) => accumulator.concat(' '.concat(words))); + const maxWordLength = Math.max(...allWords.split(' ').map((word) => word.length)); + fontRatio = maxWordLength > charPerLine ? minRatio : fontRatio; + chart.options.scales.xAxes[0].ticks.fontSize = Math.min(parseInt(chart.options.scales.xAxes[0].ticks.fontSize), chart.width * fontRatio / (nbrCol)); + } + + chart.data.labels.forEach(function (label, index, labelsList) { + // Split all the words of the label + const words = label.split(" "); + let resultLines = []; + let currentLine = []; + for (let i = 0; i < words.length; i++) { + // If the word we are adding exceed already the number of characters for the line, we add it anyway before passing to a new line + currentLine.push(words[i]); + + // Continue to add words in the line if there is enough space and if there is at least one more word to add + const nextWord = i+1 < words.length ? words[i+1] : null; + if (nextWord) { + const nextLength = currentLine.join(' ').length + nextWord.length; + if (nextLength <= charPerLine) { + continue; + } + } + // Add the constructed line and reset the variable for the next line + const newLabelLine = currentLine.join(' '); + resultLines.push(newLabelLine); + currentLine = []; + } + labelsList[index] = resultLines; + }); + }, + }], }; }, @@ -176,7 +259,7 @@ publicWidget.registry.SurveySessionChart = publicWidget.Widget.extend({ */ _extractChartLabels: function () { return this.questionStatistics.map(function (point) { - return point.text.length > 20 ? point.text.substring(0, 17) + '...' : point.text; + return point.text; }); },