Commit
·
22aa376
1
Parent(s):
b6b0a70
add spaces, datasets created
Browse files- src/components/Heatmap.tsx +50 -0
- src/pages/[author]/index.tsx +26 -38
- src/pages/index.tsx +59 -57
- src/styles/globals.css +1 -1
- src/utils/calendar.ts +67 -62
src/components/Heatmap.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import ActivityCalendar from "react-activity-calendar";
|
| 3 |
+
import { Tooltip } from "@mui/material";
|
| 4 |
+
import Link from "next/link";
|
| 5 |
+
|
| 6 |
+
type HeatmapProps = {
|
| 7 |
+
data: Array<{ date: string; count: number; level: number }>;
|
| 8 |
+
color: string;
|
| 9 |
+
providerName: string;
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
const Heatmap: React.FC<HeatmapProps> = ({ data, color, providerName }) => {
|
| 13 |
+
return (
|
| 14 |
+
<div className="flex flex-col items-center">
|
| 15 |
+
<div className="w-full overflow-x-auto flex justify-center">
|
| 16 |
+
<ActivityCalendar
|
| 17 |
+
data={data}
|
| 18 |
+
theme={{
|
| 19 |
+
dark: ["#161b22", color],
|
| 20 |
+
light: ["#e0e0e0", color],
|
| 21 |
+
}}
|
| 22 |
+
hideTotalCount
|
| 23 |
+
renderBlock={(block, activity) => (
|
| 24 |
+
<Tooltip
|
| 25 |
+
title={`${activity.count} events on ${activity.date}`}
|
| 26 |
+
arrow
|
| 27 |
+
>
|
| 28 |
+
{block}
|
| 29 |
+
</Tooltip>
|
| 30 |
+
)}
|
| 31 |
+
/>
|
| 32 |
+
</div>
|
| 33 |
+
<div>
|
| 34 |
+
<p className="text-sm italic light:text-slate-500">
|
| 35 |
+
Models, Datasets, and Spaces created by{" "}
|
| 36 |
+
<Link
|
| 37 |
+
href={`https://huggingface.co/${providerName}`}
|
| 38 |
+
target="_blank"
|
| 39 |
+
rel="noopener noreferrer"
|
| 40 |
+
className="hover:underline text-blue-500"
|
| 41 |
+
>
|
| 42 |
+
{providerName}.
|
| 43 |
+
</Link>
|
| 44 |
+
</p>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
);
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
export default Heatmap;
|
src/pages/[author]/index.tsx
CHANGED
|
@@ -1,11 +1,10 @@
|
|
| 1 |
import React, { useState, useEffect } from "react";
|
| 2 |
-
import ActivityCalendar from "react-activity-calendar";
|
| 3 |
-
import { Tooltip } from "@mui/material";
|
| 4 |
import { GetServerSidePropsContext } from "next";
|
| 5 |
-
import {
|
| 6 |
import { generateCalendarData } from "../../utils/calendar";
|
|
|
|
| 7 |
|
| 8 |
-
const DEFAULT_COLOR = "#
|
| 9 |
|
| 10 |
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
| 11 |
calendarData,
|
|
@@ -21,39 +20,23 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
|
| 21 |
|
| 22 |
return (
|
| 23 |
<div className="w-full max-w-screen-lg mx-auto p-4">
|
| 24 |
-
|
| 25 |
{isLoading ? (
|
| 26 |
<p className="text-center">Loading...</p>
|
| 27 |
) : (
|
| 28 |
-
<div
|
| 29 |
{Object.entries(providers)
|
| 30 |
.sort(
|
| 31 |
([keyA], [keyB]) =>
|
| 32 |
calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
|
| 33 |
-
calendarData[keyA].reduce((sum, day) => sum + day.count, 0)
|
| 34 |
)
|
| 35 |
.map(([providerName, { color }]) => (
|
| 36 |
-
<
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
dark: ["#161b22", color],
|
| 43 |
-
light: ["#e0e0e0", color],
|
| 44 |
-
}}
|
| 45 |
-
hideTotalCount
|
| 46 |
-
renderBlock={(block, activity) => (
|
| 47 |
-
<Tooltip
|
| 48 |
-
title={`${activity.count} models created on ${activity.date}`}
|
| 49 |
-
arrow
|
| 50 |
-
>
|
| 51 |
-
{block}
|
| 52 |
-
</Tooltip>
|
| 53 |
-
)}
|
| 54 |
-
/>
|
| 55 |
-
</div>
|
| 56 |
-
</div>
|
| 57 |
))}
|
| 58 |
</div>
|
| 59 |
)}
|
|
@@ -74,17 +57,23 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|
| 74 |
};
|
| 75 |
|
| 76 |
try {
|
| 77 |
-
const
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
);
|
| 80 |
-
const data = await response.json();
|
| 81 |
-
|
| 82 |
-
const modelData = data.map((item: any) => ({
|
| 83 |
-
createdAt: item.createdAt,
|
| 84 |
-
id: item.id,
|
| 85 |
-
}));
|
| 86 |
|
| 87 |
-
const
|
|
|
|
| 88 |
|
| 89 |
return {
|
| 90 |
props: {
|
|
@@ -105,5 +94,4 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|
| 105 |
}
|
| 106 |
}
|
| 107 |
|
| 108 |
-
|
| 109 |
export default OpenSourceHeatmap;
|
|
|
|
| 1 |
import React, { useState, useEffect } from "react";
|
|
|
|
|
|
|
| 2 |
import { GetServerSidePropsContext } from "next";
|
| 3 |
+
import { OpenSourceHeatmapProps } from "../../types/heatmap";
|
| 4 |
import { generateCalendarData } from "../../utils/calendar";
|
| 5 |
+
import Heatmap from "../../components/Heatmap";
|
| 6 |
|
| 7 |
+
const DEFAULT_COLOR = "#FF9D00";
|
| 8 |
|
| 9 |
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
| 10 |
calendarData,
|
|
|
|
| 20 |
|
| 21 |
return (
|
| 22 |
<div className="w-full max-w-screen-lg mx-auto p-4">
|
|
|
|
| 23 |
{isLoading ? (
|
| 24 |
<p className="text-center">Loading...</p>
|
| 25 |
) : (
|
| 26 |
+
<div>
|
| 27 |
{Object.entries(providers)
|
| 28 |
.sort(
|
| 29 |
([keyA], [keyB]) =>
|
| 30 |
calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
|
| 31 |
+
calendarData[keyA].reduce((sum, day) => sum + day.count, 0)
|
| 32 |
)
|
| 33 |
.map(([providerName, { color }]) => (
|
| 34 |
+
<Heatmap
|
| 35 |
+
key={providerName}
|
| 36 |
+
data={calendarData[providerName]}
|
| 37 |
+
color={color}
|
| 38 |
+
providerName={providerName}
|
| 39 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
))}
|
| 41 |
</div>
|
| 42 |
)}
|
|
|
|
| 57 |
};
|
| 58 |
|
| 59 |
try {
|
| 60 |
+
const entityTypes = ["models", "datasets", "spaces"];
|
| 61 |
+
const allData = await Promise.all(
|
| 62 |
+
entityTypes.map(async (type) => {
|
| 63 |
+
const response = await fetch(
|
| 64 |
+
`https://huggingface.co/api/${type}?author=${author}&sort=createdAt&direction=-1`,
|
| 65 |
+
);
|
| 66 |
+
const data = await response.json();
|
| 67 |
+
return data.map((item: any) => ({
|
| 68 |
+
createdAt: item.createdAt,
|
| 69 |
+
id: item.id,
|
| 70 |
+
type: type,
|
| 71 |
+
}));
|
| 72 |
+
}),
|
| 73 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
+
const flatData = allData.flat();
|
| 76 |
+
const calendarData = generateCalendarData(flatData, providers);
|
| 77 |
|
| 78 |
return {
|
| 79 |
props: {
|
|
|
|
| 94 |
}
|
| 95 |
}
|
| 96 |
|
|
|
|
| 97 |
export default OpenSourceHeatmap;
|
src/pages/index.tsx
CHANGED
|
@@ -1,44 +1,55 @@
|
|
| 1 |
-
import React, { useState, useEffect } from
|
| 2 |
-
import ActivityCalendar from "react-activity-calendar";
|
| 3 |
-
import { Tooltip } from '@mui/material';
|
| 4 |
import { generateCalendarData } from "../utils/calendar";
|
| 5 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
const PROVIDERS_MAP: Record<string, ProviderInfo> = {
|
| 8 |
"Mistral AI": { color: "#ff7000", authors: ["mistralai"] },
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
"Allen Institute for AI": { color: "#5E35B1", authors: ["allenai"] },
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
"Cohere For AI": { color: "#4C6EE6", authors: ["CohereForAI"] },
|
| 20 |
-
|
| 21 |
"Stability AI": { color: "#A020F0", authors: ["stabilityai"] },
|
| 22 |
};
|
| 23 |
|
| 24 |
export async function getStaticProps() {
|
| 25 |
try {
|
| 26 |
-
const allAuthors = Object.values(PROVIDERS_MAP).flatMap(
|
|
|
|
|
|
|
| 27 |
const uniqueAuthors = Array.from(new Set(allAuthors));
|
| 28 |
|
| 29 |
-
const
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
);
|
| 39 |
|
| 40 |
-
const
|
| 41 |
-
const calendarData = generateCalendarData(
|
| 42 |
|
| 43 |
return {
|
| 44 |
props: {
|
|
@@ -59,7 +70,10 @@ export async function getStaticProps() {
|
|
| 59 |
}
|
| 60 |
}
|
| 61 |
|
| 62 |
-
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
|
|
|
|
|
|
|
|
|
| 63 |
const [isLoading, setIsLoading] = useState(true);
|
| 64 |
|
| 65 |
useEffect(() => {
|
|
@@ -70,10 +84,12 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({ calendarData, pro
|
|
| 70 |
|
| 71 |
return (
|
| 72 |
<div className="w-full max-w-screen-lg mx-auto p-4">
|
| 73 |
-
<h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2">
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
| 77 |
<a
|
| 78 |
href="https://huggingface.co/spaces/cfahlgren1/model-release-heatmap/discussions/new"
|
| 79 |
target="_blank"
|
|
@@ -87,40 +103,26 @@ const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({ calendarData, pro
|
|
| 87 |
{isLoading ? (
|
| 88 |
<p className="text-center">Loading...</p>
|
| 89 |
) : (
|
| 90 |
-
<div className="space-y-
|
| 91 |
{Object.entries(providers)
|
| 92 |
-
.sort(
|
| 93 |
-
|
| 94 |
-
|
|
|
|
| 95 |
)
|
| 96 |
.map(([providerName, { color }]) => (
|
| 97 |
<div key={providerName} className="flex flex-col items-center">
|
| 98 |
-
<
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
dark: ['#161b22', color],
|
| 104 |
-
light: ['#e0e0e0', color],
|
| 105 |
-
}}
|
| 106 |
-
hideTotalCount
|
| 107 |
-
renderBlock={(block, activity) => (
|
| 108 |
-
<Tooltip
|
| 109 |
-
title={`${activity.count} models created on ${activity.date}`}
|
| 110 |
-
arrow
|
| 111 |
-
>
|
| 112 |
-
{block}
|
| 113 |
-
</Tooltip>
|
| 114 |
-
)}
|
| 115 |
-
/>
|
| 116 |
-
</div>
|
| 117 |
</div>
|
| 118 |
-
))
|
| 119 |
-
}
|
| 120 |
</div>
|
| 121 |
)}
|
| 122 |
</div>
|
| 123 |
);
|
| 124 |
-
}
|
| 125 |
|
| 126 |
export default OpenSourceHeatmap;
|
|
|
|
| 1 |
+
import React, { useState, useEffect } from "react";
|
|
|
|
|
|
|
| 2 |
import { generateCalendarData } from "../utils/calendar";
|
| 3 |
+
import {
|
| 4 |
+
OpenSourceHeatmapProps,
|
| 5 |
+
ProviderInfo,
|
| 6 |
+
ModelData,
|
| 7 |
+
} from "../types/heatmap";
|
| 8 |
+
import Heatmap from "../components/Heatmap";
|
| 9 |
|
| 10 |
const PROVIDERS_MAP: Record<string, ProviderInfo> = {
|
| 11 |
"Mistral AI": { color: "#ff7000", authors: ["mistralai"] },
|
| 12 |
+
Meta: { color: "#1877F2", authors: ["facebook", "meta-llama"] },
|
| 13 |
+
OpenAI: { color: "#10A37F", authors: ["openai"] },
|
| 14 |
+
Anthropic: { color: "#cc785c", authors: ["Anthropic"] },
|
| 15 |
+
Google: { color: "#DB4437", authors: ["google"] },
|
| 16 |
"Allen Institute for AI": { color: "#5E35B1", authors: ["allenai"] },
|
| 17 |
+
Apple: { color: "#0088cc", authors: ["apple"] },
|
| 18 |
+
Microsoft: { color: "#FEB800", authors: ["microsoft"] },
|
| 19 |
+
NVIDIA: { color: "#76B900", authors: ["nvidia"] },
|
| 20 |
+
DeepSeek: { color: "#0088cc", authors: ["deepseek-ai"] },
|
| 21 |
+
Qwen: { color: "#0088cc", authors: ["Qwen"] },
|
| 22 |
"Cohere For AI": { color: "#4C6EE6", authors: ["CohereForAI"] },
|
| 23 |
+
IBM: { color: "#4C6EE6", authors: ["ibm-granite"] },
|
| 24 |
"Stability AI": { color: "#A020F0", authors: ["stabilityai"] },
|
| 25 |
};
|
| 26 |
|
| 27 |
export async function getStaticProps() {
|
| 28 |
try {
|
| 29 |
+
const allAuthors = Object.values(PROVIDERS_MAP).flatMap(
|
| 30 |
+
({ authors }) => authors,
|
| 31 |
+
);
|
| 32 |
const uniqueAuthors = Array.from(new Set(allAuthors));
|
| 33 |
|
| 34 |
+
const entityTypes = ["models", "datasets", "spaces"];
|
| 35 |
+
const allData = await Promise.all(
|
| 36 |
+
uniqueAuthors.flatMap((author) =>
|
| 37 |
+
entityTypes.map(async (type) => {
|
| 38 |
+
const response = await fetch(
|
| 39 |
+
`https://huggingface.co/api/${type}?author=${author}&sort=createdAt&direction=-1`,
|
| 40 |
+
);
|
| 41 |
+
const data = await response.json();
|
| 42 |
+
return data.map((item: any) => ({
|
| 43 |
+
createdAt: item.createdAt,
|
| 44 |
+
id: item.id,
|
| 45 |
+
type: type,
|
| 46 |
+
}));
|
| 47 |
+
}),
|
| 48 |
+
),
|
| 49 |
);
|
| 50 |
|
| 51 |
+
const flatData: ModelData[] = allData.flat();
|
| 52 |
+
const calendarData = generateCalendarData(flatData, PROVIDERS_MAP);
|
| 53 |
|
| 54 |
return {
|
| 55 |
props: {
|
|
|
|
| 70 |
}
|
| 71 |
}
|
| 72 |
|
| 73 |
+
const OpenSourceHeatmap: React.FC<OpenSourceHeatmapProps> = ({
|
| 74 |
+
calendarData,
|
| 75 |
+
providers,
|
| 76 |
+
}) => {
|
| 77 |
const [isLoading, setIsLoading] = useState(true);
|
| 78 |
|
| 79 |
useEffect(() => {
|
|
|
|
| 84 |
|
| 85 |
return (
|
| 86 |
<div className="w-full max-w-screen-lg mx-auto p-4">
|
| 87 |
+
<h1 className="text-3xl lg:text-5xl mt-16 font-bold text-center mb-2">
|
| 88 |
+
Hugging Face Heatmap 🤗
|
| 89 |
+
</h1>
|
| 90 |
+
<p className="text-center text-sm my-8">
|
| 91 |
+
Models, Datasets, and Spaces from the top AI labs. <br />
|
| 92 |
+
Request more heatmaps by{" "}
|
| 93 |
<a
|
| 94 |
href="https://huggingface.co/spaces/cfahlgren1/model-release-heatmap/discussions/new"
|
| 95 |
target="_blank"
|
|
|
|
| 103 |
{isLoading ? (
|
| 104 |
<p className="text-center">Loading...</p>
|
| 105 |
) : (
|
| 106 |
+
<div className="space-y-16">
|
| 107 |
{Object.entries(providers)
|
| 108 |
+
.sort(
|
| 109 |
+
([keyA], [keyB]) =>
|
| 110 |
+
calendarData[keyB].reduce((sum, day) => sum + day.count, 0) -
|
| 111 |
+
calendarData[keyA].reduce((sum, day) => sum + day.count, 0),
|
| 112 |
)
|
| 113 |
.map(([providerName, { color }]) => (
|
| 114 |
<div key={providerName} className="flex flex-col items-center">
|
| 115 |
+
<Heatmap
|
| 116 |
+
data={calendarData[providerName]}
|
| 117 |
+
color={color}
|
| 118 |
+
providerName={providerName}
|
| 119 |
+
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
</div>
|
| 121 |
+
))}
|
|
|
|
| 122 |
</div>
|
| 123 |
)}
|
| 124 |
</div>
|
| 125 |
);
|
| 126 |
+
};
|
| 127 |
|
| 128 |
export default OpenSourceHeatmap;
|
src/styles/globals.css
CHANGED
|
@@ -23,4 +23,4 @@ body {
|
|
| 23 |
.text-balance {
|
| 24 |
text-wrap: balance;
|
| 25 |
}
|
| 26 |
-
}
|
|
|
|
| 23 |
.text-balance {
|
| 24 |
text-wrap: balance;
|
| 25 |
}
|
| 26 |
+
}
|
src/utils/calendar.ts
CHANGED
|
@@ -1,66 +1,71 @@
|
|
| 1 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
export const generateCalendarData = (
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
});
|
| 28 |
});
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
});
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
// calculate average counts for each provider
|
| 41 |
-
const avgCounts = Object.fromEntries(
|
| 42 |
-
Object.entries(data).map(([provider, days]) => [
|
| 43 |
-
provider,
|
| 44 |
-
days.reduce((sum, day) => sum + day.count, 0) / days.length || 0,
|
| 45 |
-
]),
|
| 46 |
-
);
|
| 47 |
-
|
| 48 |
-
// assign levels based on count relative to average
|
| 49 |
-
Object.entries(data).forEach(([provider, days]) => {
|
| 50 |
-
const avgCount = avgCounts[provider];
|
| 51 |
-
days.forEach((day) => {
|
| 52 |
-
day.level =
|
| 53 |
-
day.count === 0
|
| 54 |
-
? 0
|
| 55 |
-
: day.count <= avgCount * 0.5
|
| 56 |
-
? 1
|
| 57 |
-
: day.count <= avgCount
|
| 58 |
-
? 2
|
| 59 |
-
: day.count <= avgCount * 1.5
|
| 60 |
-
? 3
|
| 61 |
-
: 4;
|
| 62 |
-
});
|
| 63 |
});
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import {
|
| 2 |
+
ModelData,
|
| 3 |
+
ProviderInfo,
|
| 4 |
+
CalendarData,
|
| 5 |
+
Activity,
|
| 6 |
+
} from "../types/heatmap";
|
| 7 |
|
| 8 |
export const generateCalendarData = (
|
| 9 |
+
modelData: ModelData[],
|
| 10 |
+
providers: Record<string, ProviderInfo>,
|
| 11 |
+
): CalendarData => {
|
| 12 |
+
const data: Record<string, Activity[]> = Object.fromEntries(
|
| 13 |
+
Object.keys(providers).map((provider) => [provider, []]),
|
| 14 |
+
);
|
| 15 |
+
|
| 16 |
+
const today = new Date();
|
| 17 |
+
const startDate = new Date(today);
|
| 18 |
+
startDate.setMonth(today.getMonth() - 11);
|
| 19 |
+
startDate.setDate(1);
|
| 20 |
+
|
| 21 |
+
// create a map to store counts for each provider and date
|
| 22 |
+
const countMap: Record<string, Record<string, number>> = {};
|
| 23 |
+
|
| 24 |
+
modelData.forEach((item) => {
|
| 25 |
+
const dateString = item.createdAt.split("T")[0];
|
| 26 |
+
Object.entries(providers).forEach(([provider, { authors }]) => {
|
| 27 |
+
if (authors.some((author) => item.id.startsWith(author))) {
|
| 28 |
+
countMap[provider] = countMap[provider] || {};
|
| 29 |
+
countMap[provider][dateString] =
|
| 30 |
+
(countMap[provider][dateString] || 0) + 1;
|
| 31 |
+
}
|
|
|
|
| 32 |
});
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
// fill in with 0s for days with no activity
|
| 36 |
+
for (let d = new Date(startDate); d <= today; d.setDate(d.getDate() + 1)) {
|
| 37 |
+
const dateString = d.toISOString().split("T")[0];
|
| 38 |
+
|
| 39 |
+
Object.entries(providers).forEach(([provider]) => {
|
| 40 |
+
const count = countMap[provider]?.[dateString] || 0;
|
| 41 |
+
data[provider].push({ date: dateString, count, level: 0 });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
});
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// calculate average counts for each provider
|
| 46 |
+
const avgCounts = Object.fromEntries(
|
| 47 |
+
Object.entries(data).map(([provider, days]) => [
|
| 48 |
+
provider,
|
| 49 |
+
days.reduce((sum, day) => sum + day.count, 0) / days.length || 0,
|
| 50 |
+
]),
|
| 51 |
+
);
|
| 52 |
+
|
| 53 |
+
// assign levels based on count relative to average
|
| 54 |
+
Object.entries(data).forEach(([provider, days]) => {
|
| 55 |
+
const avgCount = avgCounts[provider];
|
| 56 |
+
days.forEach((day) => {
|
| 57 |
+
day.level =
|
| 58 |
+
day.count === 0
|
| 59 |
+
? 0
|
| 60 |
+
: day.count <= avgCount * 0.5
|
| 61 |
+
? 1
|
| 62 |
+
: day.count <= avgCount
|
| 63 |
+
? 2
|
| 64 |
+
: day.count <= avgCount * 1.5
|
| 65 |
+
? 3
|
| 66 |
+
: 4;
|
| 67 |
+
});
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
return data;
|
| 71 |
+
};
|