diff --git a/.eslintrc.json b/.eslintrc.json index ff57dffacbb..d5483ab0790 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,6 +47,8 @@ "tabWidth": 2, "ignoreComments": true }], + "no-unexpected-multiline": "off", + "no-unused-vars": "warn", "no-useless-escape": "off", "no-dupe-keys": "off", "react/no-direct-mutation-state": "off", diff --git a/modules/dashboard/php/widget.class.inc b/modules/dashboard/php/widget.class.inc index dad176c7401..f74c474e8ca 100644 --- a/modules/dashboard/php/widget.class.inc +++ b/modules/dashboard/php/widget.class.inc @@ -33,6 +33,8 @@ class Widget implements \LORIS\GUI\Widget private WidgetContent $_content; private WidgetDisplayProps $_displayprops; private ?WidgetDependencies $_dependencies; + private $_template = 'panel.tpl'; + private $_template_variables = []; /** * Construct a dashboard widget with the specified properties. @@ -54,6 +56,30 @@ class Widget implements \LORIS\GUI\Widget $this->_dependencies = $deps; } + /** + * The setTemplate - change default smarty template + * + * @param string $template_file The template file to use. + * + * @return void + */ + public function setTemplate(string $template_file) + { + $this->_template = $template_file; + } + + /** + * The setTemplateVariables - set additional smarty template variables + * + * @param array $template_variables The template variables to use. + * + * @return void + */ + public function setTemplateVariables(array $template_variables) + { + $this->_template_variables = $template_variables; + } + /** * Return the size of this widget. * @@ -119,8 +145,9 @@ class Widget implements \LORIS\GUI\Widget 'body' => $this->_content->getBody(), 'footer' => $this->_content->getFooter(), 'menus' => $this->_displayprops->getMenus(), + ...$this->_template_variables, ] ); - return $renderer->fetch("panel.tpl"); + return $renderer->fetch($this->_template); } } diff --git a/modules/dashboard/templates/div.tpl b/modules/dashboard/templates/div.tpl new file mode 100644 index 00000000000..5d63ecd7e84 --- /dev/null +++ b/modules/dashboard/templates/div.tpl @@ -0,0 +1 @@ +
{$body}
diff --git a/modules/dashboard/test/DashboardTest.php b/modules/dashboard/test/DashboardTest.php index 835d80979b2..addaa3c310d 100644 --- a/modules/dashboard/test/DashboardTest.php +++ b/modules/dashboard/test/DashboardTest.php @@ -356,27 +356,31 @@ public function testDashboardRecruitmentView() { $this->safeGet($this->url . '/dashboard/'); $views = $this->safeFindElement( - WebDriverBy::Xpath( - "//*[@id='lorisworkspace']/div[1]". - "/div[2]/div[1]/div/button" + WebDriverBy::cssSelector( + "#statistics_widgets .panel:nth-child(1) .views button" ) ); $views->click(); $assertText1 = $this->safeFindElement( - WebDriverBy::XPath( - "//*[@id='lorisworkspace']/div[1]". - "/div[2]/div[1]/div/ul/li[1]/a" + WebDriverBy::cssSelector( + "#statistics_widgets .panel:nth-child(1)". + " .dropdown-menu li:nth-child(1)" ) )->getText(); + $assertText2 = $this->safeFindElement( - WebDriverBy::XPath( - "//*[@id='lorisworkspace']/div[1]". - "/div[2]/div[1]/div/ul/li[2]/a" + WebDriverBy::cssSelector( + "#statistics_widgets .panel:nth-child(1)". + " .dropdown-menu li:nth-child(2)" ) )->getText(); - $this->assertStringContainsString("View overall recruitment", $assertText1); - $this->assertStringContainsString("View site breakdown", $assertText2); + + $this->assertStringContainsString("Recruitment - overall", $assertText1); + $this->assertStringContainsString( + "Recruitment - site breakdown", + $assertText2 + ); } /** @@ -667,8 +671,11 @@ private function _testPlan7And8() { $this->safeGet($this->url . '/dashboard/'); $testText = $this->safeFindElement( - WebDriverBy::cssSelector("#scan-line-chart-panel") + WebDriverBy::cssSelector( + "#statistics_studyprogression .panel-body div:nth-child(1)" + ) )->getText(); + $this->assertStringContainsString( "Scan sessions per site", $testText diff --git a/modules/statistics/.gitignore b/modules/statistics/.gitignore new file mode 100644 index 00000000000..dd2b3c56afd --- /dev/null +++ b/modules/statistics/.gitignore @@ -0,0 +1 @@ +js/WidgetIndex.js diff --git a/modules/statistics/js/recruitment.js b/modules/statistics/js/recruitment.js deleted file mode 100644 index e72af1c26ca..00000000000 --- a/modules/statistics/js/recruitment.js +++ /dev/null @@ -1,110 +0,0 @@ -$(document).ready(function() { - // Colours for all charts broken down by only by site - var siteColours = [ - '#F0CC00', '#27328C', '#2DC3D0', '#4AE8C2', '#D90074', '#7900DB', '#FF8000', - '#0FB500', '#CC0000', '#DB9CFF', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', - '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5' - ]; - // Colours for the recruitment bar chart: breakdown by sex - var sexColours = ['#2FA4E7', '#1C70B6']; - $('.progress-bar').tooltip(); - - // Open the appropriate charts from the "views" dropdown menus - $(".dropdown-menu a").click(function () { - $(this).parent().siblings().removeClass("active"); - $(this).parent().addClass("active"); - $($(this).parent().siblings().children("a")).each(function () { - $(document.getElementById(this.getAttribute('data-target'))).addClass("hidden"); - }); - $(document.getElementById(this.getAttribute('data-target'))).removeClass("hidden"); - - /* Make sure the chart variables are defined before resizing - * They may not be defined on initial page load because they are created through - * an AJAX request. - */ - if (typeof recruitmentPieChart !== 'undefined') { - recruitmentPieChart.resize(); - } - if (typeof recruitmentBarChart !== 'undefined') { - recruitmentBarChart.resize(); - } - }); - - function formatPieData(data) { - "use strict"; - var processedData = new Array(); - for (var i in data) { - var siteData = [data[i].label, data[i].total]; - processedData.push(siteData); - } - return processedData; - } - - function formatBarData(data) { - "use strict"; - var processedData = new Array(); - if (data.datasets) { - var females = ['Female']; - processedData.push(females.concat(data.datasets.female)); - var males = ['Male']; - processedData.push(males.concat(data.datasets.male)); - } - return processedData; - } - // AJAX to get pie chart data - $.ajax({ - url: loris.BaseURL + '/statistics/charts/siterecruitment_pie', - type: 'get', - success: function(jsonData) { - var recruitmentPieData = formatPieData(jsonData); - recruitmentPieChart = c3.generate({ - bindto: '#recruitmentPieChart', - data: { - columns: recruitmentPieData, - type : 'pie' - }, - color: { - pattern: siteColours - } - }); - }, - error: function(xhr, desc, err) { - console.log(xhr); - console.log("Details: " + desc + "\nError:" + err); - } - }); - - // AJAX to get bar chart data - $.ajax({ - url: loris.BaseURL + '/statistics/charts/siterecruitment_bysex', - type: 'get', - success: function(data) { - var recruitmentBarData = formatBarData(data); - var recruitmentBarLabels = data.labels; - recruitmentBarChart = c3.generate({ - bindto: '#recruitmentBarChart', - data: { - columns: recruitmentBarData, - type: 'bar' - }, - axis: { - x: { - type : 'categorized', - categories: recruitmentBarLabels - }, - y: { - label: 'Candidates registered' - } - }, - color: { - pattern: sexColours - } - }); - }, - error: function(xhr, desc, err) { - console.log(xhr); - console.log("Details: " + desc + "\nError:" + err); - } - }); -} -); diff --git a/modules/statistics/js/studyprogression.js b/modules/statistics/js/studyprogression.js deleted file mode 100644 index c2a6c42e18e..00000000000 --- a/modules/statistics/js/studyprogression.js +++ /dev/null @@ -1,212 +0,0 @@ -$(document).ready(function() { - - // Colours for all charts broken down by only by site - var siteColours = [ - '#F0CC00', '#27328C', '#2DC3D0', '#4AE8C2', '#D90074', '#7900DB', '#FF8000', - '#0FB500', '#CC0000', '#DB9CFF', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', - '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5' - ]; - - // Open the appropriate charts from the "views" dropdown menus - $(".dropdown-menu a").click(function () { - $(this).parent().siblings().removeClass("active"); - $(this).parent().addClass("active"); - $($(this).parent().siblings().children("a")).each(function () { - $(document.getElementById(this.getAttribute('data-target'))).addClass("hidden"); - }); - $(document.getElementById(this.getAttribute('data-target'))).removeClass("hidden"); - - if (typeof recruitmentLineChart !== 'undefined') { - recruitmentLineChart.resize(); - } - if (typeof scanLineChart !== 'undefined') { - scanLineChart.resize(); - } - }); - - function formatLineData(data) { - "use strict"; - var processedData = new Array(); - var labels = new Array(); - labels.push('x'); - for (var i in data.labels) { - labels.push(data.labels[i]); - } - processedData.push(labels); - for (var i in data.datasets) { - var dataset = new Array(); - dataset.push(data.datasets[i].name); - processedData.push(dataset.concat(data.datasets[i].data)); - } - var totals = new Array(); - totals.push("Total"); - for(var j=0; j' + id; - }) - .each(function(id) { - d3.select(this).select('span').style('background-color', scanLineChart.color(id)); - }) - .on('mouseover', function(id) { - scanLineChart.focus(id); - }) - .on('mouseout', function(id) { - scanLineChart.revert(); - }) - .on('click', function(id) { - $(this).toggleClass("c3-legend-item-hidden") - scanLineChart.toggle(id); - }); - }, - error: function(xhr, desc, err) { - console.log(xhr); - console.log("Details: " + desc + "\nError:" + err); - } - }); - // AJAX to get recruitment line chart data - $.ajax({ - url: loris.BaseURL + '/statistics/charts/siterecruitment_line', - type: 'get', - success: function(data) { - let legendNames = []; - for (let j=0; j' + id; - }) - .each(function(id) { - d3.select(this).select('span').style('background-color', recruitmentLineChart.color(id)); - }) - .on('mouseover', function(id) { - recruitmentLineChart.focus(id); - }) - .on('mouseout', function(id) { - recruitmentLineChart.revert(); - }) - .on('click', function(id) { - $(this).toggleClass("c3-legend-item-hidden") - recruitmentLineChart.toggle(id); - }); - }, - error: function(xhr, desc, err) { - console.log(xhr); - console.log("Details: " + desc + "\nError:" + err); - } - }); - -}); diff --git a/modules/statistics/jsx/Fetch.js b/modules/statistics/jsx/Fetch.js new file mode 100644 index 00000000000..b9c7c349773 --- /dev/null +++ b/modules/statistics/jsx/Fetch.js @@ -0,0 +1,41 @@ +// todo move to jslib +// after params have been adjusted +// for get or post requests. + +/** + * fetchData - retrieve data from the provided URL. + * + * @param {string} url for the request + * @return {object} json retrieved from the server. + */ +const fetchData = async (url) => { + let response; + try { + response = await fetch(url, + { + method: 'GET', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + } + ); + if (response.ok) { + // Success + return await response.json(); + } + } catch (e) { + let errorMessage; + if (response.status) { + // Error from the response. + errorMessage = `An error occurred: ${response.status}`; + } else { + // All other possible errors. + errorMessage = `An error occurred: ${e.message}`; + } + throw new Error(errorMessage); + } +}; +export { + fetchData, +}; diff --git a/modules/statistics/jsx/WidgetIndex.js b/modules/statistics/jsx/WidgetIndex.js new file mode 100644 index 00000000000..7e1dbcbf6f5 --- /dev/null +++ b/modules/statistics/jsx/WidgetIndex.js @@ -0,0 +1,77 @@ +import {createRoot} from 'react-dom/client'; +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import Recruitment from './widgets/recruitment'; +import StudyProgression from './widgets/studyprogression'; +import {fetchData} from './Fetch'; +import { + recruitmentCharts, + studyProgressionCharts, +} from './widgets/chartBuilder'; + +/** + * WidgetIndex - the main window. + * + * @param {object} props + * @return {JSX.Element} + */ +const WidgetIndex = (props) => { + const [recruitmentData, setRecruitmentData] = useState({}); + const [studyProgressionData, setStudyProgressionData] = useState({}); + /** + * Similar to componentDidMount and componentDidUpdate. + */ + useEffect(() => { + /** + * setup - fetch recruitment and study progression data. + * + * @return {Promise} + */ + const setup = async () => { + const data = await fetchData( + `${props.baseURL}/Widgets` + ); + setRecruitmentData(data); + setStudyProgressionData(data); + // setup statistics for c3.js charts. + await studyProgressionCharts(); + await recruitmentCharts(); + }; + setup().catch((error) => { + console.error(error); + }); + }, []); + + /** + * Renders the React component. + * + * @return {JSX.Element} - React markup for component. + */ + return ( + <> + + + + ); +}; +WidgetIndex.propTypes = { + baseURL: PropTypes.string, +}; + +/** + * Render StatisticsIndex on page load. + */ +window.addEventListener('load', () => { + const root = createRoot( + document.getElementById('statistics_widgets') + ); + root.render( + + ); +}); diff --git a/modules/statistics/jsx/widgets/chartBuilder.js b/modules/statistics/jsx/widgets/chartBuilder.js new file mode 100644 index 00000000000..eb48d62dbd8 --- /dev/null +++ b/modules/statistics/jsx/widgets/chartBuilder.js @@ -0,0 +1,343 @@ +import 'c3/c3.min.css'; +import c3 from 'c3'; +import {select} from 'd3'; +import {fetchData} from '../Fetch'; + +const baseURL = window.location.origin; + +// Charts +let scanLineChart; +let recruitmentPieChart; +let recruitmentBarChart; +let recruitmentLineChart; + +// Colours for all charts broken down by only by site +const siteColours = [ + '#F0CC00', '#27328C', '#2DC3D0', '#4AE8C2', '#D90074', '#7900DB', '#FF8000', + '#0FB500', '#CC0000', '#DB9CFF', '#8c564b', '#c49c94', '#e377c2', '#f7b6d2', + '#7f7f7f', '#c7c7c7', '#bcbd22', '#dbdb8d', '#17becf', '#9edae5', +]; + +// Colours for the recruitment bar chart: breakdown by sex +const sexColours = ['#2FA4E7', '#1C70B6']; + +/** + * elementVisibility - used to resize charts when element becomes visible. + * + * @param {HTMLElement} element + * @param {function} callback + */ +const elementVisibility = (element, callback) => { + const options = { + root: document.documentElement, + }; + const observer = new IntersectionObserver((entries, observer) => { + entries.forEach((entry) => { + callback(entry.intersectionRatio > 0); + }); + }, options); + observer.observe(element); +}; + +/** + * formatPieData - used for the recruitment widget + * + * @param {object} data + * @return {*[]} + */ +const formatPieData = (data) => { + const processedData = []; + for (const [i] of Object.entries(data)) { + const siteData = [data[i].label, data[i].total]; + processedData.push(siteData); + } + return processedData; +}; + +/** + * formatBarData - used for the recruitment widget + * + * @param {object} data + * @return {*[]} + */ +const formatBarData = (data) => { + const processedData = []; + if (data['datasets']) { + const females = ['Female']; + processedData.push(females.concat(data['datasets']['female'])); + } + if (data['datasets']) { + const males = ['Male']; + processedData.push(males.concat(data['datasets']['male'])); + } + return processedData; +}; + +/** + * formatLineData - used for the study progression widget + * + * @param {object} data + * @return {*[]} + */ +const formatLineData = (data) => { + const processedData = []; + const labels = []; + labels.push('x'); + for (const [i] of Object.entries(data.labels)) { + labels.push(data.labels[i]); + } + processedData.push(labels); + for (const [i] of Object.entries(data['datasets'])) { + const dataset = []; + dataset.push(data['datasets'][i].name); + processedData.push(dataset.concat(data['datasets'][i].data)); + } + const totals = []; + totals.push('Total'); + for (let j = 0; j < data['datasets'][0].data.length; j++) { + let total = 0; + for (let i = 0; i < data['datasets'].length; i++) { + total += parseInt(data['datasets'][i].data[j]); + } + totals.push(total); + } + processedData.push(totals); + return processedData; +}; + +/** + * maxY - used for the study progression widget + * + * @param {object} data + * @return {number} + */ +const maxY = (data) => { + let maxi = 0; + for (let j = 0; j < data['datasets'][0].data.length; j++) { + for (let i = 0; i < data['datasets'].length; i++) { + maxi = Math.max(maxi, parseInt(data['datasets'][i].data[j])); + } + } + return maxi; +}; + +/** + * recruitmentCharts - fetch data for recruitments + */ +const recruitmentCharts = async () => { + // fetch data for the pie chart. + let data = await fetchData( + `${baseURL}/statistics/charts/siterecruitment_pie`, + ); + const recruitmentPieData = formatPieData(data); + recruitmentPieChart = c3.generate({ + bindto: '#recruitmentPieChart', + size: { + width: 227, + }, + data: { + columns: recruitmentPieData, + type: 'pie', + }, + color: { + pattern: siteColours, + }, + }); + elementVisibility(recruitmentPieChart.element, (visible) => { + if (visible) { + recruitmentPieChart.resize(); + } + }); + + // fetch data for the bar chart. + data = await fetchData( + `${baseURL}/statistics/charts/siterecruitment_bysex`, + ); + const recruitmentBarData = formatBarData(data); + const recruitmentBarLabels = data.labels; + recruitmentBarChart = c3.generate({ + bindto: '#recruitmentBarChart', + size: { + width: 461, + }, + data: { + columns: recruitmentBarData, + type: 'bar', + }, + axis: { + x: { + type: 'categorized', + categories: recruitmentBarLabels, + }, + y: { + label: 'Candidates registered', + }, + }, + color: { + pattern: sexColours, + }, + }); + elementVisibility(recruitmentBarChart.element, (visible) => { + if (visible) { + recruitmentBarChart.resize(); + } + }); +}; + +/** + * studyProgressionCharts - fetch data for study progression + */ +const studyProgressionCharts = async () => { + // fetch data for the line chart. + let data = await fetchData( + `${baseURL}/statistics/charts/scans_bymonth`, + ); + let legendNames = []; + for (let j = 0; j < data['datasets'].length; j++) { + legendNames.push(data['datasets'][j].name); + } + const scanLineData = formatLineData(data); + scanLineChart = c3.generate({ + size: { + height: '100%', + }, + bindto: '#scanChart', + data: { + x: 'x', + xFormat: '%m-%Y', + columns: scanLineData, + type: 'area-spline', + }, + legend: { + show: false, + }, + axis: { + x: { + type: 'timeseries', + tick: { + format: '%m-%Y', + }, + }, + y: { + max: maxY(data), + label: 'Scans', + }, + }, + zoom: { + enabled: true, + }, + color: { + pattern: siteColours, + }, + }); + select('.scanChartLegend') + .insert('div', '.scanChart') + .attr('class', 'legend') + .selectAll('div').data(legendNames).enter() + .append('div') + .attr('data-id', function(id) { + return id; + }) + .html(function(id) { + return '' + id; + }) + .each(function(id) { + select(this).select('span').style( + 'background-color', scanLineChart.color(id), + ); + }) + .on('mouseover', function(id) { + scanLineChart.focus(id); + }) + .on('mouseout', function(id) { + scanLineChart.revert(); + }) + .on('click', function(id) { + scanLineChart.toggle(id); + }); + elementVisibility(scanLineChart.element, (visible) => { + if (visible) { + scanLineChart.resize(); + } + }); + // scanLineChart.resize(); + + // fetch data for the line chart. + data = await fetchData( + `${baseURL}/statistics/charts/siterecruitment_line`, + ); + legendNames = []; + for (let j = 0; j < data['datasets'].length; j++) { + legendNames.push(data['datasets'][j].name); + } + const recruitmentLineData = formatLineData(data); + recruitmentLineChart = c3.generate({ + size: { + height: '100%', + }, + bindto: '#recruitmentChart', + data: { + x: 'x', + xFormat: '%m-%Y', + columns: recruitmentLineData, + type: 'area-spline', + }, + legend: { + show: false, + }, + axis: { + x: { + type: 'timeseries', + tick: { + format: '%m-%Y', + }, + }, + y: { + max: maxY(data), + label: 'Candidates registered', + }, + }, + zoom: { + enabled: true, + }, + color: { + pattern: siteColours, + }, + }); + select('.recruitmentChartLegend') + .insert('div', '.recruitmentChart') + .attr('class', 'legend') + .selectAll('div').data(legendNames).enter() + .append('div') + .attr('data-id', function(id) { + return id; + }) + .html(function(id) { + return '' + id; + }) + .each(function(id) { + select(this).select('span').style( + 'background-color', + recruitmentLineChart.color(id)); + }) + .on('mouseover', function(id) { + recruitmentLineChart.focus(id); + }) + .on('mouseout', function(id) { + recruitmentLineChart.revert(); + }) + .on('click', function(id) { + recruitmentLineChart.toggle(id); + }); + elementVisibility(recruitmentLineChart.element, (visible) => { + if (visible) { + recruitmentLineChart.resize(); + } + }); + // recruitmentLineChart.resize(); +}; + +export { + recruitmentCharts, + studyProgressionCharts, +}; diff --git a/modules/statistics/jsx/widgets/recruitment.js b/modules/statistics/jsx/widgets/recruitment.js new file mode 100644 index 00000000000..6f4225edf6c --- /dev/null +++ b/modules/statistics/jsx/widgets/recruitment.js @@ -0,0 +1,205 @@ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import Loader from 'Loader'; +import Panel from 'jsx/Panel'; + +/** + * Recruitment - a widget containing statistics for recruitment data. + * + * @param {object} props + * @return {JSX.Element} + */ +const Recruitment = (props) => { + const [loading, setLoading] = useState(true); + const [overall, setOverall] = useState({}); + const [siteBreakdown, setSiteBreakdown] = useState({}); + const [projectBreakdown, setProjectBreakdown] = useState({}); + + /** + * useEffect - modified to run when props.data updates. + */ + useEffect(() => { + const json = props.data; + if (json && Object.keys(json).length !== 0) { + const overallData = ( +
+ {progressBarBuilder(json['recruitment']['overall'])} +
+ ); + let siteBreakdownData; + if (json['recruitment']['overall'] && + json['recruitment']['overall']['total_recruitment'] > 0 + ) { + siteBreakdownData = ( + <> +
+
+ Total recruitment per site +
+
+
+
+
+ Biological sex breakdown by site +
+
+
+ + ); + } else { + siteBreakdownData = ( +

There have been no candidates registered yet.

+ ); + } + let projectBreakdownData = []; + for (const [key, value] of Object.entries(json['recruitment'])) { + if (key !== 'overall') { + projectBreakdownData.push( +
+ {progressBarBuilder(value)} +
+ ); + } + } + setProjectBreakdown(projectBreakdownData); + setOverall(overallData); + setSiteBreakdown(siteBreakdownData); + setLoading(false); + } + }, [props.data]); + + /** + * progressBarBuilder - generates the graph content. + * + * @param {object} data - data needed to generate the graph content. + * @return {JSX.Element} the charts to render to the widget panel. + */ + const progressBarBuilder = (data) => { + let title; + let content; + if (data['recruitment_target']) { + title =
+ {data['title']} +
; + if (data['surpassed_recruitment']) { + content = ( +
+

+ The recruitment target ( + {data['recruitment_target']} + ) has been passed. +

+
+
+

+ {data['female_total']}
Females +

+
+
+

+ {data['male_total']}
Males +

+
+

+ Target: {data['recruitment_target']} +

+
+
+ ); + } else { + content = ( +
+
+

+ {data['female_total']}
Females +

+
+
+

+ {data['male_total']}
Males +

+
+

+ Target: {data['recruitment_target']} +

+
+ ); + } + } else { + content = ( +
+ Please add a recruitment target for {data['title']}. +
+ ); + } + return ( + <> + {title} + {content} + + ); + }; + + /** + * Renders the React component. + * + * @return {JSX.Element} - React markup for component. + */ + return loading ? : ( + + {overall} + , + title: 'Recruitment - overall', + }, + { + content: + <> + {siteBreakdown} + , + title: 'Recruitment - site breakdown', + }, + { + content: + <> + {projectBreakdown} + , + title: 'Recruitment - project breakdown', + }, + ]} + /> + ); +}; +Recruitment.propTypes = { + data: PropTypes.object, +}; +Recruitment.defaultProps = { + data: {}, +}; + +export default Recruitment; diff --git a/modules/statistics/jsx/widgets/studyprogression.js b/modules/statistics/jsx/widgets/studyprogression.js new file mode 100644 index 00000000000..8da62572a7b --- /dev/null +++ b/modules/statistics/jsx/widgets/studyprogression.js @@ -0,0 +1,91 @@ +import React, {useEffect, useState} from 'react'; +import PropTypes from 'prop-types'; +import Loader from 'Loader'; +import Panel from 'jsx/Panel'; + +/** + * StudyProgression - a widget containing statistics for study data. + * + * @param {object} props + * @return {JSX.Element} + */ +const StudyProgression = (props) => { + const [loading, setLoading] = useState(true); + const [siteScans, setSiteScans] = useState({}); + const [siteRecruitments, setSiteRecruitments] = useState({}); + + /** + * useEffect - modified to run when props.data updates. + */ + useEffect(() => { + const json = props.data; + if (json && Object.keys(json).length !== 0) { + setSiteScans( + json['studyprogression']['total_scans'] > 0 + ?
+
Scan sessions per site
+
+
+ + Note that the Recruitment and Study Progression charts +  include data from ineligible, excluded, and consent +  withdrawn candidates. + +
+ :

There have been no scans yet.

+ ); + setSiteRecruitments( + json['studyprogression']['recruitment']['overall'] + ['total_recruitment'] > 0 + ?
+
Recruitment per site
+
+
+ + Note that the Recruitment and Study Progression charts +  include data from ineligible, excluded, and consent +  withdrawn candidates. + +
+ :

There have been no candidates registered yet.

+ ); + setLoading(false); + } + }, [props.data]); + + /** + * Renders the React component. + * + * @return {JSX.Element} - React markup for component. + */ + return loading ? : ( + + {siteScans} + , + title: 'Study Progression - site scans', + }, + { + content: <> + {siteRecruitments} + , + title: 'Study Progression - site recruitment', + }, + ]} + /> + ); +}; +StudyProgression.propTypes = { + data: PropTypes.object, +}; +StudyProgression.defaultProps = { + data: {}, +}; + +export default StudyProgression; diff --git a/modules/statistics/php/module.class.inc b/modules/statistics/php/module.class.inc index bc1186b5c24..d1486a3d861 100644 --- a/modules/statistics/php/module.class.inc +++ b/modules/statistics/php/module.class.inc @@ -73,257 +73,33 @@ class Module extends \Module switch ($type) { case 'dashboard': $factory = \NDB_Factory::singleton(); - $config = $factory->config(); - $db = $factory->database(); $baseURL = $factory->settings()->getBaseURL(); - - $recruitmentTarget = $config->getSetting('recruitmentTarget'); - - $totalScans = $this->_getTotalRecruitment($db); - $recruitment = [ - 'overall' => $this->_createProjectProgressBar( - 'overall', - "Overall Recruitment", - $recruitmentTarget, - $totalScans, - $db, - ) - ]; - - $projects = \Utility::getProjectList(); - foreach (array_keys($projects) as $projectID) { - $projectInfo = $config->getProjectSettings($projectID); - if (is_null($projectInfo)) { - throw new \LorisException( - 'No project settings exist in the Database for ' . - 'project ID ' . intval($projectID) - ); - } - $recruitment[$projectID] = $this->_createProjectProgressBar( - $projectID, - $projectInfo['Name'], - $projectInfo['recruitmentTarget'], - $this->getTotalRecruitmentByProject($db, $projectID), - $db - ); - } - - // These are just here to avoid phpcs complaining about line length. - $overallstring = 'View overall recruitment'; - $sitebreakdownstring = 'View site breakdown'; - $projbreakdownstring = 'View project breakdown'; - - $recpersite = 'View recruitment per site'; - - return [ - new \LORIS\dashboard\Widget( - new \LORIS\dashboard\WidgetContent( - "Recruitment", - $this->renderTemplate( - "recruitment.tpl", - [ - 'recruitment' => $recruitment - ] - ), - "", - ), - new \LORIS\dashboard\WidgetDisplayProps( - "normal", - 10, - [ - 'overall-recruitment' => $overallstring, - 'recruitment-site-breakdown' => $sitebreakdownstring, - 'recruitment-project-breakdown' => $projbreakdownstring, - ] - ), - new \LORIS\dashboard\WidgetDependencies( - [ - $baseURL . "/css/c3.css", - $baseURL . "/statistics/css/recruitment.css" - ], - [ - $baseURL. '/js/d3.min.js', - $baseURL . '/js/c3.min.js', - $baseURL . "/statistics/js/recruitment.js" - ], - ) + $widget = new \LORIS\dashboard\Widget( + new \LORIS\dashboard\WidgetContent( + '', + '', + '', ), - new \LORIS\dashboard\Widget( - new \LORIS\dashboard\WidgetContent( - "Study Progression", - $this->renderTemplate( - "studyprogression.tpl", - [ - 'total_scans' => $totalScans, - 'recruitment' => $recruitment - ] - ), - "", - ), - new \LORIS\dashboard\WidgetDisplayProps( - "normal", - 10, - [ - 'scan-line-chart-panel' => 'View scans per site', - 'recruitment-line-chart-panel' => $recpersite - ] - ), - new \LORIS\dashboard\WidgetDependencies( - [ - $baseURL . "/css/c3.css" - ], - [ - $baseURL . '/js/d3.min.js', - $baseURL . '/js/c3.min.js', - $baseURL . "/statistics/js/studyprogression.js" - ], - ) + new \LORIS\dashboard\WidgetDisplayProps( + 'normal', + 10, + [] + ), + new \LORIS\dashboard\WidgetDependencies( + [ + $baseURL . '/css/c3.css', + $baseURL . '/statistics/css/recruitment.css' + ], + [ + $baseURL . '/statistics/js/WidgetIndex.js' + ], ) - ]; - } - return []; - } - - /** - * Gets the total count of candidates associated with a specific project - * - * @param \Database $DB The database connection to get the count from. - * - * @return int - */ - private function _getTotalRecruitment(\Database $DB): int - { - return $DB->pselectOneInt( - "SELECT COUNT(*) FROM candidate c - WHERE c.Active='Y' AND c.Entity_type='Human' - AND c.RegistrationCenterID <> 1", - [] - ) ?? 0; - } + ); + $widget->setTemplate('div.tpl'); + $widget->setTemplateVariables(['id' => 'statistics_widgets']); - /** - * Generates the template data for a progress bar. - * - * @param string $ID The name of the progress bar being - * created. - * @param string $title The title to add to the template - * variables. - * @param int $recruitmentTarget The target for this recruitment type. - * @param int $totalRecruitment The total number of candidates of all - * types. - * @param \Database $db The database connection to get data from. - * - * @return array Smarty template data - */ - private function _createProjectProgressBar( - $ID, - $title, - $recruitmentTarget, - $totalRecruitment, - \Database $db - ): array { - $rv = [ - 'total_recruitment' => $totalRecruitment, - 'title' => $title, - ]; - if (empty($recruitmentTarget)) { - return $rv; - } - - $rv['recruitment_target'] = $recruitmentTarget; - if ($ID == 'overall') { - $totalFemales = $this->_getTotalSex($db, "Female"); - } else { - $totalFemales = $this->getTotalSexByProject($db, "Female", intval($ID)); - } - $rv['female_total'] = $totalFemales; - $rv['female_percent'] - = round($totalFemales / $recruitmentTarget * 100); - if ($ID == 'overall') { - $totalMales = $this->_getTotalSex($db, "Male"); - } else { - $totalMales = $this->getTotalSexByProject($db, "Male", intval($ID)); + return [$widget]; } - $rv['male_total'] = $totalMales; - $rv['male_percent'] - = round($totalMales / $recruitmentTarget * 100); - if ($totalRecruitment > $recruitmentTarget) { - $rv['surpassed_recruitment'] = "true"; - - $rv['female_full_percent'] - = round($totalFemales / $totalRecruitment * 100); - - $rv['male_full_percent'] - = round($totalMales / $totalRecruitment * 100); - } - return $rv; - } - - /** - * Gets the total count of candidates of a specific sex - * - * @param \Database $db A database connection to retrieve information - * from. - * @param string $sex Biological sex (male or female) - * - * @return int - */ - private function _getTotalSex(\Database $db, string $sex) : int - { - return $db->pselectOneInt( - "SELECT COUNT(c.CandID) - FROM candidate c - WHERE c.Sex=:sex AND c.Active='Y' AND c.Entity_type='Human' - AND c.RegistrationCenterID <> 1", - ['sex' => $sex] - ) ?? 0; - } - - /** - * Gets the total count of candidates of a specific sex, - * associated with a specific project - * - * @param \Database $DB A database connection to retrieve information - * from. - * @param string $sex A biological sex (male or female) - * @param int $projectID Project ID - * - * @return int - */ - function getTotalSexByProject(\Database $DB, string $sex, int $projectID) : int - { - return $DB->pselectOneInt( - "SELECT COUNT(c.CandID) - FROM candidate c - WHERE c.Sex=:sex AND c.Active='Y' AND c.RegistrationProjectID=:PID - AND c.Entity_type='Human' AND c.RegistrationCenterID <> 1", - [ - 'sex' => $sex, - 'PID' => "$projectID", - ] - ) ?? 0; - } - - - /** - * Gets the total count of candidates associated with a specific project. - * - * @param \Database $db A database connection to retrieve information - * from. - * @param int $projectID The Project ID to get recruitment for. - * - * @return int - */ - function getTotalRecruitmentByProject(\Database $db, int $projectID): int - { - return $db->pselectOneInt( - "SELECT COUNT(*) - FROM candidate c - WHERE c.Active='Y' - AND c.RegistrationProjectID=:PID - AND c.Entity_type='Human' - AND c.RegistrationCenterID <> 1", - ['PID' => "$projectID"] - ) ?? 0; + return []; } } diff --git a/modules/statistics/php/widgets.class.inc b/modules/statistics/php/widgets.class.inc new file mode 100644 index 00000000000..9afb6d07466 --- /dev/null +++ b/modules/statistics/php/widgets.class.inc @@ -0,0 +1,275 @@ + + * @license http://www.gnu.org/licenses/gpl-3.0.txt GPLv3 + * @link https://www.github.com/aces/Loris/ + */ +class Widgets extends \NDB_Page implements ETagCalculator +{ + /** + * A cache of the endpoint results, so that it doesn't need to be + * recalculated for the ETag and handler. + */ + private $_cache; + + /** + * The handle function will return a json response. + * + * @param ServerRequestInterface $request The incoming PSR7 request + * + * @return ResponseInterface The outgoing PSR7 response + */ + public function handle(ServerRequestInterface $request) : ResponseInterface + { + switch ($request->getMethod()) { + case 'GET': + $this->_cache = $this->_handleGET($request); + break; + default: + return new \LORIS\Http\Response\JSON\MethodNotAllowed( + $this->allowedMethods() + ); + } + return $this->_cache; + } + + /** + * Handles the retrieval of statistical data for the request. + * + * @param ServerRequestInterface $request The incoming PSR7 request. + * + * @return ResponseInterface The outgoing PSR7 response + */ + private function _handleGET(ServerRequestInterface $request) + { + if (isset($this->_cache)) { + return $this->_cache; + } + $factory = \NDB_Factory::singleton(); + $config = $factory->config(); + $db = $request->getAttribute('loris')->getDatabaseConnection(); + + $recruitmentTarget = $config->getSetting('recruitmentTarget'); + + $totalScans = $this->_getTotalRecruitment($db); + $recruitment = [ + 'overall' => $this->_createProjectProgressBar( + 'overall', + "Overall Recruitment", + $recruitmentTarget, + $totalScans, + $db, + ) + ]; + + $projects = \Utility::getProjectList(); + foreach (array_keys($projects) as $projectID) { + $projectInfo = $config->getProjectSettings($projectID); + if (is_null($projectInfo)) { + throw new \LorisException( + 'No project settings exist in the Database for ' . + 'project ID ' . intval($projectID) + ); + } + $recruitment[$projectID] = $this->_createProjectProgressBar( + $projectID, + $projectInfo['Name'], + $projectInfo['recruitmentTarget'], + $this->getTotalRecruitmentByProject($db, $projectID), + $db + ); + } + + $values = []; + // Used for the react widget recruitment.js + $values['recruitment'] = $recruitment; + // Used for the react widget studyprogression.js + $values['studyprogression'] = [ + 'total_scans' => $totalScans, + 'recruitment' => $recruitment + ]; + + $this->_cache = new \LORIS\Http\Response\JsonResponse($values); + + return $this->_cache; + } + + /** + * Gets the total count of candidates associated with a specific project + * + * @param \Database $DB The database connection to get the count from. + * + * @return int + */ + private function _getTotalRecruitment(\Database $DB): int + { + return $DB->pselectOneInt( + "SELECT COUNT(*) FROM candidate c + WHERE c.Active='Y' AND c.Entity_type='Human' + AND c.RegistrationCenterID <> 1", + [] + ) ?? 0; + } + + /** + * Generates the template data for a progress bar. + * + * @param string $ID The name of the progress bar being + * created. + * @param string $title The title to add to the template + * variables. + * @param int $recruitmentTarget The target for this recruitment type. + * @param int $totalRecruitment The total number of candidates of all + * types. + * @param \Database $db The database connection to get data from. + * + * @return array Smarty template data + */ + private function _createProjectProgressBar( + $ID, + $title, + $recruitmentTarget, + $totalRecruitment, + \Database $db + ): array { + $rv = [ + 'total_recruitment' => $totalRecruitment, + 'title' => $title, + ]; + if (empty($recruitmentTarget)) { + return $rv; + } + + $rv['recruitment_target'] = $recruitmentTarget; + if ($ID == 'overall') { + $totalFemales = $this->_getTotalSex($db, "Female"); + } else { + $totalFemales = $this->getTotalSexByProject($db, "Female", intval($ID)); + } + $rv['female_total'] = $totalFemales; + $rv['female_percent'] + = round($totalFemales / $recruitmentTarget * 100); + if ($ID == 'overall') { + $totalMales = $this->_getTotalSex($db, "Male"); + } else { + $totalMales = $this->getTotalSexByProject($db, "Male", intval($ID)); + } + $rv['male_total'] = $totalMales; + $rv['male_percent'] + = round($totalMales / $recruitmentTarget * 100); + if ($totalRecruitment > $recruitmentTarget) { + $rv['surpassed_recruitment'] = "true"; + + $rv['female_full_percent'] + = round($totalFemales / $totalRecruitment * 100); + + $rv['male_full_percent'] + = round($totalMales / $totalRecruitment * 100); + } + return $rv; + } + + /** + * Gets the total count of candidates of a specific sex + * + * @param \Database $db A database connection to retrieve information + * from. + * @param string $sex Biological sex (male or female) + * + * @return int + */ + private function _getTotalSex(\Database $db, string $sex) : int + { + return $db->pselectOneInt( + "SELECT COUNT(c.CandID) + FROM candidate c + WHERE c.Sex=:sex AND c.Active='Y' AND c.Entity_type='Human' + AND c.RegistrationCenterID <> 1", + ['sex' => $sex] + ) ?? 0; + } + + /** + * Gets the total count of candidates of a specific sex, + * associated with a specific project + * + * @param \Database $DB A database connection to retrieve information + * from. + * @param string $sex A biological sex (male or female) + * @param int $projectID Project ID + * + * @return int + */ + function getTotalSexByProject(\Database $DB, string $sex, int $projectID) : int + { + return $DB->pselectOneInt( + "SELECT COUNT(c.CandID) + FROM candidate c + WHERE c.Sex=:sex AND c.Active='Y' AND c.RegistrationProjectID=:PID + AND c.Entity_type='Human' AND c.RegistrationCenterID <> 1", + [ + 'sex' => $sex, + 'PID' => "$projectID", + ] + ) ?? 0; + } + + /** + * Gets the total count of candidates associated with a specific project. + * + * @param \Database $db A database connection to retrieve information + * from. + * @param int $projectID The Project ID to get recruitment for. + * + * @return int + */ + function getTotalRecruitmentByProject(\Database $db, int $projectID): int + { + return $db->pselectOneInt( + "SELECT COUNT(*) + FROM candidate c + WHERE c.Active='Y' + AND c.RegistrationProjectID=:PID + AND c.Entity_type='Human' + AND c.RegistrationCenterID <> 1", + ['PID' => "$projectID"] + ) ?? 0; + } + + /** + * The ETagCalculator provides the ability to calculate + * an ETag for an incoming HTTP request. + * + * @param ServerRequestInterface $request The incoming PSR7 request. + * + * @return string The value to use for the ETag header. + */ + public function ETag(ServerRequestInterface $request): string + { + return md5(json_encode($this->_handleGET($request)->getBody())); + } + + /** + * Return an array of valid HTTP methods for this endpoint. + * + * @return string[] Valid versions + */ + protected function allowedMethods(): array + { + return ['GET']; + } +} diff --git a/modules/statistics/templates/progress_bar.tpl b/modules/statistics/templates/progress_bar.tpl deleted file mode 100644 index f5a822a7ac5..00000000000 --- a/modules/statistics/templates/progress_bar.tpl +++ /dev/null @@ -1,43 +0,0 @@ - {if $project['recruitment_target']|default neq ""} -
{$project['title']}
- {if $project['surpassed_recruitment']|default eq "true"} -

The recruitment target ({$project['recruitment_target']}) has been passed.

-
-
-

- {$project['female_total']} -
- Females -

-
-
-

- {$project['male_total']} -
- Males -

-
-

Target: {$project['recruitment_target']}

-
- {else} -
-
-

- {$project['female_total']} -
- Females -

-
-
-

- {$project['male_total']} -
- Males -

-
-

Target: {$project['recruitment_target']}

-
- {/if} -{else} - Please add a recruitment target for {$project['title']}. -{/if} diff --git a/modules/statistics/templates/recruitment.tpl b/modules/statistics/templates/recruitment.tpl deleted file mode 100644 index 2ef76e3ce05..00000000000 --- a/modules/statistics/templates/recruitment.tpl +++ /dev/null @@ -1,28 +0,0 @@ -
- {include file='progress_bar.tpl' project=$recruitment["overall"]} -
- - diff --git a/modules/statistics/templates/studyprogression.tpl b/modules/statistics/templates/studyprogression.tpl deleted file mode 100644 index 5cde6d2a56f..00000000000 --- a/modules/statistics/templates/studyprogression.tpl +++ /dev/null @@ -1,23 +0,0 @@ -
-
Scan sessions per site
- {if $total_scans neq 0} -
-
-
-
- {else} -

There have been no scans yet.

- {/if} -
- -Note that the Recruitment and Study Progression charts include data from ineligible, excluded, and consent withdrawn candidates. diff --git a/package-lock.json b/package-lock.json index 1d27faf2cea..5ace5ba5201 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,8 @@ "@babel/eslint-parser": "^7.19.1", "@babel/preset-env": "^7.9.6", "@babel/preset-react": "^7.6.3", + "@types/c3": "^0.7.8", + "@types/d3": "^7.4.0", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", "@types/react-redux": "7.1.16", @@ -41,9 +43,9 @@ "@typescript-eslint/parser": "^5.45.0", "alex": ">=11.0.0", "babel-loader": "^8.0.5", - "c3": "^0.7.15", + "c3": "^0.7.20", "css-loader": "^3.4.2", - "d3": "^5.15.0", + "d3": "^5.16.0", "eslint": "^7.17.0", "eslint-config-google": "0.9.1", "eslint-plugin-jsdoc": "^39.6.4", @@ -2187,6 +2189,237 @@ "@types/estree": "*" } }, + "node_modules/@types/c3": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/c3/-/c3-0.7.8.tgz", + "integrity": "sha512-qUhbhHIa7SzpDZVHTUx51XUKPzkG3xLHKZGhwvfIs5Fy3NSc8qtH8I1u6N3Dp44Ih54qyUMw6xTIiDuOUBanxA==", + "dev": true, + "dependencies": { + "@types/d3": "^4" + } + }, + "node_modules/@types/c3/node_modules/@types/d3": { + "version": "4.13.12", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-4.13.12.tgz", + "integrity": "sha512-/bbFtkOBc04gGGN8N9rMG5ps3T0eIj5I8bnYe9iIyeM5qoOrydPCbFYlEPUnj2h9ibc2i+QZfDam9jY5XTrTxQ==", + "dev": true, + "dependencies": { + "@types/d3-array": "^1", + "@types/d3-axis": "^1", + "@types/d3-brush": "^1", + "@types/d3-chord": "^1", + "@types/d3-collection": "*", + "@types/d3-color": "^1", + "@types/d3-dispatch": "^1", + "@types/d3-drag": "^1", + "@types/d3-dsv": "^1", + "@types/d3-ease": "^1", + "@types/d3-force": "^1", + "@types/d3-format": "^1", + "@types/d3-geo": "^1", + "@types/d3-hierarchy": "^1", + "@types/d3-interpolate": "^1", + "@types/d3-path": "^1", + "@types/d3-polygon": "^1", + "@types/d3-quadtree": "^1", + "@types/d3-queue": "*", + "@types/d3-random": "^1", + "@types/d3-request": "*", + "@types/d3-scale": "^1", + "@types/d3-selection": "^1", + "@types/d3-shape": "^1", + "@types/d3-time": "^1", + "@types/d3-time-format": "^2", + "@types/d3-timer": "^1", + "@types/d3-transition": "^1", + "@types/d3-voronoi": "*", + "@types/d3-zoom": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-array": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.9.tgz", + "integrity": "sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-axis": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.16.tgz", + "integrity": "sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-chord": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.11.tgz", + "integrity": "sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-color": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", + "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-dispatch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz", + "integrity": "sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-dsv": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.1.tgz", + "integrity": "sha512-LLmJmjiqp/fTNEdij5bIwUJ6P6TVNk5hKM9/uk5RPO2YNgEu9XvKO0dJ7Iqd3psEdmZN1m7gB1bOsjr4HmO2BA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-ease": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.11.tgz", + "integrity": "sha512-wUigPL0kleGZ9u3RhzBP07lxxkMcUjL5IODP42mN/05UNL+JJCDnpEPpFbJiPvLcTeRKGIRpBBJyP/1BNwYsVA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-force": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.2.4.tgz", + "integrity": "sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-geo": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.12.3.tgz", + "integrity": "sha512-yZbPb7/5DyL/pXkeOmZ7L5ySpuGr4H48t1cuALjnJy5sXQqmSSAYBiwa6Ya/XpWKX2rJqGDDubmh3nOaopOpeA==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-interpolate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", + "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "dev": true, + "dependencies": { + "@types/d3-color": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-polygon": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.8.tgz", + "integrity": "sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-quadtree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz", + "integrity": "sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-random": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.3.tgz", + "integrity": "sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-scale": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-1.0.17.tgz", + "integrity": "sha512-baIP5/gw+PS8Axs1lfZCeIjcOXen/jxQmgFEjbYThwaj2drvivOIrJMh2Ig4MeenrogCH6zkhiOxCPRkvN1scA==", + "dev": true, + "dependencies": { + "@types/d3-time": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-selection": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.3.tgz", + "integrity": "sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", + "dev": true, + "dependencies": { + "@types/d3-path": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-time": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz", + "integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-time-format": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz", + "integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg==", + "dev": true + }, + "node_modules/@types/c3/node_modules/@types/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg==", + "dev": true, + "dependencies": { + "@types/d3-selection": "^1" + } + }, + "node_modules/@types/c3/node_modules/@types/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-3kHkL6sPiDdbfGhzlp5gIHyu3kULhtnHTTAl3UBZVtWB1PzcLL8vdmz5mTx7plLiUqOA2Y+yT2GKjt/TdA2p7Q==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "^1", + "@types/d3-selection": "^1" + } + }, "node_modules/@types/concat-stream": { "version": "2.0.0", "dev": true, @@ -2195,6 +2428,292 @@ "@types/node": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "node_modules/@types/d3-collection": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-collection/-/d3-collection-1.0.10.tgz", + "integrity": "sha512-54Fdv8u5JbuXymtmXm2SYzi1x/Svt+jfWBU5junkhrCewL92VjqtCBDn97coBRVwVFmYNnVTNDyV8gQyPYfm+A==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "node_modules/@types/d3-queue": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-queue/-/d3-queue-3.0.8.tgz", + "integrity": "sha512-1FWOiI/MYwS5Z1Sa9EvS1Xet3isiVIIX5ozD6iGnwHonGcqL+RcC1eThXN5VfDmAiYt9Me9EWNEv/9J9k9RIKQ==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "node_modules/@types/d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-4nRKDUBg3EBx8VowpMvM3NAVMiMMI1qFUOYv3OJsclGjHX6xjtu09nsWhRQ0fvSUla3MEjb5Ch4IeaYarMEi1w==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "^1" + } + }, + "node_modules/@types/d3-request/node_modules/@types/d3-dsv": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.1.tgz", + "integrity": "sha512-LLmJmjiqp/fTNEdij5bIwUJ6P6TVNk5hKM9/uk5RPO2YNgEu9XvKO0dJ7Iqd3psEdmZN1m7gB1bOsjr4HmO2BA==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", + "integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz", + "integrity": "sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-voronoi": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.9.tgz", + "integrity": "sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ==", + "dev": true + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.7", "dev": true, @@ -2231,6 +2750,12 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "node_modules/@types/hast": { "version": "2.3.4", "dev": true, @@ -13177,6 +13702,239 @@ "@types/estree": "*" } }, + "@types/c3": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@types/c3/-/c3-0.7.8.tgz", + "integrity": "sha512-qUhbhHIa7SzpDZVHTUx51XUKPzkG3xLHKZGhwvfIs5Fy3NSc8qtH8I1u6N3Dp44Ih54qyUMw6xTIiDuOUBanxA==", + "dev": true, + "requires": { + "@types/d3": "^4" + }, + "dependencies": { + "@types/d3": { + "version": "4.13.12", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-4.13.12.tgz", + "integrity": "sha512-/bbFtkOBc04gGGN8N9rMG5ps3T0eIj5I8bnYe9iIyeM5qoOrydPCbFYlEPUnj2h9ibc2i+QZfDam9jY5XTrTxQ==", + "dev": true, + "requires": { + "@types/d3-array": "^1", + "@types/d3-axis": "^1", + "@types/d3-brush": "^1", + "@types/d3-chord": "^1", + "@types/d3-collection": "*", + "@types/d3-color": "^1", + "@types/d3-dispatch": "^1", + "@types/d3-drag": "^1", + "@types/d3-dsv": "^1", + "@types/d3-ease": "^1", + "@types/d3-force": "^1", + "@types/d3-format": "^1", + "@types/d3-geo": "^1", + "@types/d3-hierarchy": "^1", + "@types/d3-interpolate": "^1", + "@types/d3-path": "^1", + "@types/d3-polygon": "^1", + "@types/d3-quadtree": "^1", + "@types/d3-queue": "*", + "@types/d3-random": "^1", + "@types/d3-request": "*", + "@types/d3-scale": "^1", + "@types/d3-selection": "^1", + "@types/d3-shape": "^1", + "@types/d3-time": "^1", + "@types/d3-time-format": "^2", + "@types/d3-timer": "^1", + "@types/d3-transition": "^1", + "@types/d3-voronoi": "*", + "@types/d3-zoom": "^1" + } + }, + "@types/d3-array": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.9.tgz", + "integrity": "sha512-E/7RgPr2ylT5dWG0CswMi9NpFcjIEDqLcUSBgNHe/EMahfqYaTx4zhcggG3khqoEB/leY4Vl6nTSbwLUPjXceA==", + "dev": true + }, + "@types/d3-axis": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-1.0.16.tgz", + "integrity": "sha512-p7085weOmo4W+DzlRRVC/7OI/jugaKbVa6WMQGCQscaMylcbuaVEGk7abJLNyGVFLeCBNrHTdDiqRGnzvL0nXQ==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-brush": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-1.1.5.tgz", + "integrity": "sha512-4zGkBafJf5zCsBtLtvDj/pNMo5X9+Ii/1hUz0GvQ+wEwelUBm2AbIDAzJnp2hLDFF307o0fhxmmocHclhXC+tw==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-chord": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-1.0.11.tgz", + "integrity": "sha512-0DdfJ//bxyW3G9Nefwq/LDgazSKNN8NU0lBT3Cza6uVuInC2awMNsAcv1oKyRFLn9z7kXClH5XjwpveZjuz2eg==", + "dev": true + }, + "@types/d3-color": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-1.4.2.tgz", + "integrity": "sha512-fYtiVLBYy7VQX+Kx7wU/uOIkGQn8aAEY8oWMoyja3N4dLd8Yf6XgSIR/4yWvMuveNOH5VShnqCgRqqh/UNanBA==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-1.0.9.tgz", + "integrity": "sha512-zJ44YgjqALmyps+II7b1mZLhrtfV/FOxw9owT87mrweGWcg+WK5oiJX2M3SYJ0XUAExBduarysfgbR11YxzojQ==", + "dev": true + }, + "@types/d3-drag": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-1.2.5.tgz", + "integrity": "sha512-7NeTnfolst1Js3Vs7myctBkmJWu6DMI3k597AaHUX98saHjHWJ6vouT83UrpE+xfbSceHV+8A0JgxuwgqgmqWw==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-dsv": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.1.tgz", + "integrity": "sha512-LLmJmjiqp/fTNEdij5bIwUJ6P6TVNk5hKM9/uk5RPO2YNgEu9XvKO0dJ7Iqd3psEdmZN1m7gB1bOsjr4HmO2BA==", + "dev": true + }, + "@types/d3-ease": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-1.0.11.tgz", + "integrity": "sha512-wUigPL0kleGZ9u3RhzBP07lxxkMcUjL5IODP42mN/05UNL+JJCDnpEPpFbJiPvLcTeRKGIRpBBJyP/1BNwYsVA==", + "dev": true + }, + "@types/d3-force": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-1.2.4.tgz", + "integrity": "sha512-fkorLTKvt6AQbFBQwn4aq7h9rJ4c7ZVcPMGB8X6eFFveAyMZcv7t7m6wgF4Eg93rkPgPORU7sAho1QSHNcZu6w==", + "dev": true + }, + "@types/d3-format": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.2.tgz", + "integrity": "sha512-WeGCHAs7PHdZYq6lwl/+jsl+Nfc1J2W1kNcMeIMYzQsT6mtBDBgtJ/rcdjZ0k0rVIvqEZqhhuD5TK/v3P2gFHQ==", + "dev": true + }, + "@types/d3-geo": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-1.12.3.tgz", + "integrity": "sha512-yZbPb7/5DyL/pXkeOmZ7L5ySpuGr4H48t1cuALjnJy5sXQqmSSAYBiwa6Ya/XpWKX2rJqGDDubmh3nOaopOpeA==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz", + "integrity": "sha512-AbStKxNyWiMDQPGDguG2Kuhlq1Sv539pZSxYbx4UZeYkutpPwXCcgyiRrlV4YH64nIOsKx7XVnOMy9O7rJsXkg==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-1.4.2.tgz", + "integrity": "sha512-ylycts6llFf8yAEs1tXzx2loxxzDZHseuhPokrqKprTQSTcD3JbJI1omZP1rphsELZO3Q+of3ff0ZS7+O6yVzg==", + "dev": true, + "requires": { + "@types/d3-color": "^1" + } + }, + "@types/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-NaIeSIBiFgSC6IGUBjZWcscUJEq7vpVu7KthHN8eieTV9d9MqkSOZLH4chq1PmcKy06PNe3axLeKmRIyxJ+PZQ==", + "dev": true + }, + "@types/d3-polygon": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-1.0.8.tgz", + "integrity": "sha512-1TOJPXCBJC9V3+K3tGbTqD/CsqLyv/YkTXAcwdsZzxqw5cvpdnCuDl42M4Dvi8XzMxZNCT9pL4ibrK2n4VmAcw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-1.0.9.tgz", + "integrity": "sha512-5E0OJJn2QVavITFEc1AQlI8gLcIoDZcTKOD3feKFckQVmFV4CXhqRFt83tYNVNIN4ZzRkjlAMavJa1ldMhf5rA==", + "dev": true + }, + "@types/d3-random": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-1.1.3.tgz", + "integrity": "sha512-XXR+ZbFCoOd4peXSMYJzwk0/elP37WWAzS/DG+90eilzVbUSsgKhBcWqylGWe+lA2ubgr7afWAOBaBxRgMUrBQ==", + "dev": true + }, + "@types/d3-scale": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-1.0.17.tgz", + "integrity": "sha512-baIP5/gw+PS8Axs1lfZCeIjcOXen/jxQmgFEjbYThwaj2drvivOIrJMh2Ig4MeenrogCH6zkhiOxCPRkvN1scA==", + "dev": true, + "requires": { + "@types/d3-time": "^1" + } + }, + "@types/d3-selection": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-1.4.3.tgz", + "integrity": "sha512-GjKQWVZO6Sa96HiKO6R93VBE8DUW+DDkFpIMf9vpY5S78qZTlRRSNUsHr/afDpF7TvLDV7VxrUFOWW7vdIlYkA==", + "dev": true + }, + "@types/d3-shape": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.8.tgz", + "integrity": "sha512-gqfnMz6Fd5H6GOLYixOZP/xlrMtJms9BaS+6oWxTKHNqPGZ93BkWWupQSCYm6YHqx6h9wjRupuJb90bun6ZaYg==", + "dev": true, + "requires": { + "@types/d3-path": "^1" + } + }, + "@types/d3-time": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.1.tgz", + "integrity": "sha512-ULX7LoqXTCYtM+tLYOaeAJK7IwCT+4Gxlm2MaH0ErKLi07R5lh8NHCAyWcDkCCmx1AfRcBEV6H9QE9R25uP7jw==", + "dev": true + }, + "@types/d3-time-format": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.1.tgz", + "integrity": "sha512-fck0Z9RGfIQn3GJIEKVrp15h9m6Vlg0d5XXeiE/6+CQiBmMDZxfR21XtjEPuDeg7gC3bBM0SdieA5XF3GW1wKA==", + "dev": true + }, + "@types/d3-timer": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-1.0.10.tgz", + "integrity": "sha512-ZnAbquVqy+4ZjdW0cY6URp+qF/AzTVNda2jYyOzpR2cPT35FTXl78s15Bomph9+ckOiI1TtkljnWkwbIGAb6rg==", + "dev": true + }, + "@types/d3-transition": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-1.3.2.tgz", + "integrity": "sha512-J+a3SuF/E7wXbOSN19p8ZieQSFIm5hU2Egqtndbc54LXaAEOpLfDx4sBu/PKAKzHOdgKK1wkMhINKqNh4aoZAg==", + "dev": true, + "requires": { + "@types/d3-selection": "^1" + } + }, + "@types/d3-zoom": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-1.8.3.tgz", + "integrity": "sha512-3kHkL6sPiDdbfGhzlp5gIHyu3kULhtnHTTAl3UBZVtWB1PzcLL8vdmz5mTx7plLiUqOA2Y+yT2GKjt/TdA2p7Q==", + "dev": true, + "requires": { + "@types/d3-interpolate": "^1", + "@types/d3-selection": "^1" + } + } + } + }, "@types/concat-stream": { "version": "2.0.0", "dev": true, @@ -13184,6 +13942,294 @@ "@types/node": "*" } }, + "@types/d3": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz", + "integrity": "sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "@types/d3-array": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", + "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==", + "dev": true + }, + "@types/d3-axis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz", + "integrity": "sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-brush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz", + "integrity": "sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==", + "dev": true + }, + "@types/d3-collection": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-collection/-/d3-collection-1.0.10.tgz", + "integrity": "sha512-54Fdv8u5JbuXymtmXm2SYzi1x/Svt+jfWBU5junkhrCewL92VjqtCBDn97coBRVwVFmYNnVTNDyV8gQyPYfm+A==", + "dev": true + }, + "@types/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==", + "dev": true + }, + "@types/d3-contour": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz", + "integrity": "sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==", + "dev": true, + "requires": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "@types/d3-delaunay": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz", + "integrity": "sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==", + "dev": true + }, + "@types/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==", + "dev": true + }, + "@types/d3-drag": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz", + "integrity": "sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-dsv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz", + "integrity": "sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==", + "dev": true + }, + "@types/d3-ease": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", + "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==", + "dev": true + }, + "@types/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==", + "dev": true, + "requires": { + "@types/d3-dsv": "*" + } + }, + "@types/d3-force": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz", + "integrity": "sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==", + "dev": true + }, + "@types/d3-format": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz", + "integrity": "sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==", + "dev": true + }, + "@types/d3-geo": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz", + "integrity": "sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==", + "dev": true, + "requires": { + "@types/geojson": "*" + } + }, + "@types/d3-hierarchy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz", + "integrity": "sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==", + "dev": true + }, + "@types/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", + "dev": true, + "requires": { + "@types/d3-color": "*" + } + }, + "@types/d3-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", + "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==", + "dev": true + }, + "@types/d3-polygon": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz", + "integrity": "sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==", + "dev": true + }, + "@types/d3-quadtree": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz", + "integrity": "sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==", + "dev": true + }, + "@types/d3-queue": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-queue/-/d3-queue-3.0.8.tgz", + "integrity": "sha512-1FWOiI/MYwS5Z1Sa9EvS1Xet3isiVIIX5ozD6iGnwHonGcqL+RcC1eThXN5VfDmAiYt9Me9EWNEv/9J9k9RIKQ==", + "dev": true + }, + "@types/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==", + "dev": true + }, + "@types/d3-request": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-request/-/d3-request-1.0.6.tgz", + "integrity": "sha512-4nRKDUBg3EBx8VowpMvM3NAVMiMMI1qFUOYv3OJsclGjHX6xjtu09nsWhRQ0fvSUla3MEjb5Ch4IeaYarMEi1w==", + "dev": true, + "requires": { + "@types/d3-dsv": "^1" + }, + "dependencies": { + "@types/d3-dsv": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-1.2.1.tgz", + "integrity": "sha512-LLmJmjiqp/fTNEdij5bIwUJ6P6TVNk5hKM9/uk5RPO2YNgEu9XvKO0dJ7Iqd3psEdmZN1m7gB1bOsjr4HmO2BA==", + "dev": true + } + } + }, + "@types/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", + "dev": true, + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-scale-chromatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz", + "integrity": "sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==", + "dev": true + }, + "@types/d3-selection": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz", + "integrity": "sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==", + "dev": true + }, + "@types/d3-shape": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", + "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", + "dev": true, + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", + "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==", + "dev": true + }, + "@types/d3-time-format": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz", + "integrity": "sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==", + "dev": true + }, + "@types/d3-timer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", + "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==", + "dev": true + }, + "@types/d3-transition": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz", + "integrity": "sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==", + "dev": true, + "requires": { + "@types/d3-selection": "*" + } + }, + "@types/d3-voronoi": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@types/d3-voronoi/-/d3-voronoi-1.1.9.tgz", + "integrity": "sha512-DExNQkaHd1F3dFPvGA/Aw2NGyjMln6E9QzsiqOcBgnE+VInYnFBHBBySbZQts6z6xD+5jTfKCP7M4OqMyVjdwQ==", + "dev": true + }, + "@types/d3-zoom": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz", + "integrity": "sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==", + "dev": true, + "requires": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "@types/debug": { "version": "4.1.7", "dev": true, @@ -13215,6 +14261,12 @@ "@types/estree": "*" } }, + "@types/geojson": { + "version": "7946.0.10", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", + "dev": true + }, "@types/hast": { "version": "2.3.4", "dev": true, diff --git a/package.json b/package.json index 67a12b90dce..ee1b42c2d9e 100644 --- a/package.json +++ b/package.json @@ -33,14 +33,16 @@ "@babel/preset-react": "^7.6.3", "@types/react": "^18.0.26", "@types/react-dom": "^18.0.9", + "@types/c3": "^0.7.8", + "@types/d3": "^7.4.0", "@types/react-redux": "7.1.16", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "alex": ">=11.0.0", "babel-loader": "^8.0.5", - "c3": "^0.7.15", + "c3": "^0.7.20", "css-loader": "^3.4.2", - "d3": "^5.15.0", + "d3": "^5.16.0", "eslint": "^7.17.0", "eslint-config-google": "0.9.1", "eslint-plugin-jsdoc": "^39.6.4", diff --git a/webpack.config.js b/webpack.config.js index 733a4d050b8..558451669ac 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -314,6 +314,7 @@ const lorisModules = { module_manager: ['modulemanager'], imaging_qc: ['imagingQCIndex'], server_processes_manager: ['server_processes_managerIndex'], + statistics: ['WidgetIndex'], instruments: ['CandidateInstrumentList'], candidate_profile: ['CandidateInfo'], api_docs: ['swagger-ui_custom'],