diff --git a/frontend/src/components/projectDetail/messages.js b/frontend/src/components/projectDetail/messages.js index fab862bc0e..f021b45580 100644 --- a/frontend/src/components/projectDetail/messages.js +++ b/frontend/src/components/projectDetail/messages.js @@ -275,6 +275,14 @@ export default defineMessages({ id: 'projects.link.stats', defaultMessage: 'More statistics', }, + mappedTasks: { + id: 'projects.stats.mapped', + defaultMessage: 'Mapped tasks', + }, + validatedTasks: { + id: 'projects.stats.validated', + defaultMessage: 'Validated tasks', + }, shareMessage: { id: 'project.share.twitter', defaultMessage: 'Contribute mapping the project #{id} on {site}', diff --git a/frontend/src/components/projectDetail/timeline.js b/frontend/src/components/projectDetail/timeline.js index 13e2aed58d..15e1852f52 100644 --- a/frontend/src/components/projectDetail/timeline.js +++ b/frontend/src/components/projectDetail/timeline.js @@ -1,19 +1,33 @@ import React from 'react'; import { Line } from 'react-chartjs-2'; +import { useIntl } from 'react-intl'; +import messages from './messages'; import { formatTimelineData, formatTimelineTooltip } from '../../utils/formatChartJSData'; import { CHART_COLOURS } from '../../config'; import { useTimeDiff } from '../../hooks/UseTimeDiff'; export default function ProjectTimeline({ tasksByDay }: Object) { + const intl = useIntl(); const unit = useTimeDiff(tasksByDay); + const mappedTasksConfig = { + color: CHART_COLOURS.orange, + label: intl.formatMessage(messages.mappedTasks), + }; + const validatedTasksConfig = { + color: CHART_COLOURS.red, + label: intl.formatMessage(messages.validatedTasks), + }; + return ( formatTimelineTooltip(tooltip, data, true) }, + plugins: { + legend: { position: 'top', align: 'end', labels: { boxWidth: 12 } }, + tooltip: { + callbacks: { label: (context) => formatTimelineTooltip(context, true) }, + }, }, scales: { xAxes: [{ type: 'time', time: { unit: unit } }] }, }} diff --git a/frontend/src/components/projectStats/contributorsStats.js b/frontend/src/components/projectStats/contributorsStats.js index 0b94575710..4398b4468b 100644 --- a/frontend/src/components/projectStats/contributorsStats.js +++ b/frontend/src/components/projectStats/contributorsStats.js @@ -95,8 +95,10 @@ export default function ContributorsStats({ contributors }) { formatTooltip(tooltip, data) } }, + plugins: { + legend: { display: false }, + tooltip: { callbacks: { label: (context) => formatTooltip(context) } }, + }, }} /> @@ -110,8 +112,10 @@ export default function ContributorsStats({ contributors }) { data={formatChartData(userLevelsReference, stats)} options={{ aspectRatio: 2, - plugins: { legend: { position: 'right', labels: { boxWidth: 12 } } }, - tooltips: { callbacks: { label: (tooltip, data) => formatTooltip(tooltip, data) } }, + plugins: { + legend: { position: 'right', labels: { boxWidth: 12 } }, + tooltip: { callbacks: { label: (context) => formatTooltip(context) } }, + }, }} /> diff --git a/frontend/src/components/projectStats/taskStatus.js b/frontend/src/components/projectStats/taskStatus.js index 66d0e21203..797a8e1af7 100644 --- a/frontend/src/components/projectStats/taskStatus.js +++ b/frontend/src/components/projectStats/taskStatus.js @@ -68,8 +68,10 @@ const TasksByStatus = ({ stats }) => { data={data} options={{ aspectRatio: 2, - plugins: { legend: { position: 'right', labels: { boxWidth: 12 } } }, - tooltips: { callbacks: { label: (tooltip, data) => formatTooltip(tooltip, data) } }, + plugins: { + legend: { position: 'right', labels: { boxWidth: 12 } }, + tooltip: { callbacks: { label: (context) => formatTooltip(context) } }, + }, }} /> diff --git a/frontend/src/components/teamsAndOrgs/tasksStatsChart.js b/frontend/src/components/teamsAndOrgs/tasksStatsChart.js index a6c3d5bcb2..6923e87c6e 100644 --- a/frontend/src/components/teamsAndOrgs/tasksStatsChart.js +++ b/frontend/src/components/teamsAndOrgs/tasksStatsChart.js @@ -1,16 +1,30 @@ import React from 'react'; import { Bar } from 'react-chartjs-2'; +import { useIntl } from 'react-intl'; +import messages from '../projectDetail/messages'; import { CHART_COLOURS } from '../../config'; import { useTimeDiff } from '../../hooks/UseTimeDiff'; import { formatTasksStatsData, formatTimelineTooltip } from '../../utils/formatChartJSData'; const TasksStatsChart = ({ stats }) => { + const intl = useIntl(); const unit = useTimeDiff(stats); + + const mappedTasksConfig = { + color: CHART_COLOURS.orange, + label: intl.formatMessage(messages.mappedTasks), + }; + const validatedTasksConfig = { + color: CHART_COLOURS.red, + label: intl.formatMessage(messages.validatedTasks), + }; const options = { - plugins: { legend: { position: 'top', align: 'end', labels: { boxWidth: 12 } } }, - tooltips: { - callbacks: { label: (tooltip, data) => formatTimelineTooltip(tooltip, data, false) }, + plugins: { + legend: { position: 'top', align: 'end', labels: { boxWidth: 12 } }, + tooltip: { + callbacks: { label: (context) => formatTimelineTooltip(context, false) }, + }, }, scales: { yAxes: [ @@ -32,7 +46,7 @@ const TasksStatsChart = ({ stats }) => { }; return ( ); diff --git a/frontend/src/components/userDetail/editsByNumbers.js b/frontend/src/components/userDetail/editsByNumbers.js index 0959010ae7..59ffa8d081 100644 --- a/frontend/src/components/userDetail/editsByNumbers.js +++ b/frontend/src/components/userDetail/editsByNumbers.js @@ -48,8 +48,10 @@ const EditsByNumbers = ({ osmStats }) => { data={data} options={{ aspectRatio: 2, - plugins: { legend: { position: 'right', labels: { boxWidth: 12 } } }, - tooltips: { callbacks: { label: (tooltip, data) => formatTooltip(tooltip, data) } }, + plugins: { + legend: { position: 'right', labels: { boxWidth: 12 } }, + tooltip: { callbacks: { label: (context) => formatTooltip(context) } }, + }, }} /> ) : ( diff --git a/frontend/src/components/userDetail/topCauses.js b/frontend/src/components/userDetail/topCauses.js index 35c76fec54..a3b93cf2fa 100644 --- a/frontend/src/components/userDetail/topCauses.js +++ b/frontend/src/components/userDetail/topCauses.js @@ -48,8 +48,10 @@ const TopCauses = ({ userStats }) => { data={data} options={{ aspectRatio: 2, - plugins: { legend: { position: 'right', labels: { boxWidth: 12 } } }, - tooltips: { callbacks: { label: (tooltip, data) => formatTooltip(tooltip, data) } }, + plugins: { + legend: { position: 'right', labels: { boxWidth: 12 } }, + tooltip: { callbacks: { label: (context) => formatTooltip(context) } }, + }, }} /> ) : ( diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 82c11cb095..799a19f121 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -315,6 +315,8 @@ "projects.data.download.aoi": "Download AOI", "projects.data.download.taskGrid": "Download Tasks Grid", "projects.link.stats": "More statistics", + "projects.stats.mapped": "Mapped tasks", + "projects.stats.validated": "Validated tasks", "project.share.twitter": "Contribute mapping the project #{id} on {site}", "project.share.facebook": "Post on Facebook", "project.share.linkedin": "Share on LinkedIn", diff --git a/frontend/src/utils/formatChartJSData.js b/frontend/src/utils/formatChartJSData.js index 634d21ec78..b352de49c6 100644 --- a/frontend/src/utils/formatChartJSData.js +++ b/frontend/src/utils/formatChartJSData.js @@ -11,20 +11,20 @@ export const formatChartData = (reference, stats) => { return data; }; -export const formatTimelineData = (stats, mappedColour, validatedColour) => { +export const formatTimelineData = (stats, mappedTasksConfig, validatedTasksConfig) => { let mapped = { data: [], - backgroundColor: mappedColour, - borderColor: mappedColour, + backgroundColor: mappedTasksConfig.color, + borderColor: mappedTasksConfig.color, fill: false, - label: 'Mapped tasks', + label: mappedTasksConfig.label, }; let validated = { data: [], - backgroundColor: validatedColour, - borderColor: validatedColour, + backgroundColor: validatedTasksConfig.color, + borderColor: validatedTasksConfig.color, fill: false, - label: 'Validated tasks', + label: validatedTasksConfig.label, }; const labels = stats.map((entry) => entry.date); @@ -38,16 +38,16 @@ export const formatTimelineData = (stats, mappedColour, validatedColour) => { return { datasets: [validated, mapped], labels: labels }; }; -export const formatTasksStatsData = (stats, mappedColour, validatedColour) => { +export const formatTasksStatsData = (stats, mappedTasksConfig, validatedTasksConfig) => { let mapped = { data: [], - backgroundColor: mappedColour, - label: 'Mapped tasks', + backgroundColor: mappedTasksConfig.color, + label: mappedTasksConfig.label, }; let validated = { data: [], - backgroundColor: validatedColour, - label: 'Validated tasks', + backgroundColor: validatedTasksConfig.color, + label: validatedTasksConfig.label, }; const labels = stats.map((entry) => entry.date); @@ -57,18 +57,18 @@ export const formatTasksStatsData = (stats, mappedColour, validatedColour) => { return { datasets: [mapped, validated], labels: labels }; }; -export const formatTooltip = (tooltipItem, data) => { - var label = data.labels[tooltipItem.index] || ''; +export const formatTooltip = (context) => { + var label = context.label; if (label) label += ': '; - label += data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + label += context.dataset.data[context.dataIndex]; - return (label += '%'); + return `${label}%`; }; -export const formatTimelineTooltip = (tooltipItem, data, isPercent) => { - var label = data.datasets[tooltipItem.datasetIndex].label || ''; +export const formatTimelineTooltip = (context, isPercent) => { + var label = context.dataset.label || ''; if (label) label += ': '; - label += data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + label += context.dataset.data[context.dataIndex]; return `${label}${isPercent ? '%' : ''}`; }; diff --git a/frontend/src/utils/tests/formatChartJSData.test.js b/frontend/src/utils/tests/formatChartJSData.test.js index 95ca4e11eb..8a0f5a15ee 100644 --- a/frontend/src/utils/tests/formatChartJSData.test.js +++ b/frontend/src/utils/tests/formatChartJSData.test.js @@ -51,7 +51,13 @@ describe('formatChartData', () => { describe('formatTimelineData', () => { it('return the correct information about the datasets', () => { - expect(formatTimelineData(projectContributionsByDay.stats, '#fff', '#092')).toEqual({ + expect( + formatTimelineData( + projectContributionsByDay.stats, + { color: '#fff', label: 'Mapped tasks' }, + { color: '#092', label: 'Validated tasks' }, + ), + ).toEqual({ datasets: [ { data: [0, 6, 19], @@ -74,16 +80,6 @@ describe('formatTimelineData', () => { }); describe('formatTimelineTooltip', () => { - const tooltipItem = { - xLabel: '2020-06-26', - yLabel: 18, - label: '2020-06-26', - value: '18', - index: 2, - datasetIndex: 1, - x: 1074.8309643713924, - y: 78.45394354462593, - }; const data = { datasets: [ { @@ -103,9 +99,21 @@ describe('formatTimelineTooltip', () => { ], labels: ['2020-05-19', '2020-06-01', '2020-06-26'], }; + const tooltipItem = { + xLabel: '2020-06-26', + yLabel: 18, + label: 'Mapped tasks', + value: '18', + dataIndex: 2, + datasetIndex: 1, + x: 1074.8309643713924, + y: 78.45394354462593, + dataset: data.datasets[1], + }; it('returns correct information for Mapped tasks', () => { - expect(formatTimelineTooltip(tooltipItem, data, true)).toBe('Mapped tasks: 31%'); - expect(formatTimelineTooltip(tooltipItem, data)).toBe('Mapped tasks: 31'); + expect(formatTimelineTooltip(tooltipItem, true)).toBe('Mapped tasks: 31%'); + expect(formatTimelineTooltip(tooltipItem, false)).toBe('Mapped tasks: 31'); + expect(formatTimelineTooltip(tooltipItem)).toBe('Mapped tasks: 31'); }); it('returns correct information for Validated tasks', () => { const tooltipItem2 = { @@ -113,27 +121,19 @@ describe('formatTimelineTooltip', () => { yLabel: 18, label: '2020-06-26', value: '18', - index: 0, + dataIndex: 0, datasetIndex: 0, x: 1074.8309643713924, y: 78.45394354462593, + dataset: data.datasets[0], }; - expect(formatTimelineTooltip(tooltipItem2, data, true)).toBe('Validated tasks: 0%'); - expect(formatTimelineTooltip(tooltipItem2, data)).toBe('Validated tasks: 0'); + expect(formatTimelineTooltip(tooltipItem2, true)).toBe('Validated tasks: 0%'); + expect(formatTimelineTooltip(tooltipItem2, false)).toBe('Validated tasks: 0'); + expect(formatTimelineTooltip(tooltipItem2)).toBe('Validated tasks: 0'); }); }); describe('formatTooltip', () => { - const tooltipItem = { - xLabel: '', - yLabel: '', - label: '', - value: '', - index: 1, - datasetIndex: 0, - x: 173.3499984741211, - y: 124, - }; const data = { datasets: [ { @@ -148,20 +148,32 @@ describe('formatTooltip', () => { ], labels: ['Building', 'Roads', 'Points of interests', 'Waterways'], }; + const tooltipItem = { + xLabel: '', + yLabel: '', + label: 'Roads', + value: '', + dataIndex: 1, + datasetIndex: 0, + x: 173.3499984741211, + y: 124, + dataset: data.datasets[0], + }; it('returns correct text with 30 percent', () => { - expect(formatTooltip(tooltipItem, data)).toBe('Roads: 30%'); + expect(formatTooltip(tooltipItem)).toBe('Roads: 30%'); }); it('returns correct text with 42 percent', () => { const tooltipItem = { xLabel: '', yLabel: '', - label: '', + label: 'Waterways', value: '', - index: 3, + dataIndex: 3, datasetIndex: 0, x: 173.3499984741211, y: 124, + dataset: data.datasets[0], }; - expect(formatTooltip(tooltipItem, data)).toBe('Waterways: 42%'); + expect(formatTooltip(tooltipItem)).toBe('Waterways: 42%'); }); });