Skip to content

Commit

Permalink
bar chart
Browse files Browse the repository at this point in the history
  • Loading branch information
walteh committed Jun 28, 2024
1 parent 9c243ea commit 579842d
Show file tree
Hide file tree
Showing 13 changed files with 1,148 additions and 396 deletions.
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"@types/node": "^20.14.8",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/react-tagcloud": "^2.3.2",
"@types/tape": "^5.6.4",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"@vitejs/plugin-basic-ssl": "^1.1.0",
Expand All @@ -33,7 +35,9 @@
"ava": "^6.1.3",
"body-parser": "^1.20.2",
"chai": "^5.1.1",
"compromise": "^14.13.0",
"daisyui": "^4.12.7",
"emoji-regex": "^10.3.0",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-import-resolver-vite": "^2.0.1",
Expand All @@ -53,12 +57,14 @@
"prettier-plugin-tailwindcss": "^0.6.5",
"react-apexcharts": "^1.4.1",
"react-router-dom": "^6.23.1",
"react-tagcloud": "^2.3.3",
"recharts": "^2.12.7",
"tailwindcss": "^3.4.4",
"tape": "^5.8.1",
"ts-node": "^10.9.2",
"typescript": "^5.2.2",
"usehooks-ts": "^3.1.0",
"uuid": "^10.0.0",
"vite": "^5.3.1",
"zustand": "^4.5.2"
}
Expand Down
307 changes: 307 additions & 0 deletions src/components/UserInsightsChartView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
import { ApexOptions } from "apexcharts";
import { FC, useEffect, useMemo, useRef, useState } from "react";
import ReactApexChart from "react-apexcharts";

import ErrorMessage from "@src/components/ErrorMessage";
import Loader from "@src/components/Loader";
import useTimePeriod, { useTimePeriodCountPerDay, useTimePeriodFilteredData, useTimePeriodListOfDays } from "@src/hooks/useTimePeriod";
import { ThreadMedia } from "@src/threadsapi/api";
import { useUserDataStore } from "@src/threadsapi/store";

const UserInsightChartView: FC = () => {
const data = useUserDataStore((state) => state.user_insights);
const threads = useUserDataStore((state) => state.user_threads);

if (!data || !threads) return null;

if (data.is_loading || threads.is_loading) return <Loader />;
if (data.error) return <ErrorMessage message={data.error} />;
if (!data.data || !threads.data) return <ErrorMessage message="No insights data available" />;

if (!data.data.views) return <ErrorMessage message="No views data available" />;

return (
<div className="container mx-auto p-6">
<h1 className="text-3xl font-bold text-center mb-8">Profile Views</h1>
<ObservedChart views={data.data.views.values} threads={threads.data.data} />
</div>
);
};

const ObservedChart: FC<{ views: { end_time: string; value: number }[]; threads: ThreadMedia[] }> = ({ views, threads }) => {
const chartContainerRef = useRef<HTMLDivElement>(null);
const [chartWidth, setChartWidth] = useState<number>(0);

// const likes = useUserLikesByDay();
// const threadViews = useUserThreadViewsByDay();

const [timePeriod, timePeriods, handleTimePeriodChange] = useTimePeriod();

const viewByTimePeriod = useTimePeriodFilteredData(views, (value) => value.end_time, timePeriod);

const threadsByTimePeriod = useTimePeriodFilteredData(threads, (thread) => thread.timestamp, timePeriod);

// const currentValues = getFilteredData(allValues);

// Filter threads based on the selected time period
// function getFilteredThreads<T extends { timestamp: string }>(threads: T[]) {
// if (timePeriod === "last7days") {
// const last7Days = new Date();
// last7Days.setDate(last7Days.getDate() - 7);
// return threads.filter((thread) => new Date(thread.timestamp) > last7Days);
// } else if (timePeriod === "last30days") {
// const last30Days = new Date();
// last30Days.setDate(last30Days.getDate() - 30);
// return threads.filter((thread) => new Date(thread.timestamp) > last30Days);
// } else {
// const month = timePeriod;
// return threads.filter((thread) => new Date(thread.timestamp).toISOString().slice(0, 7) === month);
// }
// }

// function getTotalPerDay(threads: { timestamp: string; total_value: number }[], startDate: Date, endDate: Date) {
// const threadCountPerDay: { end_date: string; count: number }[] = [];

// // Initialize an object to hold the counts per day
// const countMap: Record<string, number> = {};

// // Populate the countMap with thread counts
// threads.forEach((thread) => {
// const date = new Date(thread.timestamp).toLocaleDateString();
// if (!countMap[date]) {
// countMap[date] = thread.total_value;
// } else {
// countMap[date] += thread.total_value;
// }
// });

// // Fill in the threadCountPerDay array with all dates in the range
// for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
// const dateStr = d.toLocaleDateString();
// threadCountPerDay.push({ end_date: dateStr, count: countMap[dateStr] || 0 });
// }

// return threadCountPerDay;
// }

// getTotalPerDay;

const viewCountPerDay = useTimePeriodCountPerDay(
viewByTimePeriod,
(value) => value.end_time,
(value) => value.value,
timePeriod,
);

const threadCountPerDay = useTimePeriodCountPerDay(
threadsByTimePeriod,
(thread) => thread.timestamp,
() => 1,
timePeriod,
);

const currentDays = useTimePeriodListOfDays(timePeriod);

// const currentLikes = getTotalPerDay(
// getFilteredThreads(likes),
// new Date(currentValues[0].end_time),
// new Date(currentValues[currentValues.length - 1].end_time),
// );

// const currentThreadViews = getTotalPerDay(
// getFilteredThreads(threadViews),
// new Date(currentValues[0].end_time),
// new Date(currentValues[currentValues.length - 1].end_time),
// );

const [chartOptions, chartSeries] = useMemo(() => {
const opts: ApexOptions = {
chart: {
type: "area",
fontFamily: "Inter, sans-serif",
dropShadow: {
enabled: false,
},
toolbar: {
show: false,
},
},
tooltip: {
x: {
format: "dd MMM yyyy",
},
y: [
{
formatter: (val) => `${val.toLocaleString()} views`,
},
{
formatter: (val) => `${val.toLocaleString()} posts`,
},
// {
// formatter: (val) => `${val.toLocaleString()} likes`,
// },
// {
// formatter: (val) => `${val.toLocaleString()} thread views`,
// },
],
},
yaxis: [
{
labels: {
formatter: (val) => {
const formatter = Intl.NumberFormat("en", { notation: "compact" });
return formatter.format(val);
},
},
},
{
opposite: true,
labels: {
formatter: (val) => {
const formatter = Intl.NumberFormat("en", { notation: "compact" });
return formatter.format(val);
},
},
},
// {
// opposite: true,
// labels: {
// formatter: (val) => {
// const formatter = Intl.NumberFormat("en", { notation: "compact" });
// return formatter.format(val);
// },
// },
// },
// {
// opposite: true,
// labels: {
// formatter: (val) => {
// const formatter = Intl.NumberFormat("en", { notation: "compact" });
// return formatter.format(val);
// },
// },
// },
],
xaxis: {
categories: currentDays.map((value) => new Date(value).toLocaleDateString()),
labels: {
show: true,
rotate: -45,
style: {
fontSize: "12px",
fontFamily: "Inter, sans-serif",
colors: "#9aa0ac",
},
},
axisBorder: {
show: false,
},
axisTicks: {
show: false,
},
},
grid: {
show: true,
strokeDashArray: 4,
padding: {
left: 2,
right: 2,
top: 0,
},
},
stroke: {
curve: "smooth",
width: 6,
},
markers: {
size: 4,
},
fill: {
type: "gradient",
gradient: {
opacityFrom: 0.55,
opacityTo: 0,
shade: "#1C64F2",
gradientToColors: ["#1C64F2"],
},
colors: ["#1C64F2", "#F39C12"],
},
dataLabels: {
enabled: false,
},
};

const chartSeries: ApexAxisChartSeries = [
{
name: "Views",
data: viewCountPerDay.map((value) => ({
x: value.end_date,
y: value.count,
})),
color: "#1C64F2",
},
{
name: "Posts",
data: threadCountPerDay.map((value) => ({
x: value.end_date,
y: value.count,
})),
color: "#10B981",
},
// {
// name: "Likes",
// data: currentLikes.map((value) => value.count),
// // green
// color: "#10B981",
// },
// {
// name: "Thread Views",
// data: currentThreadViews.map((value) => value.count),
// // red
// color: "#EF4444",
// },
];

return [opts, chartSeries] as const;
}, [viewCountPerDay, threadCountPerDay, currentDays]);

useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
setChartWidth(entry.contentRect.width);
}
});

if (chartContainerRef.current) {
resizeObserver.observe(chartContainerRef.current);
}

const curr = chartContainerRef.current;

return () => {
if (curr) {
resizeObserver.unobserve(curr);
}
};
}, []);

return (
<div ref={chartContainerRef} className="bg-white p-6 rounded-lg shadow-lg flex flex-col items-center w-full">
<div className="mb-4">
<label htmlFor="timePeriod" className="mr-2">
Select Time Period:
</label>
<select id="timePeriod" value={timePeriod.label} onChange={handleTimePeriodChange} className="p-2 border rounded">
{Object.entries(timePeriods).map(([, tp]) => (
<option key={tp.label} value={tp.label}>
{!tp.label.includes("days") ? tp.label : `Last ${tp.label.replace("days", "").replace("last", "")} Days`}
</option>
))}
</select>
</div>
<ReactApexChart options={chartOptions} series={chartSeries} width={chartWidth} height={300} type="area" />
</div>
);
};

export default UserInsightChartView;
Loading

0 comments on commit 579842d

Please sign in to comment.