Xianbao QIAN commited on
Commit
c88f96b
·
1 Parent(s): 31ec36d

allow download csv

Browse files
Files changed (2) hide show
  1. src/pages/trend/index.tsx +191 -158
  2. src/utils/modelData.ts +35 -0
src/pages/trend/index.tsx CHANGED
@@ -16,7 +16,8 @@ import {
16
  getTotalMonthlyData,
17
  processDetailedModelData,
18
  MonthlyActivity,
19
- DetailedModelData
 
20
  } from '../../utils/modelData';
21
 
22
  interface TrendProps {
@@ -161,180 +162,212 @@ const TrendPage: React.FC<TrendProps> = ({ monthlyData = [], totalData = [], det
161
  Track the growth of Chinese AI models and datasets over time
162
  </p>
163
 
164
- <div className="flex justify-center mb-6 space-x-4">
165
- <button
166
- onClick={() => setContentType('all')}
167
- className={`px-4 py-2 rounded-lg ${
168
- contentType === 'all'
169
- ? 'bg-blue-500 text-white'
170
- : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
171
- }`}
172
- >
173
- All
174
- </button>
175
- <button
176
- onClick={() => setContentType('models')}
177
- className={`px-4 py-2 rounded-lg ${
178
- contentType === 'models'
179
- ? 'bg-blue-500 text-white'
180
- : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
181
- }`}
182
- >
183
- Models
184
- </button>
185
- <button
186
- onClick={() => setContentType('datasets')}
187
- className={`px-4 py-2 rounded-lg ${
188
- contentType === 'datasets'
189
- ? 'bg-blue-500 text-white'
190
- : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
191
- }`}
192
- >
193
- Datasets
194
- </button>
195
- </div>
196
-
197
- {/* Controls */}
198
- <div className="flex flex-wrap gap-4 mb-6 justify-center items-center">
199
- <div className="flex gap-2">
200
  <button
201
- onClick={() => setShowTotal(!showTotal)}
202
- className={`px-4 py-2 rounded transition-colors ${
203
- showTotal
204
- ? 'bg-green-500 text-white dark:bg-green-600'
205
- : 'bg-gray-200 dark:bg-gray-700 dark:text-gray-300'
206
  }`}
207
  >
208
- Total
209
  </button>
210
- {Object.entries(PROVIDERS_MAP).map(([provider, { color }]) => (
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  <button
212
- key={provider}
213
- onClick={() => toggleProvider(provider)}
214
- className={`px-4 py-2 rounded transition-colors ${
215
- selectedProviders.includes(provider)
216
- ? 'text-white'
217
- : 'text-gray-600 dark:text-gray-400'
218
  }`}
219
- style={{
220
- backgroundColor: selectedProviders.includes(provider)
221
- ? color
222
- : 'transparent'
223
- }}
224
  >
225
- {provider}
226
  </button>
227
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  </div>
229
- </div>
230
 
231
- {/* Chart */}
232
- <div className="w-full h-[600px] dark:bg-gray-900 p-4 rounded-lg">
233
- <ResponsiveContainer width="100%" height="100%">
234
- <LineChart
235
- margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
236
- >
237
- <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
238
- <XAxis
239
- dataKey="date"
240
- type="category"
241
- allowDuplicatedCategory={false}
242
- tick={{ fontSize: 12 }}
243
- interval="preserveStartEnd"
244
- stroke="#9CA3AF"
245
- />
246
- <YAxis stroke="#9CA3AF" />
247
- <Tooltip content={<CustomTooltip />} />
248
- <Legend />
249
-
250
- {/* Total Line */}
251
- {showTotal && (
252
- <Line
253
- data={filteredTotalData}
254
- type="monotone"
255
- dataKey="count"
256
- stroke={COLORS['Total']}
257
- name="Total"
258
- strokeWidth={2}
259
- dot={false}
260
  />
261
- )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
 
263
- {/* Provider Lines */}
264
- {selectedProviders.map(provider => (
265
- <Line
266
- key={provider}
267
- data={providerData[provider]}
268
- type="monotone"
269
- dataKey="count"
270
- stroke={COLORS[provider]}
271
- name={provider}
272
- strokeWidth={1.5}
273
- dot={false}
274
- />
275
- ))}
276
- </LineChart>
277
- </ResponsiveContainer>
278
- </div>
279
 
280
- {/* Major Releases Section */}
281
- <div className="mt-12">
282
- <div className="flex items-center justify-between mb-6">
283
- <h2 className="text-2xl font-bold dark:text-white">
284
- Major Releases ({Object.values(filteredModels).flat().length})
285
- </h2>
286
- <div className="flex items-center gap-3 bg-white dark:bg-gray-800 px-4 py-2 rounded-lg shadow-sm">
287
- <label className="text-sm font-medium dark:text-gray-300">Min Likes:</label>
288
- <input
289
- type="number"
290
- value={minLikes}
291
- onChange={(e) => setMinLikes(Math.max(0, parseInt(e.target.value) || 0))}
292
- className="w-20 px-2 py-1 border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white focus:outline-none focus:ring-2 focus:ring-green-500"
293
- />
294
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
  </div>
296
- <div className="space-y-8">
297
- {Object.entries(filteredModels)
298
- .map(([monthKey, models]) => ({
299
- monthKey,
300
- models,
301
- sortKey: models[0]?.sortKey || '' // Use the first model's sortKey
302
- }))
303
- .sort((a, b) => b.sortKey.localeCompare(a.sortKey)) // Sort by sortKey in descending order
304
- .map(({ monthKey, models }) => (
305
- <div key={monthKey} className="border-b border-gray-200 dark:border-gray-700 pb-4">
306
- <h3 className="text-xl font-semibold mb-3 dark:text-white">{monthKey}</h3>
307
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
308
- {models
309
- .sort((a, b) => b.likes - a.likes) // Sort models within each month by likes
310
- .map(model => (
311
- <div
312
- key={model.id}
313
- className="p-4 rounded-lg border dark:border-gray-700 hover:shadow-md transition-shadow bg-white dark:bg-gray-800"
314
- style={{ borderColor: COLORS[model.provider] }}
315
- >
316
- <div className="flex justify-between items-start">
317
- <div>
318
- <a
319
- href={`https://huggingface.co/${model.id}`}
320
- target="_blank"
321
- rel="noopener noreferrer"
322
- className="text-blue-600 hover:underline font-medium dark:text-blue-400"
323
- >
324
- {model.name}
325
- </a>
326
- <p className="text-sm text-gray-600 dark:text-gray-400">{model.provider}</p>
327
- </div>
328
- <div className="text-right">
329
- <p className="text-sm font-medium dark:text-white">❤️ {model.likes}</p>
330
- <p className="text-xs text-gray-500 dark:text-gray-400">{formatDate(model.createdAt)}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  </div>
332
  </div>
333
- </div>
334
- ))}
335
  </div>
336
- </div>
337
- ))}
338
  </div>
339
  </div>
340
  </div>
 
16
  getTotalMonthlyData,
17
  processDetailedModelData,
18
  MonthlyActivity,
19
+ DetailedModelData,
20
+ convertToCSV
21
  } from '../../utils/modelData';
22
 
23
  interface TrendProps {
 
162
  Track the growth of Chinese AI models and datasets over time
163
  </p>
164
 
165
+ <div className="flex flex-col gap-4 p-4">
166
+ <div className="flex justify-center mb-6 space-x-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  <button
168
+ onClick={() => setContentType('all')}
169
+ className={`px-4 py-2 rounded-lg ${
170
+ contentType === 'all'
171
+ ? 'bg-blue-500 text-white'
172
+ : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
173
  }`}
174
  >
175
+ All
176
  </button>
177
+ <button
178
+ onClick={() => setContentType('models')}
179
+ className={`px-4 py-2 rounded-lg ${
180
+ contentType === 'models'
181
+ ? 'bg-blue-500 text-white'
182
+ : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
183
+ }`}
184
+ >
185
+ Models
186
+ </button>
187
+ <button
188
+ onClick={() => setContentType('datasets')}
189
+ className={`px-4 py-2 rounded-lg ${
190
+ contentType === 'datasets'
191
+ ? 'bg-blue-500 text-white'
192
+ : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
193
+ }`}
194
+ >
195
+ Datasets
196
+ </button>
197
+ </div>
198
+
199
+ <div className="flex flex-wrap gap-4 mb-6 justify-center items-center">
200
+ <div className="flex gap-2">
201
  <button
202
+ onClick={() => setShowTotal(!showTotal)}
203
+ className={`px-4 py-2 rounded-lg ${
204
+ showTotal
205
+ ? 'bg-blue-500 text-white'
206
+ : 'bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-300'
 
207
  }`}
 
 
 
 
 
208
  >
209
+ {showTotal ? 'Hide Total' : 'Show Total'}
210
  </button>
211
+ {Object.entries(PROVIDERS_MAP).map(([provider, { color }]) => (
212
+ <button
213
+ key={provider}
214
+ onClick={() => toggleProvider(provider)}
215
+ className={`px-4 py-2 rounded-lg transition-colors ${
216
+ selectedProviders.includes(provider)
217
+ ? 'text-white'
218
+ : 'text-gray-600 dark:text-gray-400'
219
+ }`}
220
+ style={{
221
+ backgroundColor: selectedProviders.includes(provider)
222
+ ? color
223
+ : 'transparent'
224
+ }}
225
+ >
226
+ {provider}
227
+ </button>
228
+ ))}
229
+ </div>
230
  </div>
 
231
 
232
+ {/* Chart */}
233
+ <div className="w-full h-[600px] dark:bg-gray-900 p-4 rounded-lg">
234
+ <ResponsiveContainer width="100%" height="100%">
235
+ <LineChart
236
+ margin={{ top: 20, right: 30, left: 20, bottom: 20 }}
237
+ >
238
+ <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
239
+ <XAxis
240
+ dataKey="date"
241
+ type="category"
242
+ allowDuplicatedCategory={false}
243
+ tick={{ fontSize: 12 }}
244
+ interval="preserveStartEnd"
245
+ stroke="#9CA3AF"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  />
247
+ <YAxis stroke="#9CA3AF" />
248
+ <Tooltip content={<CustomTooltip />} />
249
+ <Legend />
250
+
251
+ {/* Total Line */}
252
+ {showTotal && (
253
+ <Line
254
+ data={filteredTotalData}
255
+ type="monotone"
256
+ dataKey="count"
257
+ stroke={COLORS['Total']}
258
+ name="Total"
259
+ strokeWidth={2}
260
+ dot={false}
261
+ />
262
+ )}
263
 
264
+ {/* Provider Lines */}
265
+ {selectedProviders.map(provider => (
266
+ <Line
267
+ key={provider}
268
+ data={providerData[provider]}
269
+ type="monotone"
270
+ dataKey="count"
271
+ stroke={COLORS[provider]}
272
+ name={provider}
273
+ strokeWidth={1.5}
274
+ dot={false}
275
+ />
276
+ ))}
277
+ </LineChart>
278
+ </ResponsiveContainer>
279
+ </div>
280
 
281
+ {/* Download Button */}
282
+ <div className="flex justify-center mt-4 mb-8">
283
+ <button
284
+ className="px-4 py-2 rounded-lg bg-green-500 text-white hover:bg-green-600"
285
+ onClick={() => {
286
+ // Get the currently visible data based on filters
287
+ const visibleData = [];
288
+
289
+ // Add provider data if showing
290
+ selectedProviders.forEach(provider => {
291
+ visibleData.push(...providerData[provider].map(d => ({ ...d, provider })));
292
+ });
293
+
294
+ // Add total data if showing
295
+ if (showTotal) {
296
+ visibleData.push(...filteredTotalData);
297
+ }
298
+
299
+ const csvContent = convertToCSV(visibleData);
300
+ const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
301
+ const link = document.createElement('a');
302
+ link.href = URL.createObjectURL(blob);
303
+ link.download = `${contentType}_counts.csv`;
304
+ link.click();
305
+ URL.revokeObjectURL(link.href);
306
+ }}
307
+ >
308
+ Download CSV
309
+ </button>
310
  </div>
311
+
312
+ {/* Major Releases Section */}
313
+ <div className="mt-12">
314
+ <div className="flex items-center justify-between mb-6">
315
+ <h2 className="text-2xl font-bold dark:text-white">
316
+ Major Releases ({Object.values(filteredModels).flat().length})
317
+ </h2>
318
+ <div className="flex items-center gap-3 bg-white dark:bg-gray-800 px-4 py-2 rounded-lg shadow-sm">
319
+ <label className="text-sm font-medium dark:text-gray-300">Min Likes:</label>
320
+ <input
321
+ type="number"
322
+ value={minLikes}
323
+ onChange={(e) => setMinLikes(Math.max(0, parseInt(e.target.value) || 0))}
324
+ className="w-20 px-2 py-1 border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white focus:outline-none focus:ring-2 focus:ring-green-500"
325
+ />
326
+ </div>
327
+ </div>
328
+ <div className="space-y-8">
329
+ {Object.entries(filteredModels)
330
+ .map(([monthKey, models]) => ({
331
+ monthKey,
332
+ models,
333
+ sortKey: models[0]?.sortKey || '' // Use the first model's sortKey
334
+ }))
335
+ .sort((a, b) => b.sortKey.localeCompare(a.sortKey)) // Sort by sortKey in descending order
336
+ .map(({ monthKey, models }) => (
337
+ <div key={monthKey} className="border-b border-gray-200 dark:border-gray-700 pb-4">
338
+ <h3 className="text-xl font-semibold mb-3 dark:text-white">{monthKey}</h3>
339
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
340
+ {models
341
+ .sort((a, b) => b.likes - a.likes) // Sort models within each month by likes
342
+ .map(model => (
343
+ <div
344
+ key={model.id}
345
+ className="p-4 rounded-lg border dark:border-gray-700 hover:shadow-md transition-shadow bg-white dark:bg-gray-800"
346
+ style={{ borderColor: COLORS[model.provider] }}
347
+ >
348
+ <div className="flex justify-between items-start">
349
+ <div>
350
+ <a
351
+ href={`https://huggingface.co/${model.id}`}
352
+ target="_blank"
353
+ rel="noopener noreferrer"
354
+ className="text-blue-600 hover:underline font-medium dark:text-blue-400"
355
+ >
356
+ {model.name}
357
+ </a>
358
+ <p className="text-sm text-gray-600 dark:text-gray-400">{model.provider}</p>
359
+ </div>
360
+ <div className="text-right">
361
+ <p className="text-sm font-medium dark:text-white">❤️ {model.likes}</p>
362
+ <p className="text-xs text-gray-500 dark:text-gray-400">{formatDate(model.createdAt)}</p>
363
+ </div>
364
  </div>
365
  </div>
366
+ ))}
367
+ </div>
368
  </div>
369
+ ))}
370
+ </div>
371
  </div>
372
  </div>
373
  </div>
src/utils/modelData.ts CHANGED
@@ -318,3 +318,38 @@ export const getTotalMonthlyData = (monthlyData: MonthlyActivity[]): MonthlyActi
318
  }
319
  ]).sort((a, b) => a.date.localeCompare(b.date));
320
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  }
319
  ]).sort((a, b) => a.date.localeCompare(b.date));
320
  };
321
+
322
+ // Convert monthly activity data to CSV format
323
+ export const convertToCSV = (data: MonthlyActivity[]): string => {
324
+ // Group data by date
325
+ const dataByDate: Record<string, Record<string, number>> = {};
326
+ const providers = new Set<string>();
327
+
328
+ // Initialize and collect data
329
+ data.forEach(({ date, provider, count }) => {
330
+ if (!dataByDate[date]) {
331
+ dataByDate[date] = {};
332
+ }
333
+ dataByDate[date][provider] = count;
334
+ providers.add(provider);
335
+ });
336
+
337
+ // Create CSV header
338
+ const header = ['Date', ...Array.from(providers)];
339
+
340
+ // Create CSV rows
341
+ const rows = Object.entries(dataByDate)
342
+ .sort(([a], [b]) => a.localeCompare(b))
343
+ .map(([date, providerData]) => {
344
+ const row = [date];
345
+ header.slice(1).forEach(provider => {
346
+ row.push((providerData[provider] || 0).toString());
347
+ });
348
+ return row;
349
+ });
350
+
351
+ // Combine header and rows
352
+ return [header, ...rows]
353
+ .map(row => row.join(','))
354
+ .join('\n');
355
+ };