Statetrail chart in uibuilder

The below animation is from DB1 using ui-statetrail

Example Data
[{"id":"cfa20ab9553a7307","type":"inject","z":"7f8982c35e9b9f8c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":4470,"y":3240,"wires":[["1dd53e046a65a15e"]]},{"id":"1dd53e046a65a15e","type":"change","z":"7f8982c35e9b9f8c","name":"Example Data","rules":[{"t":"set","p":"payload","pt":"msg","to":"[{\"state\":\"FALSE\",\"timestamp\":1733819340000},{\"state\":\"TRUE\",\"timestamp\":1733819280000},{\"state\":\"FALSE\",\"timestamp\":1733819220000},{\"state\":\"TRUE\",\"timestamp\":1733819160000},{\"state\":\"FALSE\",\"timestamp\":1733819100000},{\"state\":\"FALSE\",\"timestamp\":1733819040000},{\"state\":\"TRUE\",\"timestamp\":1733818980000},{\"state\":\"FALSE\",\"timestamp\":1733818920000},{\"state\":\"TRUE\",\"timestamp\":1733818860000},{\"state\":\"TRUE\",\"timestamp\":1733818800000},{\"state\":\"TRUE\",\"timestamp\":1733818740000},{\"state\":\"TRUE\",\"timestamp\":1733818680000},{\"state\":\"FALSE\",\"timestamp\":1733818620000},{\"state\":\"FALSE\",\"timestamp\":1733818560000},{\"state\":\"FALSE\",\"timestamp\":1733818500000},{\"state\":\"FALSE\",\"timestamp\":1733818440000},{\"state\":\"FALSE\",\"timestamp\":1733818380000},{\"state\":\"FALSE\",\"timestamp\":1733818320000},{\"state\":\"FALSE\",\"timestamp\":1733818260000},{\"state\":\"FALSE\",\"timestamp\":1733818200000},{\"state\":\"FALSE\",\"timestamp\":1733818140000},{\"state\":\"FALSE\",\"timestamp\":1733818080000},{\"state\":\"FALSE\",\"timestamp\":1733818020000},{\"state\":\"FALSE\",\"timestamp\":1733817960000},{\"state\":\"FALSE\",\"timestamp\":1733817900000},{\"state\":\"TRUE\",\"timestamp\":1733817840000},{\"state\":\"TRUE\",\"timestamp\":1733817780000},{\"state\":\"TRUE\",\"timestamp\":1733817720000},{\"state\":\"TRUE\",\"timestamp\":1733817660000},{\"state\":\"TRUE\",\"timestamp\":1733817600000},{\"state\":\"TRUE\",\"timestamp\":1733817540000},{\"state\":\"TRUE\",\"timestamp\":1733817480000},{\"state\":\"TRUE\",\"timestamp\":1733817420000},{\"state\":\"TRUE\",\"timestamp\":1733817360000},{\"state\":\"TRUE\",\"timestamp\":1733817300000},{\"state\":\"TRUE\",\"timestamp\":1733817240000},{\"state\":\"TRUE\",\"timestamp\":1733817180000},{\"state\":\"TRUE\",\"timestamp\":1733817120000},{\"state\":\"TRUE\",\"timestamp\":1733817060000},{\"state\":\"TRUE\",\"timestamp\":1733817000000},{\"state\":\"TRUE\",\"timestamp\":1733816940000},{\"state\":\"FALSE\",\"timestamp\":1733816880000},{\"state\":\"FALSE\",\"timestamp\":1733816820000},{\"state\":\"TRUE\",\"timestamp\":1733816760000},{\"state\":\"TRUE\",\"timestamp\":1733816700000},{\"state\":\"FALSE\",\"timestamp\":1733816640000},{\"state\":\"FALSE\",\"timestamp\":1733816580000},{\"state\":\"FALSE\",\"timestamp\":1733816520000},{\"state\":\"FALSE\",\"timestamp\":1733816460000},{\"state\":\"FALSE\",\"timestamp\":1733816400000},{\"state\":\"TRUE\",\"timestamp\":1733816340000},{\"state\":\"TRUE\",\"timestamp\":1733816280000},{\"state\":\"TRUE\",\"timestamp\":1733816220000},{\"state\":\"TRUE\",\"timestamp\":1733816160000},{\"state\":\"TRUE\",\"timestamp\":1733816100000},{\"state\":\"TRUE\",\"timestamp\":1733816040000},{\"state\":\"TRUE\",\"timestamp\":1733815980000},{\"state\":\"TRUE\",\"timestamp\":1733815920000},{\"state\":\"TRUE\",\"timestamp\":1733815860000},{\"state\":\"TRUE\",\"timestamp\":1733815800000},{\"state\":\"TRUE\",\"timestamp\":1733815740000},{\"state\":\"TRUE\",\"timestamp\":1733815680000},{\"state\":\"TRUE\",\"timestamp\":1733815620000},{\"state\":\"TRUE\",\"timestamp\":1733815560000},{\"state\":\"TRUE\",\"timestamp\":1733815500000},{\"state\":\"TRUE\",\"timestamp\":1733815440000},{\"state\":\"TRUE\",\"timestamp\":1733815380000},{\"state\":\"TRUE\",\"timestamp\":1733815320000},{\"state\":\"TRUE\",\"timestamp\":1733815260000},{\"state\":\"TRUE\",\"timestamp\":1733815200000},{\"state\":\"TRUE\",\"timestamp\":1733815140000},{\"state\":\"TRUE\",\"timestamp\":1733815080000},{\"state\":\"TRUE\",\"timestamp\":1733815020000},{\"state\":\"TRUE\",\"timestamp\":1733814960000},{\"state\":\"TRUE\",\"timestamp\":1733814900000},{\"state\":\"TRUE\",\"timestamp\":1733814840000},{\"state\":\"TRUE\",\"timestamp\":1733814780000},{\"state\":\"TRUE\",\"timestamp\":1733814720000},{\"state\":\"TRUE\",\"timestamp\":1733814660000},{\"state\":\"TRUE\",\"timestamp\":1733814600000},{\"state\":\"TRUE\",\"timestamp\":1733814540000},{\"state\":\"TRUE\",\"timestamp\":1733814480000},{\"state\":\"TRUE\",\"timestamp\":1733814420000},{\"state\":\"TRUE\",\"timestamp\":1733814360000},{\"state\":\"TRUE\",\"timestamp\":1733814300000},{\"state\":\"TRUE\",\"timestamp\":1733814240000},{\"state\":\"TRUE\",\"timestamp\":1733814180000},{\"state\":\"TRUE\",\"timestamp\":1733814120000},{\"state\":\"TRUE\",\"timestamp\":1733814060000},{\"state\":\"TRUE\",\"timestamp\":1733814000000},{\"state\":\"FALSE\",\"timestamp\":1733813940000},{\"state\":\"FALSE\",\"timestamp\":1733813880000},{\"state\":\"FALSE\",\"timestamp\":1733813820000},{\"state\":\"FALSE\",\"timestamp\":1733813760000},{\"state\":\"FALSE\",\"timestamp\":1733813700000},{\"state\":\"FALSE\",\"timestamp\":1733813640000},{\"state\":\"TRUE\",\"timestamp\":1733813580000},{\"state\":\"TRUE\",\"timestamp\":1733813520000},{\"state\":\"TRUE\",\"timestamp\":1733813460000},{\"state\":\"TRUE\",\"timestamp\":1733813400000},{\"state\":\"TRUE\",\"timestamp\":1733813340000},{\"state\":\"TRUE\",\"timestamp\":1733813280000},{\"state\":\"TRUE\",\"timestamp\":1733813220000},{\"state\":\"TRUE\",\"timestamp\":1733813160000},{\"state\":\"TRUE\",\"timestamp\":1733813100000},{\"state\":\"FALSE\",\"timestamp\":1733813040000},{\"state\":\"FALSE\",\"timestamp\":1733812980000},{\"state\":\"FALSE\",\"timestamp\":1733812920000},{\"state\":\"FALSE\",\"timestamp\":1733812860000},{\"state\":\"TRUE\",\"timestamp\":1733812800000},{\"state\":\"TRUE\",\"timestamp\":1733812740000},{\"state\":\"TRUE\",\"timestamp\":1733812680000},{\"state\":\"TRUE\",\"timestamp\":1733812620000},{\"state\":\"TRUE\",\"timestamp\":1733812560000},{\"state\":\"TRUE\",\"timestamp\":1733812500000},{\"state\":\"FALSE\",\"timestamp\":1733812440000},{\"state\":\"FALSE\",\"timestamp\":1733812380000},{\"state\":\"FALSE\",\"timestamp\":1733812320000},{\"state\":\"FALSE\",\"timestamp\":1733812260000},{\"state\":\"FALSE\",\"timestamp\":1733812200000},{\"state\":\"FALSE\",\"timestamp\":1733812140000},{\"state\":\"FALSE\",\"timestamp\":1733812080000},{\"state\":\"FALSE\",\"timestamp\":1733812020000},{\"state\":\"FALSE\",\"timestamp\":1733811960000},{\"state\":\"FALSE\",\"timestamp\":1733811900000},{\"state\":\"FALSE\",\"timestamp\":1733811840000},{\"state\":\"FALSE\",\"timestamp\":1733811780000},{\"state\":\"FALSE\",\"timestamp\":1733811720000},{\"state\":\"FALSE\",\"timestamp\":1733811660000},{\"state\":\"FALSE\",\"timestamp\":1733811600000},{\"state\":\"FALSE\",\"timestamp\":1733811540000},{\"state\":\"FALSE\",\"timestamp\":1733811480000},{\"state\":\"FALSE\",\"timestamp\":1733811420000},{\"state\":\"FALSE\",\"timestamp\":1733811360000},{\"state\":\"FALSE\",\"timestamp\":1733811300000},{\"state\":\"FALSE\",\"timestamp\":1733811240000},{\"state\":\"FALSE\",\"timestamp\":1733811180000},{\"state\":\"FALSE\",\"timestamp\":1733811120000},{\"state\":\"FALSE\",\"timestamp\":1733811060000},{\"state\":\"FALSE\",\"timestamp\":1733811000000},{\"state\":\"FALSE\",\"timestamp\":1733810940000},{\"state\":\"FALSE\",\"timestamp\":1733810880000},{\"state\":\"FALSE\",\"timestamp\":1733810820000},{\"state\":\"FALSE\",\"timestamp\":1733810760000},{\"state\":\"FALSE\",\"timestamp\":1733810700000},{\"state\":\"FALSE\",\"timestamp\":1733810640000},{\"state\":\"FALSE\",\"timestamp\":1733810580000},{\"state\":\"FALSE\",\"timestamp\":1733810520000},{\"state\":\"FALSE\",\"timestamp\":1733810460000},{\"state\":\"TRUE\",\"timestamp\":1733810400000},{\"state\":\"TRUE\",\"timestamp\":1733810340000},{\"state\":\"TRUE\",\"timestamp\":1733810280000},{\"state\":\"TRUE\",\"timestamp\":1733810220000},{\"state\":\"TRUE\",\"timestamp\":1733810160000},{\"state\":\"TRUE\",\"timestamp\":1733810100000},{\"state\":\"TRUE\",\"timestamp\":1733810040000},{\"state\":\"TRUE\",\"timestamp\":1733809980000},{\"state\":\"TRUE\",\"timestamp\":1733809920000},{\"state\":\"TRUE\",\"timestamp\":1733809860000},{\"state\":\"TRUE\",\"timestamp\":1733809800000},{\"state\":\"TRUE\",\"timestamp\":1733809740000},{\"state\":\"TRUE\",\"timestamp\":1733809680000},{\"state\":\"FALSE\",\"timestamp\":1733809620000},{\"state\":\"FALSE\",\"timestamp\":1733809560000},{\"state\":\"FALSE\",\"timestamp\":1733809500000},{\"state\":\"FALSE\",\"timestamp\":1733809440000},{\"state\":\"TRUE\",\"timestamp\":1733809380000},{\"state\":\"TRUE\",\"timestamp\":1733809320000},{\"state\":\"TRUE\",\"timestamp\":1733809260000},{\"state\":\"TRUE\",\"timestamp\":1733809200000},{\"state\":\"TRUE\",\"timestamp\":1733809140000},{\"state\":\"TRUE\",\"timestamp\":1733809080000},{\"state\":\"TRUE\",\"timestamp\":1733809020000},{\"state\":\"TRUE\",\"timestamp\":1733808960000},{\"state\":\"TRUE\",\"timestamp\":1733808900000},{\"state\":\"TRUE\",\"timestamp\":1733808840000},{\"state\":\"TRUE\",\"timestamp\":1733808780000},{\"state\":\"TRUE\",\"timestamp\":1733808720000},{\"state\":\"TRUE\",\"timestamp\":1733808660000},{\"state\":\"TRUE\",\"timestamp\":1733808600000},{\"state\":\"TRUE\",\"timestamp\":1733808540000},{\"state\":\"TRUE\",\"timestamp\":1733808480000},{\"state\":\"TRUE\",\"timestamp\":1733808420000},{\"state\":\"TRUE\",\"timestamp\":1733808360000},{\"state\":\"TRUE\",\"timestamp\":1733808300000},{\"state\":\"TRUE\",\"timestamp\":1733808240000},{\"state\":\"TRUE\",\"timestamp\":1733808180000},{\"state\":\"TRUE\",\"timestamp\":1733808120000},{\"state\":\"TRUE\",\"timestamp\":1733808060000},{\"state\":\"TRUE\",\"timestamp\":1733808000000},{\"state\":\"TRUE\",\"timestamp\":1733807940000},{\"state\":\"TRUE\",\"timestamp\":1733807880000},{\"state\":\"TRUE\",\"timestamp\":1733807820000},{\"state\":\"TRUE\",\"timestamp\":1733807760000},{\"state\":\"TRUE\",\"timestamp\":1733807700000},{\"state\":\"TRUE\",\"timestamp\":1733807640000},{\"state\":\"TRUE\",\"timestamp\":1733807580000},{\"state\":\"TRUE\",\"timestamp\":1733807520000},{\"state\":\"TRUE\",\"timestamp\":1733807460000},{\"state\":\"TRUE\",\"timestamp\":1733807400000},{\"state\":\"TRUE\",\"timestamp\":1733807340000},{\"state\":\"TRUE\",\"timestamp\":1733807280000},{\"state\":\"TRUE\",\"timestamp\":1733807220000},{\"state\":\"TRUE\",\"timestamp\":1733807160000},{\"state\":\"TRUE\",\"timestamp\":1733807100000},{\"state\":\"TRUE\",\"timestamp\":1733807040000},{\"state\":\"TRUE\",\"timestamp\":1733806980000},{\"state\":\"FALSE\",\"timestamp\":1733806920000},{\"state\":\"TRUE\",\"timestamp\":1733806860000},{\"state\":\"TRUE\",\"timestamp\":1733806800000},{\"state\":\"TRUE\",\"timestamp\":1733806740000},{\"state\":\"TRUE\",\"timestamp\":1733806680000},{\"state\":\"FALSE\",\"timestamp\":1733806620000},{\"state\":\"FALSE\",\"timestamp\":1733806560000},{\"state\":\"TRUE\",\"timestamp\":1733806500000},{\"state\":\"TRUE\",\"timestamp\":1733806440000},{\"state\":\"TRUE\",\"timestamp\":1733806380000},{\"state\":\"TRUE\",\"timestamp\":1733806320000},{\"state\":\"TRUE\",\"timestamp\":1733806260000},{\"state\":\"TRUE\",\"timestamp\":1733806200000},{\"state\":\"TRUE\",\"timestamp\":1733806140000},{\"state\":\"FALSE\",\"timestamp\":1733806080000},{\"state\":\"TRUE\",\"timestamp\":1733806020000},{\"state\":\"TRUE\",\"timestamp\":1733805960000},{\"state\":\"FALSE\",\"timestamp\":1733805900000},{\"state\":\"TRUE\",\"timestamp\":1733805840000},{\"state\":\"TRUE\",\"timestamp\":1733805780000},{\"state\":\"TRUE\",\"timestamp\":1733805720000},{\"state\":\"TRUE\",\"timestamp\":1733805660000},{\"state\":\"TRUE\",\"timestamp\":1733805600000},{\"state\":\"TRUE\",\"timestamp\":1733805540000},{\"state\":\"TRUE\",\"timestamp\":1733805480000},{\"state\":\"TRUE\",\"timestamp\":1733805420000},{\"state\":\"TRUE\",\"timestamp\":1733805360000},{\"state\":\"TRUE\",\"timestamp\":1733805300000},{\"state\":\"TRUE\",\"timestamp\":1733805240000},{\"state\":\"TRUE\",\"timestamp\":1733805180000},{\"state\":\"TRUE\",\"timestamp\":1733805120000},{\"state\":\"TRUE\",\"timestamp\":1733805060000},{\"state\":\"TRUE\",\"timestamp\":1733805000000},{\"state\":\"TRUE\",\"timestamp\":1733804940000},{\"state\":\"TRUE\",\"timestamp\":1733804880000},{\"state\":\"TRUE\",\"timestamp\":1733804820000},{\"state\":\"TRUE\",\"timestamp\":1733804760000},{\"state\":\"TRUE\",\"timestamp\":1733804700000},{\"state\":\"TRUE\",\"timestamp\":1733804640000},{\"state\":\"TRUE\",\"timestamp\":1733804580000},{\"state\":\"TRUE\",\"timestamp\":1733804520000},{\"state\":\"TRUE\",\"timestamp\":1733804460000},{\"state\":\"TRUE\",\"timestamp\":1733804400000},{\"state\":\"TRUE\",\"timestamp\":1733804340000},{\"state\":\"TRUE\",\"timestamp\":1733804280000},{\"state\":\"TRUE\",\"timestamp\":1733804220000},{\"state\":\"TRUE\",\"timestamp\":1733804160000},{\"state\":\"TRUE\",\"timestamp\":1733804100000},{\"state\":\"TRUE\",\"timestamp\":1733804040000},{\"state\":\"TRUE\",\"timestamp\":1733803980000},{\"state\":\"TRUE\",\"timestamp\":1733803920000},{\"state\":\"TRUE\",\"timestamp\":1733803860000},{\"state\":\"TRUE\",\"timestamp\":1733803800000},{\"state\":\"TRUE\",\"timestamp\":1733803740000},{\"state\":\"TRUE\",\"timestamp\":1733803680000},{\"state\":\"TRUE\",\"timestamp\":1733803620000},{\"state\":\"TRUE\",\"timestamp\":1733803560000},{\"state\":\"TRUE\",\"timestamp\":1733803500000},{\"state\":\"TRUE\",\"timestamp\":1733803440000},{\"state\":\"TRUE\",\"timestamp\":1733803380000},{\"state\":\"TRUE\",\"timestamp\":1733803320000},{\"state\":\"TRUE\",\"timestamp\":1733803260000},{\"state\":\"TRUE\",\"timestamp\":1733803200000},{\"state\":\"TRUE\",\"timestamp\":1733803140000},{\"state\":\"TRUE\",\"timestamp\":1733803080000},{\"state\":\"TRUE\",\"timestamp\":1733803020000},{\"state\":\"TRUE\",\"timestamp\":1733802960000},{\"state\":\"TRUE\",\"timestamp\":1733802900000},{\"state\":\"TRUE\",\"timestamp\":1733802840000},{\"state\":\"TRUE\",\"timestamp\":1733802780000},{\"state\":\"TRUE\",\"timestamp\":1733802720000},{\"state\":\"TRUE\",\"timestamp\":1733802660000},{\"state\":\"TRUE\",\"timestamp\":1733802600000},{\"state\":\"TRUE\",\"timestamp\":1733802540000},{\"state\":\"TRUE\",\"timestamp\":1733802480000},{\"state\":\"TRUE\",\"timestamp\":1733802420000},{\"state\":\"TRUE\",\"timestamp\":1733802360000},{\"state\":\"TRUE\",\"timestamp\":1733802300000},{\"state\":\"TRUE\",\"timestamp\":1733802240000},{\"state\":\"TRUE\",\"timestamp\":1733802180000},{\"state\":\"TRUE\",\"timestamp\":1733802120000},{\"state\":\"TRUE\",\"timestamp\":1733802060000},{\"state\":\"TRUE\",\"timestamp\":1733802000000},{\"state\":\"TRUE\",\"timestamp\":1733801940000},{\"state\":\"TRUE\",\"timestamp\":1733801880000},{\"state\":\"TRUE\",\"timestamp\":1733801820000},{\"state\":\"TRUE\",\"timestamp\":1733801760000},{\"state\":\"TRUE\",\"timestamp\":1733801700000},{\"state\":\"TRUE\",\"timestamp\":1733801640000},{\"state\":\"TRUE\",\"timestamp\":1733801580000},{\"state\":\"TRUE\",\"timestamp\":1733801520000},{\"state\":\"TRUE\",\"timestamp\":1733801460000},{\"state\":\"TRUE\",\"timestamp\":1733801400000},{\"state\":\"TRUE\",\"timestamp\":1733801340000},{\"state\":\"TRUE\",\"timestamp\":1733801280000},{\"state\":\"TRUE\",\"timestamp\":1733801220000},{\"state\":\"TRUE\",\"timestamp\":1733801160000},{\"state\":\"TRUE\",\"timestamp\":1733801100000},{\"state\":\"TRUE\",\"timestamp\":1733801040000},{\"state\":\"TRUE\",\"timestamp\":1733800980000},{\"state\":\"TRUE\",\"timestamp\":1733800920000},{\"state\":\"TRUE\",\"timestamp\":1733800860000},{\"state\":\"TRUE\",\"timestamp\":1733800800000},{\"state\":\"TRUE\",\"timestamp\":1733800740000},{\"state\":\"TRUE\",\"timestamp\":1733800680000},{\"state\":\"TRUE\",\"timestamp\":1733800620000},{\"state\":\"TRUE\",\"timestamp\":1733800560000},{\"state\":\"TRUE\",\"timestamp\":1733800500000},{\"state\":\"TRUE\",\"timestamp\":1733800440000},{\"state\":\"TRUE\",\"timestamp\":1733800380000},{\"state\":\"TRUE\",\"timestamp\":1733800320000},{\"state\":\"TRUE\",\"timestamp\":1733800260000},{\"state\":\"TRUE\",\"timestamp\":1733800200000},{\"state\":\"TRUE\",\"timestamp\":1733800140000},{\"state\":\"FALSE\",\"timestamp\":1733800080000},{\"state\":\"FALSE\",\"timestamp\":1733800020000},{\"state\":\"FALSE\",\"timestamp\":1733799960000},{\"state\":\"FALSE\",\"timestamp\":1733799900000},{\"state\":\"FALSE\",\"timestamp\":1733799840000},{\"state\":\"FALSE\",\"timestamp\":1733799780000},{\"state\":\"FALSE\",\"timestamp\":1733799720000},{\"state\":\"FALSE\",\"timestamp\":1733799660000},{\"state\":\"FALSE\",\"timestamp\":1733799600000},{\"state\":\"FALSE\",\"timestamp\":1733799540000},{\"state\":\"FALSE\",\"timestamp\":1733799480000},{\"state\":\"FALSE\",\"timestamp\":1733799420000},{\"state\":\"FALSE\",\"timestamp\":1733799360000},{\"state\":\"FALSE\",\"timestamp\":1733799300000},{\"state\":\"FALSE\",\"timestamp\":1733799240000},{\"state\":\"FALSE\",\"timestamp\":1733799180000},{\"state\":\"FALSE\",\"timestamp\":1733799120000},{\"state\":\"FALSE\",\"timestamp\":1733799060000},{\"state\":\"FALSE\",\"timestamp\":1733799000000},{\"state\":\"FALSE\",\"timestamp\":1733798940000},{\"state\":\"FALSE\",\"timestamp\":1733798880000},{\"state\":\"FALSE\",\"timestamp\":1733798820000},{\"state\":\"FALSE\",\"timestamp\":1733798760000},{\"state\":\"FALSE\",\"timestamp\":1733798700000},{\"state\":\"FALSE\",\"timestamp\":1733798640000},{\"state\":\"FALSE\",\"timestamp\":1733798580000},{\"state\":\"FALSE\",\"timestamp\":1733798520000},{\"state\":\"FALSE\",\"timestamp\":1733798460000},{\"state\":\"FALSE\",\"timestamp\":1733798400000},{\"state\":\"FALSE\",\"timestamp\":1733798340000},{\"state\":\"FALSE\",\"timestamp\":1733798280000},{\"state\":\"FALSE\",\"timestamp\":1733798220000},{\"state\":\"FALSE\",\"timestamp\":1733798160000},{\"state\":\"FALSE\",\"timestamp\":1733798100000},{\"state\":\"FALSE\",\"timestamp\":1733798040000},{\"state\":\"TRUE\",\"timestamp\":1733797980000},{\"state\":\"TRUE\",\"timestamp\":1733797920000},{\"state\":\"TRUE\",\"timestamp\":1733797860000},{\"state\":\"TRUE\",\"timestamp\":1733797800000},{\"state\":\"TRUE\",\"timestamp\":1733797740000},{\"state\":\"FALSE\",\"timestamp\":1733797680000},{\"state\":\"FALSE\",\"timestamp\":1733797620000},{\"state\":\"FALSE\",\"timestamp\":1733797560000},{\"state\":\"FALSE\",\"timestamp\":1733797500000},{\"state\":\"TRUE\",\"timestamp\":1733797440000},{\"state\":\"TRUE\",\"timestamp\":1733797380000},{\"state\":\"TRUE\",\"timestamp\":1733797320000},{\"state\":\"TRUE\",\"timestamp\":1733797260000},{\"state\":\"TRUE\",\"timestamp\":1733797200000},{\"state\":\"TRUE\",\"timestamp\":1733797140000},{\"state\":\"TRUE\",\"timestamp\":1733797080000},{\"state\":\"TRUE\",\"timestamp\":1733797020000},{\"state\":\"TRUE\",\"timestamp\":1733796960000},{\"state\":\"TRUE\",\"timestamp\":1733796900000},{\"state\":\"TRUE\",\"timestamp\":1733796840000},{\"state\":\"TRUE\",\"timestamp\":1733796780000},{\"state\":\"TRUE\",\"timestamp\":1733796720000},{\"state\":\"TRUE\",\"timestamp\":1733796660000},{\"state\":\"TRUE\",\"timestamp\":1733796600000},{\"state\":\"TRUE\",\"timestamp\":1733796540000},{\"state\":\"TRUE\",\"timestamp\":1733796480000},{\"state\":\"TRUE\",\"timestamp\":1733796420000},{\"state\":\"TRUE\",\"timestamp\":1733796360000},{\"state\":\"TRUE\",\"timestamp\":1733796300000},{\"state\":\"TRUE\",\"timestamp\":1733796240000},{\"state\":\"TRUE\",\"timestamp\":1733796180000},{\"state\":\"TRUE\",\"timestamp\":1733796120000},{\"state\":\"TRUE\",\"timestamp\":1733796060000},{\"state\":\"TRUE\",\"timestamp\":1733796000000},{\"state\":\"TRUE\",\"timestamp\":1733795940000},{\"state\":\"TRUE\",\"timestamp\":1733795880000},{\"state\":\"TRUE\",\"timestamp\":1733795820000},{\"state\":\"TRUE\",\"timestamp\":1733795760000},{\"state\":\"TRUE\",\"timestamp\":1733795700000},{\"state\":\"TRUE\",\"timestamp\":1733795640000},{\"state\":\"TRUE\",\"timestamp\":1733795580000},{\"state\":\"TRUE\",\"timestamp\":1733795520000},{\"state\":\"TRUE\",\"timestamp\":1733795460000},{\"state\":\"TRUE\",\"timestamp\":1733795400000},{\"state\":\"TRUE\",\"timestamp\":1733795340000},{\"state\":\"TRUE\",\"timestamp\":1733795280000},{\"state\":\"TRUE\",\"timestamp\":1733795220000},{\"state\":\"TRUE\",\"timestamp\":1733795160000},{\"state\":\"TRUE\",\"timestamp\":1733795100000},{\"state\":\"TRUE\",\"timestamp\":1733795040000},{\"state\":\"TRUE\",\"timestamp\":1733794980000},{\"state\":\"TRUE\",\"timestamp\":1733794920000},{\"state\":\"TRUE\",\"timestamp\":1733794860000},{\"state\":\"TRUE\",\"timestamp\":1733794800000},{\"state\":\"TRUE\",\"timestamp\":1733794740000},{\"state\":\"TRUE\",\"timestamp\":1733794680000},{\"state\":\"TRUE\",\"timestamp\":1733794620000},{\"state\":\"TRUE\",\"timestamp\":1733794560000},{\"state\":\"TRUE\",\"timestamp\":1733794500000},{\"state\":\"TRUE\",\"timestamp\":1733794440000},{\"state\":\"TRUE\",\"timestamp\":1733794380000},{\"state\":\"TRUE\",\"timestamp\":1733794320000},{\"state\":\"TRUE\",\"timestamp\":1733794260000},{\"state\":\"TRUE\",\"timestamp\":1733794200000},{\"state\":\"TRUE\",\"timestamp\":1733794140000},{\"state\":\"TRUE\",\"timestamp\":1733794080000},{\"state\":\"TRUE\",\"timestamp\":1733794020000},{\"state\":\"TRUE\",\"timestamp\":1733793960000},{\"state\":\"TRUE\",\"timestamp\":1733793900000},{\"state\":\"TRUE\",\"timestamp\":1733793840000},{\"state\":\"TRUE\",\"timestamp\":1733793780000},{\"state\":\"TRUE\",\"timestamp\":1733793720000},{\"state\":\"TRUE\",\"timestamp\":1733793660000},{\"state\":\"TRUE\",\"timestamp\":1733793600000},{\"state\":\"TRUE\",\"timestamp\":1733793540000},{\"state\":\"TRUE\",\"timestamp\":1733793480000},{\"state\":\"TRUE\",\"timestamp\":1733793420000},{\"state\":\"TRUE\",\"timestamp\":1733793360000},{\"state\":\"TRUE\",\"timestamp\":1733793300000},{\"state\":\"TRUE\",\"timestamp\":1733793240000},{\"state\":\"TRUE\",\"timestamp\":1733793180000},{\"state\":\"TRUE\",\"timestamp\":1733793120000},{\"state\":\"TRUE\",\"timestamp\":1733793060000},{\"state\":\"TRUE\",\"timestamp\":1733793000000},{\"state\":\"TRUE\",\"timestamp\":1733792940000},{\"state\":\"TRUE\",\"timestamp\":1733792880000},{\"state\":\"TRUE\",\"timestamp\":1733792820000},{\"state\":\"TRUE\",\"timestamp\":1733792760000},{\"state\":\"TRUE\",\"timestamp\":1733792700000},{\"state\":\"TRUE\",\"timestamp\":1733792640000},{\"state\":\"TRUE\",\"timestamp\":1733792580000},{\"state\":\"TRUE\",\"timestamp\":1733792520000},{\"state\":\"TRUE\",\"timestamp\":1733792460000},{\"state\":\"FALSE\",\"timestamp\":1733792400000},{\"state\":\"FALSE\",\"timestamp\":1733792340000},{\"state\":\"FALSE\",\"timestamp\":1733792280000},{\"state\":\"FALSE\",\"timestamp\":1733792220000},{\"state\":\"FALSE\",\"timestamp\":1733792160000},{\"state\":\"FALSE\",\"timestamp\":1733792100000},{\"state\":\"FALSE\",\"timestamp\":1733792040000},{\"state\":\"FALSE\",\"timestamp\":1733791980000},{\"state\":\"FALSE\",\"timestamp\":1733791920000},{\"state\":\"FALSE\",\"timestamp\":1733791860000},{\"state\":\"FALSE\",\"timestamp\":1733791800000},{\"state\":\"FALSE\",\"timestamp\":1733791740000},{\"state\":\"FALSE\",\"timestamp\":1733791680000},{\"state\":\"FALSE\",\"timestamp\":1733791620000},{\"state\":\"FALSE\",\"timestamp\":1733791560000},{\"state\":\"FALSE\",\"timestamp\":1733791500000},{\"state\":\"FALSE\",\"timestamp\":1733791440000},{\"state\":\"FALSE\",\"timestamp\":1733791380000},{\"state\":\"FALSE\",\"timestamp\":1733791320000},{\"state\":\"FALSE\",\"timestamp\":1733791260000},{\"state\":\"FALSE\",\"timestamp\":1733791200000},{\"state\":\"FALSE\",\"timestamp\":1733791140000},{\"state\":\"FALSE\",\"timestamp\":1733791080000},{\"state\":\"FALSE\",\"timestamp\":1733791020000},{\"state\":\"FALSE\",\"timestamp\":1733790960000},{\"state\":\"FALSE\",\"timestamp\":1733790900000},{\"state\":\"FALSE\",\"timestamp\":1733790840000},{\"state\":\"FALSE\",\"timestamp\":1733790780000},{\"state\":\"FALSE\",\"timestamp\":1733790720000},{\"state\":\"FALSE\",\"timestamp\":1733790660000}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":4660,"y":3240,"wires":[["baad0d9737c3e760"]]},{"id":"baad0d9737c3e760","type":"debug","z":"7f8982c35e9b9f8c","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":4830,"y":3240,"wires":[]}]

statetrail

Sequence:

  1. I First get the data from a mysql database in the format timestamp,status. the data frequency is one datapoint / minute and state is either 'on' or 'off'.
  2. the data is used to chart the statetrail. statetrail automatically 'merges' the similar states.
  3. when i click on any 'segment' of the chart, it returns me the starttime and endtime of the segment and also the duration
  4. this in turn triggers a dropdown and upon selecting any option, another database is updated with the selected option along with the starttime and endtime retrieved from the click even on the chart.

In the link provided, i was able to generate a statetrail chart (hard coded in the js file) but thats it. i couldnt go any further.

Help with UIBuilder - #66 by smanjunath211

OK, been a while since I looked at that. So lets get some example data together.

Are you able to share:

  • An example of the SQL data output
  • Your front-end html/js code
  • A simple flow - not sure if I need that, not if you are just sending the data direct to uibuilder but I will if you are using other nodes to help.

That should be a lot easier to start from rather than trying to start from scratch.

UIBUILDER example (static data directly put in js file)

mysql data

OK, going to take me a bit of time to get my head back around what you are trying to do.

Update 1: Already side-tracked! Finally fixed a small issue where renaming a url didn't change the IDE edit link. Now it does. :slight_smile: Also, your HTML had some errors which I needed to sort out first.

Update 2: A bit of progress. Not complete.

Firstly corrected HTML:

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="../uibuilder/images/node-blue.ico">

    <!-- Your own CSS (defaults to loading uibuilders css)-->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <!-- <script defer src="https://cdn.jsdelivr.net/npm/apexcharts"></script> -->
    <script defer src="https://www.gstatic.com/charts/loader.js"></script>
    <script defer src="../uibuilder/uibuilder.iife.min.js"></script>
    <script defer src="./index.js"></script>
    <!-- #endregion -->

</head><body>

    <div class="btn-group">
        <img src="\bbbdlogo1.gif" width="60" height="40" border-radius="30px" style="margin: 5px 5px 25px 25px" />

        <button id="b01" class="header" onclick="uibuilder.eventSend(event)">MyCompany Name</button>
        <button id="m01" class="button1" onclick="uibuilder.eventSend(event)">M-1</button>
        <button id="m02" class="button1" onclick="uibuilder.eventSend(event)">M-2</button>
        <button id="m03" class="button1" onclick="uibuilder.eventSend(event)">M-3</button>
        <button id="m04" class="button1" onclick="uibuilder.eventSend(event)">M-4</button>
        <button id="m05" class="button1" onclick="uibuilder.eventSend(event)">M-5</button>
        <button id="m06" class="button1" onclick="uibuilder.eventSend(event)">M-6</button>
        <button id="m07" class="button1" onclick="uibuilder.eventSend(event)">M-7</button>
        <button id="m08" class="button1" onclick="uibuilder.eventSend(event)">M-8</button>
        <button id="b10" class="time_display">Time</button>
    </div>

    <div class="inputs">
        <form id="sform1">
            <div title="Required. Type: Date" class="required">
                <label for="input-date">Select Date :</label>
                <input type="Date" label="Select Date :" id="input-date" required="true"
                    onchange="this.dataset.newValue = this.value" onfocus="this.dataset.oldValue = this.value"
                    name="input-date" title="Required. Type: Date"
                />

                <button type="button" title="Send the form data back to Node-RED" onclick="uibuilder.eventSend(event)" id="sform1-btn-send">
                    Send
                </button>
                <button type="button" title="Reset the form" onclick="uibuilder.eventSend(event)" id="sform1-btn-reset">
                    Reset
                </button>
            </div>
        </form>

        <div uib-topic="btnreport"></div>
    </div>

    <div id="timeline"></div>

    <div id="more"></div>

</body></html>

Next, some additional CSS:

body {
  padding: 1rem;
}

#timeline {
  /* height:7em; */
  /* margin: 1rem; */
  padding: 1rem;
}

#timeline > div > div:nth-child(1) > div > svg {
  background-color: hsl(72, 24%, 75%);
}

.inputs {
  margin: 1rem auto;
}

NOTE that I haven't cracked Google's very strange layout options yet. There is weird stuff going on.

Finally, here is the new, updated JavaScript:

// @ts-nocheck

/** @type {HTMLInputElement} Default date input to today */
const element = document.getElementById('input-date');
element.valueAsNumber = Date.now()-(new Date()).getTimezoneOffset()*60000;

const container = document.getElementById('timeline');
let chart
let dataTable

google.charts.load('current', { 'packages': ['timeline'] });
google.charts.setOnLoadCallback(initialiseChart);

const chartOptions = {
    timeline: { showRowLabels: true },
    avoidOverlappingGridLines: false,
    backgroundColor: 'white',
};

let mydata = [
    ['Machine 1', 'ON',  new Date(2024, 10, 27, 6, 0),  new Date(2024, 10, 27, 6, 10)],
    ['Machine 1', 'OFF', new Date(2024, 10, 27, 6, 10), new Date(2024, 10, 27, 6, 45)],
    ['Machine 1', 'ON',  new Date(2024, 10, 27, 6, 45), new Date(2024, 10, 27, 6, 59)],
    ['Machine 1', 'OFF', new Date(2024, 10, 27, 6, 59), new Date(2024, 10, 27, 7, 45)],
    ['Machine 2', 'ON',  new Date(2024, 10, 27, 7, 45), new Date(2024, 10, 27, 8, 19)],
    ['Machine 2', 'OFF', new Date(2024, 10, 27, 8, 19), new Date(2024, 10, 27, 9, 45)],
    ['Machine 2', 'ON',  new Date(2024, 10, 27, 9, 45), new Date(2024, 10, 27, 10, 29)],
]

function initialiseChart(data) {
    chart = new google.visualization.Timeline(container);
    dataTable = new google.visualization.DataTable();

    dataTable.addColumn({ type: 'string', id: 'Machine' });
    dataTable.addColumn({ type: 'string', id: 'Name' });
    dataTable.addColumn({ type: 'date', id: 'Start' });
    dataTable.addColumn({ type: 'date', id: 'End' });

    dataTable.addRows(mydata);

    // The select handler. Call the chart's getSelection() method
    function selectHandler() {
        const selectedItem = chart.getSelection()[0];
        // console.log('Selected Item: ', selectedItem);
        
        if (selectedItem) {
            const selectedMachine = dataTable.getValue(selectedItem.row, 0);
            const selectedValue = dataTable.getValue(selectedItem.row, 1);
            const selectedStart = dataTable.getValue(selectedItem.row, 2);
            const selectedEnd = dataTable.getValue(selectedItem.row, 3);
            console.log(`SELECTED: ${selectedMachine}, ${selectedValue}, ${selectedStart.toISOString()}, ${selectedEnd.toISOString()}`);
            // Let's get the selection back to Node-RED ...
            uibuilder.send({
                'topic': 'selected',
                'payload': {
                    'machine': selectedMachine,
                    'value': selectedValue,
                    'start': selectedStart.toISOString(),
                    'end': selectedEnd.toISOString(),
                }
            })
        }
    }

    // Listen for the 'select' event, and call my function selectHandler() when
    // the user selects something on the chart.
    google.visualization.events.addListener(chart, 'select', selectHandler);
    
    // Draw the chart - note that Google charts don't automatically resize!! ☹️
    chart.draw(dataTable, chartOptions);
}

// Listen for incoming messages from Node-RED
uibuilder.onChange('msg', function(msg){
    console.info('[indexjs:msg] Received', msg)
    if (msg.topic === 'update') {
        mydata = msg.payload
        dataTable.removeRows(0, dataTable.getNumberOfRows())
        dataTable.addRows(mydata)
        chart.draw(dataTable, chartOptions)
    }
})

I've refactored the code to make a bit more sense. The example Google code is both dated and pretty poor quality in places.

If you click on one of the cells, you will now get a message back to Node-RED.

There is also a function to listen for data from Node-RED but I've not quite finished or tested that bit.

Still, good progress. Ultimately, this should be wrapped into a simple web component. It could then be used in uibuilder, the Dashboards and even with http-in/-response nodes.

Enough for tonight though.

1 Like

Thanks for the effort.

eagerly waiting ....

I tried to input a message to the uibuilder and
I get a continuous warning of table has no rows when i just hover on the chart..
what is the syntax expected of new data input. i am supplying like this.

[{"Machine":"Machine 3","Name":"OFF","Start":"new Date(2024, 10, 27, 6, 0)","End":"new Date(2024, 10, 27, 6, 30)   "},{"Machine":"Machine 3","Name":"ON","Start":"new Date(2024, 10, 27, 6, 30)","End":"new Date(2024, 10, 27, 6, 50)   "}]

(Note: the new data loads when I click M1 button)

statetrail1

Yes, I think that if you checked your browsers dev console, you would see some errors - the input data is not quite in the right format. I will get it working.

Something weird with the uib-cache node in your original example flow:

Should look like:

Not sure what has happened there. I've replaced with a new node in my example.

It was there in my original post, now I dragged another node from pallate, and it looks like the latter.

1 Like
jsapi_compiled_default_module.js:294 
Uncaught Error: Row 0 is not null or an array.
    at gvjs_.G_ (jsapi_compiled_default_module.js:294:238)
    at gvjs_.Yn (jsapi_compiled_default_module.js:295:73)
    at Object.<anonymous> (index.js:79:19)
    at HTMLDocument.f (uibuilder.module.js:683:31)
    at E._dispatchCustomEvent (uibuilder.module.js:644:18)
    at E.set (uibuilder.module.js:530:14)
    at E._stdMsgFromServer (uibuilder.module.js:2146:22)

Yup, just been through those. Here is an updated index.js

// @ts-nocheck

/** @type {HTMLInputElement} Default date input to today */
const element = document.getElementById('input-date');
element.valueAsNumber = Date.now()-(new Date()).getTimezoneOffset()*60000;

const container = document.getElementById('timeline');
let chart
let dataTable

google.charts.load('current', { 'packages': ['timeline'] });
google.charts.setOnLoadCallback(initialiseChart);

const chartOptions = {
    timeline: { showRowLabels: true },
    avoidOverlappingGridLines: false,
    backgroundColor: 'white',
};

let mydata = [
    ['Machine 1', 'ON',  new Date(2024, 10, 27, 6, 0),  new Date(2024, 10, 27, 6, 10)],
    ['Machine 1', 'OFF', new Date(2024, 10, 27, 6, 10), new Date(2024, 10, 27, 6, 45)],
    ['Machine 1', 'ON',  new Date(2024, 10, 27, 6, 45), new Date(2024, 10, 27, 6, 59)],
    ['Machine 1', 'OFF', new Date(2024, 10, 27, 6, 59), new Date(2024, 10, 27, 7, 45)],
    ['Machine 2', 'ON',  new Date(2024, 10, 27, 7, 45), new Date(2024, 10, 27, 8, 19)],
    ['Machine 2', 'OFF', new Date(2024, 10, 27, 8, 19), new Date(2024, 10, 27, 9, 45)],
    ['Machine 2', 'ON',  new Date(2024, 10, 27, 9, 45), new Date(2024, 10, 27, 10, 29)],
]

function initialiseChart(data) {
    chart = new google.visualization.Timeline(container);
    dataTable = new google.visualization.DataTable();

    dataTable.addColumn({ type: 'string', id: 'Machine' });
    dataTable.addColumn({ type: 'string', id: 'Name' });
    dataTable.addColumn({ type: 'date', id: 'Start' });
    dataTable.addColumn({ type: 'date', id: 'End' });

    dataTable.addRows(mydata);

    // The select handler. Call the chart's getSelection() method
    function selectHandler() {
        const selectedItem = chart.getSelection()[0];
        // console.log('Selected Item: ', selectedItem);
        
        if (selectedItem) {
            const selectedMachine = dataTable.getValue(selectedItem.row, 0);
            const selectedValue = dataTable.getValue(selectedItem.row, 1);
            const selectedStart = dataTable.getValue(selectedItem.row, 2);
            const selectedEnd = dataTable.getValue(selectedItem.row, 3);
            console.log(`SELECTED: ${selectedMachine}, ${selectedValue}, ${selectedStart.toISOString()}, ${selectedEnd.toISOString()}`);
            // Let's get the selection back to Node-RED ...
            uibuilder.send({
                'topic': 'selected',
                'payload': {
                    'machine': selectedMachine,
                    'value': selectedValue,
                    'start': selectedStart.toISOString(),
                    'end': selectedEnd.toISOString(),
                }
            })
        }
    }

    // Listen for the 'select' event, and call my function selectHandler() when
    // the user selects something on the chart.
    google.visualization.events.addListener(chart, 'select', selectHandler);
    
    // Draw the chart - note that Google charts don't automatically resize!! ☹️
    chart.draw(dataTable, chartOptions);
}

function convertDateString(dateString) {
    const date = new Date(dateString)
    return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes())
}

// Listen for incoming messages from Node-RED
uibuilder.onChange('msg', function(msg){
    console.info('[indexjs:msg] Received', msg)
    if (msg.topic === 'update') {
        mydata = msg.payload
        
        // Convert date strings to Date objects needed for Google Charts
        mydata.forEach(row => {
            row[2] = convertDateString(row[2])
            row[3] = convertDateString(row[3])
        })

        // Merge in the sqlData object generated in Node-RED
        msg.sqlData.forEach(row => {
            mydata.push([
                row.machine,
                row.value,
                convertDateString(row.start),
                convertDateString(row.end)
            ])
        })

        console.info('[indexjs:msg:update] ', mydata)

        // dataTable.removeRows(0, dataTable.getNumberOfRows())
        
        dataTable.addRows(mydata)
        chart.draw(dataTable, chartOptions)
    }
})

And I replaced the simple static change node input with a function node so that I could try out the example SQL data you shared previously. I have to say that the SQL data is not ideal.

A bit long so let me share the final issue here. The example sql data does not include a machine name so I assumed it was all one machine - however, the timestamps overlap which Google timeline does not appear to like. I don't really have time to fix that I'm afraid.

Function node code moved to a 2nd post as this is too long :slight_smile:

Function node:

// Needed for the uibuilder cache to work
// & the front end msg listener
msg.topic = 'update'

// Example SQL data - has to be converted to an array of arrays
msg.sqlData = [
    // I'VE TRIMMED THIS - PUT YOUR SQL DATA HERE - IT LOOKS LIKE THIS:
    {
        "state": "FALSE",
        "timestamp": 1733790660000
    }
]

// Correct the input data types
// As we have to fix the timestamps in the front-end
// anyway, we will convert this object data to an array there
msg.sqlData.forEach( (row, i) => {
    // Convert the state from TRUE/FALSE to ON/OFF
    row.state = row.state === "TRUE" ? "ON" : "OFF"
    // Convert the timestamp to start Date object
    row.start = new Date(row.timestamp)

    // End timestamp is start of next entry or NOW
    try {
    if (i < msg.sqlData.length - 1) {
        row.end = new Date(msg.sqlData[i+1].timestamp)
    } else {
        row.end = new Date() // now
    }
    } catch (e) {
        node.error(`i=${i}, Len=${msg.sqlData.length}`, msg);
    }

    // We need a machine id
    row.machine = 'Machine 4'
})

// This is what we will use to extend the chart.
// We will add the sqlData in the front-end.
// Google charts seem to want arrays not objects.
msg.payload = [
    [
        "Machine 3",
        "OFF",
        new Date(2024, 10, 27, 6, 0),
        new Date(2024, 10, 27, 6, 30)
    ],
    [
        "Machine 3",
        "ON",
        new Date(2024, 10, 27, 6, 30),
        new Date(2024, 10, 27, 6, 50)
    ]
]

return msg

This input in the function node is giving me error.

I am sorry, i dont understand what is array of array ?

[
{"state":"FALSE","timestamp":1733790660000},
{"state":"FALSE","timestamp":1733890660000},
{"state":"FALSE","timestamp":1733990660000}
]

I thought like this? but it is not working i am not sure, whether it is due to array syntax error or date time conversion error.

statetrail2

for my use case, i need one machine at a time only. the built in example from google charts had actually multiple presidents service time, so i just copied that and replaced with machine.

Please let me know how to get mysql data out put, so that i can modify my mysql query

detailed error text

Invalid data at row #9: start(Tue Dec 10 2024 13:59:00 GMT+0530 (India Standard Time)) > end(Tue Dec 10 2024 13:58:00 GMT+0530 (India Standard Time)).

Yes, that is the error I mentioned. in your data, you have some entries where the timestamp from the following entry is BEFORE the timestamp of the previous. The Google Timeline chart really does not like that at all.

If the data really is on/off data for a single device, this cannot happen so something is off with your source data.

I'm not terribly happy with the very restrictive way that the Google charts work. So I asked GitHub Copilot AI to build me something from scratch.

Has taken a few attempts to get something close and it certainly isn't anything "production" ready or even especially robust right now but it is interesting and likely to be rather more lightweight than having to use a full charting library.

The following example is a standalone HTML file so no Node-RED/uibuilder integration but it will show you this:

I think that it is close enough as a proof of concept to maybe make it worth while continuing to develop. Because it is pure HTML/CSS/JavaScript and has no dependencies, it should be performant and have good flexibility. But it is going to take some CSS wrangling to make it look good.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Timeline</title>
    <style>
        .timeline-container {
            display: flex;
            flex-direction: column;
            gap: 1rem;
        }

        .timeline-item-container {
            display: grid;
            grid-template-columns: 200px 1fr;
            align-items: center;
            gap: 0.5rem;
        }

        .timeline-item-title {
            font-weight: bold;
        }

        .timeline-line {
            position: relative;
            height: 20px;
            width: 100%;
            border: 1px solid #000;
        }

        .timeline-block {
            position: absolute;
            height: 100%;
            border: 1px solid #000;
        }

        .timeline-axis-container {
            display: grid;
            grid-template-columns: 200px 1fr;
            /* justify-content: center; */
            align-items: center;
            height: 20px;
            /* width: 100%; */
            gap: 0.5rem;
        }

        .timeline-axis-line {
            position: relative;
            height: 20px;
            width: 100%;
            border-top: 1px solid #000;
        }

        .timeline-axis-tick {
            position: absolute;
            top: -10px;
            transform: translateX(-50%);
            font-size: 10px;
        }
    </style>
</head>

<body>
    <script>
        const inputData = [
            { group: "grp1", item: "item1", value: 1, startTime: "2024-12-10T07:01:00.000Z", endTime: "2024-12-10T07:23:05.000Z" },
            { group: "grp1", item: "item1", value: 0, startTime: "2024-12-10T07:25:00.000Z", endTime: "2024-12-10T08:03:05.000Z" },

            { group: "grp1", item: "item2", value: 1, startTime: "2024-12-10T07:01:00.000Z", endTime: "2024-12-10T07:23:05.000Z" },
            { group: "grp1", item: "item2", value: 0, startTime: "2024-12-10T07:25:00.000Z", endTime: "2024-12-10T08:03:05.000Z" },

            { group: "grp2", item: "item3", value: 1, startTime: "2024-12-10T08:01:00.000Z", endTime: "2024-12-10T08:23:05.000Z" },
            { group: "grp2", item: "item3", value: 0, startTime: "2024-12-10T08:25:00.000Z", endTime: "2024-12-10T09:03:05.000Z" },
        ];

        const categoryMap = [
            { range: [0, 0.3], title: "Off", color: "#ff0000" },
            { range: [0.3, 0.6], title: "Partial", color: "#ffff00" },
            { range: [0.6, 1], title: "On", color: "#00ff00" }
        ];

        function generateTimeline(data, categories, showAxis = 'both') {
            const container = document.createElement('div');
            container.className = 'timeline-container';

            const groups = [...new Set(data.map(item => item.group))];

            // Calculate the minimum and maximum time span
            const times = data.flatMap(item => [new Date(item.startTime), new Date(item.endTime)]);
            const minTime = Math.min(...times);
            const maxTime = Math.max(...times);
            const totalDuration = maxTime - minTime;

            const axisLine = createAxisLine(minTime, maxTime);

            if (showAxis === 'before' || showAxis === 'both') {
                container.appendChild(axisLine.cloneNode(true));
            }

            groups.forEach(group => {
                const itemsByGroup = data.filter(item => item.group === group);
                const items = [...new Set(itemsByGroup.map(item => item.item))];

                items.forEach(itemName => {
                    const itemContainer = document.createElement('div');
                    itemContainer.className = 'timeline-item-container';
                    itemContainer.setAttribute('role', 'listitem');
                    itemContainer.setAttribute('aria-label', `${group} ${itemName}`);

                    const itemTitle = document.createElement('div');
                    itemTitle.className = 'timeline-item-title';
                    itemTitle.textContent = `${group} ${itemName}`;
                    itemContainer.appendChild(itemTitle);

                    const timelineLine = document.createElement('div');
                    timelineLine.className = 'timeline-line';

                    const itemEntries = itemsByGroup.filter(item => item.item === itemName);
                    itemEntries.forEach(item => {
                        const itemElement = document.createElement('div');
                        itemElement.className = 'timeline-block';
                        itemElement.setAttribute('aria-label', `${item.item}: ${item.startTime} to ${item.endTime}`);

                        const category = categories.find(cat => item.value >= cat.range[0] && item.value <= cat.range[1]);
                        itemElement.style.backgroundColor = category.color;
                        itemElement.title = category.title;

                        const startTime = new Date(item.startTime).getTime();
                        const endTime = new Date(item.endTime).getTime();
                        const startPercent = ((startTime - minTime) / totalDuration) * 100;
                        const endPercent = ((endTime - minTime) / totalDuration) * 100;
                        const widthPercent = endPercent - startPercent;

                        itemElement.style.left = `${startPercent}%`;
                        itemElement.style.width = `${widthPercent}%`;

                        timelineLine.appendChild(itemElement);
                    });

                    itemContainer.appendChild(timelineLine);
                    container.appendChild(itemContainer);
                });
            });

            if (showAxis === 'after' || showAxis === 'both') {
                container.appendChild(axisLine.cloneNode(true));
            }

            return container;
        }

        function createAxisLine(minTime, maxTime) {
            const axisContainer = document.createElement('div');
            axisContainer.className = 'timeline-axis-container';

            const itemTitle = document.createElement('div');
            itemTitle.className = 'timeline-item-title';
            // itemTitle.textContent = `${group} ${itemName}`;
            axisContainer.appendChild(itemTitle);

            const axisLine = document.createElement('div');
            axisLine.className = 'timeline-axis-line';

            const totalDuration = maxTime - minTime;
            const numTicks = 10;
            for (let i = 0; i <= numTicks; i++) {
                const tick = document.createElement('div');
                tick.className = 'timeline-axis-tick';
                tick.style.left = `${(i / numTicks) * 100}%`;
                const tickTime = new Date(minTime + (i / numTicks) * totalDuration);
                tick.textContent = tickTime.toISOString().substring(11, 19); // Show only the time part
                axisLine.appendChild(tick);
            }

            axisContainer.appendChild(axisLine);
            return axisContainer;
        }

        document.body.appendChild(generateTimeline(inputData, categoryMap, 'both'));
    </script>
</body>

</html>

Slow progress on my alternative, but progress none-the-less:

The block colours are specified in a mapping table:

const categoryMap = [
    { range: [0, 0.3], title: "Off", color: "#ff0000" },
    { range: [0.3, 0.6], title: "Partial", color: "#ffff00" },
    { range: [0.6, 1], title: "On", color: "#00ff00" }
];

So you can use any numeric value in the input data and it automatically maps to an output name and colour.