11import React , { useState , useMemo } from "react"
2+ import { setIn } from "seamless-immutable"
23import useEventCallback from "use-event-callback"
4+ import { useAsyncMemo } from "use-async-memo"
5+ import { RecoilRoot } from "recoil"
6+ import useGetRandomColorUsingHash from "../../hooks/use-get-random-color-using-hash"
37
48import MainLayout from "../MainLayout"
59
@@ -17,12 +21,15 @@ const defaultEnabledTools = [
1721]
1822const defaultGraphs = [ { keyName : "value" } ]
1923
20- export const ReactTimeSeries = ( {
24+ export const ReactTimeSeriesWithoutContext = ( {
2125 corsProxy = defaultCorsProxy ,
2226 interface : iface ,
2327 sample,
2428 onModifySample,
2529} ) => {
30+ if ( ! iface ) throw new Error ( `"interface" is a required prop` )
31+ if ( ! sample ) throw new Error ( `"sample" is a required prop` )
32+ const getRandomColorUsingHash = useGetRandomColorUsingHash ( )
2633 const {
2734 timeFormat,
2835 enabledTools = defaultEnabledTools ,
@@ -33,27 +40,137 @@ export const ReactTimeSeries = ({
3340 } = iface
3441 let { timeData : sampleTimeData , audioUrl, csvUrl, annotation } = sample
3542
36- const timeData = useMemo ( ( ) => {
37- if ( sampleTimeData ) return sampleTimeData
38- // TODO load audioUrl
39- // TODO load csvUrl
40- } , [ sampleTimeData , audioUrl , csvUrl ] )
43+ const timeDataAvailable = [ sampleTimeData , audioUrl , csvUrl ] . some ( Boolean )
4144
42- const curveGroups = useMemo ( ( ) => { } , [ timeData , graphs ] )
45+ const timeData = useAsyncMemo (
46+ async ( ) => {
47+ if ( sampleTimeData ) return sampleTimeData
48+ // TODO load audioUrl
49+ // TODO load csvUrl
50+ } ,
51+ [ sampleTimeData , audioUrl , csvUrl ] ,
52+ null
53+ )
54+
55+ const timeDataLoading = ! timeData && timeDataAvailable
56+
57+ const curveGroups = useMemo ( ( ) => {
58+ if ( ! timeData ) return [ ]
59+ const anonRows = [ ]
60+ const namedRows = { }
61+ for ( const graph of graphs ) {
62+ const curveData = timeData
63+ . filter (
64+ ( a ) => a [ graph . keyName ] !== undefined && a [ graph . keyName ] !== null
65+ )
66+ . map ( ( a ) => [ a . time , a [ graph . keyName ] ] )
67+ if ( graph . row === undefined || graph . row === null ) {
68+ const curveGroup = [
69+ {
70+ data : curveData ,
71+ color : graph . color || getRandomColorUsingHash ( graph . keyName ) ,
72+ } ,
73+ ]
74+ anonRows . push ( curveGroup )
75+ } else {
76+ if ( ! namedRows [ graph . row ] ) {
77+ namedRows [ graph . row ] = [ ]
78+ }
79+ namedRows [ graph . row ] . push ( {
80+ data : curveData ,
81+ color : graph . color || getRandomColorUsingHash ( graph . keyName ) ,
82+ } )
83+ }
84+ }
85+ return anonRows . concat (
86+ Object . entries ( namedRows ) . sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] ) )
87+ )
88+ } , [ timeData , graphs , getRandomColorUsingHash ] )
4389
44- const [ timestamps , setTimestamps ] = useState ( ( ) => {
90+ const timestamps = useMemo ( ( ) => {
4591 if ( ! annotation ?. timestamps ) return [ ]
46- // TODO derive timestamps
47- return [ ]
48- } )
49- const [ durationGroups , setDurationGroups ] = useState ( ( ) => {
92+ return annotation ?. timestamps . map ( ( ts ) => ( {
93+ time : ts . time ,
94+ label : ts . label ,
95+ color : ts . color || getRandomColorUsingHash ( ts . label ) ,
96+ } ) )
97+ // eslint-disable-next-line
98+ } , [ annotation ?. timestamps , getRandomColorUsingHash ] )
99+
100+ const durationGroups = useMemo ( ( ) => {
50101 if ( ! annotation ?. durations ) return [ ]
51- // TODO derive timestamps
52- return [ ]
102+
103+ const availableLabels = Array . from (
104+ new Set (
105+ annotation . durations . flatMap ( ( d ) => [ d . label , d . layer ] ) . filter ( Boolean )
106+ )
107+ )
108+ availableLabels . sort ( )
109+
110+ // TODO no more than 5 layers, after 5 layers start reusing layers
111+
112+ let durationGroups = availableLabels
113+ . map ( ( label ) => {
114+ return {
115+ label,
116+ color : getRandomColorUsingHash ( label ) ,
117+ durations : annotation . durations
118+ . filter ( ( d ) => d . label === label )
119+ . map ( ( d ) => ( {
120+ start : d . start ,
121+ end : d . end ,
122+ label : d . label ,
123+ } ) ) ,
124+ }
125+ } )
126+ . filter ( ( dg ) => dg . durations . length > 0 )
127+
128+ durationGroups . push ( {
129+ color : "#888888" ,
130+ misc : true ,
131+ durations : durationGroups
132+ . filter ( ( dg ) => dg . durations . length === 1 )
133+ . flatMap ( ( dg ) => dg . durations )
134+ . concat ( annotation . durations . filter ( ( d ) => ! d . label ) )
135+ . map ( ( d ) => ( { ...d , color : getRandomColorUsingHash ( d . label ) } ) ) ,
136+ } )
137+ durationGroups = durationGroups . filter (
138+ ( dg ) => dg . misc || dg . durations . length > 1
139+ )
140+
141+ return durationGroups
142+ // eslint-disable-next-line
143+ } , [ annotation ?. durations ] )
144+
145+ const onChangeDurationGroups = useEventCallback ( ( newDurationGroups ) => {
146+ onModifySample (
147+ setIn (
148+ sample ,
149+ [ "annotation" , "durations" ] ,
150+ newDurationGroups . flatMap ( ( dg ) =>
151+ dg . durations . map ( ( d ) => ( {
152+ ...( d . label && dg . label !== d . label ? { } : { label : dg . label } ) ,
153+ ...d ,
154+ } ) )
155+ )
156+ )
157+ )
158+ } )
159+ const onChangeTimestamps = useEventCallback ( ( newTimestamps ) => {
160+ onModifySample ( setIn ( sample , [ "annotation" , "timestamps" ] , newTimestamps ) )
53161 } )
54162
55- const onChangeDurationGroups = useEventCallback ( ( ) => { } )
56- const onChangeTimestamps = useEventCallback ( ( ) => { } )
163+ if ( timeDataLoading ) return "loading" // TODO real loader
164+
165+ if ( ! timeData ) {
166+ throw new Error (
167+ `No time data provided. Try sample={{timeData: [{time: 0, value: 1}, ...]}} or sample={{audioUrl:"https://..."}}`
168+ )
169+ }
170+
171+ if ( curveGroups . length === 0 ) {
172+ throw new Error ( `For some reason, no curves are able to be displayed.` )
173+ }
57174
58175 return (
59176 < MainLayout
@@ -67,4 +184,12 @@ export const ReactTimeSeries = ({
67184 )
68185}
69186
187+ export const ReactTimeSeries = ( props ) => {
188+ return (
189+ < RecoilRoot >
190+ < ReactTimeSeriesWithoutContext { ...props } />
191+ </ RecoilRoot >
192+ )
193+ }
194+
70195export default ReactTimeSeries
0 commit comments