Skip to content

Commit

Permalink
feat: geo map coloring
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
  • Loading branch information
explodingcamera committed Aug 15, 2024
1 parent 46e5097 commit 2ab4325
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 80 deletions.
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion data/geo.json

Large diffs are not rendered by default.

Binary file modified web/bun.lockb
Binary file not shown.
4 changes: 2 additions & 2 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"@astrojs/react": "^3.6.2",
"@explodingcamera/css": "^0.0.4",
"@fontsource-variable/figtree": "^5.0.21",
"@icons-pack/react-simple-icons": "^9.6.0",
"@icons-pack/react-simple-icons": "^9.7.0",
"@nivo/geo": "^0.87.0",
"@nivo/line": "^0.87.0",
"@picocss/pico": "^2.0.6",
Expand All @@ -23,7 +23,7 @@
"@types/react-dom": "^18.3.0",
"@types/react-simple-maps": "^3.0.6",
"@uidotdev/usehooks": "^2.4.1",
"astro": "^4.13.4",
"astro": "^4.14.0",
"date-fns": "^3.6.0",
"fets": "^0.8.2",
"lightningcss": "^1.26.0",
Expand Down
17 changes: 17 additions & 0 deletions web/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ export const dimensionNames: Record<Dimension, string> = {
fqdn: "Domain",
};

export const formatMetricVal = (metric: Metric, value: number) => {
let res = value;
if (metric === "avg_views_per_session") {
res = value / 1000;
}

if (res >= 1000) {
return `${(res / 1000).toFixed(1).replace(/\.0$/, "")}K`;
}

if (res >= 1000000) {
return `${(res / 1000000).toFixed(1).replace(/\.0$/, "")}M`;
}

return res.toFixed(1).replace(/\.0$/, "") || "0";
};

export const useMe = () => {
const { data, isLoading } = useQuery({
queryKey: ["me"],
Expand Down
37 changes: 27 additions & 10 deletions web/src/components/project.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,33 +31,50 @@ div.graph {
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}

.card {
padding: 1rem;
background-color: var(--pico-form-element-background-color);
border-radius: var(--pico-border-radius);
background: var(--pico-card-background-color);
box-shadow: var(--pico-card-box-shadow);

&[data-full-width="true"] {
grid-column: span 2;
}
}

.geoCard {
display: flex;
gap: 2rem;
> div {
padding: 1rem;
background-color: var(--pico-form-element-background-color);
border-radius: var(--pico-border-radius);
background: var(--pico-card-background-color);
box-shadow: var(--pico-card-box-shadow);
flex: 1;
flex-direction: column;
gap: 0.5rem;
}
}

.dimTable {
display: flex;
flex-direction: column;

> div {
.header {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.2rem;
}

.header {
color: var(--pico-h5-color);
margin-bottom: 1rem;
}
}

.dimRow {
display: flex;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.2rem;
}

.percentage {
flex: 1;
position: relative;
Expand Down
124 changes: 96 additions & 28 deletions web/src/components/project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styles from "./project.module.css";
import {
api,
dimensionNames,
formatMetricVal,
metricNames,
useQuery,
type DateRange,
Expand Down Expand Up @@ -51,17 +52,34 @@ export const Project = () => {
graphClassName={styles.graph}
/>
<div className={styles.tables}>
<DimTable project={data} dimension={"platform"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"browser"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"url"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"fqdn"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"mobile"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"referrer"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"city"} metric={metric} range={resolveRange(dateRange).range} />
<DimTable project={data} dimension={"country"} metric={metric} range={resolveRange(dateRange).range} />
<Suspense>
<WorldMap />
</Suspense>
<Card>
<DimTable project={data} dimension={"platform"} metric={metric} range={resolveRange(dateRange).range} />
</Card>

<Card>
<DimTable project={data} dimension={"browser"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card fullWidth>
<GeoCard project={data} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"url"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"fqdn"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"mobile"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"referrer"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"city"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
<Card>
<DimTable project={data} dimension={"country"} metric={metric} range={resolveRange(dateRange).range} />
</Card>
</div>
</div>
);
Expand All @@ -79,21 +97,24 @@ const Entities = ({ entities }: { entities: { id: string; displayName: string }[
);
};

const DimTable = ({
project,
dimension,
metric,
range,
}: { project: ProjectResponse; dimension: Dimension; metric: Metric; range: DateRange }) => {
const Card = ({ children, fullWidth }: { children: React.ReactNode; fullWidth?: boolean }) => {
return (
<div className={styles.card} data-full-width={fullWidth ?? undefined}>
<Suspense>{children}</Suspense>
</div>
);
};

const GeoCard = ({ project, metric, range }: { project: ProjectResponse; metric: Metric; range: DateRange }) => {
const { data } = useQuery({
placeholderData: (prev) => prev,
queryKey: ["dimension", project.id, dimension, metric, range],
queryKey: ["dimension", project.id, "country", metric, range],
queryFn: () =>
api["/api/dashboard/project/{project_id}/dimension"]
.post({
params: { project_id: project.id },
json: {
dimension,
dimension: "country",
metric,
range,
},
Expand All @@ -102,24 +123,22 @@ const DimTable = ({
});

const biggest = useMemo(() => data?.data?.reduce((acc, d) => Math.max(acc, d.value), 0) ?? 0, [data]);
// e.g ["Chrome", "Firefox", "Safari"]
const order = useMemo(() => data?.data?.sort((a, b) => b.value - a.value).map((d) => d.dimensionValue), [data]);

return (
<div>
<div className={styles.dimTable}>
<div className={styles.header}>
<div>{dimensionNames[dimension]}</div>
<div>{metricNames[metric]}</div>
</div>
<div className={styles.geoCard}>
<div>
<WorldMap data={data?.data} metric={metric} />
</div>
<div>
{data?.data?.map((d) => {
const value = metric === "avg_views_per_session" ? d.value / 1000 : d.value;
const biggestVal = metric === "avg_views_per_session" ? biggest / 1000 : biggest;

return (
<div key={d.dimensionValue} style={{ order: order?.indexOf(d.dimensionValue) }} className={styles.row}>
<div key={d.dimensionValue} style={{ order: order?.indexOf(d.dimensionValue) }} className={styles.dimRow}>
<DimensionValueBar value={value} biggest={biggestVal}>
<DimensionLabel dimension={dimension} value={d} />
<DimensionLabel dimension={"country"} value={d} />
</DimensionValueBar>

<div>{value.toFixed(1).replace(/\.0$/, "") || "0"}</div>
Expand All @@ -131,6 +150,55 @@ const DimTable = ({
);
};

const DimTable = ({
project,
dimension,
metric,
range,
}: { project: ProjectResponse; dimension: Dimension; metric: Metric; range: DateRange }) => {
const { data } = useQuery({
placeholderData: (prev) => prev,
queryKey: ["dimension", project.id, dimension, metric, range],
queryFn: () =>
api["/api/dashboard/project/{project_id}/dimension"]
.post({
params: { project_id: project.id },
json: {
dimension,
metric,
range,
},
})
.json(),
});

const biggest = useMemo(() => data?.data?.reduce((acc, d) => Math.max(acc, d.value), 0) ?? 0, [data]);
const order = useMemo(() => data?.data?.sort((a, b) => b.value - a.value).map((d) => d.dimensionValue), [data]);

return (
<div className={styles.dimTable}>
<div className={styles.header}>
<div>{dimensionNames[dimension]}</div>
<div>{metricNames[metric]}</div>
</div>
{data?.data?.map((d) => {
const value = d.value;
const biggestVal = biggest;

return (
<div key={d.dimensionValue} style={{ order: order?.indexOf(d.dimensionValue) }} className={styles.dimRow}>
<DimensionValueBar value={value} biggest={biggestVal}>
<DimensionLabel dimension={dimension} value={d} />
</DimensionValueBar>

<div>{formatMetricVal(metric, value)}</div>
</div>
);
})}
</div>
);
};

const DimensionLabel = ({ dimension, value }: { dimension: Dimension; value: DimensionTableRow }) => {
if (dimension === "platform")
return (
Expand Down
13 changes: 11 additions & 2 deletions web/src/components/worldmap.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
}

.geo {
opacity: 0.9;
stroke: var(--pico-card-background-color);
stroke-width: 1;
transition: fill 0.3s, opacity 0.3s;

fill: hsl(
90,
calc(0% + 60% * var(--percent) * var(--percent)),
calc(40% + 1% * var(--percent))
);

&:hover {
opacity: 1;
opacity: 0.8;
}
}

Expand Down
Loading

0 comments on commit 2ab4325

Please sign in to comment.