mirror of
https://github.com/kremalicious/umami.git
synced 2024-12-26 23:07:40 +01:00
Added world map component.
This commit is contained in:
parent
e4e7f5b05c
commit
a65f637df2
@ -1,7 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2020": true
|
||||
"es2020": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier", "prettier/react"],
|
||||
"parserOptions": {
|
||||
|
@ -2,6 +2,7 @@ import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useSpring, animated } from 'react-spring';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lib/web';
|
||||
import { percentFilter } from 'lib/filters';
|
||||
import styles from './RankingsChart.module.css';
|
||||
|
||||
export default function RankingsChart({
|
||||
@ -13,6 +14,7 @@ export default function RankingsChart({
|
||||
heading,
|
||||
className,
|
||||
dataFilter,
|
||||
onDataLoad = () => {},
|
||||
}) {
|
||||
const [data, setData] = useState();
|
||||
|
||||
@ -23,16 +25,17 @@ export default function RankingsChart({
|
||||
return [];
|
||||
}, [data]);
|
||||
|
||||
const total = useMemo(() => rankings?.reduce((n, { y }) => n + y, 0) || 0, [rankings]);
|
||||
|
||||
async function loadData() {
|
||||
setData(
|
||||
await get(`/api/website/${websiteId}/rankings`, {
|
||||
const data = await get(`/api/website/${websiteId}/rankings`, {
|
||||
start_at: +startDate,
|
||||
end_at: +endDate,
|
||||
type,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
const updated = percentFilter(data);
|
||||
|
||||
setData(updated);
|
||||
onDataLoad(updated);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@ -51,26 +54,25 @@ export default function RankingsChart({
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.heading}>{heading}</div>
|
||||
</div>
|
||||
{rankings.map(({ x, y }) => (
|
||||
<Row key={x} label={x} value={y} total={total} />
|
||||
{rankings.map(({ x, y, z }) => (
|
||||
<Row key={x} label={x} value={y} percent={z} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Row = ({ label, value, total }) => {
|
||||
const pct = total ? (value / total) * 100 : 0;
|
||||
const props = useSpring({ width: pct, from: { width: 0 } });
|
||||
const Row = ({ label, value, percent }) => {
|
||||
const props = useSpring({ width: percent, from: { width: 0 } });
|
||||
const valueProps = useSpring({ y: value, from: { y: 0 } });
|
||||
|
||||
return (
|
||||
<div className={styles.row}>
|
||||
<div className={styles.label}>{label}</div>
|
||||
<animated.div className={styles.value}>
|
||||
{valueProps.y.interpolate(y => y.toFixed(0))}
|
||||
{valueProps.y.interpolate(n => n.toFixed(0))}
|
||||
</animated.div>
|
||||
<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 }} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,74 +1,19 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { get } from 'lib/web';
|
||||
import WebsiteChart from './WebsiteChart';
|
||||
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';
|
||||
|
||||
const pageviewClasses = 'col-md-12 col-lg-6';
|
||||
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' }) {
|
||||
const [data, setData] = useState();
|
||||
const [countryData, setCountryData] = useState();
|
||||
const [dateRange, setDateRange] = useState(getDateRange(defaultDateRange));
|
||||
const { startDate, endDate } = dateRange;
|
||||
|
||||
@ -151,6 +96,20 @@ export default function WebsiteDetails({ websiteId, defaultDateRange = '7day' })
|
||||
dataFilter={deviceFilter}
|
||||
/>
|
||||
</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
71
components/WorldMap.js
Normal 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>
|
||||
);
|
||||
}
|
5
components/WorldMap.module.css
Normal file
5
components/WorldMap.module.css
Normal file
@ -0,0 +1,5 @@
|
||||
.container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border-top: 1px solid #e1e1e1;
|
||||
}
|
314
lib/filters.js
Normal file
314
lib/filters.js
Normal 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 }));
|
||||
};
|
@ -60,8 +60,11 @@
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-simple-maps": "^2.1.2",
|
||||
"react-spring": "^8.0.27",
|
||||
"react-tooltip": "^4.2.7",
|
||||
"request-ip": "^2.1.3",
|
||||
"tinycolor2": "^1.4.1",
|
||||
"unfetch": "^4.1.0",
|
||||
"uuid": "^8.3.0",
|
||||
"yargs": "^15.4.1"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { getRankings } from 'lib/db';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
|
||||
const sessionColumns = ['browser', 'os', 'screen'];
|
||||
const sessionColumns = ['browser', 'os', 'screen', 'country'];
|
||||
const pageviewColumns = ['url', 'referrer'];
|
||||
|
||||
export default async (req, res) => {
|
||||
|
1
public/world-110m.json
Normal file
1
public/world-110m.json
Normal file
File diff suppressed because one or more lines are too long
112
yarn.lock
112
yarn.lock
@ -2645,7 +2645,7 @@ colorette@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
|
||||
integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
|
||||
|
||||
commander@^2.20.0:
|
||||
commander@2, commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
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"
|
||||
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:
|
||||
version "1.0.1"
|
||||
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"
|
||||
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:
|
||||
version "8.0.27"
|
||||
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"
|
||||
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:
|
||||
version "16.13.1"
|
||||
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"
|
||||
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:
|
||||
version "0.0.33"
|
||||
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"
|
||||
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:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
|
||||
@ -8969,6 +9074,11 @@ util@^0.11.0:
|
||||
dependencies:
|
||||
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:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
|
||||
|
Loading…
Reference in New Issue
Block a user