|
| 1 | +import { useMemo, useState } from 'react'; |
| 2 | +import { ResponsivePie } from '@nivo/pie'; |
| 3 | +import { Checkbox, ComboBox, SelectMode } from '@exogee/graphweaver-admin-ui-components'; |
| 4 | +import { ChartColorScheme, theme } from '../utils'; |
| 5 | +import { useGenrePopularityQuery } from './graphql.generated'; |
| 6 | +import { defaultPieChartControls, getPieData, PieChartControls } from './utils'; |
| 7 | +import styles from './styles.module.css'; |
| 8 | + |
| 9 | +export const GenrePopularity = () => { |
| 10 | + const { data: queryData, loading, error } = useGenrePopularityQuery(); |
| 11 | + const [controls, setControls] = useState(defaultPieChartControls); |
| 12 | + const [genresRange, setGenresRange] = useState(10); |
| 13 | + |
| 14 | + const data = useMemo(() => getPieData(queryData), [queryData]); |
| 15 | + |
| 16 | + const rangeData = useMemo(() => data.slice(0, genresRange), [data, genresRange]); |
| 17 | + |
| 18 | + if (loading) return <p>Loading...</p>; |
| 19 | + if (error) return <p>Error: {error.message}</p>; |
| 20 | + |
| 21 | + const handleChange = (key: keyof PieChartControls, value: any) => { |
| 22 | + setControls((prevControls) => ({ |
| 23 | + ...prevControls, |
| 24 | + [key]: value, |
| 25 | + })); |
| 26 | + }; |
| 27 | + |
| 28 | + return ( |
| 29 | + <div className={styles.container}> |
| 30 | + <div className={styles.controlsContainer}> |
| 31 | + <div className={styles.controlItem}> |
| 32 | + <label>Data points: </label> |
| 33 | + <input |
| 34 | + className={styles.rangeInput} |
| 35 | + type="range" |
| 36 | + min={0} |
| 37 | + max={data.length - 1} |
| 38 | + value={genresRange} |
| 39 | + onChange={(e) => setGenresRange(parseInt(e.target.value))} |
| 40 | + /> |
| 41 | + </div> |
| 42 | + <div className={styles.controlItem}> |
| 43 | + <label>Color scheme: </label> |
| 44 | + <ComboBox |
| 45 | + mode={SelectMode.SINGLE} |
| 46 | + options={Object.values(ChartColorScheme).map((value) => ({ value, label: value }))} |
| 47 | + onChange={(selected) => handleChange('colorScheme', selected[0].value)} |
| 48 | + value={{ value: controls.colorScheme }} |
| 49 | + placeholder="Select" |
| 50 | + /> |
| 51 | + </div> |
| 52 | + <div className={styles.controlItem}> |
| 53 | + <label>Angle: </label> |
| 54 | + <input |
| 55 | + className={styles.rangeInput} |
| 56 | + type="range" |
| 57 | + min={0} |
| 58 | + max={360} |
| 59 | + value={controls.angle} |
| 60 | + onChange={(e) => handleChange('angle', parseInt(e.target.value))} |
| 61 | + /> |
| 62 | + </div> |
| 63 | + <div className={styles.controlItem}> |
| 64 | + <label>Inner radius: </label> |
| 65 | + <input |
| 66 | + className={styles.rangeInput} |
| 67 | + type="range" |
| 68 | + min={0} |
| 69 | + max={95} |
| 70 | + value={controls.innerRadius * 100} |
| 71 | + onChange={(e) => handleChange('innerRadius', parseInt(e.target.value) / 100)} |
| 72 | + /> |
| 73 | + </div> |
| 74 | + <div className={styles.controlItem}> |
| 75 | + <label>Pad angle: </label> |
| 76 | + <input |
| 77 | + className={styles.rangeInput} |
| 78 | + type="range" |
| 79 | + min={0} |
| 80 | + max={45} |
| 81 | + value={controls.padAngle} |
| 82 | + onChange={(e) => handleChange('padAngle', parseInt(e.target.value))} |
| 83 | + /> |
| 84 | + </div> |
| 85 | + <div className={styles.controlItem}> |
| 86 | + <label>Corner radius: </label> |
| 87 | + <input |
| 88 | + className={styles.rangeInput} |
| 89 | + type="range" |
| 90 | + min={0} |
| 91 | + max={45} |
| 92 | + value={controls.cornerRadius} |
| 93 | + onChange={(e) => handleChange('cornerRadius', parseInt(e.target.value))} |
| 94 | + /> |
| 95 | + </div> |
| 96 | + <div className={styles.checkBoxContainer}> |
| 97 | + <Checkbox |
| 98 | + id="sortByValue" |
| 99 | + checked={controls.sortByValue} |
| 100 | + onChange={(e) => handleChange('sortByValue', (e.target as any).checked)} |
| 101 | + /> |
| 102 | + <label htmlFor="sortByValue">Sort by value</label> |
| 103 | + </div> |
| 104 | + <div className={styles.checkBoxContainer}> |
| 105 | + <Checkbox |
| 106 | + id="enableArcLabels" |
| 107 | + checked={controls.enableArcLabels} |
| 108 | + onChange={(e) => handleChange('enableArcLabels', (e.target as any).checked)} |
| 109 | + /> |
| 110 | + <label htmlFor="enableArcLabels">Enable arc labels</label> |
| 111 | + </div> |
| 112 | + <div className={styles.checkBoxContainer}> |
| 113 | + <Checkbox |
| 114 | + id="enableArcLinkLabels" |
| 115 | + checked={controls.enableArcLinkLabels} |
| 116 | + onChange={(e) => handleChange('enableArcLinkLabels', (e.target as any).checked)} |
| 117 | + /> |
| 118 | + <label htmlFor="enableArcLinkLabels">Enable arc link labels</label> |
| 119 | + </div> |
| 120 | + </div> |
| 121 | + <div className={styles.chartContainer}> |
| 122 | + <ResponsivePie |
| 123 | + data={rangeData} |
| 124 | + margin={{ top: 30, right: 150, bottom: 70, left: 60 }} |
| 125 | + theme={theme} |
| 126 | + colors={{ scheme: controls.colorScheme }} |
| 127 | + startAngle={controls.angle} |
| 128 | + innerRadius={controls.innerRadius} |
| 129 | + padAngle={controls.padAngle} |
| 130 | + cornerRadius={controls.cornerRadius} |
| 131 | + sortByValue={controls.sortByValue} |
| 132 | + enableArcLabels={controls.enableArcLabels} |
| 133 | + enableArcLinkLabels={controls.enableArcLinkLabels} |
| 134 | + activeOuterRadiusOffset={8} |
| 135 | + borderWidth={1} |
| 136 | + animate |
| 137 | + borderColor={{ |
| 138 | + from: 'color', |
| 139 | + modifiers: [['darker', 0.2]], |
| 140 | + }} |
| 141 | + arcLinkLabelsSkipAngle={10} |
| 142 | + arcLinkLabelsThickness={2} |
| 143 | + arcLinkLabelsColor={{ from: 'color' }} |
| 144 | + arcLinkLabel="label" |
| 145 | + arcLabelsSkipAngle={10} |
| 146 | + arcLabelsTextColor={{ |
| 147 | + from: 'color', |
| 148 | + modifiers: [['darker', 2]], |
| 149 | + }} |
| 150 | + legends={[ |
| 151 | + { |
| 152 | + anchor: 'bottom-right', |
| 153 | + direction: 'column', |
| 154 | + justify: false, |
| 155 | + translateX: 100, |
| 156 | + translateY: 0, |
| 157 | + itemsSpacing: 0, |
| 158 | + itemDirection: 'left-to-right', |
| 159 | + itemWidth: 80, |
| 160 | + itemHeight: 20, |
| 161 | + itemOpacity: 0.75, |
| 162 | + symbolSize: 12, |
| 163 | + symbolShape: 'circle', |
| 164 | + symbolBorderColor: 'rgba(0, 0, 0, .5)', |
| 165 | + effects: [ |
| 166 | + { |
| 167 | + on: 'hover', |
| 168 | + style: { |
| 169 | + itemBackground: 'rgba(0, 0, 0, .03)', |
| 170 | + itemOpacity: 1, |
| 171 | + }, |
| 172 | + }, |
| 173 | + ], |
| 174 | + }, |
| 175 | + ]} |
| 176 | + /> |
| 177 | + </div> |
| 178 | + </div> |
| 179 | + ); |
| 180 | +}; |
0 commit comments