diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d3b82ff..c92e2c8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1933,11 +1933,47 @@ "@babel/types": "^7.3.0" } }, + "@types/classnames": { + "version": "2.2.10", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz", + "integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ==" + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" }, + "@types/d3-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-2.0.0.tgz", + "integrity": "sha512-rGqfPVowNDTszSFvwoZIXvrPG7s/qKzm9piCRIH6xwTTRu7pPZ3ootULFnPkTt74B6i5lN0FpLQL24qGOw1uZA==" + }, + "@types/d3-path": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-1.0.8.tgz", + "integrity": "sha512-AZGHWslq/oApTAHu9+yH/Bnk63y9oFOMROtqPAtxl5uB6qm1x2lueWdVEjsjjV3Qc2+QfuzKIwIR5MvVBakfzA==" + }, + "@types/d3-scale": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-2.2.0.tgz", + "integrity": "sha512-oQFanN0/PiR2oySHfj+zAAkK1/p4LD32Nt1TMVmzk+bYHk7vgIg/iTXQWitp1cIkDw4LMdcgvO63wL+mNs47YA==", + "requires": { + "@types/d3-time": "*" + } + }, + "@types/d3-shape": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-1.3.2.tgz", + "integrity": "sha512-LtD8EaNYCaBRzHzaAiIPrfcL3DdIysc81dkGlQvv7WQP3+YXV7b0JJTtR1U3bzeRieS603KF4wUo+ZkJVenh8w==", + "requires": { + "@types/d3-path": "*" + } + }, + "@types/d3-time": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.0.10.tgz", + "integrity": "sha512-aKf62rRQafDQmSiv1NylKhIMmznsjRN+MnXRXTqHoqm0U/UZzVpdrtRnSIfdiLS616OuC1soYeX1dBg2n1u8Xw==" + }, "@types/eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", @@ -2101,6 +2137,11 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==" }, + "@types/lodash": { + "version": "4.14.150", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.150.tgz", + "integrity": "sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w==" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -2422,6 +2463,126 @@ } } }, + "@vx/axis": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/axis/-/axis-0.0.196.tgz", + "integrity": "sha512-AtVjoDXCmecKRp671ATV9Qky/MpQY1pV/O1VWULxhq5dujKXWPj0GtnuRR7I7VgB0XCoKpEh12gVTi7dUq2FhQ==", + "requires": { + "@types/classnames": "^2.2.9", + "@types/react": "*", + "@vx/group": "0.0.196", + "@vx/point": "0.0.196", + "@vx/shape": "0.0.196", + "@vx/text": "0.0.196", + "classnames": "^2.2.5", + "prop-types": "^15.6.0" + } + }, + "@vx/curve": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/curve/-/curve-0.0.196.tgz", + "integrity": "sha512-uGucJuuAOa0ezrSTqQE8BHkyNPAwGV1bNB8BP1V5Ln1GA7o0LBkqwHOdzShkFzsVoeDadRRGy2mNga6gHDGh8A==", + "requires": { + "@types/d3-shape": "^1.3.1", + "d3-shape": "^1.0.6" + } + }, + "@vx/gradient": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/gradient/-/gradient-0.0.196.tgz", + "integrity": "sha512-euz0GRKIMXil7cx3Bx9lVmgfncz3M2Yv2+AEMyRiBe3Ln3Y04rTP2W6o94bvQ+TXWRD5U18LPQQg7Myr6cnFDA==", + "requires": { + "@types/react": "*", + "prop-types": "^15.5.7" + } + }, + "@vx/grid": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/grid/-/grid-0.0.196.tgz", + "integrity": "sha512-u9zqpRA+k2qfickspWHCAgpqg/Sq55T3e3crPUvuAOsqiX/7doOd+LKbJJv0LRq6PVufdVRUBthTsW5F5qffrg==", + "requires": { + "@types/classnames": "^2.2.9", + "@types/react": "*", + "@vx/group": "0.0.196", + "@vx/point": "0.0.196", + "@vx/shape": "0.0.196", + "classnames": "^2.2.5", + "prop-types": "^15.6.2" + } + }, + "@vx/group": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/group/-/group-0.0.196.tgz", + "integrity": "sha512-neWYucGoyrWloEali12yEn//5pRVwSBtKpuzKlQ43yYTav8OLa869DtCSVeRlDG8TIcPvg9YWfWKKhGWKXRCtg==", + "requires": { + "@types/classnames": "^2.2.9", + "@types/react": "*", + "classnames": "^2.2.5", + "prop-types": "^15.6.2" + } + }, + "@vx/point": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/point/-/point-0.0.196.tgz", + "integrity": "sha512-dya1cwMwqQtRP+oQwC5x0N2bCiKENGqDd6Y4IuAKwkWMvxpjOAPoOlglZ9QKLDPzftUqLFxrjrsT5RlHTxyKnw==" + }, + "@vx/scale": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/scale/-/scale-0.0.196.tgz", + "integrity": "sha512-s3lVbwEni0F9AGACaJG3m071uXVryoOgogiU6+yXv1O1gUoZMMm6AjzCndcz9jq1sCFDNcIrxyf5LomOA2tQaQ==", + "requires": { + "@types/d3-scale": "^2.1.1", + "d3-scale": "^2.2.2" + } + }, + "@vx/shape": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/shape/-/shape-0.0.196.tgz", + "integrity": "sha512-09s4yv0IKmUrB/z/bRLW936DzbhbsxExHv0ocowh2Zv3zgObAaeyf0MkkLyr1nDTqKDCZlfwOEM+1moOyBFQBA==", + "requires": { + "@types/classnames": "^2.2.9", + "@types/d3-path": "^1.0.8", + "@types/d3-shape": "^1.3.1", + "@types/react": "*", + "@vx/curve": "0.0.196", + "@vx/group": "0.0.196", + "classnames": "^2.2.5", + "d3-path": "^1.0.5", + "d3-shape": "^1.2.0", + "prop-types": "^15.5.10" + } + }, + "@vx/text": { + "version": "0.0.196", + "resolved": "https://registry.npmjs.org/@vx/text/-/text-0.0.196.tgz", + "integrity": "sha512-KhhOtQyzqcZvO/cgYdMrsLLf0LYFT0u1cqJ4j8RVD8JcmTrhHmzFvL9lE6OXADroZxTSOFY3mNIbAPlshIabpw==", + "requires": { + "@types/classnames": "^2.2.9", + "@types/lodash": "^4.14.146", + "@types/react": "*", + "classnames": "^2.2.5", + "lodash": "^4.17.15", + "prop-types": "^15.7.2", + "reduce-css-calc": "^1.3.0" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=" + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + } + } + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -3934,6 +4095,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -4603,6 +4769,80 @@ "type": "^1.0.1" } }, + "d3-array": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz", + "integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw==" + }, + "d3-collection": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", + "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" + }, + "d3-color": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz", + "integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q==" + }, + "d3-format": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz", + "integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw==" + }, + "d3-interpolate": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", + "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", + "requires": { + "d3-color": "1" + } + }, + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", + "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", + "requires": { + "d3-array": "^1.2.0", + "d3-collection": "1", + "d3-format": "1", + "d3-interpolate": "1", + "d3-time": "1", + "d3-time-format": "2" + }, + "dependencies": { + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + } + } + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + }, + "d3-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", + "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" + }, + "d3-time-format": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz", + "integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==", + "requires": { + "d3-time": "1" + } + }, "damerau-levenshtein": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", @@ -9689,6 +9929,11 @@ "object-visit": "^1.0.0" } }, + "math-expression-evaluator": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz", + "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -12922,6 +13167,14 @@ } } }, + "reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "requires": { + "balanced-match": "^1.0.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index fe2cdca..db6cb10 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,12 +12,21 @@ "@testing-library/jest-dom": "^5.5.0", "@testing-library/react": "^10.0.4", "@testing-library/user-event": "^10.1.0", + "@types/d3-array": "^2.0.0", "@types/jest": "^25.2.1", "@types/node": "^12.12.38", "@types/react": "^16.9.34", "@types/react-dom": "^16.9.7", "@types/react-router-dom": "^5.1.5", + "@vx/axis": "0.0.196", + "@vx/curve": "0.0.196", + "@vx/gradient": "0.0.196", + "@vx/grid": "0.0.196", + "@vx/group": "0.0.196", + "@vx/scale": "0.0.196", + "@vx/shape": "0.0.196", "autoprefixer": "^9.7.6", + "d3-array": "^2.4.0", "date-fns": "^2.13.0", "npm-run-all": "^4.1.5", "postcss-cli": "^7.1.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ad9d84f..c1bc37b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,9 +2,10 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; import { LoginFailure } from "./components/LoginFailure"; import { NavBar } from "./components/NavBar"; +import { RecentListens } from "./components/RecentListens"; +import { ReportListens } from "./components/ReportListens"; import { useAuth } from "./hooks/use-auth"; import "./tailwind/generated.css"; -import { RecentListens } from "./components/RecentListens"; export function App() { const { isLoaded } = useAuth(); @@ -22,6 +23,7 @@ export function App() { + ); diff --git a/frontend/src/api/api.ts b/frontend/src/api/api.ts index ad77d54..2d6efdc 100644 --- a/frontend/src/api/api.ts +++ b/frontend/src/api/api.ts @@ -1,4 +1,7 @@ +import { formatISO, parseISO } from "date-fns"; import { Listen } from "./entities/listen"; +import { ListenReportItem } from "./entities/listen-report-item"; +import { ListenReportOptions } from "./entities/listen-report-options"; import { Pagination } from "./entities/pagination"; import { PaginationOptions } from "./entities/pagination-options"; import { User } from "./entities/user"; @@ -67,3 +70,33 @@ export const getRecentListens = async ( console.log("getRecentListens", { listens }); return listens; }; + +export const getListensReport = async ( + options: ListenReportOptions +): Promise => { + const { timeFrame, timeStart, timeEnd } = options; + + const res = await fetch( + `/api/v1/reports/listens?timeFrame=${timeFrame}&timeStart=${formatISO( + timeStart + )}&timeEnd=${formatISO(timeEnd)}`, + { + headers: getDefaultHeaders(), + } + ); + + switch (res.status) { + case 200: { + break; + } + case 401: { + throw new UnauthenticatedError(`No token or token expired`); + } + default: { + throw new Error(`Unable to getRecentListens: ${res.status}`); + } + } + + const rawItems: { count: number; date: string }[] = (await res.json()).items; + return rawItems.map(({ count, date }) => ({ count, date: parseISO(date) })); +}; diff --git a/frontend/src/api/entities/listen-report-item.ts b/frontend/src/api/entities/listen-report-item.ts new file mode 100644 index 0000000..39929c4 --- /dev/null +++ b/frontend/src/api/entities/listen-report-item.ts @@ -0,0 +1,4 @@ +export interface ListenReportItem { + date: Date; + count: number; +} diff --git a/frontend/src/api/entities/listen-report-options.ts b/frontend/src/api/entities/listen-report-options.ts new file mode 100644 index 0000000..c8b2006 --- /dev/null +++ b/frontend/src/api/entities/listen-report-options.ts @@ -0,0 +1,5 @@ +export interface ListenReportOptions { + timeFrame: "day" | "week" | "month" | "year"; + timeStart: Date; + timeEnd: Date; +} diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx index 9afc362..19c9942 100644 --- a/frontend/src/components/NavBar.tsx +++ b/frontend/src/components/NavBar.tsx @@ -22,6 +22,9 @@ export const NavBar: React.FC = () => { Your Listens + + Listens Report + > )} diff --git a/frontend/src/components/ReportListens.tsx b/frontend/src/components/ReportListens.tsx new file mode 100644 index 0000000..4ad42b9 --- /dev/null +++ b/frontend/src/components/ReportListens.tsx @@ -0,0 +1,252 @@ +import { AxisBottom, AxisLeft } from "@vx/axis"; +import { curveBasis } from "@vx/curve"; +import { GradientLightgreenGreen } from "@vx/gradient"; +import { Grid } from "@vx/grid"; +import { Group } from "@vx/group"; +import { scaleLinear, scaleTime } from "@vx/scale"; +import { Area, Line, LinePath } from "@vx/shape"; +import { extent } from "d3-array"; +import React, { useEffect, useState } from "react"; +import { Redirect } from "react-router-dom"; +import { getListensReport } from "../api/api"; +import { ListenReportItem } from "../api/entities/listen-report-item"; +import { ListenReportOptions } from "../api/entities/listen-report-options"; +import { useAuth } from "../hooks/use-auth"; + +export const ReportListens: React.FC = () => { + const { user } = useAuth(); + + const [reportOptions, setReportOptions] = useState({ + timeFrame: "day", + timeStart: new Date("2020-05-01"), + timeEnd: new Date(), + }); + const [report, setReport] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + (async () => { + setIsLoading(true); + + try { + const reportFromApi = await getListensReport(reportOptions); + setReport(reportFromApi); + } catch (err) { + console.error("Error while fetching recent listens:", err); + } finally { + setIsLoading(false); + } + })(); + }, [reportOptions, setReport, setIsLoading]); + + if (!user) { + return ; + } + + return ( + + + + Listen Report + + + {isLoading && ( + + Loading Listens + + )} + {report.length === 0 && ( + + Report is emtpy! :( + + )} + {report.length > 0 && ( + + + + )} + + + + ); +}; + +const ReportGraph: React.FC<{ + options: ListenReportOptions; + data: ListenReportItem[]; + width?: number; + height?: number; + margin?: { + top: number; + right: number; + bottom: number; + left: number; + }; +}> = ({ + options, + data, + width = 900, + height = 500, + margin = { left: 70, right: 70, top: 20, bottom: 80 }, +}) => { + // Then we'll create some bounds + const xMax = width - margin.left - margin.right; + const yMax = height - margin.top - margin.bottom; + + // We'll make some helpers to get at the data we want + const x = (d: ListenReportItem) => d.date; + const y = (d: ListenReportItem) => d.count; + + // responsive utils for axis ticks + const numTicksForHeight = (heightT: number): number => { + if (heightT <= 300) return 3; + if (300 < heightT && heightT <= 600) return 5; + return 10; + }; + + const numTicksForWidth = (widthT: number): number => { + if (widthT <= 300) return 2; + if (300 < widthT && widthT <= 400) return 5; + return 10; + }; + + // And then scale the graph by our data + const xScaleTime = scaleTime({ + range: [0, xMax], + domain: extent(data, x) as [Date, Date], + }); + + /* + const xScaleBand = scaleBand({ + range: [0, xMax], + domain: extent(data, x) as [Date, Date], + padding: 0.2, + }); + */ + const yScale = scaleLinear({ + range: [yMax, 0], + domain: [0, Math.max(...data.map(y))], + nice: true, + }); + + // Compose together the scale and accessor functions to get point functions + const compose = (scale: any, accessor: any) => (d: ListenReportItem) => + scale(accessor(d)); + const xPoint = compose(xScaleTime, x); + const yPoint = compose(yScale, y); + + return ( + + + + + xScaleTime(x(d))} + y0={(d) => yScale.range()[0]} + y1={(d) => yScale(y(d))} + strokeWidth={2} + stroke={"transparent"} + fill={"url(#linear)"} + curve={curveBasis} + /> + xScaleTime(x(d))} + y={(d) => yScale(y(d))} + stroke={"url('#linear')"} + strokeWidth={2} + curve={curveBasis} + /> + + + ({ + fill: "#8e205f", + textAnchor: "end", + fontSize: 10, + dx: "-0.25em", + dy: "0.25em", + })} + tickComponent={({ formattedValue, ...tickProps }) => ( + {formattedValue} + )} + /> + /> + + {(axis) => { + const tickLabelSize = 10; + const tickRotate = 45; + const tickColor = "#8e205f"; + const axisCenter = (axis.axisToPoint.x - axis.axisFromPoint.x) / 2; + return ( + + {axis.ticks.map((tick, i) => { + const tickX = tick.to.x; + const tickY = tick.to.y + tickLabelSize + axis.tickLength; + return ( + + + + {tick.formattedValue} + + + ); + })} + + {axis.label} + + + ); + }} + + + + ); +};
Listen Report
Report is emtpy! :(