chart.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import { Text } from 'app/design/typography'
  2. import { View, Row } from 'app/design/view'
  3. import { useFetch } from 'app/lib/hooks/use-fetch';
  4. import { useState, useEffect, useMemo } from 'react';
  5. import { appSetting } from 'app/lib/util';
  6. import Dropdown from 'app/ui/atoms/dropdown'
  7. import Calendar from 'app/ui/atoms/calendar'
  8. import { BlockWrapper } from 'app/components/block-wrapper'
  9. export function getColor(color) {
  10. if (color == 'orange') return '#f97316';
  11. if (color == 'yellow') return '#eab308';
  12. if (color == 'green') return '#22c55e';
  13. if (color == 'teal') return '#14b8a6';
  14. if (color == 'sky') return '#0ea5e9';
  15. if (color == 'indigo') return '#6366f1';
  16. if (color == 'purple') return '#8b5cf6';
  17. if (color == 'pink') return '#ec4899';
  18. if (color == 'rose') return '#f43f5e';
  19. if (color == 'red') return '#ef4444';
  20. }
  21. export function VictoryPieChart({data, colorScale}) {
  22. const [VictoryPie, setVictoryPie] = useState(null);
  23. useEffect(() => {
  24. const loadVictoryComponents = async () => {
  25. const victoryModule = await import('victory-native');
  26. setVictoryPie(() => victoryModule.VictoryPie);
  27. };
  28. loadVictoryComponents();
  29. }, []);
  30. if (!VictoryPie) {
  31. return null; // or return a loading spinner
  32. }
  33. return <VictoryPie data={data} colorScale={colorScale}/>
  34. }
  35. function VictoryLineChart({size, dataChart}) {
  36. const [VictoryChart, setVictoryChart] = useState(null);
  37. const [VictoryLine, setVictoryLine] = useState(null);
  38. useEffect(() => {
  39. const loadVictoryComponents = async () => {
  40. const victoryModule = await import('victory-native');
  41. setVictoryChart(() => victoryModule.VictoryChart);
  42. setVictoryLine(() => victoryModule.VictoryLine);
  43. };
  44. loadVictoryComponents();
  45. }, []);
  46. if (!VictoryChart || !VictoryLine) {
  47. return null; // or return a loading spinner
  48. }
  49. return (
  50. <VictoryChart
  51. width={size[0]}
  52. height={size[1]}
  53. domainPadding={{ x: [0, 0], y: [2, 2] }}
  54. >
  55. <VictoryLine
  56. interpolation="basis"
  57. animate={{
  58. duration: 2000,
  59. onLoad: { duration: 1000 }
  60. }}
  61. style={{
  62. data: { stroke: getColor("green"), },
  63. }}
  64. data={dataChart}
  65. />
  66. </VictoryChart>
  67. );
  68. }
  69. export default function ElementChart({ data, blockWrapperProps }) {
  70. const [chartParams, setChartParams] = useState(data.params);
  71. const [size, setSize] = useState([300, 300]);
  72. const chartUrl = data.endpoint
  73. ? '/api.php?r=' + data.endpoint + Object.values(chartParams).map((value) => `&params[]=${value}`).join('')
  74. : null;
  75. // keepPreviousData: the current line stays while a new range loads.
  76. const { data: sResponse } = useFetch(chartUrl, { keepPreviousData: true });
  77. const points = sResponse?.data?.data;
  78. const dataChart = useMemo(
  79. () => (points ? points.map(([x, y]) => ({ x: new Date(x), y: y })) : []),
  80. [points]
  81. );
  82. const setParamValue = (key, value, format = '') => {
  83. if (format == 'date') {
  84. const date = new Date(value * 1000);
  85. value = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
  86. }
  87. setChartParams({
  88. ...chartParams,
  89. [key]: value
  90. });
  91. }
  92. let CharComponent = null;
  93. if (data.type == 'pie') {
  94. const transformedData = data.labels.map((label, index) => {
  95. return { x: '', y: data.data.data[index] };
  96. });
  97. var backgroundColor = appSetting('theme', 'profile_colors');
  98. var backgroundColor2 = backgroundColor.map(color => getColor(color));
  99. CharComponent = (
  100. <View>
  101. <View className='w-full aspect-square'>
  102. <VictoryPieChart colorScale={backgroundColor2} data={transformedData} />
  103. </View>
  104. <Row className='gap-x-2 mb-4 justify-center'>
  105. {data.labels.map((label, index) => (
  106. <Row key={"chart" + index} className='gap-x-2'>
  107. <View className={'aspect-square w-4 bg-' + backgroundColor[index] + '-500'}></View>
  108. <Text key={index}>{label}</Text>
  109. </Row>
  110. ))}
  111. </Row>
  112. {data.text && <Text className=" text-center text-xl font-medium">{data.text}</Text>}
  113. </View>
  114. )
  115. }
  116. if (data.type == 'line') {
  117. CharComponent = (
  118. <>
  119. <View className='w-full aspect-video' onLayout={(event) => {
  120. setSize([event.nativeEvent.layout.width, event.nativeEvent.layout.height]);
  121. }}
  122. >
  123. <VictoryLineChart size={size} dataChart={dataChart}/>
  124. </View>
  125. <View className='lg:flex-row gap-4 lg:mx-auto'>
  126. {
  127. Object.keys(data.form.inputs).map((key, index) => (
  128. <View key={index}>
  129. {
  130. data.form.inputs[key].type == 'select' && (
  131. (() => {
  132. let values = [];
  133. if (Array.isArray(data.form.inputs[key].values)) {
  134. values = data.form.inputs[key].values.map(function (key) {
  135. return key.value ? { label: key.value, value: key.key } : null;
  136. });
  137. values = values.filter(Boolean);
  138. }
  139. return (
  140. <View >
  141. <Dropdown
  142. labelField="label"
  143. valueField="value"
  144. onChange={(value) => setParamValue(data.form.inputs[key].name, value)}
  145. data={values}
  146. />
  147. </View>
  148. );
  149. })()
  150. )
  151. }
  152. {
  153. data.form.inputs[key].type == 'datepicker' && (
  154. <View>
  155. <Calendar value={data.form.inputs[key].value} type={data.form.inputs[key].type} name={data.form.inputs[key].name} onChange={(value) => { setParamValue(data.form.inputs[key].name, value, 'date') }} />
  156. </View>
  157. )
  158. }
  159. </View>
  160. ))
  161. }
  162. </View>
  163. </>
  164. );
  165. }
  166. return (
  167. <BlockWrapper {...blockWrapperProps}>
  168. <View className='mb-4'>
  169. <Text className="text-lg font-bold text-secondary-foreground ">{data.title}</Text>
  170. </View>
  171. <View className='max-w-5xl w-full mx-auto'>
  172. {CharComponent}
  173. </View>
  174. </BlockWrapper>
  175. )
  176. }