From 6c8eae2b3977ceb512c5b36a8f3854ddcb2e246d Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:38 +0100 Subject: [PATCH 01/11] feat(trends): add results count --- client/src/api/trends/publications.ts | 8 +++++-- .../pages/trends/components/info/index.tsx | 21 +++++++++++++++++++ client/src/pages/trends/config/views.ts | 4 ++-- client/src/pages/trends/layout/index.tsx | 6 +++--- client/src/pages/trends/locales/en.json | 11 ++++++++-- client/src/pages/trends/locales/fr.json | 1 + 6 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 client/src/pages/trends/components/info/index.tsx diff --git a/client/src/api/trends/publications.ts b/client/src/api/trends/publications.ts index 49df94c2..d6b39c5c 100644 --- a/client/src/api/trends/publications.ts +++ b/client/src/api/trends/publications.ts @@ -30,6 +30,7 @@ export async function getPublicationsTrends({ cursor, model, query, years, filte model: { terms: { field: CONFIG[model].field, size: 60000 / years.length } }, }, }, + count: { value_count: { field: "id.keyword" } }, }, } @@ -47,6 +48,7 @@ export async function getPublicationsTrends({ cursor, model, query, years, filte } const json = await res.json() + const count: number = json?.aggregations?.count?.value || 0 const aggregation: TrendsAggregation = json?.aggregations?.["years"]?.buckets if (!aggregation?.length) { @@ -55,7 +57,7 @@ export async function getPublicationsTrends({ cursor, model, query, years, filte } const trends = publicationsTrends(aggregation, cursor, years, normalized) - return trends + return { ...trends, count: count } } export async function getCitationsTrends({ cursor, model, query, years, filters, normalized }: TrendsArgs) { @@ -84,6 +86,7 @@ export async function getCitationsTrends({ cursor, model, query, years, filters, ), }, }, + count: { value_count: { field: "id.keyword" } }, }, } @@ -101,6 +104,7 @@ export async function getCitationsTrends({ cursor, model, query, years, filters, } const json = await res.json() + const count: number = json?.aggregations?.count?.value || 0 const aggregation: ElasticBuckets = json?.aggregations?.model?.buckets if (!aggregation?.length) { @@ -109,5 +113,5 @@ export async function getCitationsTrends({ cursor, model, query, years, filters, } const trends = citationsTrends(aggregation, cursor, years, normalized) - return trends + return { ...trends, count: count } } diff --git a/client/src/pages/trends/components/info/index.tsx b/client/src/pages/trends/components/info/index.tsx new file mode 100644 index 00000000..1f483981 --- /dev/null +++ b/client/src/pages/trends/components/info/index.tsx @@ -0,0 +1,21 @@ +import { Container, Text } from "@dataesr/dsfr-plus" +import useTrends from "../../hooks/useTrends" +import { useIntl } from "react-intl" + +export default function TrendsViewInfo() { + const intl = useIntl() + const { trends, isFetching } = useTrends() + + if (isFetching) return null + + const topicsCount = trends?.pages?.[0]?.total || 0 + const publicationsCount = trends?.pages?.[0]?.count || 0 + + return ( + + + {intl.formatMessage({ id: "trends.views.info.text" }, { topics: topicsCount, publications: publicationsCount })} + + + ) +} diff --git a/client/src/pages/trends/config/views.ts b/client/src/pages/trends/config/views.ts index cf4f0f22..22fc0b97 100644 --- a/client/src/pages/trends/config/views.ts +++ b/client/src/pages/trends/config/views.ts @@ -2,8 +2,8 @@ import { TrendsView, TrendsViews } from "../../../types/trends" export const TRENDS_VIEWS: TrendsViews = [ { id: "count-top", label: "count", order: "top", nextView: "" }, - { id: "diff-top", label: "diff", order: "top", nextView: "diff-bot" }, - { id: "diff-bot", label: "diff", order: "bot", nextView: "diff-top" }, + // { id: "diff-top", label: "diff", order: "top", nextView: "diff-bot" }, + // { id: "diff-bot", label: "diff", order: "bot", nextView: "diff-top" }, { id: "trend-top", label: "trend", order: "top", nextView: "trend-bot" }, { id: "trend-bot", label: "trend", order: "bot", nextView: "trend-top" }, ] diff --git a/client/src/pages/trends/layout/index.tsx b/client/src/pages/trends/layout/index.tsx index 5b8dc9f7..3e230619 100644 --- a/client/src/pages/trends/layout/index.tsx +++ b/client/src/pages/trends/layout/index.tsx @@ -3,6 +3,7 @@ import TrendsHeader from "../components/header" import TrendsView from "../components/panel" import TrendsOptionsBar from "../components/options-bar" import TrendsOptionsModals from "../components/options-bar/modals" +import TrendsViewInfo from "../components/info" import useIntegration from "../hooks/useIntegration" export default function TrendsLayout() { @@ -15,9 +16,8 @@ export default function TrendsLayout() { - - - + + ) } diff --git a/client/src/pages/trends/locales/en.json b/client/src/pages/trends/locales/en.json index 0731e15e..b41272f9 100644 --- a/client/src/pages/trends/locales/en.json +++ b/client/src/pages/trends/locales/en.json @@ -22,8 +22,14 @@ "trends.select-model.label": "Themes", "trends.select-model.entity-fishing": "Wikidata", "trends.select-model.entity-fishing.description": "Themes detected by entity-fishing", - "trends.select-model.open-alex": "OpenAlex", - "trends.select-model.open-alex.description": "Themes detected by OpenAlex", + "trends.select-model.open-alex-topics": "OpenAlex - Topics", + "trends.select-model.open-alex-topics.description": "Themes detected by OpenAlex", + "trends.select-model.open-alex-subfields": "OpenAlex - Subfields", + "trends.select-model.open-alex-subfields.description": "Themes detected by OpenAlex", + "trends.select-model.open-alex-fields": "OpenAlex - Fields", + "trends.select-model.open-alex-fields.description": "Themes detected by OpenAlex", + "trends.select-model.open-alex-domains": "OpenAlex - Domains", + "trends.select-model.open-alex-domains.description": "Themes detected by OpenAlex", "trends.select-source.label": "Volume", "trends.select-source.publications": "Publications", "trends.select-source.publications.description": "Number of publications", @@ -35,6 +41,7 @@ "trends.views.header.trend": "Trend ({count} years)", "trends.views.page-next": "Next", "trends.views.page-previous": "Back", + "trends.views.info.text": "Found {topics, plural, =0 {# topics} one {# topic} other {# topics}} (within {publications, plural, =0 {# publications} one {# publication} other {# publications}})", "trends.line-chart.xAxis.accessibility.description": "Years from {min, number} to {max, number}", "trends.line-chart.yAxis.accessibility.description": "Number of {source}", "trends.line-chart.yAxis.title.text": "Number of {source}", diff --git a/client/src/pages/trends/locales/fr.json b/client/src/pages/trends/locales/fr.json index fbcc7bb7..083d545d 100644 --- a/client/src/pages/trends/locales/fr.json +++ b/client/src/pages/trends/locales/fr.json @@ -41,6 +41,7 @@ "trends.views.header.trend": "Tendance ({count} ans)", "trends.views.page-next": "Suivante", "trends.views.page-previous": "Précédent", + "trends.views.info.text": "{topics, plural, =0 {# thématiques} one {# thématique} other {# thématiques}} détectées (parmi {publications, plural, =0 {# publications} one {# publication} other {# publications}})", "trends.line-chart.xAxis.accessibility.description": "Années de {min, number} à {max, number}", "trends.line-chart.yAxis.accessibility.description": "Nombre de {source}", "trends.line-chart.yAxis.title.text": "Nombre de {source}", From dfc99ccba6c60d3dd1a820dc2caa2a8b43f1db60 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:38 +0100 Subject: [PATCH 02/11] feat(trends): remove item diff --- .../pages/trends/components/panel/index.tsx | 8 +++-- .../trends/components/panel/item/index.tsx | 30 +++++++++---------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/client/src/pages/trends/components/panel/index.tsx b/client/src/pages/trends/components/panel/index.tsx index a5153c49..8f4bf4c3 100644 --- a/client/src/pages/trends/components/panel/index.tsx +++ b/client/src/pages/trends/components/panel/index.tsx @@ -97,9 +97,11 @@ function TrendsViewItems() { export default function TrendsView() { return ( - - - + + + + + ) } diff --git a/client/src/pages/trends/components/panel/item/index.tsx b/client/src/pages/trends/components/panel/item/index.tsx index 4c640ea9..73ae6b3a 100644 --- a/client/src/pages/trends/components/panel/item/index.tsx +++ b/client/src/pages/trends/components/panel/item/index.tsx @@ -13,13 +13,13 @@ export default function TrendsViewItem({ item }) { const isMobile = ["xs", "sm"].includes(screen) const viewLabel = trendsViewGetConfig(view).label - const diffColor = itemGetColor(item, "diff", normalized) - const diffLabel = - item.diff === Infinity - ? Object.keys(item.count).length === 1 - ? "NEW" - : "RESURGENT" - : `${Number(item.diff * 100).toFixed(0.1)} %` + // const diffColor = itemGetColor(item, "diff", normalized) + // const diffLabel = + // item.diff === Infinity + // ? Object.keys(item.count).length === 1 + // ? "NEW" + // : "RESURGENT" + // : `${Number(item.diff * 100).toFixed(0.1)} %` const slopeColor = itemGetColor(item, normalized ? "norm_slope" : "slope", normalized) const trendState = itemGetTrendState(item, normalized) @@ -43,13 +43,13 @@ export default function TrendsViewItem({ item }) { {(!isMobile || viewLabel == "count") && ( - - -
{item?.count?.[trendsYears.max] || 0}
+ + +
{item?.count?.[trendsYears.max] || 0}
)} - {(!isMobile || viewLabel == "diff") && ( + {/* {(!isMobile || viewLabel == "diff") && ( @@ -57,11 +57,11 @@ export default function TrendsViewItem({ item }) { - )} + )} */} {(!isMobile || viewLabel == "trend") && ( - - - + + + {trendState} From 468cd46f5e9839596bc29a1a71e1030e055b91ef Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:38 +0100 Subject: [PATCH 03/11] feat(trends): mobile layout --- client/src/pages/trends/components/panel/focus/index.tsx | 2 +- client/src/pages/trends/components/panel/header/index.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/pages/trends/components/panel/focus/index.tsx b/client/src/pages/trends/components/panel/focus/index.tsx index a85cdedc..a69a3a9a 100644 --- a/client/src/pages/trends/components/panel/focus/index.tsx +++ b/client/src/pages/trends/components/panel/focus/index.tsx @@ -7,7 +7,7 @@ export default function TrendsFocus({ item }) { const { source } = useTrendsContext() return ( - + diff --git a/client/src/pages/trends/components/panel/header/index.tsx b/client/src/pages/trends/components/panel/header/index.tsx index 9518c354..972d4093 100644 --- a/client/src/pages/trends/components/panel/header/index.tsx +++ b/client/src/pages/trends/components/panel/header/index.tsx @@ -71,7 +71,7 @@ export default function TrendsViewHeader() { return ( - + {intl.formatMessage({ id: `trends.views.header.domains` })} @@ -90,7 +90,7 @@ export default function TrendsViewHeader() { )} {isMobile && ( - + From 792c21c5ca7ded7fd9bb0d7d121611d36d28d5e6 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:39 +0100 Subject: [PATCH 04/11] refactor(trends): rename ranking --- client/src/api/trends/publications.ts | 20 +++- client/src/api/trends/trends.ts | 2 +- .../pages/trends/components/info/index.tsx | 2 +- .../trends/components/panel/button/index.tsx | 29 ----- .../trends/components/panel/header/index.tsx | 101 ----------------- .../trends/components/parameters/modal.tsx | 4 +- .../{panel => ranking}/_utils/index.ts | 0 .../{panel => ranking}/focus/index.tsx | 5 +- .../components/ranking/header/index.tsx | 103 ++++++++++++++++++ .../components/{panel => ranking}/index.tsx | 30 ++--- .../{panel => ranking}/item/index.tsx | 26 +++-- .../{panel => ranking}/skeleton/index.tsx | 6 +- .../src/pages/trends/config/rankingSorts.ts | 17 +++ client/src/pages/trends/config/views.ts | 15 --- client/src/pages/trends/context/index.tsx | 24 ---- .../pages/trends/context/rankingContext.tsx | 15 +++ client/src/pages/trends/hooks/useTrends.ts | 5 +- client/src/pages/trends/hooks/useWikidata.ts | 14 ++- client/src/pages/trends/index.tsx | 5 +- client/src/pages/trends/integration/index.tsx | 5 +- client/src/pages/trends/layout/index.tsx | 10 +- client/src/pages/trends/locales/en.json | 14 +-- client/src/pages/trends/locales/fr.json | 14 +-- client/src/types/trends.ts | 27 ++++- 24 files changed, 250 insertions(+), 243 deletions(-) delete mode 100644 client/src/pages/trends/components/panel/button/index.tsx delete mode 100644 client/src/pages/trends/components/panel/header/index.tsx rename client/src/pages/trends/components/{panel => ranking}/_utils/index.ts (100%) rename client/src/pages/trends/components/{panel => ranking}/focus/index.tsx (62%) create mode 100644 client/src/pages/trends/components/ranking/header/index.tsx rename client/src/pages/trends/components/{panel => ranking}/index.tsx (78%) rename client/src/pages/trends/components/{panel => ranking}/item/index.tsx (71%) rename client/src/pages/trends/components/{panel => ranking}/skeleton/index.tsx (62%) create mode 100644 client/src/pages/trends/config/rankingSorts.ts delete mode 100644 client/src/pages/trends/config/views.ts delete mode 100644 client/src/pages/trends/context/index.tsx create mode 100644 client/src/pages/trends/context/rankingContext.tsx diff --git a/client/src/api/trends/publications.ts b/client/src/api/trends/publications.ts index d6b39c5c..6b281d2c 100644 --- a/client/src/api/trends/publications.ts +++ b/client/src/api/trends/publications.ts @@ -1,13 +1,20 @@ import { postHeaders, publicationsIndex } from "../../config/api" import { ElasticAggregation, ElasticBucket, ElasticBuckets } from "../../types/commons" -import { TrendsArgs } from "../../types/trends" +import { TrendsArgs, TrendsRanking } from "../../types/trends" import { FIELDS } from "../publications/_utils/constants" import { CONFIG } from "./config" import { citationsTrends, publicationsTrends } from "./trends" type TrendsAggregation = Array -export async function getPublicationsTrends({ cursor, model, query, years, filters, normalized }: TrendsArgs) { +export async function getPublicationsTrends({ + cursor, + model, + query, + years, + filters, + normalized, +}: TrendsArgs): Promise { const body: any = { size: 0, query: { @@ -60,7 +67,14 @@ export async function getPublicationsTrends({ cursor, model, query, years, filte return { ...trends, count: count } } -export async function getCitationsTrends({ cursor, model, query, years, filters, normalized }: TrendsArgs) { +export async function getCitationsTrends({ + cursor, + model, + query, + years, + filters, + normalized, +}: TrendsArgs): Promise { const body: any = { size: 0, query: { diff --git a/client/src/api/trends/trends.ts b/client/src/api/trends/trends.ts index cf876d89..9a132bad 100644 --- a/client/src/api/trends/trends.ts +++ b/client/src/api/trends/trends.ts @@ -49,7 +49,7 @@ function computeTrends(data: Array, cursor: number, years: Array, n .slice(minItems, maxItems) const trends = { - views: { + ranking: { "count-top": topCount, // "diff-top": topDiff, // "diff-bot": botDiff, diff --git a/client/src/pages/trends/components/info/index.tsx b/client/src/pages/trends/components/info/index.tsx index 1f483981..86765f83 100644 --- a/client/src/pages/trends/components/info/index.tsx +++ b/client/src/pages/trends/components/info/index.tsx @@ -14,7 +14,7 @@ export default function TrendsViewInfo() { return ( - {intl.formatMessage({ id: "trends.views.info.text" }, { topics: topicsCount, publications: publicationsCount })} + {intl.formatMessage({ id: "trends.ranking.info.text" }, { topics: topicsCount, publications: publicationsCount })} ) diff --git a/client/src/pages/trends/components/panel/button/index.tsx b/client/src/pages/trends/components/panel/button/index.tsx deleted file mode 100644 index bd733453..00000000 --- a/client/src/pages/trends/components/panel/button/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { useIntl } from "react-intl" -import { useTrendsContext } from "../../../context" -import { trendsViewFromLabel, trendsViewGetConfig } from "../../../config/views" -import { Button } from "@dataesr/dsfr-plus" - -export default function TrendsViewButton({ label }) { - const intl = useIntl() - const { view, setView, setFocus } = useTrendsContext() - const viewConfig = trendsViewGetConfig(view) - const defaultView = trendsViewFromLabel(label) - - const isSelected = Boolean(label === viewConfig.label) - const onButtonClick = () => { - isSelected ? viewConfig?.nextView && setView(viewConfig.nextView) : setView(defaultView) - setFocus("") - } - - return ( - - ) -} diff --git a/client/src/pages/trends/components/panel/header/index.tsx b/client/src/pages/trends/components/panel/header/index.tsx deleted file mode 100644 index 972d4093..00000000 --- a/client/src/pages/trends/components/panel/header/index.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Button, Col, MenuButton, MenuItem, Row, Text } from "@dataesr/dsfr-plus" -import { useIntl } from "react-intl" -import { useTrendsContext } from "../../../context" -import { trendsViewFromLabel, trendsViewGetConfig } from "../../../config/views" -import useScreenSize from "../../../../../hooks/useScreenSize" -import useTrends from "../../../hooks/useTrends" -import useOptions from "../../../hooks/useOptions" - -function TrendsViewMobileButton() { - const intl = useIntl() - const { - trendsYears: { min, max }, - } = useTrends() - const { view, setView, setFocus } = useTrendsContext() - const viewConfig = trendsViewGetConfig(view) - - const onMenuAction = (key: string) => { - key === viewConfig.label ? viewConfig?.nextView && setView(viewConfig.nextView) : setView(trendsViewFromLabel(key)) - setFocus("") - } - - return ( - - {intl.formatMessage({ id: `trends.views.header.count` }, { max: max })} - {/* {intl.formatMessage({ id: `trends.views.header.diff` })} */} - {intl.formatMessage({ id: `trends.views.header.trend` }, { count: max - min + 1 })} - - ) -} - -function TrendsViewButton({ label }) { - const intl = useIntl() - const { - trendsYears: { min, max }, - } = useTrends() - const { view, setView, setFocus } = useTrendsContext() - const { handlePageChange } = useOptions() - const viewConfig = trendsViewGetConfig(view) - const defaultView = trendsViewFromLabel(label) - - const isSelected = Boolean(label === viewConfig.label) - const onButtonClick = () => { - isSelected ? viewConfig?.nextView && setView(viewConfig.nextView) : setView(defaultView) // change view - handlePageChange(1) // reset page - setFocus("") // reset focus - } - - return ( - - ) -} - -export default function TrendsViewHeader() { - const intl = useIntl() - const { screen } = useScreenSize() - const isMobile = ["xs", "sm"].includes(screen) - - return ( - - - - {intl.formatMessage({ id: `trends.views.header.domains` })} - - - {!isMobile && ( - <> - - - - {/* - - */} - - - - - )} - {isMobile && ( - - - - - - )} - - ) -} diff --git a/client/src/pages/trends/components/parameters/modal.tsx b/client/src/pages/trends/components/parameters/modal.tsx index ddafcc65..cf17e211 100644 --- a/client/src/pages/trends/components/parameters/modal.tsx +++ b/client/src/pages/trends/components/parameters/modal.tsx @@ -2,13 +2,13 @@ import { Button, Container } from "@dataesr/dsfr-plus" import Modal from "../../../../components/modal" import ToggleNormalize from "./toggle-normalize" import { useIntl } from "react-intl" -import { useTrendsContext } from "../../context" import useTrends from "../../hooks/useTrends" +import useOptions from "../../hooks/useOptions" export default function TrendsParametersModal() { const intl = useIntl() const { isFetching } = useTrends() - const { setNormalized } = useTrendsContext() + const { setNormalized } = useOptions() const resetParameters = () => setNormalized(true) diff --git a/client/src/pages/trends/components/panel/_utils/index.ts b/client/src/pages/trends/components/ranking/_utils/index.ts similarity index 100% rename from client/src/pages/trends/components/panel/_utils/index.ts rename to client/src/pages/trends/components/ranking/_utils/index.ts diff --git a/client/src/pages/trends/components/panel/focus/index.tsx b/client/src/pages/trends/components/ranking/focus/index.tsx similarity index 62% rename from client/src/pages/trends/components/panel/focus/index.tsx rename to client/src/pages/trends/components/ranking/focus/index.tsx index a69a3a9a..41c0cbea 100644 --- a/client/src/pages/trends/components/panel/focus/index.tsx +++ b/client/src/pages/trends/components/ranking/focus/index.tsx @@ -1,10 +1,9 @@ import { Container } from "@dataesr/dsfr-plus" -import { useTrendsContext } from "../../../context" import LineChart from "../../line-chart" import Wikidata from "../../wiki" -export default function TrendsFocus({ item }) { - const { source } = useTrendsContext() +export default function TrendsRankingFocus({ item }) { + const source = "publications" return ( diff --git a/client/src/pages/trends/components/ranking/header/index.tsx b/client/src/pages/trends/components/ranking/header/index.tsx new file mode 100644 index 00000000..f05e2fc8 --- /dev/null +++ b/client/src/pages/trends/components/ranking/header/index.tsx @@ -0,0 +1,103 @@ +import { Button, Col, MenuButton, MenuItem, Row, Text } from "@dataesr/dsfr-plus" +import { useIntl } from "react-intl" +import { useTrendsRankingContext } from "../../../context/rankingContext" +import { trendsRankingSortFromId, trendsRankingSortFromLabel } from "../../../config/rankingSorts" +import useScreenSize from "../../../../../hooks/useScreenSize" +import useTrends from "../../../hooks/useTrends" +import useOptions from "../../../hooks/useOptions" + +function TrendsRankingHeaderMobileButton() { + const intl = useIntl() + const { + trendsYears: { min, max }, + } = useTrends() + const { sort, setSort, setFocus } = useTrendsRankingContext() + const currentSort = trendsRankingSortFromId(sort) + + const onMenuAction = (key: string) => { + key === currentSort.label + ? currentSort?.nextSort && setSort(currentSort.nextSort) + : setSort(trendsRankingSortFromLabel(key).id) + setFocus("") + } + + return ( + + {intl.formatMessage({ id: `trends.ranking.header.count` }, { max: max })} + {/* {intl.formatMessage({ id: `trends.ranking.header.diff` })} */} + {intl.formatMessage({ id: `trends.ranking.header.trend` }, { count: max - min + 1 })} + + ) +} + +function TrendsRankingHeaderButton({ label }) { + const intl = useIntl() + const { + trendsYears: { min, max }, + } = useTrends() + const { sort, setSort, setFocus } = useTrendsRankingContext() + const { handlePageChange } = useOptions() + const currentSort = trendsRankingSortFromId(sort) + const defaultSort = trendsRankingSortFromLabel(label) + + const isSelected = Boolean(label === currentSort.label) + const onButtonClick = () => { + isSelected ? currentSort?.nextSort && setSort(currentSort.nextSort) : setSort(defaultSort.id) // change sorting + handlePageChange(1) // reset page + setFocus("") // reset focus + } + + return ( + + ) +} + +export default function TrendsRankingHeader() { + const intl = useIntl() + const { screen } = useScreenSize() + const isMobile = ["xs", "sm"].includes(screen) + + return ( + + + + {intl.formatMessage({ id: `trends.ranking.header.domains` })} + + + {!isMobile && ( + <> + + + + {/* + + */} + + + + + )} + {isMobile && ( + + + + + + )} + + ) +} diff --git a/client/src/pages/trends/components/panel/index.tsx b/client/src/pages/trends/components/ranking/index.tsx similarity index 78% rename from client/src/pages/trends/components/panel/index.tsx rename to client/src/pages/trends/components/ranking/index.tsx index 8f4bf4c3..bdd4584f 100644 --- a/client/src/pages/trends/components/panel/index.tsx +++ b/client/src/pages/trends/components/ranking/index.tsx @@ -3,20 +3,20 @@ import { useIntl } from "react-intl" import { useInView } from "react-intersection-observer" import { Button, ButtonGroup, Container } from "@dataesr/dsfr-plus" import useTrends from "../../hooks/useTrends" -import { useTrendsContext } from "../../context" +import { TrendsRankingContext, useTrendsRankingContext } from "../../context/rankingContext" import useOptions from "../../hooks/useOptions" -import TrendsViewItem from "./item" -import TrendsViewHeader from "./header" +import TrendsRankingItem from "./item" +import TrendsRankingHeader from "./header" import BaseSkeleton from "../../../../components/skeleton/base-skeleton" const itemsPerScroll = 10 const scrollPerPage = 3 const scrollYTop = 200 -function TrendsViewItems() { +function TrendsRankingItems() { const intl = useIntl() const [ref, inView] = useInView() - const { view } = useTrendsContext() + const { sort } = useTrendsRankingContext() const { currentPage, handlePageChange } = useOptions() const { trends, fetchNextPage, isFetching, isFetchingNextPage, hasNextPage, error } = useTrends() @@ -40,7 +40,9 @@ function TrendsViewItems() { return ( <>
- {trends.pages.map((page) => page.views?.[view].map((item, index) => ))} + {trends.pages.map((page) => + page.ranking?.[sort].map((item, index) => ) + )}
{isFetchingNextPage && } {!isFetchingNextPage && !shouldChangePage && hasNextPage &&
} @@ -54,7 +56,7 @@ function TrendsViewItems() { onClick={() => navigateToPage(currentPage - 1)} disabled={currentPage === 1} > - {intl.formatMessage({ id: "trends.views.page-previous" })} + {intl.formatMessage({ id: "trends.ranking.page-previous" })} {currentPage > 1 && ( @@ -95,13 +97,15 @@ function TrendsViewItems() { ) } -export default function TrendsView() { +export default function TrendsRanking() { return ( - - - - + + + + + + ) } diff --git a/client/src/pages/trends/components/panel/item/index.tsx b/client/src/pages/trends/components/ranking/item/index.tsx similarity index 71% rename from client/src/pages/trends/components/panel/item/index.tsx rename to client/src/pages/trends/components/ranking/item/index.tsx index 73ae6b3a..259718e3 100644 --- a/client/src/pages/trends/components/panel/item/index.tsx +++ b/client/src/pages/trends/components/ranking/item/index.tsx @@ -1,17 +1,20 @@ import { Badge, Col, Container, Row } from "@dataesr/dsfr-plus" -import { useTrendsContext } from "../../../context" +import { useTrendsRankingContext } from "../../../context/rankingContext" import useTrends from "../../../hooks/useTrends" +import useOptions from "../../../hooks/useOptions" import useScreenSize from "../../../../../hooks/useScreenSize" import { itemGetColor, itemGetTrendState } from "../_utils" -import { trendsViewGetConfig } from "../../../config/views" +import { trendsRankingSortFromId } from "../../../config/rankingSorts" import TrendsFocus from "../focus" -export default function TrendsViewItem({ item }) { - const { view, focus, setFocus, normalized } = useTrendsContext() +export default function TrendsRankingItem({ item }) { + const { sort, focus, setFocus } = useTrendsRankingContext() const { trendsYears } = useTrends() + const { normalized } = useOptions() const { screen } = useScreenSize() + const isMobile = ["xs", "sm"].includes(screen) - const viewLabel = trendsViewGetConfig(view).label + const currentSortLabel = trendsRankingSortFromId(sort).label // const diffColor = itemGetColor(item, "diff", normalized) // const diffLabel = @@ -20,16 +23,17 @@ export default function TrendsViewItem({ item }) { // ? "NEW" // : "RESURGENT" // : `${Number(item.diff * 100).toFixed(0.1)} %` + const slopeColor = itemGetColor(item, normalized ? "norm_slope" : "slope", normalized) const trendState = itemGetTrendState(item, normalized) const isFocused = Boolean(focus === item.id) - const focusKey = `focus-${view}-${item.id}` + const focusKey = `focus-${sort}-${item.id}` isFocused && console.log("item", item) return ( - +
- {(!isMobile || viewLabel == "count") && ( + {(!isMobile || currentSortLabel == "count") && (
{item?.count?.[trendsYears.max] || 0}
)} - {/* {(!isMobile || viewLabel == "diff") && ( + {/* {(!isMobile || currentSortLabel == "diff") && ( @@ -58,7 +62,7 @@ export default function TrendsViewItem({ item }) { )} */} - {(!isMobile || viewLabel == "trend") && ( + {(!isMobile || currentSortLabel == "trend") && ( @@ -69,7 +73,7 @@ export default function TrendsViewItem({ item }) { )} -
+
{isFocused && }
diff --git a/client/src/pages/trends/components/panel/skeleton/index.tsx b/client/src/pages/trends/components/ranking/skeleton/index.tsx similarity index 62% rename from client/src/pages/trends/components/panel/skeleton/index.tsx rename to client/src/pages/trends/components/ranking/skeleton/index.tsx index 8b73d308..7c5d5c74 100644 --- a/client/src/pages/trends/components/panel/skeleton/index.tsx +++ b/client/src/pages/trends/components/ranking/skeleton/index.tsx @@ -1,11 +1,11 @@ import { Container } from "@dataesr/dsfr-plus" -import TrendsViewHeader from "../header" +import TrendsRankingHeader from "../header" import BaseSkeleton from "../../../../../components/skeleton/base-skeleton" -export default function TrendsViewSkeleton() { +export default function TrendsRankingSkeleton() { return ( - +
diff --git a/client/src/pages/trends/config/rankingSorts.ts b/client/src/pages/trends/config/rankingSorts.ts new file mode 100644 index 00000000..560f8c0f --- /dev/null +++ b/client/src/pages/trends/config/rankingSorts.ts @@ -0,0 +1,17 @@ +import { TrendsRankingSort, TrendsRankingSorts } from "../../../types/trends" + +export const TRENDS_RANKING_SORTS_MAPPING: TrendsRankingSorts = [ + { id: "count-top", label: "count", order: "top", nextSort: "" }, + // { id: "diff-top", label: "diff", order: "top", nextView: "diff-bot" }, + // { id: "diff-bot", label: "diff", order: "bot", nextView: "diff-top" }, + { id: "trend-top", label: "trend", order: "top", nextSort: "trend-bot" }, + { id: "trend-bot", label: "trend", order: "bot", nextSort: "trend-top" }, +] + +export const TRENDS_RANKING_SORTS_IDS = TRENDS_RANKING_SORTS_MAPPING.map((sort) => sort.id) +export const TRENDS_RANKING_SORTS_NAMES = [...new Set([...TRENDS_RANKING_SORTS_MAPPING.map((sort) => sort.label)])] + +export const trendsRankingSortFromId = (id: string): TrendsRankingSort => + TRENDS_RANKING_SORTS_MAPPING.find((sort) => sort.id === id) +export const trendsRankingSortFromLabel = (label: string): TrendsRankingSort => + TRENDS_RANKING_SORTS_MAPPING.find((sort) => sort.label === label) diff --git a/client/src/pages/trends/config/views.ts b/client/src/pages/trends/config/views.ts deleted file mode 100644 index 22fc0b97..00000000 --- a/client/src/pages/trends/config/views.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { TrendsView, TrendsViews } from "../../../types/trends" - -export const TRENDS_VIEWS: TrendsViews = [ - { id: "count-top", label: "count", order: "top", nextView: "" }, - // { id: "diff-top", label: "diff", order: "top", nextView: "diff-bot" }, - // { id: "diff-bot", label: "diff", order: "bot", nextView: "diff-top" }, - { id: "trend-top", label: "trend", order: "top", nextView: "trend-bot" }, - { id: "trend-bot", label: "trend", order: "bot", nextView: "trend-top" }, -] - -export const TRENDS_VIEWS_KEYS = TRENDS_VIEWS.map((view) => view.id) -export const TRENDS_VIEWS_LABELS = [...new Set([...TRENDS_VIEWS.map((view) => view.label)])] - -export const trendsViewGetConfig = (id: string): TrendsView => TRENDS_VIEWS.find((view) => view.id === id) -export const trendsViewFromLabel = (label: string): string => TRENDS_VIEWS.find((view) => view.label === label).id diff --git a/client/src/pages/trends/context/index.tsx b/client/src/pages/trends/context/index.tsx deleted file mode 100644 index e4e1306c..00000000 --- a/client/src/pages/trends/context/index.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useContext, createContext, useState } from "react" -import { TRENDS_VIEWS } from "../config/views" - -const Context = createContext(null) - -export function useTrendsContext() { - return useContext(Context) -} - -export function TrendsContext({ children }) { - const [view, setView] = useState(TRENDS_VIEWS[0].id) - const [model, setModel] = useState("entity-fishing") - const [source, setSource] = useState("publications") - const [normalized, setNormalized] = useState(false) - const [focus, setFocus] = useState("") - - return ( - - {children} - - ) -} diff --git a/client/src/pages/trends/context/rankingContext.tsx b/client/src/pages/trends/context/rankingContext.tsx new file mode 100644 index 00000000..87a0788e --- /dev/null +++ b/client/src/pages/trends/context/rankingContext.tsx @@ -0,0 +1,15 @@ +import { useContext, createContext, useState } from "react" +import { TRENDS_RANKING_SORTS_MAPPING } from "../config/rankingSorts" + +const Context = createContext(null) + +export function useTrendsRankingContext() { + return useContext(Context) +} + +export function TrendsRankingContext({ children }) { + const [sort, setSort] = useState(TRENDS_RANKING_SORTS_MAPPING[0].id) + const [focus, setFocus] = useState("") + + return {children} +} diff --git a/client/src/pages/trends/hooks/useTrends.ts b/client/src/pages/trends/hooks/useTrends.ts index 5a8987d8..5d61ff36 100644 --- a/client/src/pages/trends/hooks/useTrends.ts +++ b/client/src/pages/trends/hooks/useTrends.ts @@ -5,6 +5,7 @@ import useUrl from "../../search/hooks/useUrl" import { MAX_YEAR, MIN_YEAR } from "../config/years" import { rangeArray } from "../../../utils/helpers" import useOptions from "./useOptions" +import { TrendsRanking } from "../../../types/trends" const API_MAPPING = { publications: getPublicationsTrends, @@ -20,11 +21,11 @@ export default function useTrends() { max: Number(currentFilters?.year?.values?.[1]?.value || MAX_YEAR), } - const { data, error, isFetchingNextPage, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ + const { data, error, isFetchingNextPage, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ["trends", currentSource, currentModel, currentQuery, currentPage, filters, normalized], queryFn: ({ pageParam }) => API_MAPPING[currentSource]({ - cursor: pageParam + (currentPage - 1) * 3, + cursor: Number(pageParam) + (currentPage - 1) * 3, model: currentModel, query: currentQuery, years: rangeArray(trendsYears.min, trendsYears.max), diff --git a/client/src/pages/trends/hooks/useWikidata.ts b/client/src/pages/trends/hooks/useWikidata.ts index 8e379eaa..b15478bd 100644 --- a/client/src/pages/trends/hooks/useWikidata.ts +++ b/client/src/pages/trends/hooks/useWikidata.ts @@ -3,21 +3,27 @@ import { useMemo } from "react" import { WikipediaResult } from "../../../components/wiki/types" import { useDSFRConfig } from "@dataesr/dsfr-plus" import { getWikidataPreviews } from "../../../components/wiki/api" -import { useTrendsContext } from "../context" +import { useTrendsRankingContext } from "../context/rankingContext" import useTrends from "./useTrends" +import useOptions from "./useOptions" export default function useWikidata() { const { locale } = useDSFRConfig() - const { view } = useTrendsContext() + const { sort } = useTrendsRankingContext() const { trends } = useTrends() - const codes = trends?.pages?.flatMap((page) => page?.views?.[view].map((item) => ({ code: item.id }))) + const { currentModel } = useOptions() + + const codes = + currentModel === "entity-fishing" + ? trends?.pages?.flatMap((page) => page?.ranking?.[sort].map((item) => ({ code: item.id }))) + : null const { data: wikis, isFetching, error, } = useQuery({ - queryKey: ["wikidatas", codes.map((c) => c.code), locale], + queryKey: ["wikidatas", codes?.map((c) => c.code), locale], queryFn: () => getWikidataPreviews(codes, locale), enabled: !!codes?.length, }) diff --git a/client/src/pages/trends/index.tsx b/client/src/pages/trends/index.tsx index 7fd8be0e..1028797a 100644 --- a/client/src/pages/trends/index.tsx +++ b/client/src/pages/trends/index.tsx @@ -1,7 +1,6 @@ import { createIntl, RawIntlProvider } from "react-intl" import { useDSFRConfig } from "@dataesr/dsfr-plus" import { messages } from "./config/messages" -import { TrendsContext } from "./context" import TrendsLayout from "./layout" export default function Trends() { @@ -13,9 +12,7 @@ export default function Trends() { return ( - - - + ) } diff --git a/client/src/pages/trends/integration/index.tsx b/client/src/pages/trends/integration/index.tsx index 2badab14..fcff770b 100644 --- a/client/src/pages/trends/integration/index.tsx +++ b/client/src/pages/trends/integration/index.tsx @@ -2,7 +2,6 @@ import { Suspense } from "react" import { createIntl, RawIntlProvider } from "react-intl" import { messages } from "../config/messages" import useIntegration from "../hooks/useIntegration" -import { TrendsContext } from "../context" import TrendsLayout from "../layout" export default function TrendsIntegration() { @@ -17,9 +16,7 @@ export default function TrendsIntegration() { return ( - - - + ) diff --git a/client/src/pages/trends/layout/index.tsx b/client/src/pages/trends/layout/index.tsx index 3e230619..95251c25 100644 --- a/client/src/pages/trends/layout/index.tsx +++ b/client/src/pages/trends/layout/index.tsx @@ -1,23 +1,23 @@ import { Container } from "@dataesr/dsfr-plus" import TrendsHeader from "../components/header" -import TrendsView from "../components/panel" +import TrendsRanking from "../components/ranking" import TrendsOptionsBar from "../components/options-bar" import TrendsOptionsModals from "../components/options-bar/modals" -import TrendsViewInfo from "../components/info" +import TrendsInfo from "../components/info" import useIntegration from "../hooks/useIntegration" export default function TrendsLayout() { const { integrationOptions } = useIntegration() - if (integrationOptions?.showTrendsOnly) return + if (integrationOptions?.showTrendsOnly) return return ( - - + + ) } diff --git a/client/src/pages/trends/locales/en.json b/client/src/pages/trends/locales/en.json index b41272f9..3b53c4eb 100644 --- a/client/src/pages/trends/locales/en.json +++ b/client/src/pages/trends/locales/en.json @@ -35,13 +35,13 @@ "trends.select-source.publications.description": "Number of publications", "trends.select-source.citations": "Citations", "trends.select-source.citations.description": "Number of citations", - "trends.views.header.domains": "Themes", - "trends.views.header.count": "Volume ({max})", - "trends.views.header.diff": "Variation (1 year)", - "trends.views.header.trend": "Trend ({count} years)", - "trends.views.page-next": "Next", - "trends.views.page-previous": "Back", - "trends.views.info.text": "Found {topics, plural, =0 {# topics} one {# topic} other {# topics}} (within {publications, plural, =0 {# publications} one {# publication} other {# publications}})", + "trends.ranking.header.domains": "Themes", + "trends.ranking.header.count": "Volume ({max})", + "trends.ranking.header.diff": "Variation (1 year)", + "trends.ranking.header.trend": "Trend ({count} years)", + "trends.ranking.page-next": "Next", + "trends.ranking.page-previous": "Back", + "trends.ranking.info.text": "Found {topics, plural, =0 {# topics} one {# topic} other {# topics}} (within {publications, plural, =0 {# publications} one {# publication} other {# publications}})", "trends.line-chart.xAxis.accessibility.description": "Years from {min, number} to {max, number}", "trends.line-chart.yAxis.accessibility.description": "Number of {source}", "trends.line-chart.yAxis.title.text": "Number of {source}", diff --git a/client/src/pages/trends/locales/fr.json b/client/src/pages/trends/locales/fr.json index 083d545d..b85c6ffe 100644 --- a/client/src/pages/trends/locales/fr.json +++ b/client/src/pages/trends/locales/fr.json @@ -35,13 +35,13 @@ "trends.select-source.publications.description": "Nombre de publications", "trends.select-source.citations": "Citations", "trends.select-source.citations.description": "Nombre de citations", - "trends.views.header.domains": "Thématiques", - "trends.views.header.count": "Volume ({max})", - "trends.views.header.diff": "Variation (1 an)", - "trends.views.header.trend": "Tendance ({count} ans)", - "trends.views.page-next": "Suivante", - "trends.views.page-previous": "Précédent", - "trends.views.info.text": "{topics, plural, =0 {# thématiques} one {# thématique} other {# thématiques}} détectées (parmi {publications, plural, =0 {# publications} one {# publication} other {# publications}})", + "trends.ranking.header.domains": "Thématiques", + "trends.ranking.header.count": "Volume ({max})", + "trends.ranking.header.diff": "Variation (1 an)", + "trends.ranking.header.trend": "Tendance ({count} ans)", + "trends.ranking.page-next": "Suivant", + "trends.ranking.page-previous": "Précédent", + "trends.ranking.info.text": "{topics, plural, =0 {# thématiques} one {# thématique} other {# thématiques}} détectées (parmi {publications, plural, =0 {# publications} one {# publication} other {# publications}})", "trends.line-chart.xAxis.accessibility.description": "Années de {min, number} à {max, number}", "trends.line-chart.yAxis.accessibility.description": "Nombre de {source}", "trends.line-chart.yAxis.title.text": "Nombre de {source}", diff --git a/client/src/types/trends.ts b/client/src/types/trends.ts index f0bf6344..1dff55f9 100644 --- a/client/src/types/trends.ts +++ b/client/src/types/trends.ts @@ -1,9 +1,28 @@ -export type TrendsViews = Array -export type TrendsView = { +export type TrendsRanking = { + ranking: Record + nextCursor: number + total: number + count: number +} + +export type TrendsRankingItems = Array +export type TrendsRankingItem = { + id: string + label: string + count: Record + slope: number + norm_slope: number + intercept: number + r2: number + sum: number +} + +export type TrendsRankingSorts = Array +export type TrendsRankingSort = { id: string label: string order: string - nextView?: string + nextSort?: string } export type TrendsFilters = Array @@ -30,4 +49,4 @@ export type TrendsIntegrationOptions = { showFilters: boolean showParameters: boolean showExports: boolean -} \ No newline at end of file +} From 5d7897daf61ebc959c97a0d777a277eb2ad94477 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:39 +0100 Subject: [PATCH 05/11] feat(trends): add variation --- client/src/api/trends/_utils/variation.ts | 11 +++---- client/src/api/trends/trends.ts | 12 ++----- .../trends/components/ranking/_utils/index.ts | 23 +++++++++----- .../components/ranking/header/index.tsx | 7 ++--- .../trends/components/ranking/item/index.tsx | 31 +++++-------------- .../src/pages/trends/config/rankingSorts.ts | 2 -- client/src/pages/trends/locales/en.json | 2 +- client/src/pages/trends/locales/fr.json | 2 +- client/src/types/trends.ts | 1 + 9 files changed, 35 insertions(+), 56 deletions(-) diff --git a/client/src/api/trends/_utils/variation.ts b/client/src/api/trends/_utils/variation.ts index 48c849e4..6537c47b 100644 --- a/client/src/api/trends/_utils/variation.ts +++ b/client/src/api/trends/_utils/variation.ts @@ -1,9 +1,8 @@ -export default function variation(count, max_year) { - const startValue = count?.[max_year - 1] || 0 - const endValue = count?.[max_year] || 0 +export default function variation(count, sum, minYear, maxYear) { + const currentVolume = count?.[maxYear] || 0 + const averageVolume = (sum - currentVolume) / (maxYear - minYear - 1) - if (!startValue && !endValue) return 0 - if (!startValue) return Infinity + const variation = currentVolume >= averageVolume ? currentVolume / averageVolume : -averageVolume / currentVolume - return (endValue - startValue) / startValue + return variation } diff --git a/client/src/api/trends/trends.ts b/client/src/api/trends/trends.ts index 9a132bad..012486f7 100644 --- a/client/src/api/trends/trends.ts +++ b/client/src/api/trends/trends.ts @@ -9,6 +9,7 @@ type TrendsAggregation = Array, cursor: number, years: Array, normalized: boolean) { const maxYear = years[years.length - 1] + const minYear = years[0] const minItems = (cursor || 0) * PAGE_ITEMS const maxItems = ((cursor || 0) + 1) * PAGE_ITEMS @@ -22,7 +23,7 @@ function computeTrends(data: Array, cursor: number, years: Array, n item.norm_slope = slope / item.sum item.intercept = intercept item.r2 = r2 - item.diff = variation(item.count, maxYear) + item.variation = variation(item.count, item.sum, minYear, maxYear) }) // Sort items by volume max year @@ -30,15 +31,6 @@ function computeTrends(data: Array, cursor: number, years: Array, n // Compute top items const topCount = sortedItems.slice(minItems, maxItems) - // const topDiff = sortedItems - // .slice() - // .sort((a, b) => b.diff - a.diff) - // .slice(0, MAX_ITEMS) - // const botDiff = sortedItems - // .slice() - // .sort((a, b) => (b?.count?.[min_year - 1] || 0) - (a?.count?.[maxYear - 1] || 0)) - // .sort((a, b) => a.diff - b.diff) - // .slice(0, MAX_ITEMS) const topSlope = sortedItems .slice() .sort((a, b) => (normalized ? b.norm_slope - a.norm_slope : b.slope - a.slope)) diff --git a/client/src/pages/trends/components/ranking/_utils/index.ts b/client/src/pages/trends/components/ranking/_utils/index.ts index 0f60cff3..691ceb50 100644 --- a/client/src/pages/trends/components/ranking/_utils/index.ts +++ b/client/src/pages/trends/components/ranking/_utils/index.ts @@ -1,18 +1,15 @@ import { DSFRColors } from "@dataesr/dsfr-plus" import { useIntl } from "react-intl" +import { TrendsRankingItem } from "../../../../../types/trends" const DEFAULT_COLOR = "beige-gris-galet" -const DIFF_THRESHOLD = 0.15 // 15% +// const DIFF_THRESHOLD = 0.15 // 15% const SLOPE_THRESHOLD = (normalized: boolean): number => { return normalized ? 0.005 : 0.5 } -export function itemGetColor(item: any, field: "diff" | "slope" | "norm_slope", normalized: boolean): DSFRColors { - - if (field === "diff" && item.diff === Infinity) - return Object.keys(item.count).length === 1 ? "green-bourgeon" : "green-emeraude" - - const threshold = field === "diff" ? DIFF_THRESHOLD : SLOPE_THRESHOLD(normalized) +export function itemGetColor(item: TrendsRankingItem, field: "slope" | "norm_slope", normalized: boolean): DSFRColors { + const threshold = SLOPE_THRESHOLD(normalized) const value = item?.[field] || 0 if (value > threshold) return "success" @@ -21,7 +18,7 @@ export function itemGetColor(item: any, field: "diff" | "slope" | "norm_slope", return DEFAULT_COLOR } -export function itemGetTrendState(item: any, normalized: boolean) { +export function itemGetTrendState(item: TrendsRankingItem, normalized: boolean) { const intl = useIntl() const threshold = SLOPE_THRESHOLD(normalized) @@ -32,3 +29,13 @@ export function itemGetTrendState(item: any, normalized: boolean) { return intl.formatMessage({ id: "trends.stable" }) } + +export function itemGetTrendVariation(item: TrendsRankingItem) { + const variation = item.variation + + if (variation === Infinity) return "Breakout" + if (variation === -Infinity) return "Exctint" + + const prefix = variation >= 0 ? "+" : "-" + return `${prefix}${Math.abs(variation * 100).toFixed(0)}%` +} diff --git a/client/src/pages/trends/components/ranking/header/index.tsx b/client/src/pages/trends/components/ranking/header/index.tsx index f05e2fc8..c1dab3e1 100644 --- a/client/src/pages/trends/components/ranking/header/index.tsx +++ b/client/src/pages/trends/components/ranking/header/index.tsx @@ -73,19 +73,16 @@ export default function TrendsRankingHeader() { return ( - + {intl.formatMessage({ id: `trends.ranking.header.domains` })} {!isMobile && ( <> - + - {/* - - */} diff --git a/client/src/pages/trends/components/ranking/item/index.tsx b/client/src/pages/trends/components/ranking/item/index.tsx index 259718e3..839af708 100644 --- a/client/src/pages/trends/components/ranking/item/index.tsx +++ b/client/src/pages/trends/components/ranking/item/index.tsx @@ -3,7 +3,7 @@ import { useTrendsRankingContext } from "../../../context/rankingContext" import useTrends from "../../../hooks/useTrends" import useOptions from "../../../hooks/useOptions" import useScreenSize from "../../../../../hooks/useScreenSize" -import { itemGetColor, itemGetTrendState } from "../_utils" +import { itemGetColor, itemGetTrendState, itemGetTrendVariation } from "../_utils" import { trendsRankingSortFromId } from "../../../config/rankingSorts" import TrendsFocus from "../focus" @@ -16,16 +16,9 @@ export default function TrendsRankingItem({ item }) { const isMobile = ["xs", "sm"].includes(screen) const currentSortLabel = trendsRankingSortFromId(sort).label - // const diffColor = itemGetColor(item, "diff", normalized) - // const diffLabel = - // item.diff === Infinity - // ? Object.keys(item.count).length === 1 - // ? "NEW" - // : "RESURGENT" - // : `${Number(item.diff * 100).toFixed(0.1)} %` - - const slopeColor = itemGetColor(item, normalized ? "norm_slope" : "slope", normalized) + const trendColor = itemGetColor(item, normalized ? "norm_slope" : "slope", normalized) const trendState = itemGetTrendState(item, normalized) + const trendScore = itemGetTrendVariation(item) const isFocused = Boolean(focus === item.id) const focusKey = `focus-${sort}-${item.id}` @@ -41,33 +34,25 @@ export default function TrendsRankingItem({ item }) { onClick={() => setFocus(isFocused ? "" : item.id)} > - +
{item.label}
{(!isMobile || currentSortLabel == "count") && ( - +
{item?.count?.[trendsYears.max] || 0}
)} - {/* {(!isMobile || currentSortLabel == "diff") && ( - - - - {diffLabel} - - - - )} */} {(!isMobile || currentSortLabel == "trend") && ( - + - + {trendState} +
{trendScore}
)} diff --git a/client/src/pages/trends/config/rankingSorts.ts b/client/src/pages/trends/config/rankingSorts.ts index 560f8c0f..65455ba0 100644 --- a/client/src/pages/trends/config/rankingSorts.ts +++ b/client/src/pages/trends/config/rankingSorts.ts @@ -2,8 +2,6 @@ import { TrendsRankingSort, TrendsRankingSorts } from "../../../types/trends" export const TRENDS_RANKING_SORTS_MAPPING: TrendsRankingSorts = [ { id: "count-top", label: "count", order: "top", nextSort: "" }, - // { id: "diff-top", label: "diff", order: "top", nextView: "diff-bot" }, - // { id: "diff-bot", label: "diff", order: "bot", nextView: "diff-top" }, { id: "trend-top", label: "trend", order: "top", nextSort: "trend-bot" }, { id: "trend-bot", label: "trend", order: "bot", nextSort: "trend-top" }, ] diff --git a/client/src/pages/trends/locales/en.json b/client/src/pages/trends/locales/en.json index 3b53c4eb..bbba5e72 100644 --- a/client/src/pages/trends/locales/en.json +++ b/client/src/pages/trends/locales/en.json @@ -37,7 +37,7 @@ "trends.select-source.citations.description": "Number of citations", "trends.ranking.header.domains": "Themes", "trends.ranking.header.count": "Volume ({max})", - "trends.ranking.header.diff": "Variation (1 year)", + "trends.ranking.header.variation": "Variation", "trends.ranking.header.trend": "Trend ({count} years)", "trends.ranking.page-next": "Next", "trends.ranking.page-previous": "Back", diff --git a/client/src/pages/trends/locales/fr.json b/client/src/pages/trends/locales/fr.json index b85c6ffe..cc7cae14 100644 --- a/client/src/pages/trends/locales/fr.json +++ b/client/src/pages/trends/locales/fr.json @@ -37,7 +37,7 @@ "trends.select-source.citations.description": "Nombre de citations", "trends.ranking.header.domains": "Thématiques", "trends.ranking.header.count": "Volume ({max})", - "trends.ranking.header.diff": "Variation (1 an)", + "trends.ranking.header.variation": "Variation", "trends.ranking.header.trend": "Tendance ({count} ans)", "trends.ranking.page-next": "Suivant", "trends.ranking.page-previous": "Précédent", diff --git a/client/src/types/trends.ts b/client/src/types/trends.ts index 1dff55f9..d1cd054f 100644 --- a/client/src/types/trends.ts +++ b/client/src/types/trends.ts @@ -15,6 +15,7 @@ export type TrendsRankingItem = { intercept: number r2: number sum: number + variation: number } export type TrendsRankingSorts = Array From cdac31fac3c781352c34c711e05ca031492bf6b5 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:39 +0100 Subject: [PATCH 06/11] fix(trends): correct name --- client/src/pages/trends/components/ranking/item/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/pages/trends/components/ranking/item/index.tsx b/client/src/pages/trends/components/ranking/item/index.tsx index 839af708..3c4cfb32 100644 --- a/client/src/pages/trends/components/ranking/item/index.tsx +++ b/client/src/pages/trends/components/ranking/item/index.tsx @@ -5,7 +5,7 @@ import useOptions from "../../../hooks/useOptions" import useScreenSize from "../../../../../hooks/useScreenSize" import { itemGetColor, itemGetTrendState, itemGetTrendVariation } from "../_utils" import { trendsRankingSortFromId } from "../../../config/rankingSorts" -import TrendsFocus from "../focus" +import TrendsRankingFocus from "../focus" export default function TrendsRankingItem({ item }) { const { sort, focus, setFocus } = useTrendsRankingContext() @@ -59,7 +59,7 @@ export default function TrendsRankingItem({ item }) {
- {isFocused && } + {isFocused && }
) From 53f8899e60f86d1a112393a6144ee8dd91cad53e Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 15:44:39 +0100 Subject: [PATCH 07/11] feat(trends): add ranking header search --- .../components/ranking/header/index.tsx | 35 +++++++++++++++++-- .../pages/trends/components/ranking/index.tsx | 6 ++-- .../trends/components/ranking/item/index.tsx | 4 +-- .../pages/trends/context/rankingContext.tsx | 4 +-- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/client/src/pages/trends/components/ranking/header/index.tsx b/client/src/pages/trends/components/ranking/header/index.tsx index c1dab3e1..259baa31 100644 --- a/client/src/pages/trends/components/ranking/header/index.tsx +++ b/client/src/pages/trends/components/ranking/header/index.tsx @@ -5,6 +5,7 @@ import { trendsRankingSortFromId, trendsRankingSortFromLabel } from "../../../co import useScreenSize from "../../../../../hooks/useScreenSize" import useTrends from "../../../hooks/useTrends" import useOptions from "../../../hooks/useOptions" +import { useState } from "react" function TrendsRankingHeaderMobileButton() { const intl = useIntl() @@ -69,14 +70,42 @@ function TrendsRankingHeaderButton({ label }) { export default function TrendsRankingHeader() { const intl = useIntl() const { screen } = useScreenSize() + const { setStartWith } = useTrendsRankingContext() + const [search, setSearch] = useState(false) const isMobile = ["xs", "sm"].includes(screen) return ( - - {intl.formatMessage({ id: `trends.ranking.header.domains` })} - + +
diff --git a/client/src/pages/trends/components/ranking/index.tsx b/client/src/pages/trends/components/ranking/index.tsx index 886d28ee..a8e328a0 100644 --- a/client/src/pages/trends/components/ranking/index.tsx +++ b/client/src/pages/trends/components/ranking/index.tsx @@ -3,91 +3,83 @@ import { useIntl } from "react-intl" import { useInView } from "react-intersection-observer" import { Button, ButtonGroup, Container } from "@dataesr/dsfr-plus" import useTrends from "../../hooks/useTrends" -import { TrendsRankingContext, useTrendsRankingContext } from "../../context/rankingContext" import useOptions from "../../hooks/useOptions" +import { useTrendsContext } from "../../context" import TrendsRankingItem from "./item" import TrendsRankingHeader from "./header" import BaseSkeleton from "../../../../components/skeleton/base-skeleton" -const itemsPerScroll = 10 -const scrollPerPage = 3 -const scrollYTop = 200 +const ITEMS_PER_PAGE = 25 function TrendsRankingItems() { const intl = useIntl() - const [ref, inView] = useInView() - const { sort, startsWith } = useTrendsRankingContext() + const { sort } = useTrendsContext() const { currentPage, handlePageChange } = useOptions() const { trends, fetchNextPage, isFetching, isFetchingNextPage, hasNextPage, error } = useTrends() - useEffect(() => { - if (inView) { - fetchNextPage() - } - }, [inView, fetchNextPage]) - - const navigateToPage = (page: number) => { - handlePageChange(page) - window.scrollTo(0, scrollYTop) - } + // useEffect(() => { + // if (trends?.pages?.[0]?.ranking?.[sort]?.length) { + // // fetchNextPage() + // console.log("trends items = ", trends?.pages?.[0]?.ranking?.[sort]?.length) + // } + // }, [trends?.pages?.[0]?.ranking?.[sort]?.length]) if (isFetching && !isFetchingNextPage) return if (!trends?.pages || error) return
no data
- const shouldChangePage = trends.pageParams.length >= scrollPerPage - const totalPages = Math.ceil((trends?.pages?.[0]?.total || 0) / (itemsPerScroll * scrollPerPage)) + const incudesPages = Math.ceil((trends?.pages?.[0]?.searchTotal || 0) / ITEMS_PER_PAGE) return ( <>
{trends.pages.map((page) => page.ranking?.[sort] - .filter((item) => item.label.toLowerCase().includes(startsWith)) + // .filter((item) => item.label.toLowerCase().includes(startsWith)) ?.map((item, index) => ) )}
{isFetchingNextPage && } - {!isFetchingNextPage && !shouldChangePage && hasNextPage &&
} - {!isFetchingNextPage && shouldChangePage && ( + {/* {!isFetchingNextPage && !shouldChangePage && hasNextPage &&
} */} + {!isFetchingNextPage && ( {currentPage > 1 && ( - )} {currentPage > 3 && } {currentPage > 2 && ( - )} - {currentPage < totalPages - 1 && ( - )} - {currentPage < totalPages - 2 && } - {currentPage < totalPages && ( - } + {currentPage < incudesPages && ( + )} + {currentPage > 1 && ( + + )} + {currentPage > 3 && ( + + )} + {currentPage > 2 && ( + + )} + + {currentPage < filteredPages - 1 && ( + + )} + {currentPage < filteredPages - 2 && ( + + )} + {currentPage < filteredPages && ( + + )} + + + + ) +} diff --git a/client/src/pages/trends/components/ranking/index.tsx b/client/src/pages/trends/components/ranking/index.tsx index a8e328a0..109c7179 100644 --- a/client/src/pages/trends/components/ranking/index.tsx +++ b/client/src/pages/trends/components/ranking/index.tsx @@ -1,93 +1,31 @@ -import { useEffect } from "react" -import { useIntl } from "react-intl" -import { useInView } from "react-intersection-observer" -import { Button, ButtonGroup, Container } from "@dataesr/dsfr-plus" +import { Container } from "@dataesr/dsfr-plus" import useTrends from "../../hooks/useTrends" -import useOptions from "../../hooks/useOptions" import { useTrendsContext } from "../../context" import TrendsRankingItem from "./item" import TrendsRankingHeader from "./header" import BaseSkeleton from "../../../../components/skeleton/base-skeleton" - -const ITEMS_PER_PAGE = 25 +import { TrendsRankingFooter } from "./footer" function TrendsRankingItems() { - const intl = useIntl() - const { sort } = useTrendsContext() - const { currentPage, handlePageChange } = useOptions() - const { trends, fetchNextPage, isFetching, isFetchingNextPage, hasNextPage, error } = useTrends() - - // useEffect(() => { - // if (trends?.pages?.[0]?.ranking?.[sort]?.length) { - // // fetchNextPage() - // console.log("trends items = ", trends?.pages?.[0]?.ranking?.[sort]?.length) - // } - // }, [trends?.pages?.[0]?.ranking?.[sort]?.length]) + const { sort, includes } = useTrendsContext() + const { trends, isFetching, error } = useTrends() - if (isFetching && !isFetchingNextPage) return - if (!trends?.pages || error) return
no data
+ if (!trends && isFetching) return + if (!trends?.ranking || error) return
no data
- const incudesPages = Math.ceil((trends?.pages?.[0]?.searchTotal || 0) / ITEMS_PER_PAGE) + const items = includes + ? trends.ranking[sort].filter((item) => item.label.toLowerCase().includes(includes)) + : trends.ranking[sort] return ( - <> +
- {trends.pages.map((page) => - page.ranking?.[sort] - // .filter((item) => item.label.toLowerCase().includes(startsWith)) - ?.map((item, index) => ) - )} + {items.map((item, index) => ( + + ))}
- {isFetchingNextPage && } - {/* {!isFetchingNextPage && !shouldChangePage && hasNextPage &&
} */} - {!isFetchingNextPage && ( - - - - {currentPage > 1 && ( - - )} - {currentPage > 3 && } - {currentPage > 2 && ( - - )} - - {currentPage < incudesPages - 1 && ( - - )} - {currentPage < incudesPages - 2 && } - {currentPage < incudesPages && ( - - )} - - - - )} - + {isFetching && } + ) } @@ -97,6 +35,7 @@ export default function TrendsRanking() { + ) diff --git a/client/src/pages/trends/components/ranking/item/index.tsx b/client/src/pages/trends/components/ranking/item/index.tsx index 089ba8ee..e0a92248 100644 --- a/client/src/pages/trends/components/ranking/item/index.tsx +++ b/client/src/pages/trends/components/ranking/item/index.tsx @@ -18,7 +18,7 @@ export default function TrendsRankingItem({ item }) { const trendColor = itemGetColor(item, normalized ? "norm_slope" : "slope", normalized) const trendState = itemGetTrendState(item, normalized) - const trendScore = itemGetTrendVariation(item) + const trendVariation = itemGetTrendVariation(item) const isFocused = Boolean(focus === item.id) const focusKey = `focus-${currentModel}-${sort}-${item.id}` @@ -43,6 +43,12 @@ export default function TrendsRankingItem({ item }) {
{item?.count?.[trendsYears.max] || 0}
+
+ {trendVariation} +
)} @@ -52,7 +58,6 @@ export default function TrendsRankingItem({ item }) { {trendState} -
{trendScore}
)} diff --git a/client/src/pages/trends/hooks/useTrends.ts b/client/src/pages/trends/hooks/useTrends.ts index 17b58407..495bdb00 100644 --- a/client/src/pages/trends/hooks/useTrends.ts +++ b/client/src/pages/trends/hooks/useTrends.ts @@ -1,5 +1,5 @@ import { useMemo } from "react" -import { useInfiniteQuery } from "@tanstack/react-query" +import { useQuery } from "@tanstack/react-query" import { getCitationsTrends, getPublicationsTrends } from "../../../api/trends/publications" import useUrl from "../../search/hooks/useUrl" import { MAX_YEAR, MIN_YEAR } from "../config/years" @@ -23,20 +23,19 @@ export default function useTrends() { max: Number(currentFilters?.year?.values?.[1]?.value || MAX_YEAR), } - const { data, error, isFetchingNextPage, isFetching, fetchNextPage, hasNextPage } = useInfiniteQuery({ + const { data, error, isFetching } = useQuery({ queryKey: ["trends", currentSource, currentModel, currentQuery, currentPage, filters, normalized, includes], - queryFn: ({ pageParam }) => + queryFn: () => API_MAPPING[currentSource]({ - cursor: Number(pageParam) + (currentPage - 1), model: currentModel, query: currentQuery, + page: currentPage, years: rangeArray(trendsYears.min, trendsYears.max), filters: filters, normalized: normalized, includes: includes, }), - getNextPageParam: (lastPage) => lastPage.nextCursor, - initialPageParam: 0, + placeholderData: (prev) => prev, }) const values = useMemo(() => { @@ -44,12 +43,9 @@ export default function useTrends() { trends: data, trendsYears: trendsYears, error: error, - hasNextPage: hasNextPage, - fetchNextPage: fetchNextPage, isFetching: isFetching, - isFetchingNextPage: isFetchingNextPage, } - }, [data, trendsYears, error, hasNextPage, fetchNextPage, isFetching, isFetchingNextPage, error]) + }, [data, trendsYears, error, isFetching, error]) return values } diff --git a/client/src/types/trends.ts b/client/src/types/trends.ts index 7fd61780..930989ad 100644 --- a/client/src/types/trends.ts +++ b/client/src/types/trends.ts @@ -1,9 +1,10 @@ export type TrendsRanking = { ranking: Record - nextCursor: number sourceCount: number searchTotal: number - includesTotal: number + searchPages: number + filteredTotal: number + filteredPages: number } export type TrendsRankingItems = Array @@ -31,7 +32,7 @@ export type TrendsFilters = Array export type TrendsFilter = Record export type TrendsArgs = { - cursor: number + page: number model: string query: string years: Array From 394c15bc4a07ce5cfe1e695dcaf9ed83434f74b6 Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 17:16:57 +0100 Subject: [PATCH 10/11] fix(trends): according items keys --- client/src/pages/trends/components/ranking/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/trends/components/ranking/index.tsx b/client/src/pages/trends/components/ranking/index.tsx index 109c7179..f97fc5c1 100644 --- a/client/src/pages/trends/components/ranking/index.tsx +++ b/client/src/pages/trends/components/ranking/index.tsx @@ -21,7 +21,7 @@ function TrendsRankingItems() {
{items.map((item, index) => ( - + ))}
{isFetching && } From 79f9c693853b697d37ec8b5e87453063a8625dbe Mon Sep 17 00:00:00 2001 From: victor barbier Date: Tue, 28 Jan 2025 17:19:56 +0100 Subject: [PATCH 11/11] fix(trends): usewikidata --- client/src/pages/trends/hooks/useWikidata.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/src/pages/trends/hooks/useWikidata.ts b/client/src/pages/trends/hooks/useWikidata.ts index 64854a48..896077a5 100644 --- a/client/src/pages/trends/hooks/useWikidata.ts +++ b/client/src/pages/trends/hooks/useWikidata.ts @@ -13,10 +13,7 @@ export default function useWikidata() { const { trends } = useTrends() const { currentModel } = useOptions() - const codes = - currentModel === "entity-fishing" - ? trends?.pages?.flatMap((page) => page?.ranking?.[sort].map((item) => ({ code: item.id }))) - : null + const codes = currentModel === "entity-fishing" ? trends?.ranking?.[sort].map((item) => ({ code: item.id })) : null const { data: wikis,