Team Ai
Apppublic

harshapitla/github-actions-analytics

sourceHugging Faceupdated 10mo agoView on Hugging Face
0likes
SuccessTrajectory.jsx204 linesDownload Raw Back to charts
1import React, { useMemo, useState } from "react";2import {3  ResponsiveContainer,4  ComposedChart,5  Area,6  Line,7  XAxis,8  YAxis,9  Tooltip,10  CartesianGrid,11  ReferenceArea,12} from "recharts";13import formatSI from "../components/utils/formatSI";14 15export default function SuccessTrajectory({ rows }) {16  const [viewMode, setViewMode] = useState("median"); 17 18  const data = useMemo(() => {19    const buckets = {20      1: [],21      2: [],22      3: [],23      4: [],24      5: [],25    };26 27    rows.forEach((r) => {28      const ratingRaw = Number(r.rating);29      if (!Number.isFinite(ratingRaw)) return;30 31      let bucket = Math.floor(ratingRaw);32      if (bucket <= 1) bucket = 1;33      if (bucket > 5) bucket = 5;34 35      buckets[bucket].push(Number(r.downloads_count) || 0);36    });37 38    const out = [];39    for (let b = 1; b <= 5; b++) {40      const arr = buckets[b].slice().sort((a, b) => a - b);41      if (arr.length === 0) {42        out.push({ rating: b, median: 0, p25: 0, p75: 0, count: 0 });43      } else {44        const p25 = arr[Math.floor(arr.length * 0.25)];45        const median = arr[Math.floor(arr.length * 0.5)];46        const p75 = arr[Math.floor(arr.length * 0.75)];47 48        out.push({49          rating: b,50          median,51          p25,52          p75,53          count: arr.length,54        });55      }56    }57    return out;58  }, [rows]);59 60  const tooltipFormatter = (value, key) => {61    if (key === "median") return [`${formatSI(value)}`, "Median downloads"];62    if (key === "p25") return [`${formatSI(value)}`, "Lower range (25th pct.)"];63    if (key === "p75") return [`${formatSI(value)}`, "Upper range (75th pct.)"];64    return [value, key];65  };66 67  const tooltipLabelFormatter = (label) => {68    const entry = data.find((d) => d.rating === label);69    if (!entry) return `Rating ${label}`;70    return `Rating ${label} • ${entry.count.toLocaleString()} extensions`;71  };72 73  const rangeMax = Math.max(...data.map((d) => d.p75));74 75  return (76    <>77      <h3>Success Trajectory Map</h3>78 79      <p className="small">80        Shows how downloads change across rating levels. The shaded band81        represents the typical range for most extensions, while the line shows82        the median.83      </p>84 85      <div className="feature-controls" style={{ justifyContent: "flex-start", marginBottom: 12 }}>86        <div className="filter-group">87          <span className="filter-label">View:</span>88          {[89            { id: "median", label: "Median + range" },90            { id: "range", label: "Range only" },91          ].map((option) => (92            <button93              key={option.id}94              className={`pill-button ${viewMode === option.id ? "is-active" : ""}`}95              onClick={() => setViewMode(option.id)}96            >97              {option.label}98            </button>99          ))}100        </div>101      </div>102 103      <div className="chart-area">104        <ResponsiveContainer width="100%" height={440}>105          <ComposedChart106            data={data}107            margin={{ top: 10, right: 40, left: 80, bottom: 30 }}108          >109            <CartesianGrid strokeDasharray="3 3" vertical={false} />110            <XAxis111              dataKey="rating"112              type="number"113              domain={[1, 5]}114              ticks={[1, 2, 3, 4, 5]}115              tick={{ fill: "#1f2933", fontSize: 13 }}116              label={{ value: "Rating", position: "insideBottom", offset: -6, fill: "#1f2933", fontSize: 13 }}117            />118            <YAxis119              yAxisId="range"120              tickFormatter={(v) => formatSI(v)}121              tick={{ fill: "#1f2933", fontSize: 12 }}122              label={{ value: "Downloads (range)", angle: -90, position: "insideLeft", offset: -10, fill: "#1f2933" }}123            />124            <YAxis125              yAxisId="median"126              orientation="right"127              tickFormatter={(v) => formatSI(v)}128              tick={{ fill: "#64748b", fontSize: 11 }}129              domain={[0, Math.max(...data.map((d) => d.median)) * 1.5]}130              label={{ value: "Median scale", angle: 90, position: "insideRight", offset: 18, fill: "#64748b" }}131            />132            <Tooltip133              formatter={tooltipFormatter}134              labelFormatter={tooltipLabelFormatter}135              contentStyle={{136                borderRadius: 8,137                border: "1px solid #d1d5db",138                fontSize: 13,139              }}140            />141            <Area142              type="stepAfter"143              dataKey="p75"144              yAxisId="range"145              stroke="#96c8ff"146              strokeWidth={1.5}147              fill="#dceeff"148              fillOpacity={0.7}149              isAnimationActive={false}150            />151            <Area152              type="stepAfter"153              dataKey="p25"154              yAxisId="range"155              stroke="#cde5ff"156              strokeWidth={1}157              fill="#f5fbff"158              fillOpacity={0.65}159              isAnimationActive={false}160            />161            {viewMode !== "range" && (162              <Line163                type="stepAfter"164                dataKey="median"165                yAxisId="median"166                stroke="#1d4ed8"167                strokeWidth={3}168                dot={{ r: 7, stroke: "#1d4ed8", strokeWidth: 2, fill: "#fff" }}169                activeDot={{ r: 9 }}170                isAnimationActive={false}171              />172            )}173            {rangeMax > 0 && (174              <ReferenceArea175                x1={1.95}176                x2={2.5}177                y1={data.find((d) => d.rating === 2)?.p25}178                y2={data.find((d) => d.rating === 2)?.p75}179                yAxisId="range"180                stroke="none"181                fillOpacity={0}182                label={{183                  value: "Range spike driven by a few popular extensions",184                  fill: "#94a3b8",185                  position: "top",186                  offset: 14,187                  dy: -4,188                  fontSize: 12,189                }}190              />191            )}192          </ComposedChart>193        </ResponsiveContainer>194      </div>195 196      <p className="small" style={{ marginTop: 10, color: "#475569" }}>197        Higher ratings generally align with steady download levels. Extensions198        with very high downloads sometimes receive mixed ratings because they199        serve large and diverse user groups.200      </p>201    </>202  );203}204