From 4cf19ff66c54dedbcb8c333f0cefe8766208121e Mon Sep 17 00:00:00 2001 From: Kostya Bats Date: Sat, 7 Oct 2023 05:04:01 +0300 Subject: [PATCH 1/4] Basic support statistic in new design --- config/widget_positions.json.overlay2 | 6 + .../statistics/StackedBarsStatistics.tsx | 82 ++++++++ .../components/molecules/statistics/types.ts | 22 +++ .../components/organisms/widgets/Queue.jsx | 6 +- .../organisms/widgets/Statistics.jsx | 175 ++++++++---------- src/frontend/overlay/src/config.jsx | 21 ++- 6 files changed, 202 insertions(+), 110 deletions(-) create mode 100644 src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx create mode 100644 src/frontend/overlay/src/components/molecules/statistics/types.ts diff --git a/config/widget_positions.json.overlay2 b/config/widget_positions.json.overlay2 index 113af740f..07161ba8f 100644 --- a/config/widget_positions.json.overlay2 +++ b/config/widget_positions.json.overlay2 @@ -11,6 +11,12 @@ "sizeX": 1488, "sizeY": 984 }, + "statistics": { + "positionX": 16, + "positionY": 662, + "sizeX": 1488, + "sizeY": 338 + }, "ticker": { "positionX": 16, "positionY": 1016, diff --git a/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx b/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx new file mode 100644 index 000000000..328c8a6f9 --- /dev/null +++ b/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx @@ -0,0 +1,82 @@ +import { StackedBarsData } from "./types"; +import styled from "styled-components"; +import c from "../../../config"; +import { ProblemLabel } from "../../atoms/ProblemLabel"; + +const BarsWrapper = styled.div` + width: 100%; + height: 100%; + display: grid; + gap: ${c.STATISTICS_BAR_GAP_PX}px; + grid-auto-flow: column; + grid-template-rows: repeat(${({rowsCount}) => rowsCount}, 1fr); +` + +const BarWrapper = styled.div` + width: 100%; + height: ${c.STATISTICS_BAR_HEIGHT}; + line-height: ${c.STATISTICS_BAR_HEIGHT}; + display: grid; + gap: 0; + grid-template-columns: ${c.STATISTICS_BAR_HEIGHT} auto; +` + +const BarName = styled(ProblemLabel)` + width: ${c.STATISTICS_BAR_HEIGHT}; + background-color: ${({color}) => color}; + font-size: ${c.GLOBAL_DEFAULT_FONT_SIZE}; + font-family: ${c.GLOBAL_DEFAULT_FONT_FAMILY}; + text-align: center; +` +//todo: set font widget + +const BarValues = styled.div` + width: 100%; + display: flex; + justify-content: flex-start; + background-color: ${c.QUEUE_ROW_BACKGROUND}; + border-radius: 0 ${c.GLOBAL_BORDER_RADIUS} ${c.GLOBAL_BORDER_RADIUS} 0; + overflow: hidden; +` + +const BarValue = styled.div.attrs(({ value, caption }) => ({ + style: { + width: `calc(max(${value * 100}%, ${value === 0 ? 0 : ((caption?.length ?? -1) + 1)}ch))`, + } +}))` + height: ${c.STATISTICS_BAR_HEIGHT}; + line-height: ${c.STATISTICS_BAR_HEIGHT}; + transition: width linear ${c.STATISTICS_CELL_MORPH_TIME}ms; + overflow: hidden; + box-sizing: border-box; + + font-size: ${c.GLOBAL_DEFAULT_FONT_SIZE}; + font-family: ${c.GLOBAL_DEFAULT_FONT_FAMILY}; + + background-color: ${({color}) => color}; + color: ${c.STATISTICS_TITLE_COLOR}; + text-align: center; +` + +type StackedBarsStatisticsProps = { data: StackedBarsData, rowsCount: number }; + +export const StackedBarsStatistics = ({data, rowsCount}: StackedBarsStatisticsProps) => { + return ( + + {data.bars.map((b) => { + return ( + + + + {b.values.map(v => ( + + {v.caption} + + ))} + + + ) + })} + + ); +} diff --git a/src/frontend/overlay/src/components/molecules/statistics/types.ts b/src/frontend/overlay/src/components/molecules/statistics/types.ts new file mode 100644 index 000000000..f5e867830 --- /dev/null +++ b/src/frontend/overlay/src/components/molecules/statistics/types.ts @@ -0,0 +1,22 @@ +export interface BarValue { + readonly color: string; + readonly caption: string; + readonly value: number; +} + +export interface BarData { + readonly name: string; + readonly color: string; + readonly values: BarValue[]; +} + +export interface CriterionDescription { + readonly criterion: string; + readonly caption: string; + readonly color: string; +} + +export interface StackedBarsData { + readonly criterions: CriterionDescription[]; + readonly bars: BarData[]; +} diff --git a/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx b/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx index a3ed822e0..9cacd89a4 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx @@ -69,7 +69,7 @@ const fadeOut = () => keyframes` opacity: 100%; } to { - opacity: 0%; + opacity: 0; } `; @@ -189,7 +189,7 @@ const StyledQueueRow = styled.div` gap: 5px; color: white; font-size: 18px; - background: rgba(0, 0, 0, 0.08); + background: ${c.QUEUE_ROW_BACKGROUND}; `; const QueueScoreLabel = styled(ShrinkingBox)` @@ -232,7 +232,7 @@ const QueueWrap = styled.div` position: relative; background-color: ${c.QUEUE_BACKGROUND_COLOR}; background-repeat: no-repeat; - border-radius: 16px; + border-radius: ${c.GLOBAL_BORDER_RADIUS}; padding: 8px; box-sizing: border-box; display: flex; diff --git a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx b/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx index a8f73190b..a14b96a8d 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx @@ -1,131 +1,106 @@ -import React, { Fragment } from "react"; +import React from "react"; import styled from "styled-components"; import { useSelector } from "react-redux"; import c from "../../../config"; import { getTeamTaskColor } from "../../../utils/statusInfo"; -import { Cell } from "../../atoms/Cell"; -import { ProblemCell } from "../../atoms/ContestCells"; +import { StackedBarsStatistics } from "../../molecules/statistics/StackedBarsStatistics"; -const AllDiv = styled.div` +const StatisticsWrap = styled.div` width: 100%; height: 100%; position: relative; + background-color: ${c.CONTEST_COLOR}; + background-repeat: no-repeat; + border-radius: ${c.GLOBAL_BORDER_RADIUS}; + padding: 8px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 7px; `; -const StatisticsWrap = styled.div` +const StatisticsHeader = styled.div` + font-size: 32px; + font-weight: 700; + line-height: 44px; + color: white; width: 100%; - position: absolute; - bottom: 0; display: flex; - flex-direction: column; - opacity: ${c.STATISTICS_OPACITY}; - background: ${c.STATISTICS_BG_COLOR}; `; -const Title = styled.div` - background: ${c.VERDICT_NOK}; - color: ${c.STATISTICS_TITLE_COLOR}; - font-size: ${c.STATISTICS_TITLE_FONT_SIZE}; - text-align: center; - font-family: ${c.CELL_FONT_FAMILY} +const StatisticsHeaderTitle = styled.div` + flex: 1 0 0; `; -const Table = styled.div` - height: 100%; - display: grid; - /* stylelint-disable-next-line */ - grid-template-columns: auto 1fr; -`; +const StatisticsHeaderCaption = styled.div``; -const SubmissionStats = styled.div` - grid-column: 2; - overflow: hidden; - text-align: end; - display: flex; - flex-wrap: wrap; - align-content: center; - height: 100%; - width: 100%; - font-size: ${c.STATISTICS_STATS_VALUE_FONT_SIZE}; - font-family: ${c.STATISTICS_STATS_VALUE_FONT_FAMILY}; - color: ${c.STATISTICS_STATS_VALUE_COLOR}; -`; - -const StatEntry = styled(Cell).attrs(({ targetWidth }) => ({ - style: { - width: targetWidth, +const stackedBarsData = (resultType, tasks, statistics, count) => { + if (!tasks || !statistics || !count) { + return { + legend: [], + bars: [], + }; } -}))` - background: ${props => props.color}; - transition: width linear ${c.STATISTICS_CELL_MORPH_TIME}ms; - height: 100%; - overflow: hidden; - float: left; - box-sizing: border-box; - text-align: center; - font-family: ${c.CELL_FONT_FAMILY}; - &:before { - content: ''; - display: inline-block; - } -`; - -const StatisticsProblemCell = styled(ProblemCell)` - padding: 0 10px; - box-sizing: border-box; -`; - -const getFormattedWidth = (count) => (val, fl) => { - if (fl) { - return `calc(max(${val / count * 100}%, ${val === 0 ? 0 : (val + "").length + 1}ch))`; - } else { - return `${val / count * 100}%`; + const bars = []; + if (resultType === "ICPC") { + bars.push(...statistics?.map(({result, success, pending, wrong}, index) => ({ + name: tasks[index].letter, + color: tasks[index].color, + values: [ + { + color: c.VERDICT_OK2, + caption: success ? success.toString() : "", + value: count ? success / count : 0.0, + }, + { + color: c.VERDICT_UNKNOWN2, + caption: pending ? pending.toString() : "", + value: count ? pending / count : 0.0, + }, + { + color: c.VERDICT_NOK2, + caption: wrong ? wrong.toString() : "", + value: count ? wrong / count : 0.0, + }, + ] + }))); + } else if (resultType === "IOI") { + bars.push(...statistics?.map(({ result, success, pending, wrong }, index) => ({ + name: tasks[index].letter, + color: tasks[index].color, + values: result.map(({ count: rCount, score }) => ({ + color: getTeamTaskColor(score, tasks[index]?.minScore, tasks[index]?.maxScore), + value: count ? rCount / count : 0.0, + })), + }))); } -}; + return { + legend: [], + bars: bars, + }; +} -export const Statistics = () => { - const statistics = useSelector(state => state.statistics.statistics); +export const Statistics = ({ widgetData: { location } }) => { const resultType = useSelector(state => state.contestInfo?.info?.resultType); + const statistics = useSelector(state => state.statistics.statistics); const count = useSelector(state => state.contestInfo?.info?.teams?.length); const tasks = useSelector(state => state.contestInfo?.info?.problems); - const contestData = useSelector((state) => state.contestInfo?.info); + const rowsCount = Math.min( + tasks?.length ?? 0, + Math.floor((location.sizeY - 60) / (c.STATISTICS_BAR_HEIGHT_PX + c.STATISTICS_BAR_GAP_PX)) + ); - const calculator = getFormattedWidth(count); - return + return ( - Statistics - - {tasks && statistics?.map(({ result, success, pending, wrong }, index) => { - return - - {resultType === "ICPC" && - - - {success} - - - {pending} - - - {wrong} - - - } - {resultType !== "ICPC" && - - {result.map(({ count, score }, i) => { - return - ; - })} - - } + + {c.STATISTICS_TITLE} + {c.STATISTICS_CAPTION} + - ; - })} -
+
-
; + ) }; export default Statistics; diff --git a/src/frontend/overlay/src/config.jsx b/src/frontend/overlay/src/config.jsx index f19635ed0..13d90c0c9 100644 --- a/src/frontend/overlay/src/config.jsx +++ b/src/frontend/overlay/src/config.jsx @@ -10,6 +10,8 @@ const visualConfig = await fetch(VISUAL_CONFIG_URL) let config = {}; config.CONTEST_COLOR = "#4C83C3"; +config.CONTEST_CAPTION = "46th"; + config.BASE_URL_WS = (import.meta.env.VITE_WEBSOCKET_URL ?? WS_PROTO + window.location.hostname + ":" + WS_PORT + "/api/overlay"); // Non Styling configs @@ -17,8 +19,10 @@ config.WEBSOCKET_RECONNECT_TIME = 5000; // ms // Strings config.QUEUE_TITLE = "Queue"; -config.QUEUE_CAPTION = "46th"; -config.SCOREBOARD_CAPTION = "46th"; +config.QUEUE_CAPTION = config.CONTEST_CAPTION; +config.SCOREBOARD_CAPTION = config.CONTEST_CAPTION; +config.STATISTICS_TITLE = "Statistics"; +config.STATISTICS_CAPTION = config.CONTEST_CAPTION; // Behaviour config.TICKER_SCOREBOARD_REPEATS = 1; @@ -44,7 +48,7 @@ config.STATISTICS_CELL_MORPH_TIME = 200; //ms config.CELL_FLASH_PERIOD = 500; //ms // Styles > Global -config.GLOBAL_DEFAULT_FONT_FAMILY = "Helvetica; serif", // css-property +config.GLOBAL_DEFAULT_FONT_FAMILY = "Helvetica, serif"; // css-property config.GLOBAL_DEFAULT_FONT_SIZE = "18px"; // css-property config.GLOBAL_DEFAULT_FONT = config.GLOBAL_DEFAULT_FONT_SIZE + " " + config.GLOBAL_DEFAULT_FONT_FAMILY; // css property MUST HAVE FONT SIZE config.GLOBAL_BACKGROUND_COLOR = "#242425"; @@ -82,7 +86,7 @@ config.SCOREBOARD_TABLE_GAP = 3; //px config.SCOREBOARD_TABLE_ROW_GAP = 1; // px - +config.QUEUE_ROW_BACKGROUND = "rgba(0, 0, 0, 0.08)"; config.QUEUE_ROW_HEIGHT = 41; // px config.QUEUE_ROW_HEIGHT2 = 25; // px config.QUEUE_FTS_PADDING = config.QUEUE_ROW_HEIGHT / 2; // px @@ -103,14 +107,17 @@ config.STATISTICS_TITLE_COLOR = "#FFFFFF"; config.STATISTICS_STATS_VALUE_FONT_SIZE = "24pt"; config.STATISTICS_STATS_VALUE_FONT_FAMILY = config.GLOBAL_DEFAULT_FONT_FAMILY; config.STATISTICS_STATS_VALUE_COLOR = "#FFFFFF"; - +config.STATISTICS_BAR_HEIGHT_PX = 24; +config.STATISTICS_BAR_HEIGHT = `${config.STATISTICS_BAR_HEIGHT_PX}px`; +config.STATISTICS_BAR_GAP_PX = 16; +config.STATISTICS_BAR_GAP = `${config.STATISTICS_BAR_GAP_PX}px`; config.CELL_FONT_FAMILY = config.GLOBAL_DEFAULT_FONT_FAMILY; config.CELL_FONT_SIZE = "18px"; config.CELL_TEXT_COLOR = "#FFFFFF"; config.CELL_TEXT_COLOR_INVERSE = "#000000"; config.CELL_BG_COLOR = "#000000"; -config.CELL_BG_COLOR_ODD = "rgba(1; 1, 1, 0.9)", +config.CELL_BG_COLOR_ODD = "rgba(1; 1, 1, 0.9)"; config.CELL_BG_COLOR2 = "#1E1E1E"; config.CELL_BG_COLOR_ODD2 = "#242424"; @@ -130,7 +137,7 @@ config.TICKER_SMALL_BACKGROUND = config.VERDICT_NOK; config.TICKER_BACKGROUND = config.CELL_BG_COLOR; config.TICKER_OPACITY = 0.95; config.TICKER_FONT_COLOR = "#FFFFFF"; -config.TICKER_FONT_FAMILY = "Helvetica; serif", +config.TICKER_FONT_FAMILY = "Helvetica, serif"; config.TICKER_TEXT_FONT_SIZE = "32px"; // css property config.TICKER_TEXT_MARGIN_LEFT = "16px"; // css property config.TICKER_CLOCK_FONT_SIZE = "32px"; // css property From af75984c9df6f93bd1d2947b8583849a4bd158c3 Mon Sep 17 00:00:00 2001 From: Kostya Bats Date: Sun, 8 Oct 2023 16:06:08 +0300 Subject: [PATCH 2/4] Add statistics legend --- .../src/components/atoms/ContestLabels.jsx | 2 +- .../statistics/StackedBarsStatistics.tsx | 2 +- .../statistics/StatisticsLegends.tsx | 53 +++++++++++++++++++ .../components/molecules/statistics/types.ts | 5 +- .../organisms/widgets/Advertisement.jsx | 3 +- .../components/organisms/widgets/Queue.jsx | 2 +- .../organisms/widgets/Scoreboard.jsx | 4 +- src/frontend/overlay/src/config.jsx | 3 +- 8 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx diff --git a/src/frontend/overlay/src/components/atoms/ContestLabels.jsx b/src/frontend/overlay/src/components/atoms/ContestLabels.jsx index 7f29aed62..b0c6dbb40 100644 --- a/src/frontend/overlay/src/components/atoms/ContestLabels.jsx +++ b/src/frontend/overlay/src/components/atoms/ContestLabels.jsx @@ -29,7 +29,7 @@ export const IOITaskResult = PropTypes.shape({ const VerdictLabel = styled(ShrinkingBox)` background-color: ${({ color }) => color}; font-size: 14px; - font-weight: 700; + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; display: flex; justify-content: center; align-items: center; diff --git a/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx b/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx index 328c8a6f9..afd23bc66 100644 --- a/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx +++ b/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx @@ -7,7 +7,7 @@ const BarsWrapper = styled.div` width: 100%; height: 100%; display: grid; - gap: ${c.STATISTICS_BAR_GAP_PX}px; + gap: ${c.STATISTICS_BAR_GAP}; grid-auto-flow: column; grid-template-rows: repeat(${({rowsCount}) => rowsCount}, 1fr); ` diff --git a/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx b/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx new file mode 100644 index 000000000..21dcceb12 --- /dev/null +++ b/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx @@ -0,0 +1,53 @@ +import { LegendDescription } from "./types"; +import styled from "styled-components"; +import c from "../../../config"; + + +const LegendsWrapper = styled.div` + width: 100%; + height: 100%; + display: grid; + gap: ${c.STATISTICS_BAR_GAP}; + //grid-template-columns: auto; + grid-auto-flow: column; + justify-content: end; + align-content: center; +` + +const LegendCardWrapper = styled.div` + width: 100%; + background-color: ${({color}) => color}; + border-radius: ${c.GLOBAL_BORDER_RADIUS}; +` + +const LegendWrapper = styled.div` + line-height: ${c.STATISTICS_BAR_HEIGHT}; + font-size: ${c.GLOBAL_DEFAULT_FONT_SIZE}; + font-family: ${c.GLOBAL_DEFAULT_FONT_FAMILY}; + text-align: center; + margin: 8px 16px; +` + +type LegendCardProps = { color: string; caption: string }; + +export const LegendCard = ({ color, caption }: LegendCardProps) => { + return ( + + + {caption} + + + ); +} + +type StatisticsLegendsProps = { legends: LegendDescription[] }; + +export const StatisticsLegends = ({legends}: StatisticsLegendsProps) => { + return ( + + {legends?.map((l) => ( + + ))} + + ); +} diff --git a/src/frontend/overlay/src/components/molecules/statistics/types.ts b/src/frontend/overlay/src/components/molecules/statistics/types.ts index f5e867830..c294cddd0 100644 --- a/src/frontend/overlay/src/components/molecules/statistics/types.ts +++ b/src/frontend/overlay/src/components/molecules/statistics/types.ts @@ -10,13 +10,12 @@ export interface BarData { readonly values: BarValue[]; } -export interface CriterionDescription { - readonly criterion: string; +export interface LegendDescription { readonly caption: string; readonly color: string; } export interface StackedBarsData { - readonly criterions: CriterionDescription[]; + readonly legends: LegendDescription[]; readonly bars: BarData[]; } diff --git a/src/frontend/overlay/src/components/organisms/widgets/Advertisement.jsx b/src/frontend/overlay/src/components/organisms/widgets/Advertisement.jsx index 8add0f2e9..59bb74612 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Advertisement.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Advertisement.jsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import c from "../../../config"; const AdvertisementContainer = styled.div` width: 100%; @@ -15,7 +16,7 @@ const AdvertisementWrap = styled.div` background-color: white; border-radius: 12px; font-size: 24pt; - font-weight: 700; + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; font-family: Urbanist, Passageway, serif; color: black; `; diff --git a/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx b/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx index 9cacd89a4..c8d9b4e4a 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Queue.jsx @@ -249,7 +249,7 @@ const RowsContainer = styled.div` const QueueHeader = styled.div` font-size: 32px; - font-weight: 700; + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; line-height: 44px; color: white; width: 100%; diff --git a/src/frontend/overlay/src/components/organisms/widgets/Scoreboard.jsx b/src/frontend/overlay/src/components/organisms/widgets/Scoreboard.jsx index a3f369a03..a5a2c9fe0 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Scoreboard.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Scoreboard.jsx @@ -31,7 +31,7 @@ const ScoreboardHeader = styled.div` flex-direction: row; font-size: ${c.SCOREBOARD_CAPTION_FONT_SIZE}; font-style: normal; - font-weight: 700; + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; padding-top: 0.3em; `; @@ -205,7 +205,7 @@ const ScoreboardTableHeaderWrap = styled(ScoreboardTableRowWrap)` font-size: ${c.SCOREBOARD_TABLE_HEADER_FONT_SIZE}px; font-style: normal; - font-weight: 700; + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; line-height: ${c.SCOREBOARD_TABLE_HEADER_HEIGHT}; `; diff --git a/src/frontend/overlay/src/config.jsx b/src/frontend/overlay/src/config.jsx index 13d90c0c9..a3c4ad86d 100644 --- a/src/frontend/overlay/src/config.jsx +++ b/src/frontend/overlay/src/config.jsx @@ -50,6 +50,7 @@ config.CELL_FLASH_PERIOD = 500; //ms // Styles > Global config.GLOBAL_DEFAULT_FONT_FAMILY = "Helvetica, serif"; // css-property config.GLOBAL_DEFAULT_FONT_SIZE = "18px"; // css-property +config.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD = 700; // css-property config.GLOBAL_DEFAULT_FONT = config.GLOBAL_DEFAULT_FONT_SIZE + " " + config.GLOBAL_DEFAULT_FONT_FAMILY; // css property MUST HAVE FONT SIZE config.GLOBAL_BACKGROUND_COLOR = "#242425"; config.GLOBAL_TEXT_COLOR = "#FFF"; @@ -137,7 +138,7 @@ config.TICKER_SMALL_BACKGROUND = config.VERDICT_NOK; config.TICKER_BACKGROUND = config.CELL_BG_COLOR; config.TICKER_OPACITY = 0.95; config.TICKER_FONT_COLOR = "#FFFFFF"; -config.TICKER_FONT_FAMILY = "Helvetica, serif"; +config.TICKER_FONT_FAMILY = config.GLOBAL_DEFAULT_FONT_FAMILY; config.TICKER_TEXT_FONT_SIZE = "32px"; // css property config.TICKER_TEXT_MARGIN_LEFT = "16px"; // css property config.TICKER_CLOCK_FONT_SIZE = "32px"; // css property From 56ed86c1baee23fa32917f6bf34dee502126084a Mon Sep 17 00:00:00 2001 From: Kostya Bats Date: Sun, 8 Oct 2023 16:21:43 +0300 Subject: [PATCH 3/4] Add legend --- .../organisms/widgets/Statistics.jsx | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx b/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx index a14b96a8d..2b3d7a23a 100644 --- a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx +++ b/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx @@ -4,6 +4,7 @@ import { useSelector } from "react-redux"; import c from "../../../config"; import { getTeamTaskColor } from "../../../utils/statusInfo"; import { StackedBarsStatistics } from "../../molecules/statistics/StackedBarsStatistics"; +import { StatisticsLegends } from "../../molecules/statistics/StatisticsLegends"; const StatisticsWrap = styled.div` width: 100%; @@ -12,24 +13,29 @@ const StatisticsWrap = styled.div` background-color: ${c.CONTEST_COLOR}; background-repeat: no-repeat; border-radius: ${c.GLOBAL_BORDER_RADIUS}; - padding: 8px; + padding: 8px 16px; box-sizing: border-box; display: flex; flex-direction: column; - gap: 7px; + gap: 8px; `; const StatisticsHeader = styled.div` font-size: 32px; - font-weight: 700; line-height: 44px; color: white; width: 100%; display: flex; `; -const StatisticsHeaderTitle = styled.div` +const StatisticsHeaderWrapper = styled.div` flex: 1 0 0; + display: flex; + gap: 16px; +` + +const StatisticsHeaderTitle = styled.div` + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; `; const StatisticsHeaderCaption = styled.div``; @@ -38,13 +44,26 @@ const StatisticsHeaderCaption = styled.div``; const stackedBarsData = (resultType, tasks, statistics, count) => { if (!tasks || !statistics || !count) { return { - legend: [], + legends: [], bars: [], }; } + const legends = []; const bars = []; if (resultType === "ICPC") { + legends.push({ + caption: "solved", + color: c.VERDICT_OK2, + }); + legends.push({ + caption: "pending", + color: c.VERDICT_UNKNOWN2, + }); + legends.push({ + caption: "incorrect", + color: c.VERDICT_NOK2, + }); bars.push(...statistics?.map(({result, success, pending, wrong}, index) => ({ name: tasks[index].letter, color: tasks[index].color, @@ -67,6 +86,14 @@ const stackedBarsData = (resultType, tasks, statistics, count) => { ] }))); } else if (resultType === "IOI") { + legends.push({ + caption: "max score", + color: c.VERDICT_OK2, + }); + legends.push({ + caption: "min score", + color: c.VERDICT_NOK2, + }); bars.push(...statistics?.map(({ result, success, pending, wrong }, index) => ({ name: tasks[index].letter, color: tasks[index].color, @@ -77,7 +104,7 @@ const stackedBarsData = (resultType, tasks, statistics, count) => { }))); } return { - legend: [], + legends: legends, bars: bars, }; } @@ -92,14 +119,19 @@ export const Statistics = ({ widgetData: { location } }) => { Math.floor((location.sizeY - 60) / (c.STATISTICS_BAR_HEIGHT_PX + c.STATISTICS_BAR_GAP_PX)) ); + const data = stackedBarsData(resultType, tasks, statistics, count); + return ( - {c.STATISTICS_TITLE} - {c.STATISTICS_CAPTION} + + {c.STATISTICS_TITLE} + {c.STATISTICS_CAPTION} + + - + ) }; From 642eb4097ee3343ee67d52c45b6d24b07c5dfb13 Mon Sep 17 00:00:00 2001 From: Maksim Alzhanov Date: Mon, 16 Oct 2023 15:43:21 +0300 Subject: [PATCH 4/4] Review changes --- ...ckedBarsStatistics.tsx => StackedBars.tsx} | 13 +- ...isticsLegends.tsx => StatisticsLegend.tsx} | 8 +- .../components/molecules/statistics/types.ts | 10 +- .../organisms/widgets/Statistics.jsx | 138 ------------------ .../organisms/widgets/Statistics.tsx | 60 ++++++++ .../overlay/src/statistics/barData.tsx | 71 +++++++++ 6 files changed, 150 insertions(+), 150 deletions(-) rename src/frontend/overlay/src/components/molecules/statistics/{StackedBarsStatistics.tsx => StackedBars.tsx} (90%) rename src/frontend/overlay/src/components/molecules/statistics/{StatisticsLegends.tsx => StatisticsLegend.tsx} (83%) delete mode 100644 src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx create mode 100644 src/frontend/overlay/src/components/organisms/widgets/Statistics.tsx create mode 100644 src/frontend/overlay/src/statistics/barData.tsx diff --git a/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx b/src/frontend/overlay/src/components/molecules/statistics/StackedBars.tsx similarity index 90% rename from src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx rename to src/frontend/overlay/src/components/molecules/statistics/StackedBars.tsx index afd23bc66..cd26c1b23 100644 --- a/src/frontend/overlay/src/components/molecules/statistics/StackedBarsStatistics.tsx +++ b/src/frontend/overlay/src/components/molecules/statistics/StackedBars.tsx @@ -1,4 +1,4 @@ -import { StackedBarsData } from "./types"; +import {Legend, StackedBarsData} from "./types"; import styled from "styled-components"; import c from "../../../config"; import { ProblemLabel } from "../../atoms/ProblemLabel"; @@ -58,12 +58,15 @@ const BarValue = styled.div.attrs(({ value, caption }) => ({ text-align: center; ` -type StackedBarsStatisticsProps = { data: StackedBarsData, rowsCount: number }; - -export const StackedBarsStatistics = ({data, rowsCount}: StackedBarsStatisticsProps) => { +interface StackedBarsProps { + data: StackedBarsData; + legend: Legend; +} +export const StackedBars = ({data}: StackedBarsProps) => { + const rowsCount = data.length return ( - {data.bars.map((b) => { + {data.map((b) => { return ( diff --git a/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx b/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegend.tsx similarity index 83% rename from src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx rename to src/frontend/overlay/src/components/molecules/statistics/StatisticsLegend.tsx index 21dcceb12..8d47c1850 100644 --- a/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegends.tsx +++ b/src/frontend/overlay/src/components/molecules/statistics/StatisticsLegend.tsx @@ -1,4 +1,4 @@ -import { LegendDescription } from "./types"; +import {Legend} from "./types"; import styled from "styled-components"; import c from "../../../config"; @@ -40,12 +40,12 @@ export const LegendCard = ({ color, caption }: LegendCardProps) => { ); } -type StatisticsLegendsProps = { legends: LegendDescription[] }; +type StatisticsLegendsProps = { legend: Legend }; -export const StatisticsLegends = ({legends}: StatisticsLegendsProps) => { +export const StatisticsLegend = ({legend}: StatisticsLegendsProps) => { return ( - {legends?.map((l) => ( + {legend?.map((l) => ( ))} diff --git a/src/frontend/overlay/src/components/molecules/statistics/types.ts b/src/frontend/overlay/src/components/molecules/statistics/types.ts index c294cddd0..ec7155d92 100644 --- a/src/frontend/overlay/src/components/molecules/statistics/types.ts +++ b/src/frontend/overlay/src/components/molecules/statistics/types.ts @@ -15,7 +15,11 @@ export interface LegendDescription { readonly color: string; } -export interface StackedBarsData { - readonly legends: LegendDescription[]; - readonly bars: BarData[]; +export type Legend = LegendDescription[]; + +export type StackedBarsData = BarData[]; + +export interface StatisticsData { + readonly data: StackedBarsData; // | other statistics data + readonly legend: Legend; } diff --git a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx b/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx deleted file mode 100644 index 2b3d7a23a..000000000 --- a/src/frontend/overlay/src/components/organisms/widgets/Statistics.jsx +++ /dev/null @@ -1,138 +0,0 @@ -import React from "react"; -import styled from "styled-components"; -import { useSelector } from "react-redux"; -import c from "../../../config"; -import { getTeamTaskColor } from "../../../utils/statusInfo"; -import { StackedBarsStatistics } from "../../molecules/statistics/StackedBarsStatistics"; -import { StatisticsLegends } from "../../molecules/statistics/StatisticsLegends"; - -const StatisticsWrap = styled.div` - width: 100%; - height: 100%; - position: relative; - background-color: ${c.CONTEST_COLOR}; - background-repeat: no-repeat; - border-radius: ${c.GLOBAL_BORDER_RADIUS}; - padding: 8px 16px; - box-sizing: border-box; - display: flex; - flex-direction: column; - gap: 8px; -`; - -const StatisticsHeader = styled.div` - font-size: 32px; - line-height: 44px; - color: white; - width: 100%; - display: flex; -`; - -const StatisticsHeaderWrapper = styled.div` - flex: 1 0 0; - display: flex; - gap: 16px; -` - -const StatisticsHeaderTitle = styled.div` - font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; -`; - -const StatisticsHeaderCaption = styled.div``; - - -const stackedBarsData = (resultType, tasks, statistics, count) => { - if (!tasks || !statistics || !count) { - return { - legends: [], - bars: [], - }; - } - - const legends = []; - const bars = []; - if (resultType === "ICPC") { - legends.push({ - caption: "solved", - color: c.VERDICT_OK2, - }); - legends.push({ - caption: "pending", - color: c.VERDICT_UNKNOWN2, - }); - legends.push({ - caption: "incorrect", - color: c.VERDICT_NOK2, - }); - bars.push(...statistics?.map(({result, success, pending, wrong}, index) => ({ - name: tasks[index].letter, - color: tasks[index].color, - values: [ - { - color: c.VERDICT_OK2, - caption: success ? success.toString() : "", - value: count ? success / count : 0.0, - }, - { - color: c.VERDICT_UNKNOWN2, - caption: pending ? pending.toString() : "", - value: count ? pending / count : 0.0, - }, - { - color: c.VERDICT_NOK2, - caption: wrong ? wrong.toString() : "", - value: count ? wrong / count : 0.0, - }, - ] - }))); - } else if (resultType === "IOI") { - legends.push({ - caption: "max score", - color: c.VERDICT_OK2, - }); - legends.push({ - caption: "min score", - color: c.VERDICT_NOK2, - }); - bars.push(...statistics?.map(({ result, success, pending, wrong }, index) => ({ - name: tasks[index].letter, - color: tasks[index].color, - values: result.map(({ count: rCount, score }) => ({ - color: getTeamTaskColor(score, tasks[index]?.minScore, tasks[index]?.maxScore), - value: count ? rCount / count : 0.0, - })), - }))); - } - return { - legends: legends, - bars: bars, - }; -} - -export const Statistics = ({ widgetData: { location } }) => { - const resultType = useSelector(state => state.contestInfo?.info?.resultType); - const statistics = useSelector(state => state.statistics.statistics); - const count = useSelector(state => state.contestInfo?.info?.teams?.length); - const tasks = useSelector(state => state.contestInfo?.info?.problems); - const rowsCount = Math.min( - tasks?.length ?? 0, - Math.floor((location.sizeY - 60) / (c.STATISTICS_BAR_HEIGHT_PX + c.STATISTICS_BAR_GAP_PX)) - ); - - const data = stackedBarsData(resultType, tasks, statistics, count); - - return ( - - - - {c.STATISTICS_TITLE} - {c.STATISTICS_CAPTION} - - - - - - - ) -}; -export default Statistics; diff --git a/src/frontend/overlay/src/components/organisms/widgets/Statistics.tsx b/src/frontend/overlay/src/components/organisms/widgets/Statistics.tsx new file mode 100644 index 000000000..5c8962549 --- /dev/null +++ b/src/frontend/overlay/src/components/organisms/widgets/Statistics.tsx @@ -0,0 +1,60 @@ +import styled from "styled-components"; +import {useSelector} from "react-redux"; +import c from "../../../config"; +import {StackedBars} from "../../molecules/statistics/StackedBars"; +import {StatisticsLegend} from "../../molecules/statistics/StatisticsLegend"; +import {stackedBarsData} from "../../../statistics/barData"; + +const StatisticsWrap = styled.div` + width: 100%; + height: 100%; + position: relative; + background-color: ${c.CONTEST_COLOR}; + background-repeat: no-repeat; + border-radius: ${c.GLOBAL_BORDER_RADIUS}; + padding: 8px 16px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 8px; +`; + +const Header = styled.div` + font-size: 32px; + line-height: 44px; + color: white; + width: 100%; + gap: 16px; + display: flex; +`; +const Title = styled.div` + font-weight: ${c.GLOBAL_DEFAULT_FONT_WEIGHT_BOLD}; +`; + +const Caption = styled.div``; + + +export const Statistics = ({ }) => { + // @ts-ignore-start + const resultType = useSelector(state => state.contestInfo?.info?.resultType); + // @ts-ignore + const statistics = useSelector(state => state.statistics.statistics); + // @ts-ignore + const count = useSelector(state => state.contestInfo?.info?.teams?.length); + // @ts-ignore + const tasks = useSelector(state => state.contestInfo?.info?.problems); + const data = stackedBarsData(resultType, tasks, statistics, count); + + return ( + +
+ {c.STATISTICS_TITLE} + {c.STATISTICS_CAPTION} + +
+ + +
+ ) +}; +export default Statistics; diff --git a/src/frontend/overlay/src/statistics/barData.tsx b/src/frontend/overlay/src/statistics/barData.tsx new file mode 100644 index 000000000..9b452b5cf --- /dev/null +++ b/src/frontend/overlay/src/statistics/barData.tsx @@ -0,0 +1,71 @@ +import {StatisticsData} from "../components/molecules/statistics/types"; +import {getTeamTaskColor} from "../utils/statusInfo" +import c from "../config" + +export const stackedBarsData = (resultType: string, tasks: any[], statistics: any[], count: number): StatisticsData => { + if (!tasks || !statistics || !count) { + return { + legend: [], + data: [], + }; + } + + const legend = []; + const bars = []; + if (resultType === "ICPC") { + legend.push({ + caption: "solved", + color: c.VERDICT_OK2, + }); + legend.push({ + caption: "pending", + color: c.VERDICT_UNKNOWN2, + }); + legend.push({ + caption: "incorrect", + color: c.VERDICT_NOK2, + }); + bars.push(...statistics?.map(({result, success, pending, wrong}, index) => ({ + name: tasks[index].letter, + color: tasks[index].color, + values: [ + { + color: c.VERDICT_OK2, + caption: success ? success.toString() : "", + value: count ? success / count : 0.0, + }, + { + color: c.VERDICT_UNKNOWN2, + caption: pending ? pending.toString() : "", + value: count ? pending / count : 0.0, + }, + { + color: c.VERDICT_NOK2, + caption: wrong ? wrong.toString() : "", + value: count ? wrong / count : 0.0, + }, + ] + }))); + } else if (resultType === "IOI") { + legend.push({ + caption: "max score", + color: c.VERDICT_OK2, + }); + legend.push({ + caption: "min score", + color: c.VERDICT_NOK2, + }); + bars.push(...statistics?.map(({result, success, pending, wrong}, index) => ({ + name: tasks[index].letter, + color: tasks[index].color, + values: result.map(({count: rCount, score}) => ({ + color: getTeamTaskColor(score, tasks[index]?.minScore, tasks[index]?.maxScore), + value: count ? rCount / count : 0.0, + })), + }))); + } + return { + legend, + data: bars + }; +} \ No newline at end of file