diff --git a/apps/climatemappedafrica/src/components/HURUmap/Chart/index.js b/apps/climatemappedafrica/src/components/HURUmap/Chart/index.js index c818dacf8..29ec03c4f 100644 --- a/apps/climatemappedafrica/src/components/HURUmap/Chart/index.js +++ b/apps/climatemappedafrica/src/components/HURUmap/Chart/index.js @@ -1,19 +1,15 @@ +import { ChartTooltip } from "@hurumap/core"; import { Source } from "@hurumap/next"; import { useMediaQuery } from "@mui/material"; -import { ThemeProvider, StyledEngineProvider } from "@mui/material/styles"; import makeStyles from "@mui/styles/makeStyles"; -import PropTypes from "prop-types"; -import React, { useEffect, useState, useRef, useCallback } from "react"; -import ReactDOMServer from "react-dom/server"; +import React, { useState, useRef, useCallback, useEffect } from "react"; import embed from "vega-embed"; import configureScope from "./configureScope"; import Filters from "./Filters"; import { calculateTooltipPosition, idify } from "./utils"; -import ChartTooltip from "@/climatemappedafrica/components/HURUmap/ChartTooltip"; import IndicatorTitle from "@/climatemappedafrica/components/HURUmap/IndicatorTitle"; -import theme from "@/climatemappedafrica/theme"; const useStyles = makeStyles(() => ({ root: { @@ -28,7 +24,7 @@ const useStyles = makeStyles(() => ({ function Chart({ indicator, indicatorTitle, - secondaryIndicator: { indicator: secondaryIndicator }, + secondaryIndicator: sI, title, geoCode, profileNames, @@ -37,10 +33,12 @@ function Chart({ }) { const classes = useStyles(props); const chartRef = useRef(); + const tooltipRef = useRef(); const [view, setView] = useState(null); const [cSpec, setCSpec] = useState(null); - // For charts, cnsider anything less than 600px as mobile const isMobile = !useMediaQuery("(min-width:600px)"); + const [tooltipData, setTooltipData] = useState(null); + const secondaryIndicator = sI?.indicator; const { id, @@ -65,56 +63,9 @@ function Chart({ const handler = useCallback( (_, event, item, value) => { - const className = `charttooltip-${id}-${geoCode}`; - /* eslint-env browser */ - let el = document.getElementsByClassName(className)[0]; - if (!el) { - /* eslint-env browser */ - el = document.createElement("div"); - el.classList.add(className); - /* eslint-env browser */ - document.body.appendChild(el); - } - - /* eslint-env browser */ - const tooltipContainer = document.fullscreenElement || document.body; - tooltipContainer.appendChild(el); - // hide tooltip for null objects, undefined - if (!value) { - el.remove(); - return; - } - el.innerHTML = ReactDOMServer.renderToString( - - - - - , - ); - - el.classList.add("visible"); - const { x, y } = calculateTooltipPosition( - event, - el.getBoundingClientRect(), - 0, - 10, - ); - el.setAttribute( - "style", - `top: ${y}px; left: ${x}px; z-index: 1230; position: absolute`, - ); + setTooltipData({ item, value, id, geoCode, event }); }, - [defaultType, disableToggle, geoCode, id], + [id, geoCode], ); useEffect(() => { @@ -128,16 +79,13 @@ function Chart({ ); setCSpec(spec); if (chartRef?.current) { - try { - const newView = await embed(chartRef.current, spec, { - renderer: "canvas", - actions: false, - tooltip: handler, - }); - setView(newView.view); - } catch (e) { - console.error("Error rendering chart: ", e); - } + const newView = await embed(chartRef.current, spec, { + renderer: "canvas", + actions: false, + tooltip: handler, + }); + + setView(newView.view); } } renderChart(); @@ -205,9 +153,19 @@ function Chart({ }), ]; + let position = {}; + if (tooltipData?.event && tooltipRef?.current) { + position = calculateTooltipPosition( + tooltipData?.event, + tooltipRef?.current?.getBoundingClientRect(), + 0, + 10, + ); + } if (!indicator?.data) { return null; } + return (
{!isMobile && ( {source} + {tooltipData && tooltipData?.event && ( + + )}
); } -Chart.propTypes = { - indicator: PropTypes.shape({ - id: PropTypes.number, - chart_configuration: PropTypes.shape({ - disableToggle: PropTypes.bool, - defaultType: PropTypes.string, - filter: PropTypes.PropTypes.shape({ - defaults: PropTypes.arrayOf(PropTypes.shape({})), - }), - stacked_field: PropTypes.string, - }), - description: PropTypes.string, - metadata: PropTypes.shape({ - source: PropTypes.string, - url: PropTypes.string, - groups: PropTypes.arrayOf(PropTypes.shape({})), - primary_group: PropTypes.string, - }), - data: PropTypes.arrayOf(PropTypes.shape({})), - }), - indicatorTitle: PropTypes.string, - secondaryIndicator: PropTypes.shape({ - indicator: PropTypes.shape({ - id: PropTypes.number, - chart_configuration: PropTypes.shape({ - disableToggle: PropTypes.bool, - defaultType: PropTypes.string, - filter: PropTypes.PropTypes.shape({ - defaults: PropTypes.arrayOf(PropTypes.shape({})), - }), - stacked_field: PropTypes.string, - }), - description: PropTypes.string, - metadata: PropTypes.shape({ - source: PropTypes.string, - url: PropTypes.string, - groups: PropTypes.arrayOf(PropTypes.shape({})), - primary_group: PropTypes.string, - }), - data: PropTypes.arrayOf(PropTypes.shape({})), - }), - }), - title: PropTypes.string, - geoCode: PropTypes.string, - profileNames: PropTypes.shape({}), - isCompare: PropTypes.bool, -}; - -Chart.defaultProps = { - indicator: undefined, - indicatorTitle: undefined, - secondaryIndicator: {}, - title: undefined, - geoCode: undefined, - profileNames: undefined, - isCompare: false, -}; - export default Chart; diff --git a/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.js b/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.js deleted file mode 100644 index dbdd6b32c..000000000 --- a/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.js +++ /dev/null @@ -1,92 +0,0 @@ -import { Typography, Grid } from "@mui/material"; -import makeStyles from "@mui/styles/makeStyles"; -import clsx from "clsx"; -import PropTypes from "prop-types"; -import React from "react"; - -const useStyles = makeStyles(({ palette, typography }) => ({ - root: { - background: palette.grey.dark, - boxShadow: "0px 3px 6px #00000029", - borderRadius: typography.pxToRem(4), - opacity: 0.8, - color: palette.text.secondary, - padding: typography.pxToRem(12.5), - paddingRight: 0, - display: "inline-block", - width: "fit-content", - }, - text: { - marginRight: typography.pxToRem(12.5), - maxWidth: typography.pxToRem(148), - }, - value: { - fontWeight: 600, - }, - item: { - fontSize: typography.pxToRem(11), - }, - content: { - display: "flex", - alignItems: "center", - }, - circle: (props) => { - return { - width: typography.pxToRem(10), - height: typography.pxToRem(10), - border: `1px solid ${palette.background.default}`, - background: props.itemColor, - borderRadius: "100%", - marginRight: typography.pxToRem(7), - }; - }, -})); - -function ChartTooltip({ title, value, formattedValue, item, ...props }) { - const classes = useStyles(props); - return ( - - {item && ( - -
- {item} - - )} - - - {title} - - {formattedValue && ( - - {formattedValue} - - )} - - {value} - - - - ); -} - -ChartTooltip.propTypes = { - title: PropTypes.string, - value: PropTypes.string, - formattedValue: PropTypes.string, - item: PropTypes.string, - itemColor: PropTypes.string, -}; - -ChartTooltip.defaultProps = { - title: undefined, - value: undefined, - formattedValue: undefined, - item: undefined, - itemColor: undefined, -}; - -export default ChartTooltip; diff --git a/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.stories.js b/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.stories.js deleted file mode 100644 index 8bd3025d3..000000000 --- a/apps/climatemappedafrica/src/components/HURUmap/ChartTooltip/index.stories.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from "react"; - -import ChartTooltip from "@/climatemappedafrica/components/HURUmap/ChartTooltip"; - -export default { - title: "Components/HURUmap/ChartTooltip", - argTypes: { - title: { - control: { - type: "text", - }, - }, - value: { - control: { - type: "text", - }, - }, - formattedValue: { - control: { - type: "text", - }, - }, - group: { - control: { - type: "text", - }, - }, - groupColor: { - control: { - type: "text", - }, - }, - }, -}; - -function Template({ ...args }) { - return ; -} - -export const Default = Template.bind({}); - -Default.args = { - title: "15-24", - value: "1456000", - group: "cat2", - groupColor: "#7DB2D3", -}; diff --git a/apps/pesayetu/src/components/HURUmap/Chart/Tooltip.js b/apps/pesayetu/src/components/HURUmap/Chart/Tooltip.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/apps/pesayetu/src/components/HURUmap/Chart/index.js b/apps/pesayetu/src/components/HURUmap/Chart/index.js index 903460bba..3da6fdff7 100644 --- a/apps/pesayetu/src/components/HURUmap/Chart/index.js +++ b/apps/pesayetu/src/components/HURUmap/Chart/index.js @@ -1,19 +1,15 @@ +import { ChartTooltip } from "@hurumap/core"; import { Source } from "@hurumap/next"; import { useMediaQuery } from "@mui/material"; -import { ThemeProvider, StyledEngineProvider } from "@mui/material/styles"; import makeStyles from "@mui/styles/makeStyles"; -import PropTypes from "prop-types"; -import React, { useEffect, useState, useRef, useCallback } from "react"; -import ReactDOMServer from "react-dom/server"; +import React, { useState, useRef, useCallback, useEffect } from "react"; import embed from "vega-embed"; import configureScope from "./configureScope"; import Filters from "./Filters"; import { calculateTooltipPosition, idify } from "./utils"; -import ChartTooltip from "@/pesayetu/components/HURUmap/ChartTooltip"; import IndicatorTitle from "@/pesayetu/components/HURUmap/IndicatorTitle"; -import theme from "@/pesayetu/theme"; const useStyles = makeStyles(() => ({ root: { @@ -28,7 +24,7 @@ const useStyles = makeStyles(() => ({ function Chart({ indicator, indicatorTitle, - secondaryIndicator: { indicator: secondaryIndicator }, + secondaryIndicator: sI, title, geoCode, profileNames, @@ -37,10 +33,12 @@ function Chart({ }) { const classes = useStyles(props); const chartRef = useRef(); + const tooltipRef = useRef(); const [view, setView] = useState(null); const [cSpec, setCSpec] = useState(null); - // For charts, cnsider anything less than 600px as mobile const isMobile = !useMediaQuery("(min-width:600px)"); + const [tooltipData, setTooltipData] = useState(null); + const secondaryIndicator = sI?.indicator; const { id, @@ -65,56 +63,9 @@ function Chart({ const handler = useCallback( (_, event, item, value) => { - const className = `charttooltip-${id}-${geoCode}`; - /* eslint-env browser */ - let el = document.getElementsByClassName(className)[0]; - if (!el) { - /* eslint-env browser */ - el = document.createElement("div"); - el.classList.add(className); - /* eslint-env browser */ - document.body.appendChild(el); - } - - /* eslint-env browser */ - const tooltipContainer = document.fullscreenElement || document.body; - tooltipContainer.appendChild(el); - // hide tooltip for null objects, undefined - if (!value) { - el.remove(); - return; - } - el.innerHTML = ReactDOMServer.renderToString( - - - - - , - ); - - el.classList.add("visible"); - const { x, y } = calculateTooltipPosition( - event, - el.getBoundingClientRect(), - 0, - 10, - ); - el.setAttribute( - "style", - `top: ${y}px; left: ${x}px; z-index: 1230; position: absolute`, - ); + setTooltipData({ item, value, id, geoCode, event }); }, - [defaultType, disableToggle, geoCode, id], + [id, geoCode], ); useEffect(() => { @@ -202,9 +153,19 @@ function Chart({ }), ]; + let position = {}; + if (tooltipData?.event && tooltipRef?.current) { + position = calculateTooltipPosition( + tooltipData?.event, + tooltipRef?.current?.getBoundingClientRect(), + 0, + 10, + ); + } if (!indicator?.data) { return null; } + return (
{!isMobile && ( {source} + {tooltipData && tooltipData?.event && ( + + )}
); } -Chart.propTypes = { - indicator: PropTypes.shape({ - id: PropTypes.number, - chart_configuration: PropTypes.shape({ - disableToggle: PropTypes.bool, - defaultType: PropTypes.string, - filter: PropTypes.PropTypes.shape({ - defaults: PropTypes.arrayOf(PropTypes.shape({})), - }), - stacked_field: PropTypes.string, - }), - description: PropTypes.string, - metadata: PropTypes.shape({ - source: PropTypes.string, - url: PropTypes.string, - groups: PropTypes.arrayOf(PropTypes.shape({})), - primary_group: PropTypes.string, - }), - data: PropTypes.arrayOf(PropTypes.shape({})), - }), - indicatorTitle: PropTypes.string, - secondaryIndicator: PropTypes.shape({ - indicator: PropTypes.shape({ - id: PropTypes.number, - chart_configuration: PropTypes.shape({ - disableToggle: PropTypes.bool, - defaultType: PropTypes.string, - filter: PropTypes.PropTypes.shape({ - defaults: PropTypes.arrayOf(PropTypes.shape({})), - }), - stacked_field: PropTypes.string, - }), - description: PropTypes.string, - metadata: PropTypes.shape({ - source: PropTypes.string, - url: PropTypes.string, - groups: PropTypes.arrayOf(PropTypes.shape({})), - primary_group: PropTypes.string, - }), - data: PropTypes.arrayOf(PropTypes.shape({})), - }), - }), - title: PropTypes.string, - geoCode: PropTypes.string, - profileNames: PropTypes.shape({}), - isCompare: PropTypes.bool, -}; - -Chart.defaultProps = { - indicator: undefined, - indicatorTitle: undefined, - secondaryIndicator: {}, - title: undefined, - geoCode: undefined, - profileNames: undefined, - isCompare: false, -}; - export default Chart; diff --git a/apps/pesayetu/src/components/HURUmap/ChartTooltip/index.js b/apps/pesayetu/src/components/HURUmap/ChartTooltip/index.js deleted file mode 100644 index dbdd6b32c..000000000 --- a/apps/pesayetu/src/components/HURUmap/ChartTooltip/index.js +++ /dev/null @@ -1,92 +0,0 @@ -import { Typography, Grid } from "@mui/material"; -import makeStyles from "@mui/styles/makeStyles"; -import clsx from "clsx"; -import PropTypes from "prop-types"; -import React from "react"; - -const useStyles = makeStyles(({ palette, typography }) => ({ - root: { - background: palette.grey.dark, - boxShadow: "0px 3px 6px #00000029", - borderRadius: typography.pxToRem(4), - opacity: 0.8, - color: palette.text.secondary, - padding: typography.pxToRem(12.5), - paddingRight: 0, - display: "inline-block", - width: "fit-content", - }, - text: { - marginRight: typography.pxToRem(12.5), - maxWidth: typography.pxToRem(148), - }, - value: { - fontWeight: 600, - }, - item: { - fontSize: typography.pxToRem(11), - }, - content: { - display: "flex", - alignItems: "center", - }, - circle: (props) => { - return { - width: typography.pxToRem(10), - height: typography.pxToRem(10), - border: `1px solid ${palette.background.default}`, - background: props.itemColor, - borderRadius: "100%", - marginRight: typography.pxToRem(7), - }; - }, -})); - -function ChartTooltip({ title, value, formattedValue, item, ...props }) { - const classes = useStyles(props); - return ( - - {item && ( - -
- {item} - - )} - - - {title} - - {formattedValue && ( - - {formattedValue} - - )} - - {value} - - - - ); -} - -ChartTooltip.propTypes = { - title: PropTypes.string, - value: PropTypes.string, - formattedValue: PropTypes.string, - item: PropTypes.string, - itemColor: PropTypes.string, -}; - -ChartTooltip.defaultProps = { - title: undefined, - value: undefined, - formattedValue: undefined, - item: undefined, - itemColor: undefined, -}; - -export default ChartTooltip; diff --git a/apps/pesayetu/src/components/HURUmap/ChartTooltip/index.stories.js b/apps/uibook/stories/HURUmap/core/ChartTooltip.stories.js similarity index 84% rename from apps/pesayetu/src/components/HURUmap/ChartTooltip/index.stories.js rename to apps/uibook/stories/HURUmap/core/ChartTooltip.stories.js index 15b919885..076527a98 100644 --- a/apps/pesayetu/src/components/HURUmap/ChartTooltip/index.stories.js +++ b/apps/uibook/stories/HURUmap/core/ChartTooltip.stories.js @@ -1,9 +1,8 @@ +import { ChartTooltip } from "@hurumap/core"; import React from "react"; -import ChartTooltip from "@/pesayetu/components/HURUmap/ChartTooltip"; - export default { - title: "HURUmap/Components/ChartTooltip", + title: "@hurumap/core/ChartTooltip", argTypes: { title: { control: { diff --git a/packages/hurumap-core/src/ChartTooltip/ChartTooltip.js b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.js new file mode 100644 index 000000000..7f7d45be0 --- /dev/null +++ b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.js @@ -0,0 +1,65 @@ +/* eslint-env browser */ +import { StyledEngineProvider } from "@mui/material/styles"; +import React, { useEffect } from "react"; +import ReactDOM from "react-dom"; + +import Tooltip from "./Tooltip"; // Import your ChartTooltip component + +function ChartTooltip({ + id, + geoCode, + value, + itemColor, + title, + formattedValue, + event, + position, + ...props +}) { + const { tooltipRef } = props; + const { x, y } = position; + useEffect(() => { + const el = document.createElement("div"); + el.className = `charttooltip-${id}-${geoCode}`; + document.body.appendChild(el); + tooltipRef.current = el; + + const tooltipContainer = document.fullscreenElement || document.body; + tooltipContainer.appendChild(el); + + return () => { + if (el) { + el.remove(); + } + }; + }, [id, geoCode, tooltipRef]); + + useEffect(() => { + if (tooltipRef.current && value) { + tooltipRef.current.style.top = `${y}px`; + tooltipRef.current.style.left = `${x}px`; + tooltipRef.current.style.zIndex = 1230; + tooltipRef.current.style.position = "absolute"; + } + }, [value, event, x, y, tooltipRef]); + + if (!tooltipRef.current || !value) { + return null; + } + + return ReactDOM.createPortal( + + + , + tooltipRef.current, + ); +} + +export default ChartTooltip; diff --git a/packages/hurumap-core/src/ChartTooltip/ChartTooltip.snap.js b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.snap.js new file mode 100644 index 000000000..b6d8e7c9a --- /dev/null +++ b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.snap.js @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ChartTooltip renders unchanged 1`] = `
`; diff --git a/packages/hurumap-core/src/ChartTooltip/ChartTooltip.test.js b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.test.js new file mode 100644 index 000000000..b5dfce2d6 --- /dev/null +++ b/packages/hurumap-core/src/ChartTooltip/ChartTooltip.test.js @@ -0,0 +1,23 @@ +import { render } from "@commons-ui/testing-library"; +import React from "react"; + +import ChartTooltip from "./ChartTooltip"; + +const defaultTooltipProps = { + id: "1", + geoCode: "6672", + value: null, + itemColor: "", + event: null, + title: "", + formattedValue: undefined, + position: { x: 10, y: 10 }, + tooltipRef: { current: null }, +}; + +describe("ChartTooltip", () => { + it("renders unchanged", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/packages/hurumap-core/src/ChartTooltip/Tooltip.js b/packages/hurumap-core/src/ChartTooltip/Tooltip.js new file mode 100644 index 000000000..9582c334a --- /dev/null +++ b/packages/hurumap-core/src/ChartTooltip/Tooltip.js @@ -0,0 +1,96 @@ +import { Box, Typography, Grid } from "@mui/material"; +import React, { forwardRef } from "react"; + +const Tooltip = forwardRef(function Tooltip( + { title, value, formattedValue, item, sx, ...props }, + ref, +) { + return ( + ({ + background: theme.palette.grey.dark, + boxShadow: "0px 3px 6px #00000029", + borderRadius: theme.typography.pxToRem(4), + opacity: 0.8, + color: theme.palette.text.secondary, + padding: theme.typography.pxToRem(12.5), + paddingRight: 0, + display: "inline-block", + width: "fit-content", + ...(typeof sx === "function" ? sx(theme) : sx), + })} + > + {item && ( + + ({ + width: theme.typography.pxToRem(10), + height: theme.typography.pxToRem(10), + border: `1px solid ${theme.palette.background.default}`, + background: props.itemColor, + borderRadius: "100%", + marginRight: theme.typography.pxToRem(7), + })} + /> + ({ + fontSize: typography.pxToRem(11), + })} + > + {item} + + + )} + + ({ + marginRight: typography.pxToRem(12.5), + maxWidth: typography.pxToRem(148), + })} + > + {title} + + {formattedValue && ( + ({ + marginRight: typography.pxToRem(12.5), + maxWidth: typography.pxToRem(148), + })} + > + {formattedValue} + + )} + ({ + marginRight: typography.pxToRem(12.5), + maxWidth: typography.pxToRem(148), + })} + > + {value} + + + + ); +}); + +export default Tooltip; diff --git a/packages/hurumap-core/src/ChartTooltip/index.js b/packages/hurumap-core/src/ChartTooltip/index.js new file mode 100644 index 000000000..265c70533 --- /dev/null +++ b/packages/hurumap-core/src/ChartTooltip/index.js @@ -0,0 +1,3 @@ +import ChartTooltip from "./ChartTooltip"; + +export default ChartTooltip; diff --git a/packages/hurumap-core/src/index.js b/packages/hurumap-core/src/index.js index 3149c42c1..fa932f531 100644 --- a/packages/hurumap-core/src/index.js +++ b/packages/hurumap-core/src/index.js @@ -1,4 +1,5 @@ /* eslint-disable import/prefer-default-export */ +export { default as ChartTooltip } from "./ChartTooltip"; export { default as LocationTag } from "./LocationTag"; export { default as LocationHighlight } from "./LocationHighlight"; export { default as Location } from "./Location";