diff --git a/app/javascript/survey_result_chart.js b/app/javascript/survey_result_chart.js index 7ef19046be7..9d04c9af6ff 100644 --- a/app/javascript/survey_result_chart.js +++ b/app/javascript/survey_result_chart.js @@ -1,26 +1,26 @@ -import Chart from 'chart.js/auto'; -import ChartDataLabels from 'chartjs-plugin-datalabels'; -import annotationPlugin from 'chartjs-plugin-annotation'; +import Chart from 'chart.js/auto' +import ChartDataLabels from 'chartjs-plugin-datalabels' +import annotationPlugin from 'chartjs-plugin-annotation' -Chart.register(ChartDataLabels); -Chart.register(annotationPlugin); +Chart.register(ChartDataLabels) +Chart.register(annotationPlugin) -document.addEventListener('DOMContentLoaded', function() { - initRadioButtonCharts(); - initCheckBoxCharts(); - initLinearScaleCharts(); -}); +document.addEventListener('DOMContentLoaded', function () { + initRadioButtonCharts() + initCheckBoxCharts() + initLinearScaleCharts() +}) function initRadioButtonCharts() { - document.querySelectorAll('[data-radio-button-chart]').forEach(element => { - const ctx = element.getContext('2d'); - const choices = JSON.parse(element.dataset.choices); - const answers = JSON.parse(element.dataset.answers); - - const counts = choices.map(choice => { - return answers.filter(answer => answer === choice).length; - }); - + document.querySelectorAll('[data-radio-button-chart]').forEach((element) => { + const ctx = element.getContext('2d') + const choices = JSON.parse(element.dataset.choices) + const answers = JSON.parse(element.dataset.answers) + + const counts = choices.map((choice) => { + return answers.filter((answer) => answer === choice).length + }) + const backgroundColors = [ 'rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)', @@ -31,25 +31,27 @@ function initRadioButtonCharts() { 'rgba(199, 199, 199, 0.7)', 'rgba(83, 102, 255, 0.7)', 'rgba(40, 159, 64, 0.7)', - 'rgba(210, 199, 199, 0.7)', - ]; - + 'rgba(210, 199, 199, 0.7)' + ] + // eslint-disable-next-line no-new new Chart(ctx, { type: 'pie', data: { labels: choices, - datasets: [{ - data: counts, - backgroundColor: backgroundColors.slice(0, choices.length), - borderWidth: 1 - }] + datasets: [ + { + data: counts, + backgroundColor: backgroundColors.slice(0, choices.length), + borderWidth: 1 + } + ] }, options: { responsive: true, plugins: { legend: { - position: 'bottom', + position: 'bottom' }, title: { display: true, @@ -57,9 +59,13 @@ function initRadioButtonCharts() { }, datalabels: { formatter: (value, _ctx) => { - const total = _ctx.dataset.data.reduce((acc, data) => acc + data, 0); - const percentage = total > 0 ? Math.round((value / total) * 100) : 0; - return percentage > 0 ? `${percentage}%` : ''; + const total = _ctx.dataset.data.reduce( + (acc, data) => acc + data, + 0 + ) + const percentage = + total > 0 ? Math.round((value / total) * 100) : 0 + return percentage > 0 ? `${percentage}%` : '' }, color: '#fff', font: { @@ -69,21 +75,21 @@ function initRadioButtonCharts() { } } } - }); - }); + }) + }) } function initCheckBoxCharts() { - document.querySelectorAll('[data-check-box-chart]').forEach(element => { - const ctx = element.getContext('2d'); - const choices = JSON.parse(element.dataset.choices); - const allSelectedChoices = JSON.parse(element.dataset.allSelectedChoices); - const totalRespondents = parseInt(element.dataset.totalRespondents); - - const counts = choices.map(choice => { - return allSelectedChoices.filter(selected => selected === choice).length; - }); - + document.querySelectorAll('[data-check-box-chart]').forEach((element) => { + const ctx = element.getContext('2d') + const choices = JSON.parse(element.dataset.choices) + const allSelectedChoices = JSON.parse(element.dataset.allSelectedChoices) + const totalRespondents = parseInt(element.dataset.totalRespondents) + + const counts = choices.map((choice) => { + return allSelectedChoices.filter((selected) => selected === choice).length + }) + const backgroundColors = [ 'rgba(255, 99, 132, 0.7)', 'rgba(54, 162, 235, 0.7)', @@ -94,25 +100,27 @@ function initCheckBoxCharts() { 'rgba(199, 199, 199, 0.7)', 'rgba(83, 102, 255, 0.7)', 'rgba(40, 159, 64, 0.7)', - 'rgba(210, 199, 199, 0.7)', - ]; - + 'rgba(210, 199, 199, 0.7)' + ] + // eslint-disable-next-line no-new new Chart(ctx, { type: 'pie', data: { labels: choices, - datasets: [{ - data: counts, - backgroundColor: backgroundColors.slice(0, choices.length), - borderWidth: 1 - }] + datasets: [ + { + data: counts, + backgroundColor: backgroundColors.slice(0, choices.length), + borderWidth: 1 + } + ] }, options: { responsive: true, plugins: { legend: { - position: 'bottom', + position: 'bottom' }, title: { display: true, @@ -120,18 +128,24 @@ function initCheckBoxCharts() { }, tooltip: { callbacks: { - label: function(context) { - const label = context.label || ''; - const value = context.raw || 0; - const percentage = totalRespondents > 0 ? (value / totalRespondents * 100).toFixed(1) : 0; - return `${label}: ${value}件 (${percentage}%)`; + label: function (context) { + const label = context.label || '' + const value = context.raw || 0 + const percentage = + totalRespondents > 0 + ? ((value / totalRespondents) * 100).toFixed(1) + : 0 + return `${label}: ${value}件 (${percentage}%)` } } }, datalabels: { formatter: (value, _ctx) => { - const percentage = totalRespondents > 0 ? Math.round((value / totalRespondents) * 100) : 0; - return percentage > 0 ? `${percentage}%` : ''; + const percentage = + totalRespondents > 0 + ? Math.round((value / totalRespondents) * 100) + : 0 + return percentage > 0 ? `${percentage}%` : '' }, color: '#fff', font: { @@ -141,38 +155,44 @@ function initCheckBoxCharts() { } } } - }); - }); + }) + }) } function initLinearScaleCharts() { - document.querySelectorAll('[data-linear-scale-chart]').forEach(element => { - const ctx = element.getContext('2d'); - const minValue = parseInt(element.dataset.minValue); - const maxValue = parseInt(element.dataset.maxValue); - const answers = JSON.parse(element.dataset.answers); - + document.querySelectorAll('[data-linear-scale-chart]').forEach((element) => { + const ctx = element.getContext('2d') + const minValue = parseInt(element.dataset.minValue) + const maxValue = parseInt(element.dataset.maxValue) + const answers = JSON.parse(element.dataset.answers) + // 平均値と中央値を計算 - let average = null; - let median = null; - - if (element.dataset.average && !isNaN(parseFloat(element.dataset.average))) { - average = parseFloat(element.dataset.average); + let average = null + let median = null + + if ( + element.dataset.average && + !isNaN(parseFloat(element.dataset.average)) + ) { + average = parseFloat(element.dataset.average) } - + if (element.dataset.median && !isNaN(parseFloat(element.dataset.median))) { - median = parseFloat(element.dataset.median); + median = parseFloat(element.dataset.median) } - - const scaleValues = Array.from({length: maxValue - minValue + 1}, (_, i) => minValue + i); - - const counts = scaleValues.map(value => { - return answers.filter(answer => parseInt(answer) === value).length; - }); - + + const scaleValues = Array.from( + { length: maxValue - minValue + 1 }, + (_, i) => minValue + i + ) + + const counts = scaleValues.map((value) => { + return answers.filter((answer) => parseInt(answer) === value).length + }) + // アノテーションの設定 - const annotations = {}; - + const annotations = {} + // 平均値のアノテーション if (average !== null) { annotations.averageLine = { @@ -190,9 +210,9 @@ function initLinearScaleCharts() { weight: 'bold' } } - }; + } } - + // 中央値のアノテーション if (median !== null) { annotations.medianLine = { @@ -210,21 +230,23 @@ function initLinearScaleCharts() { weight: 'bold' } } - }; + } } - + // eslint-disable-next-line no-new new Chart(ctx, { type: 'bar', data: { - labels: scaleValues.map(value => `${value}`), - datasets: [{ - label: '回答数', - data: counts, - backgroundColor: 'rgba(54, 162, 235, 0.7)', - borderColor: 'rgba(54, 162, 235, 1)', - borderWidth: 1 - }] + labels: scaleValues.map((value) => `${value}`), + datasets: [ + { + label: '回答数', + data: counts, + backgroundColor: 'rgba(54, 162, 235, 0.7)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1 + } + ] }, options: { responsive: true, @@ -245,11 +267,15 @@ function initLinearScaleCharts() { }, ticks: { stepSize: 1, - callback: function(value) { - if (Number.isInteger(value) && value >= minValue && value <= maxValue) { - return value.toString(); + callback: function (value) { + if ( + Number.isInteger(value) && + value >= minValue && + value <= maxValue + ) { + return value.toString() } - return ''; + return '' } } } @@ -265,24 +291,26 @@ function initLinearScaleCharts() { }, tooltip: { callbacks: { - title: function(context) { - return `評価: ${context[0].label}`; + title: function (context) { + return `評価: ${context[0].label}` }, - footer: function() { - let footer = ''; + footer: function () { + let footer = '' if (average !== null) { - footer += `平均値: ${average.toFixed(2)}`; + footer += `平均値: ${average.toFixed(2)}` } if (median !== null) { - footer += footer ? `, 中央値: ${median.toFixed(1)}` : `中央値: ${median.toFixed(1)}`; + footer += footer + ? `, 中央値: ${median.toFixed(1)}` + : `中央値: ${median.toFixed(1)}` } - return footer; + return footer } } }, datalabels: { formatter: (value) => { - return value > 0 ? value : ''; + return value > 0 ? value : '' }, anchor: 'end', align: 'top', @@ -296,6 +324,6 @@ function initLinearScaleCharts() { } } } - }); - }); + }) + }) }