Added world map component.

This commit is contained in:
Mike Cao 2020-08-01 03:34:56 -07:00
parent e4e7f5b05c
commit a65f637df2
10 changed files with 545 additions and 79 deletions

View File

@ -1,7 +1,8 @@
{ {
"env": { "env": {
"browser": true, "browser": true,
"es2020": true "es2020": true,
"node": true
}, },
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "prettier/react"], "extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "prettier/react"],
"parserOptions": { "parserOptions": {

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react';
import { useSpring, animated } from 'react-spring'; import { useSpring, animated } from 'react-spring';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lib/web'; import { get } from 'lib/web';
import { percentFilter } from 'lib/filters';
import styles from './RankingsChart.module.css'; import styles from './RankingsChart.module.css';
export default function RankingsChart({ export default function RankingsChart({
@ -13,6 +14,7 @@ export default function RankingsChart({
heading, heading,
className, className,
dataFilter, dataFilter,
onDataLoad = () => {},
}) { }) {
const [data, setData] = useState(); const [data, setData] = useState();
@ -23,16 +25,17 @@ export default function RankingsChart({
return []; return [];
}, [data]); }, [data]);
const total = useMemo(() => rankings?.reduce((n, { y }) => n + y, 0) || 0, [rankings]);
async function loadData() { async function loadData() {
setData( const data = await get(`/api/website/${websiteId}/rankings`, {
await get(`/api/website/${websiteId}/rankings`, {
start_at: +startDate, start_at: +startDate,
end_at: +endDate, end_at: +endDate,
type, type,
}), });
);
const updated = percentFilter(data);
setData(updated);
onDataLoad(updated);
} }
useEffect(() => { useEffect(() => {
@ -51,26 +54,25 @@ export default function RankingsChart({
<div className={styles.title}>{title}</div> <div className={styles.title}>{title}</div>
<div className={styles.heading}>{heading}</div> <div className={styles.heading}>{heading}</div>
</div> </div>
{rankings.map(({ x, y }) => ( {rankings.map(({ x, y, z }) => (
<Row key={x} label={x} value={y} total={total} /> <Row key={x} label={x} value={y} percent={z} />
))} ))}
</div> </div>
); );
} }
const Row = ({ label, value, total }) => { const Row = ({ label, value, percent }) => {
const pct = total ? (value / total) * 100 : 0; const props = useSpring({ width: percent, from: { width: 0 } });
const props = useSpring({ width: pct, from: { width: 0 } });
const valueProps = useSpring({ y: value, from: { y: 0 } }); const valueProps = useSpring({ y: value, from: { y: 0 } });
return ( return (
<div className={styles.row}> <div className={styles.row}>
<div className={styles.label}>{label}</div> <div className={styles.label}>{label}</div>
<animated.div className={styles.value}> <animated.div className={styles.value}>
{valueProps.y.interpolate(y => y.toFixed(0))} {valueProps.y.interpolate(n => n.toFixed(0))}
</animated.div> </animated.div>
<div className={styles.percent}> <div className={styles.percent}>
<animated.div>{props.width.interpolate(y => `${y.toFixed(0)}%`)}</animated.div> <animated.div>{props.width.interpolate(n => `${n.toFixed(0)}%`)}</animated.div>
<animated.div className={styles.bar} style={{ ...props }} /> <animated.div className={styles.bar} style={{ ...props }} />
</div> </div>
</div> </div>

View File

@ -1,74 +1,19 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { get } from 'lib/web';
import WebsiteChart from './WebsiteChart'; import WebsiteChart from './WebsiteChart';
import RankingsChart from './RankingsChart'; import RankingsChart from './RankingsChart';
import { getDateRange } from '../lib/date'; import WorldMap from './WorldMap';
import { getDateRange } from 'lib/date';
import { get } from 'lib/web';
import { browserFilter, urlFilter, refFilter, deviceFilter, countryFilter } from 'lib/filters';
import styles from './WebsiteDetails.module.css'; import styles from './WebsiteDetails.module.css';
const pageviewClasses = 'col-md-12 col-lg-6'; const pageviewClasses = 'col-md-12 col-lg-6';
const sessionClasses = 'col-12 col-lg-4'; const sessionClasses = 'col-12 col-lg-4';
const urlFilter = data => data.filter(({ x }) => x !== '' && !x.startsWith('#'));
const refFilter = data =>
data.filter(({ x }) => x !== '' && !x.startsWith('/') && !x.startsWith('#'));
const deviceFilter = data => {
const devices = data.reduce(
(obj, { x, y }) => {
const [width] = x.split('x');
if (width >= 1920) {
obj.Desktop += +y;
} else if (width >= 1024) {
obj.Laptop += +y;
} else if (width >= 767) {
obj.Tablet += +y;
} else {
obj.Mobile += +y;
}
return obj;
},
{ Desktop: 0, Laptop: 0, Tablet: 0, Mobile: 0 },
);
return Object.keys(devices).map(key => ({ x: key, y: devices[key] }));
};
const browsers = {
aol: 'AOL',
edge: 'Edge',
'edge-ios': 'Edge (iOS)',
yandexbrowser: 'Yandex',
kakaotalk: 'KKaoTalk',
samsung: 'Samsung',
silk: 'Silk',
miui: 'MIUI',
beaker: 'Beaker',
'edge-chromium': 'Edge (Chromium)',
chrome: 'Chrome',
'chromium-webview': 'Chrome (webview)',
phantomjs: 'PhantomJS',
crios: 'Chrome (iOS)',
firefox: 'Firefox',
fxios: 'Firefox (iOS)',
'opera-mini': 'Opera Mini',
opera: 'Opera',
ie: 'IE',
bb10: 'BlackBerry 10',
android: 'Android',
ios: 'iOS',
safari: 'Safari',
facebook: 'Facebook',
instagram: 'Instagram',
'ios-webview': 'iOS (webview)',
searchbot: 'Searchbot',
};
const browserFilter = data => data.map(({ x, y }) => ({ x: browsers[x] || x, y }));
export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) { export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' }) {
const [data, setData] = useState(); const [data, setData] = useState();
const [countryData, setCountryData] = useState();
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange)); const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
const { startDate, endDate } = dateRange; const { startDate, endDate } = dateRange;
@ -151,6 +96,20 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
dataFilter={deviceFilter} dataFilter={deviceFilter}
/> />
</div> </div>
<div className="row">
<WorldMap data={countryData} className="col-12 col-md-12 col-lg-8" />
<RankingsChart
title="Countries"
type="country"
heading="Visitors"
className="col-12 col-md-12 col-lg-4"
websiteId={data.website_id}
startDate={startDate}
endDate={endDate}
dataFilter={countryFilter}
onDataLoad={data => setCountryData(data)}
/>
</div>
</> </>
); );
} }

71
components/WorldMap.js Normal file
View File

@ -0,0 +1,71 @@
import React, { useState } from 'react';
import ReactTooltip from 'react-tooltip';
import classNames from 'classnames';
import tinycolor from 'tinycolor2';
import { ComposableMap, Geographies, Geography, ZoomableGroup } from 'react-simple-maps';
import styles from './WorldMap.module.css';
const geoUrl = '/world-110m.json';
export default function WorldMap({
data,
className,
baseColor = '#e9f3fd',
fillColor = '#f5f5f5',
strokeColor = '#2680eb',
hoverColor = '#2680eb',
}) {
const [tooltip, setTooltip] = useState();
function getFillColor(code) {
if (code === 'AQ') return '#ffffff';
const country = data?.find(({ x }) => x === code);
return country ? tinycolor(baseColor).darken(country.z) : fillColor;
}
function getStrokeColor(code) {
return code === 'AQ' ? '#ffffff' : strokeColor;
}
function getHoverColor(code) {
return code === 'AQ' ? '#ffffff' : hoverColor;
}
function handleHover({ ISO_A2: code, NAME: name }) {
const country = data?.find(({ x }) => x === code);
setTooltip(`${name}: ${country?.y || 0} visitors`);
}
return (
<div className={classNames(styles.container, className)}>
<ComposableMap data-tip="" projection="geoMercator">
<ZoomableGroup zoom={0.8} minZoom={0.7} center={[0, 40]}>
<Geographies geography={geoUrl}>
{({ geographies }) => {
return geographies.map(geo => {
const code = geo.properties.ISO_A2;
return (
<Geography
key={geo.rsmKey}
geography={geo}
fill={getFillColor(code)}
stroke={getStrokeColor(code)}
style={{
default: { outline: 'none' },
hover: { outline: 'none', fill: getHoverColor(code) },
pressed: { outline: 'none' },
}}
onMouseOver={() => handleHover(geo.properties)}
onMouseOut={() => setTooltip(null)}
/>
);
});
}}
</Geographies>
</ZoomableGroup>
</ComposableMap>
<ReactTooltip>{tooltip}</ReactTooltip>
</div>
);
}

View File

@ -0,0 +1,5 @@
.container {
overflow: hidden;
position: relative;
border-top: 1px solid #e1e1e1;
}

314
lib/filters.js Normal file
View File

@ -0,0 +1,314 @@
const browsers = {
aol: 'AOL',
edge: 'Edge',
'edge-ios': 'Edge (iOS)',
yandexbrowser: 'Yandex',
kakaotalk: 'KKaoTalk',
samsung: 'Samsung',
silk: 'Silk',
miui: 'MIUI',
beaker: 'Beaker',
'edge-chromium': 'Edge (Chromium)',
chrome: 'Chrome',
'chromium-webview': 'Chrome (webview)',
phantomjs: 'PhantomJS',
crios: 'Chrome (iOS)',
firefox: 'Firefox',
fxios: 'Firefox (iOS)',
'opera-mini': 'Opera Mini',
opera: 'Opera',
ie: 'IE',
bb10: 'BlackBerry 10',
android: 'Android',
ios: 'iOS',
safari: 'Safari',
facebook: 'Facebook',
instagram: 'Instagram',
'ios-webview': 'iOS (webview)',
searchbot: 'Searchbot',
};
const isoCountries = {
AF: 'Afghanistan',
AX: 'Aland Islands',
AL: 'Albania',
DZ: 'Algeria',
AS: 'American Samoa',
AD: 'Andorra',
AO: 'Angola',
AI: 'Anguilla',
AQ: 'Antarctica',
AG: 'Antigua And Barbuda',
AR: 'Argentina',
AM: 'Armenia',
AW: 'Aruba',
AU: 'Australia',
AT: 'Austria',
AZ: 'Azerbaijan',
BS: 'Bahamas',
BH: 'Bahrain',
BD: 'Bangladesh',
BB: 'Barbados',
BY: 'Belarus',
BE: 'Belgium',
BZ: 'Belize',
BJ: 'Benin',
BM: 'Bermuda',
BT: 'Bhutan',
BO: 'Bolivia',
BA: 'Bosnia And Herzegovina',
BW: 'Botswana',
BV: 'Bouvet Island',
BR: 'Brazil',
IO: 'British Indian Ocean Territory',
BN: 'Brunei Darussalam',
BG: 'Bulgaria',
BF: 'Burkina Faso',
BI: 'Burundi',
KH: 'Cambodia',
CM: 'Cameroon',
CA: 'Canada',
CV: 'Cape Verde',
KY: 'Cayman Islands',
CF: 'Central African Republic',
TD: 'Chad',
CL: 'Chile',
CN: 'China',
CX: 'Christmas Island',
CC: 'Cocos (Keeling) Islands',
CO: 'Colombia',
KM: 'Comoros',
CG: 'Congo',
CD: 'Congo, Democratic Republic',
CK: 'Cook Islands',
CR: 'Costa Rica',
CI: "Cote D'Ivoire",
HR: 'Croatia',
CU: 'Cuba',
CY: 'Cyprus',
CZ: 'Czech Republic',
DK: 'Denmark',
DJ: 'Djibouti',
DM: 'Dominica',
DO: 'Dominican Republic',
EC: 'Ecuador',
EG: 'Egypt',
SV: 'El Salvador',
GQ: 'Equatorial Guinea',
ER: 'Eritrea',
EE: 'Estonia',
ET: 'Ethiopia',
FK: 'Falkland Islands (Malvinas)',
FO: 'Faroe Islands',
FJ: 'Fiji',
FI: 'Finland',
FR: 'France',
GF: 'French Guiana',
PF: 'French Polynesia',
TF: 'French Southern Territories',
GA: 'Gabon',
GM: 'Gambia',
GE: 'Georgia',
DE: 'Germany',
GH: 'Ghana',
GI: 'Gibraltar',
GR: 'Greece',
GL: 'Greenland',
GD: 'Grenada',
GP: 'Guadeloupe',
GU: 'Guam',
GT: 'Guatemala',
GG: 'Guernsey',
GN: 'Guinea',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HT: 'Haiti',
HM: 'Heard Island & Mcdonald Islands',
VA: 'Holy See (Vatican City State)',
HN: 'Honduras',
HK: 'Hong Kong',
HU: 'Hungary',
IS: 'Iceland',
IN: 'India',
ID: 'Indonesia',
IR: 'Iran, Islamic Republic Of',
IQ: 'Iraq',
IE: 'Ireland',
IM: 'Isle Of Man',
IL: 'Israel',
IT: 'Italy',
JM: 'Jamaica',
JP: 'Japan',
JE: 'Jersey',
JO: 'Jordan',
KZ: 'Kazakhstan',
KE: 'Kenya',
KI: 'Kiribati',
KR: 'Korea',
KW: 'Kuwait',
KG: 'Kyrgyzstan',
LA: "Lao People's Democratic Republic",
LV: 'Latvia',
LB: 'Lebanon',
LS: 'Lesotho',
LR: 'Liberia',
LY: 'Libyan Arab Jamahiriya',
LI: 'Liechtenstein',
LT: 'Lithuania',
LU: 'Luxembourg',
MO: 'Macao',
MK: 'Macedonia',
MG: 'Madagascar',
MW: 'Malawi',
MY: 'Malaysia',
MV: 'Maldives',
ML: 'Mali',
MT: 'Malta',
MH: 'Marshall Islands',
MQ: 'Martinique',
MR: 'Mauritania',
MU: 'Mauritius',
YT: 'Mayotte',
MX: 'Mexico',
FM: 'Micronesia, Federated States Of',
MD: 'Moldova',
MC: 'Monaco',
MN: 'Mongolia',
ME: 'Montenegro',
MS: 'Montserrat',
MA: 'Morocco',
MZ: 'Mozambique',
MM: 'Myanmar',
NA: 'Namibia',
NR: 'Nauru',
NP: 'Nepal',
NL: 'Netherlands',
AN: 'Netherlands Antilles',
NC: 'New Caledonia',
NZ: 'New Zealand',
NI: 'Nicaragua',
NE: 'Niger',
NG: 'Nigeria',
NU: 'Niue',
NF: 'Norfolk Island',
MP: 'Northern Mariana Islands',
NO: 'Norway',
OM: 'Oman',
PK: 'Pakistan',
PW: 'Palau',
PS: 'Palestinian Territory, Occupied',
PA: 'Panama',
PG: 'Papua New Guinea',
PY: 'Paraguay',
PE: 'Peru',
PH: 'Philippines',
PN: 'Pitcairn',
PL: 'Poland',
PT: 'Portugal',
PR: 'Puerto Rico',
QA: 'Qatar',
RE: 'Reunion',
RO: 'Romania',
RU: 'Russian Federation',
RW: 'Rwanda',
BL: 'Saint Barthelemy',
SH: 'Saint Helena',
KN: 'Saint Kitts And Nevis',
LC: 'Saint Lucia',
MF: 'Saint Martin',
PM: 'Saint Pierre And Miquelon',
VC: 'Saint Vincent And Grenadines',
WS: 'Samoa',
SM: 'San Marino',
ST: 'Sao Tome And Principe',
SA: 'Saudi Arabia',
SN: 'Senegal',
RS: 'Serbia',
SC: 'Seychelles',
SL: 'Sierra Leone',
SG: 'Singapore',
SK: 'Slovakia',
SI: 'Slovenia',
SB: 'Solomon Islands',
SO: 'Somalia',
ZA: 'South Africa',
GS: 'South Georgia And Sandwich Isl.',
ES: 'Spain',
LK: 'Sri Lanka',
SD: 'Sudan',
SR: 'Suriname',
SJ: 'Svalbard And Jan Mayen',
SZ: 'Swaziland',
SE: 'Sweden',
CH: 'Switzerland',
SY: 'Syrian Arab Republic',
TW: 'Taiwan',
TJ: 'Tajikistan',
TZ: 'Tanzania',
TH: 'Thailand',
TL: 'Timor-Leste',
TG: 'Togo',
TK: 'Tokelau',
TO: 'Tonga',
TT: 'Trinidad And Tobago',
TN: 'Tunisia',
TR: 'Turkey',
TM: 'Turkmenistan',
TC: 'Turks And Caicos Islands',
TV: 'Tuvalu',
UG: 'Uganda',
UA: 'Ukraine',
AE: 'United Arab Emirates',
GB: 'United Kingdom',
US: 'United States',
UM: 'United States Outlying Islands',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VU: 'Vanuatu',
VE: 'Venezuela',
VN: 'Viet Nam',
VG: 'Virgin Islands, British',
VI: 'Virgin Islands, U.S.',
WF: 'Wallis And Futuna',
EH: 'Western Sahara',
YE: 'Yemen',
ZM: 'Zambia',
ZW: 'Zimbabwe',
};
export const browserFilter = data =>
data.map(({ x, ...props }) => ({ x: browsers[x] || x, ...props }));
export const urlFilter = data => data.filter(({ x }) => x !== '' && !x.startsWith('#'));
export const refFilter = data =>
data.filter(({ x }) => x !== '' && !x.startsWith('/') && !x.startsWith('#'));
export const deviceFilter = data => {
const devices = data.reduce(
(obj, { x, y }) => {
const [width] = x.split('x');
if (width >= 1920) {
obj.Desktop += +y;
} else if (width >= 1024) {
obj.Laptop += +y;
} else if (width >= 767) {
obj.Tablet += +y;
} else {
obj.Mobile += +y;
}
return obj;
},
{ Desktop: 0, Laptop: 0, Tablet: 0, Mobile: 0 },
);
return percentFilter(Object.keys(devices).map(key => ({ x: key, y: devices[key] })));
};
export const countryFilter = data =>
data.map(({ x, ...props }) => ({ x: isoCountries[x] || x, ...props }));
export const percentFilter = data => {
const total = data.reduce((n, { y }) => n + y, 0);
return data.map(({ x, y }) => ({ x, y, z: total ? (y / total) * 100 : 0 }));
};

View File

@ -60,8 +60,11 @@
"promise-polyfill": "^8.1.3", "promise-polyfill": "^8.1.3",
"react": "16.13.1", "react": "16.13.1",
"react-dom": "16.13.1", "react-dom": "16.13.1",
"react-simple-maps": "^2.1.2",
"react-spring": "^8.0.27", "react-spring": "^8.0.27",
"react-tooltip": "^4.2.7",
"request-ip": "^2.1.3", "request-ip": "^2.1.3",
"tinycolor2": "^1.4.1",
"unfetch": "^4.1.0", "unfetch": "^4.1.0",
"uuid": "^8.3.0", "uuid": "^8.3.0",
"yargs": "^15.4.1" "yargs": "^15.4.1"

View File

@ -1,7 +1,7 @@
import { getRankings } from 'lib/db'; import { getRankings } from 'lib/db';
import { useAuth } from 'lib/middleware'; import { useAuth } from 'lib/middleware';
const sessionColumns = ['browser', 'os', 'screen']; const sessionColumns = ['browser', 'os', 'screen', 'country'];
const pageviewColumns = ['url', 'referrer']; const pageviewColumns = ['url', 'referrer'];
export default async (req, res) => { export default async (req, res) => {

1
public/world-110m.json Normal file

File diff suppressed because one or more lines are too long

112
yarn.lock
View File

@ -2645,7 +2645,7 @@ colorette@^1.2.0:
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
commander@^2.20.0: commander@2, commander@^2.20.0:
version "2.20.3" version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -3085,6 +3085,81 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
d3-array@1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
d3-color@1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.4.1.tgz#c52002bf8846ada4424d55d97982fef26eb3bc8a"
integrity sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==
d3-dispatch@1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/d3-dispatch/-/d3-dispatch-1.0.6.tgz#00d37bcee4dd8cd97729dd893a0ac29caaba5d58"
integrity sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==
d3-drag@1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/d3-drag/-/d3-drag-1.2.5.tgz#2537f451acd39d31406677b7dc77c82f7d988f70"
integrity sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==
dependencies:
d3-dispatch "1"
d3-selection "1"
d3-ease@1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-1.0.6.tgz#ebdb6da22dfac0a22222f2d4da06f66c416a0ec0"
integrity sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==
d3-geo@^1.11.9:
version "1.12.1"
resolved "https://registry.yarnpkg.com/d3-geo/-/d3-geo-1.12.1.tgz#7fc2ab7414b72e59fbcbd603e80d9adc029b035f"
integrity sha512-XG4d1c/UJSEX9NfU02KwBL6BYPj8YKHxgBEw5om2ZnTRSbIcego6dhHwcxuSR3clxh0EpE38os1DVPOmnYtTPg==
dependencies:
d3-array "1"
d3-interpolate@1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.4.0.tgz#526e79e2d80daa383f9e0c1c1c7dcc0f0583e987"
integrity sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==
dependencies:
d3-color "1"
d3-selection@1, d3-selection@^1.1.0, d3-selection@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/d3-selection/-/d3-selection-1.4.2.tgz#dcaa49522c0dbf32d6c1858afc26b6094555bc5c"
integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==
d3-timer@1:
version "1.0.10"
resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5"
integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==
d3-transition@1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/d3-transition/-/d3-transition-1.3.2.tgz#a98ef2151be8d8600543434c1ca80140ae23b398"
integrity sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==
dependencies:
d3-color "1"
d3-dispatch "1"
d3-ease "1"
d3-interpolate "1"
d3-selection "^1.1.0"
d3-timer "1"
d3-zoom@^1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/d3-zoom/-/d3-zoom-1.8.3.tgz#b6a3dbe738c7763121cd05b8a7795ffe17f4fc0a"
integrity sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==
dependencies:
d3-dispatch "1"
d3-drag "1"
d3-interpolate "1"
d3-selection "1"
d3-transition "1"
d@1, d@^1.0.1: d@1, d@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
@ -7263,6 +7338,16 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
react-simple-maps@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/react-simple-maps/-/react-simple-maps-2.1.2.tgz#4c6531733b9eeb0003c10ddcd822a9414a86acab"
integrity sha512-CuwuOomMmf/3zMtMqG9w8IKxpPUDhXHuF1p/8/8G6EMiRdYUJDysmDFGUIxD30CfkR4+9ItE0NV1pI/fZC/1cw==
dependencies:
d3-geo "^1.11.9"
d3-selection "^1.4.1"
d3-zoom "^1.8.3"
topojson-client "^3.0.0"
react-spring@^8.0.27: react-spring@^8.0.27:
version "8.0.27" version "8.0.27"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a" resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.27.tgz#97d4dee677f41e0b2adcb696f3839680a3aa356a"
@ -7271,6 +7356,14 @@ react-spring@^8.0.27:
"@babel/runtime" "^7.3.1" "@babel/runtime" "^7.3.1"
prop-types "^15.5.8" prop-types "^15.5.8"
react-tooltip@^4.2.7:
version "4.2.7"
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.7.tgz#245b26aa23bc8ca877f9086ad169207d77281894"
integrity sha512-z5T3gNplT76rkT7ZImfx/uXfBtS+x7+WK2H8MVZ5skSwETDDx0hs0+P0jwL5gYDaBvsZ7LYiCBzAOd3tN85CMA==
dependencies:
prop-types "^15.7.2"
uuid "^7.0.3"
react@16.13.1: react@16.13.1:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
@ -8596,6 +8689,11 @@ tiny-warning@^1.0.2:
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
tinycolor2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.1.tgz#f4fad333447bc0b07d4dc8e9209d8f39a8ac77e8"
integrity sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=
tmp@^0.0.33: tmp@^0.0.33:
version "0.0.33" version "0.0.33"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
@ -8645,6 +8743,13 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2" regex-not "^1.0.2"
safe-regex "^1.1.0" safe-regex "^1.1.0"
topojson-client@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/topojson-client/-/topojson-client-3.1.0.tgz#22e8b1ed08a2b922feeb4af6f53b6ef09a467b99"
integrity sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==
dependencies:
commander "2"
tr46@^1.0.1: tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -8969,6 +9074,11 @@ util@^0.11.0:
dependencies: dependencies:
inherits "2.0.3" inherits "2.0.3"
uuid@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
uuid@^8.3.0: uuid@^8.3.0:
version "8.3.0" version "8.3.0"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"