Team Ai
Apppublic

harshapitla/github-actions-analytics

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
FeatureInfluence.jsx224 linesDownload Raw Back to charts
1import React, { useMemo, useState } from "react";2import {3  ResponsiveContainer,4  BarChart,5  Bar,6  XAxis,7  YAxis,8  Tooltip,9  CartesianGrid,10  LabelList,11  Cell,12} from "recharts";13import { formatNumberPrecise } from "../components/utils/uiUtils";14import formatSI from "../components/utils/formatSI";15 16const MIN_COUNT = 5;17const SCALE_OPTIONS = [18  { id: "absolute", label: "Avg downloads" },19  { id: "relative", label: "% of leader" },20];21const SOURCE_OPTIONS = [22  { id: "all", label: "All features" },23  { id: "categories", label: "Categories" },24  { id: "tags", label: "Tags" },25];26 27function normalizeFeatureName(input) {28  if (!input) return "";29  let s = String(input);30  s = s.replace(/[_\-./]+/g, " ");31  s = s.replace(/([a-z])([A-Z])/g, "$1 $2");32  s = s.replace(/([A-Za-z])([0-9])/g, "$1 $2");33  s = s.replace(/\s+/g, " ").trim();34  return s35    .split(" ")36    .map((w) => w.charAt(0).toUpperCase() + w.slice(1))37    .join(" ");38}39 40function extractTokens(field) {41  if (!field) return [];42  return String(field)43    .replace(/[|/\\;]/g, ",")44    .split(",")45    .map((t) => t.trim())46    .filter(Boolean);47}48 49export default function FeatureInfluence({ rows }) {50  const [scale, setScale] = useState("absolute");51  const [source, setSource] = useState("all");52  const [activeFeature, setActiveFeature] = useState(null);53 54  const data = useMemo(() => {55    if (!rows?.length) return [];56 57    const map = {};58    rows.forEach((row) => {59      const downloads = Number(row.downloads_count) || 0;60      const categories = source !== "tags" ? extractTokens(row.categories) : [];61      const tags = source !== "categories" ? extractTokens(row.tags) : [];62      [...categories, ...tags].forEach((feat) => {63        const key = normalizeFeatureName(feat).toLowerCase();64        if (!key) return;65        map[key] = map[key] || { name: normalizeFeatureName(feat), sum: 0, count: 0 };66        map[key].sum += downloads;67        map[key].count += 1;68      });69    });70 71    return Object.values(map)72      .map((entry) => ({73        feature: entry.name,74        count: entry.count,75        avgDownloads: entry.count ? Math.round(entry.sum / entry.count) : 0,76      }))77      .filter((entry) => entry.count >= MIN_COUNT)78      .sort((a, b) => b.avgDownloads - a.avgDownloads)79      .slice(0, 14);80  }, [rows, source]);81 82  if (!data.length) {83    return (84      <div className="card" style={{ padding: 20 }}>85        <h3>Feature Influence</h3>86        <p className="small">Not enough feature data to generate insights.</p>87      </div>88    );89  }90 91  const maxDownload = Math.max(...data.map((item) => item.avgDownloads));92  const display = data.map((item, index) => ({93    ...item,94    featureShort: item.feature,95    relativeValue: maxDownload ? (item.avgDownloads / maxDownload) * 100 : 0,96    rank: index + 1,97  }));98 99  return (100    <>101      <div className="ams-simple-header">102        <div>103          <h3>Feature Influence</h3>104          <p>Which features from tags or categories correlate with higher downloads. Toggle source or scale to reframe the story.</p>105        </div>106        <div className="feature-controls">107          <div className="filter-group">108            <span className="filter-label">From:</span>109            {SOURCE_OPTIONS.map((option) => (110              <button111                key={option.id}112                className={`pill-button ${source === option.id ? "is-active" : ""}`}113                onClick={() => setSource(option.id)}114              >115                {option.label}116              </button>117            ))}118          </div>119          <div className="filter-group">120            <span className="filter-label">Scale:</span>121            {SCALE_OPTIONS.map((option) => (122              <button123                key={option.id}124                className={`pill-button ${scale === option.id ? "is-active" : ""}`}125                onClick={() => setScale(option.id)}126              >127                {option.label}128              </button>129            ))}130          </div>131        </div>132      </div>133 134      <div className="chart-area" style={{ height: 460 }}>135        <ResponsiveContainer>136          <BarChart137            data={display}138            layout="vertical"139            margin={{ top: 12, right: 40, left: 200, bottom: 12 }}140            barCategoryGap={14}141          >142            <CartesianGrid strokeDasharray="3 3" />143            <XAxis144              type="number"145              tickFormatter={(value) => (scale === "absolute" ? formatSI(value) : `${value.toFixed(0)}%`)}146            />147            <YAxis148              type="category"149              dataKey="featureShort"150              width={230}151              tick={<FeatureTick />}152            />153            <Tooltip154              formatter={(value, key, payload) => {155                if (key === "avgDownloads") return [formatNumberPrecise(value), "Avg downloads"];156                if (key === "count") return [`${value} extensions`, "Extensions"];157                if (key === "relativeValue") return [`${value.toFixed(1)}% of leader`, "Relative"];158                return value;159              }}160              labelFormatter={(label) => {161                const entry = display.find((item) => item.featureShort === label);162                return entry ? `${entry.rank}. ${entry.feature}` : label;163              }}164              contentStyle={{ borderRadius: 8, border: "1px solid #dbe3f4", fontSize: 13 }}165            />166            <Bar167              dataKey={scale === "absolute" ? "avgDownloads" : "relativeValue"}168              radius={[10, 10, 10, 10]}169              barSize={20}170              isAnimationActive={false}171              onMouseMove={(_, index) => setActiveFeature(display[index]?.feature)}172              onMouseLeave={() => setActiveFeature(null)}173            >174              {display.map((entry) => (175                <Cell176                  key={entry.feature}177                  fill={entry.feature === activeFeature ? "#7c3aed" : "rgba(124,58,237,0.75)"}178                  stroke="#7c3aed"179                  strokeWidth={entry.feature === activeFeature ? 2 : 0.6}180                />181              ))}182              <LabelList183                dataKey={scale === "absolute" ? "avgDownloads" : "relativeValue"}184                formatter={(value, entry) =>185                  scale === "absolute" ? formatSI(value) : `${value.toFixed(0)}%`186                }187                position="right"188                fontSize={11}189                fill="#0f172a"190              />191            </Bar>192          </BarChart>193        </ResponsiveContainer>194      </div>195 196      <p className="small" style={{ marginTop: 10, color: "#475569" }}>197        Features linked to popular workflows (containers, notebooks, packaging) consistently deliver higher downloads. Source toggle isolates whether the signal comes from category metadata or explicit tags.198      </p>199    </>200  );201}202 203function FeatureTick({ x, y, payload }) {204  const raw = payload.value || "";205  const lines = raw.length > 28 ? raw.match(/.{1,28}(\\s|$)/g) : [raw];206  const offsetY = -((lines.length - 1) * 6);207  return (208    <g transform={`translate(${x},${y + offsetY})`}>209      {lines.map((line, idx) => (210        <text211          key={idx}212          x={-4}213          y={idx * 12}214          textAnchor="end"215          fontSize={12}216          fill="#1f2933"217        >218          {line.trim()}219        </text>220      ))}221    </g>222  );223}224