-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f1120ce
commit b631eb9
Showing
13 changed files
with
640 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import composeClasses from '@mui/utils/composeClasses'; | ||
import { GaugeContainer, GaugeContainerProps } from './GaugeContainer'; | ||
import { GaugeValueArc } from './GaugeValueArc'; | ||
import { GaugeReferenceArc } from './GaugeReferenceArc'; | ||
import { GaugeClasses, getGaugeUtilityClass } from './gaugeClasses'; | ||
|
||
export interface GaugeProps extends GaugeContainerProps { | ||
classes?: Partial<GaugeClasses>; | ||
} | ||
|
||
const useUtilityClasses = (props: GaugeProps) => { | ||
const { classes } = props; | ||
|
||
const slots = { | ||
root: ['root'], | ||
valueArc: ['valueArc'], | ||
referenceArc: ['referenceArc'], | ||
}; | ||
|
||
return composeClasses(slots, getGaugeUtilityClass, classes); | ||
}; | ||
|
||
function Gauge(props: GaugeProps) { | ||
const classes = useUtilityClasses(props); | ||
return ( | ||
<GaugeContainer width={200} height={200} {...props} className={classes.root}> | ||
<GaugeReferenceArc className={classes.referenceArc} /> | ||
<GaugeValueArc className={classes.valueArc} /> | ||
</GaugeContainer> | ||
); | ||
} | ||
|
||
export { Gauge }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import useForkRef from '@mui/utils/useForkRef'; | ||
import { styled } from '@mui/material/styles'; | ||
import { useChartContainerDimensions } from '../ResponsiveChartContainer/useChartContainerDimensions'; | ||
import { ChartsSurface, ChartsSurfaceProps } from '../ChartsSurface'; | ||
import { DrawingProvider, DrawingProviderProps } from '../context/DrawingProvider'; | ||
import { GaugeProvider, GaugeProviderProps } from './GaugeProvider'; | ||
|
||
export interface GaugeContainerProps | ||
extends Omit<ChartsSurfaceProps, 'width' | 'height' | 'children'>, | ||
Omit<DrawingProviderProps, 'svgRef' | 'width' | 'height' | 'children'>, | ||
Omit<GaugeProviderProps, 'children'> { | ||
/** | ||
* The width of the chart in px. If not defined, it takes the width of the parent element. | ||
* @default undefined | ||
*/ | ||
width?: number; | ||
/** | ||
* The height of the chart in px. If not defined, it takes the height of the parent element. | ||
* @default undefined | ||
*/ | ||
height?: number; | ||
children?: React.ReactNode; | ||
} | ||
|
||
const ResizableContainer = styled('div', { | ||
name: 'MuiGauge', | ||
slot: 'Container', | ||
})<{ ownerState: Pick<GaugeContainerProps, 'width' | 'height'> }>(({ ownerState }) => ({ | ||
width: ownerState.width ?? '100%', | ||
height: ownerState.height ?? '100%', | ||
display: 'flex', | ||
position: 'relative', | ||
flexGrow: 1, | ||
flexDirection: 'column', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
overflow: 'hidden', | ||
'&>svg': { | ||
width: '100%', | ||
height: '100%', | ||
}, | ||
})); | ||
|
||
const GaugeContainer = React.forwardRef(function GaugeContainer(props: GaugeContainerProps, ref) { | ||
const { | ||
width: inWidth, | ||
height: inHeight, | ||
margin, | ||
title, | ||
desc, | ||
value, | ||
valueMin = 0, | ||
valueMax = 100, | ||
startAngle, | ||
endAngle, | ||
outerRadius, | ||
innerRadius, | ||
cornerRadius, | ||
cx, | ||
cy, | ||
children, | ||
...other | ||
} = props; | ||
const [containerRef, width, height] = useChartContainerDimensions(inWidth, inHeight); | ||
|
||
const svgRef = React.useRef<SVGSVGElement>(null); | ||
const handleRef = useForkRef(ref, svgRef); | ||
|
||
return ( | ||
<ResizableContainer | ||
ref={containerRef} | ||
ownerState={{ width: inWidth, height: inHeight }} | ||
role="meter" | ||
aria-valuenow={value === null ? undefined : value} | ||
aria-valuemin={valueMin} | ||
aria-valuemax={valueMax} | ||
{...other} | ||
> | ||
{width && height ? ( | ||
<DrawingProvider | ||
width={width} | ||
height={height} | ||
margin={{ left: 10, right: 10, top: 10, bottom: 10, ...margin }} | ||
svgRef={svgRef} | ||
> | ||
<GaugeProvider | ||
value={value} | ||
valueMin={valueMin} | ||
valueMax={valueMax} | ||
startAngle={startAngle} | ||
endAngle={endAngle} | ||
outerRadius={outerRadius} | ||
innerRadius={innerRadius} | ||
cornerRadius={cornerRadius} | ||
cx={cx} | ||
cy={cy} | ||
> | ||
<ChartsSurface | ||
width={width} | ||
height={height} | ||
ref={handleRef} | ||
title={title} | ||
desc={desc} | ||
disableAxisListener | ||
aria-hidden="true" | ||
> | ||
{children} | ||
</ChartsSurface> | ||
</GaugeProvider> | ||
</DrawingProvider> | ||
) : null} | ||
</ResizableContainer> | ||
); | ||
}); | ||
|
||
export { GaugeContainer }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
// @ignore - do not document. | ||
import * as React from 'react'; | ||
import { DrawingContext } from '../context/DrawingProvider'; | ||
import { getPercentageValue } from '../internals/utils'; | ||
import { getArcRatios, getAvailableRadius } from './utils'; | ||
|
||
interface CircularConfig { | ||
/** | ||
* The start angle (deg). | ||
* @default 0 | ||
*/ | ||
startAngle?: number; | ||
/** | ||
* The end angle (deg). | ||
* @default 360 | ||
*/ | ||
endAngle?: number; | ||
/** | ||
* The radius between circle center and the begining of the arc. | ||
* Can be a number (in px) or a string with a percentage such as '50%'. | ||
* The '100%' is the maximal radius that fit into the drawing area. | ||
* @default '80%' | ||
*/ | ||
innerRadius?: number | string; | ||
/** | ||
* The radius between circle center and the end of the arc. | ||
* Can be a number (in px) or a string with a percentage such as '50%'. | ||
* The '100%' is the maximal radius that fit into the drawing area. | ||
* @default '100%' | ||
*/ | ||
outerRadius?: number | string; | ||
/** | ||
* The radius applied to arc corners (similar to border radius). | ||
* @default '50%' | ||
*/ | ||
cornerRadius?: number; | ||
/** | ||
* The x coordinate of the pie center. | ||
* Can be a number (in px) or a string with a percentage such as '50%'. | ||
* The '100%' is the width the drawing area. | ||
*/ | ||
cx?: number | string; | ||
/** | ||
* The y coordinate of the pie center. | ||
* Can be a number (in px) or a string with a percentage such as '50%'. | ||
* The '100%' is the height the drawing area. | ||
*/ | ||
cy?: number | string; | ||
} | ||
|
||
interface ProcessedCircularConfig { | ||
/** | ||
* The start angle (rad). | ||
*/ | ||
startAngle: number; | ||
/** | ||
* The end angle (rad). | ||
*/ | ||
endAngle: number; | ||
/** | ||
* The radius between circle center and the begining of the arc. | ||
*/ | ||
innerRadius: number; | ||
/** | ||
* The radius between circle center and the end of the arc. | ||
*/ | ||
outerRadius: number; | ||
/** | ||
* The radius applied to arc corners (similar to border radius). | ||
*/ | ||
cornerRadius: number; | ||
/** | ||
* The x coordinate of the pie center. | ||
*/ | ||
cx: number; | ||
/** | ||
* The y coordinate of the pie center. | ||
*/ | ||
cy: number; | ||
} | ||
|
||
interface GaugeConfig { | ||
/** | ||
* The value of the gauge. | ||
* Set to `null` if no value to display. | ||
*/ | ||
value?: number | null; | ||
/** | ||
* The minimal value of the gauge. | ||
* @default 0 | ||
*/ | ||
valueMin?: number; | ||
/** | ||
* The maximal value of the gauge. | ||
* @default 100 | ||
*/ | ||
valueMax?: number; | ||
} | ||
|
||
export const GaugeContext = React.createContext<Required<GaugeConfig> & ProcessedCircularConfig>({ | ||
value: null, | ||
valueMin: 0, | ||
valueMax: 0, | ||
startAngle: 0, | ||
endAngle: 0, | ||
innerRadius: 0, | ||
outerRadius: 0, | ||
cornerRadius: 0, | ||
cx: 0, | ||
cy: 0, | ||
}); | ||
|
||
export interface GaugeProviderProps extends GaugeConfig, CircularConfig { | ||
children: React.ReactNode; | ||
} | ||
|
||
export function GaugeProvider(props: GaugeProviderProps) { | ||
const { | ||
value = null, | ||
valueMin = 0, | ||
valueMax = 100, | ||
startAngle = 0, | ||
endAngle = 360, | ||
outerRadius: outerRadiusParam, | ||
innerRadius: innerRadiusParam, | ||
cornerRadius: cornerRadiusParam, | ||
cx: cxParam, | ||
cy: cyParam, | ||
children, | ||
} = props; | ||
|
||
const { width, height, top, left } = React.useContext(DrawingContext); | ||
|
||
const ratios = getArcRatios(startAngle, endAngle); | ||
|
||
const innerCx = cxParam ? getPercentageValue(cxParam, width) : ratios.cx * width; | ||
const innerCy = cyParam ? getPercentageValue(cyParam, height) : ratios.cy * height; | ||
|
||
let cx = left + innerCx; | ||
let cy = top + innerCy; | ||
|
||
const availableRadius = getAvailableRadius(innerCx, innerCy, width, height, ratios); | ||
|
||
// If the center is not defined, after computation of the available radius, udpate the center to use the remaining space. | ||
if (cxParam === undefined) { | ||
const usedWidth = availableRadius * (ratios.maxX - ratios.minX); | ||
cx = left + (width - usedWidth) / 2 + ratios.cx * usedWidth; | ||
} | ||
if (cyParam === undefined) { | ||
const usedHeight = availableRadius * (ratios.maxY - ratios.minY); | ||
cy = top + (height - usedHeight) / 2 + ratios.cy * usedHeight; | ||
} | ||
|
||
const outerRadius = getPercentageValue(outerRadiusParam ?? availableRadius, availableRadius); | ||
const innerRadius = getPercentageValue(innerRadiusParam ?? '80%', availableRadius); | ||
const cornerRadius = getPercentageValue(cornerRadiusParam ?? '50%', outerRadius - innerRadius); | ||
|
||
const contextValue = React.useMemo( | ||
() => ({ | ||
value, | ||
valueMin, | ||
valueMax, | ||
startAngle: (Math.PI * startAngle) / 180, | ||
endAngle: (Math.PI * endAngle) / 180, | ||
outerRadius, | ||
innerRadius, | ||
cornerRadius, | ||
cx, | ||
cy, | ||
}), | ||
[ | ||
value, | ||
valueMin, | ||
valueMax, | ||
startAngle, | ||
endAngle, | ||
outerRadius, | ||
innerRadius, | ||
cornerRadius, | ||
cx, | ||
cy, | ||
], | ||
); | ||
|
||
return <GaugeContext.Provider value={contextValue}>{children}</GaugeContext.Provider>; | ||
} | ||
|
||
export function useGaugeState() { | ||
return React.useContext(GaugeContext); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import * as React from 'react'; | ||
import { arc as d3Arc } from 'd3-shape'; | ||
import styled from '@mui/system/styled'; | ||
import { useGaugeState } from './GaugeProvider'; | ||
|
||
const StyledPath = styled('path', { | ||
name: 'MuiGauge', | ||
slot: 'ReferenceArc', | ||
overridesResolver: (props, styles) => styles.referenceArc, | ||
})(({ theme }) => ({ | ||
fill: theme.palette.divider, | ||
})); | ||
|
||
export function GaugeReferenceArc(props: React.ComponentProps<'path'>) { | ||
const { startAngle, endAngle, outerRadius, innerRadius, cornerRadius, cx, cy } = useGaugeState(); | ||
|
||
return ( | ||
<StyledPath | ||
transform={`translate(${cx}, ${cy})`} | ||
d={ | ||
d3Arc().cornerRadius(cornerRadius)({ | ||
startAngle, | ||
endAngle, | ||
innerRadius, | ||
outerRadius, | ||
})! | ||
} | ||
{...props} | ||
/> | ||
); | ||
} |
Oops, something went wrong.