diff --git a/cmd/testdata/expected.png b/cmd/testdata/expected.png deleted file mode 100644 index 76c2086c..00000000 Binary files a/cmd/testdata/expected.png and /dev/null differ diff --git a/cmd/testdata/listNodeExecutions.pb b/cmd/testdata/listNodeExecutions.pb deleted file mode 100755 index e6616f5b..00000000 --- a/cmd/testdata/listNodeExecutions.pb +++ /dev/null @@ -1 +0,0 @@ -{"nodeExecutions":[{"id":{"nodeId":"anchor-delta-pisco-courier-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-delta-pisco-courier-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-delta-pisco-courier-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:43.677332131Z","duration":"358.930932274s","createdAt":"2019-12-27T04:29:43.451873593Z","updatedAt":"2019-12-27T04:35:42.608264274Z"}},{"id":{"nodeId":"anchor-delta-pisco-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-delta-pisco-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-delta-pisco-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:43.797278756Z","duration":"358.902470118s","createdAt":"2019-12-27T04:29:43.471973329Z","updatedAt":"2019-12-27T04:35:42.699749118Z"}},{"id":{"nodeId":"anchor-pt-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-pt-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-pt-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:44.017460292Z","duration":"179.695872036s","createdAt":"2019-12-27T04:29:43.493317991Z","updatedAt":"2019-12-27T04:32:43.713332036Z"}},{"id":{"nodeId":"anchor-seconds-for-pickup-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-seconds-for-pickup-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/anchor-seconds-for-pickup-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:43.894056515Z","duration":"520.845931192s","createdAt":"2019-12-27T04:29:43.515200606Z","updatedAt":"2019-12-27T04:38:24.739988192Z"}},{"id":{"nodeId":"disco-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/disco-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/disco-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:44.178781075Z","duration":"1080.756197828s","createdAt":"2019-12-27T04:29:43.540785290Z","updatedAt":"2019-12-27T04:47:44.934978828Z"}},{"id":{"nodeId":"elasticity-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/elasticity-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/elasticity-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:23:43.095553724Z","duration":"179.126524598s","createdAt":"2019-12-27T04:23:42.965347678Z","updatedAt":"2019-12-27T04:26:42.222078598Z"}},{"id":{"nodeId":"end-node","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/end-node/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/end-node/data/outputs.pb","phase":"SUCCEEDED","createdAt":"2019-12-27T09:47:40.683869598Z","updatedAt":"2019-12-27T09:47:40.683869598Z"}},{"id":{"nodeId":"feature-schema-builder-wait-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/feature-schema-builder-wait-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/feature-schema-builder-wait-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T06:44:40.563004148Z","duration":"1064.125429606s","createdAt":"2019-12-27T06:44:40.175578570Z","updatedAt":"2019-12-27T07:02:24.688433606Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"fhhmgnya"}}}},{"id":{"nodeId":"get-flyte-id-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/get-flyte-id-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/get-flyte-id-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:15:00.340617062Z","duration":"144.920672530s","createdAt":"2019-12-27T04:14:59.958244329Z","updatedAt":"2019-12-27T04:17:25.261289530Z"}},{"id":{"nodeId":"marginal-cost-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/marginal-cost-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/marginal-cost-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:44.316465112Z","duration":"715.320860515s","createdAt":"2019-12-27T04:29:44.041682085Z","updatedAt":"2019-12-27T04:41:39.637325515Z"}},{"id":{"nodeId":"merge-list-of-workflows-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/merge-list-of-workflows-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/merge-list-of-workflows-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:47:46.510436019Z","duration":"158.453999763s","createdAt":"2019-12-27T04:47:46.158032142Z","updatedAt":"2019-12-27T04:50:24.964435763Z"}},{"id":{"nodeId":"model-version-flyte-id-no-shadow-price-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/model-version-flyte-id-no-shadow-price-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/model-version-flyte-id-no-shadow-price-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:50:43.213036591Z","duration":"161.860506096s","createdAt":"2019-12-27T04:50:43.064699825Z","updatedAt":"2019-12-27T04:53:25.073543096Z"}},{"id":{"nodeId":"model-version-flyte-id-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/model-version-flyte-id-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/model-version-flyte-id-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T08:41:39.627130578Z","duration":"165.225269255s","createdAt":"2019-12-27T08:41:39.503413661Z","updatedAt":"2019-12-27T08:44:24.852400255Z"}},{"id":{"nodeId":"modify-odtm-inputs-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/modify-odtm-inputs-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/modify-odtm-inputs-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:53:42.970848438Z","duration":"6642.009784878s","createdAt":"2019-12-27T04:53:42.659340176Z","updatedAt":"2019-12-27T06:44:24.980632878Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"fx4t3koy"}}}},{"id":{"nodeId":"multi-pax-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/multi-pax-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/multi-pax-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:17:43.773337726Z","duration":"176.609824130s","createdAt":"2019-12-27T04:17:43.653601200Z","updatedAt":"2019-12-27T04:20:40.383162130Z"}},{"id":{"nodeId":"odtm-inference-features-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/odtm-inference-features-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/odtm-inference-features-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:20:39.950492300Z","duration":"345.122938002s","createdAt":"2019-12-27T04:20:39.617005169Z","updatedAt":"2019-12-27T04:26:25.073430002Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"fhctuqia"}}}},{"id":{"nodeId":"optimization-workflow-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/optimization-workflow-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/optimization-workflow-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T06:47:40.711177755Z","duration":"2503.982834235s","createdAt":"2019-12-27T06:47:40.573590871Z","updatedAt":"2019-12-27T07:29:24.694012235Z"}},{"id":{"nodeId":"otm-anchor-without-pt-wait-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/otm-anchor-without-pt-wait-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/otm-anchor-without-pt-wait-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:23:43.177061727Z","duration":"179.724839689s","createdAt":"2019-12-27T04:23:42.986520127Z","updatedAt":"2019-12-27T04:26:42.901901689Z"}},{"id":{"nodeId":"outputs-for-deploy-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/outputs-for-deploy-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/outputs-for-deploy-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T08:44:39.768729188Z","duration":"165.959508569s","createdAt":"2019-12-27T08:44:39.605890716Z","updatedAt":"2019-12-27T08:47:25.728237569Z"}},{"id":{"nodeId":"pricing-rate-cards-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/pricing-rate-cards-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/pricing-rate-cards-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:15:00.664287238Z","duration":"324.179069187s","createdAt":"2019-12-27T04:14:59.981837899Z","updatedAt":"2019-12-27T04:20:24.843356187Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"f4pdswfy"}}}},{"id":{"nodeId":"shadow-price-multimode-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/shadow-price-multimode-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/shadow-price-multimode-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T07:29:41.082403592Z","duration":"4303.476917651s","createdAt":"2019-12-27T07:29:40.200546978Z","updatedAt":"2019-12-27T08:41:24.559321651Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"fxv33jgi"}}}},{"id":{"nodeId":"shared-prices-bias-correction-wf","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/shared-prices-bias-correction-wf/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/shared-prices-bias-correction-wf/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:29:44.437690560Z","duration":"340.662112946s","createdAt":"2019-12-27T04:29:44.338366248Z","updatedAt":"2019-12-27T04:35:25.099803946Z"}},{"id":{"nodeId":"ss-wait","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/ss-wait/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/ss-wait/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T08:44:40.157569665Z","duration":"3584.692380106s","createdAt":"2019-12-27T08:44:39.781994794Z","updatedAt":"2019-12-27T09:44:24.849950106Z","workflowNodeMetadata":{"executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7-ss-wait-0"}}}},{"id":{"nodeId":"start-node","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/start-node/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/start-node/data/outputs.pb","phase":"SUCCEEDED","createdAt":"2019-12-27T04:14:59.817727087Z","updatedAt":"2019-12-27T04:14:59.817727087Z"}},{"id":{"nodeId":"sync-to-hive-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/sync-to-hive-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/sync-to-hive-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T07:29:40.399697629Z","duration":"164.402305240s","createdAt":"2019-12-27T07:29:40.181018356Z","updatedAt":"2019-12-27T07:32:24.802003240Z"}},{"id":{"nodeId":"training-model-version-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/training-model-version-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/training-model-version-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:15:00.824754Z","duration":"144.625734867s","createdAt":"2019-12-27T04:15:00.004036085Z","updatedAt":"2019-12-27T04:17:25.450488867Z"}},{"id":{"nodeId":"trigger-anchor-delta-pisco-courier-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-delta-pisco-courier-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-delta-pisco-courier-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:42.450574051Z","duration":"162.202418782s","createdAt":"2019-12-27T04:26:42.259727340Z","updatedAt":"2019-12-27T04:29:24.652992782Z"}},{"id":{"nodeId":"trigger-anchor-delta-pisco-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-delta-pisco-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-delta-pisco-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:42.556729852Z","duration":"162.230384237s","createdAt":"2019-12-27T04:26:42.278771477Z","updatedAt":"2019-12-27T04:29:24.787114237Z"}},{"id":{"nodeId":"trigger-anchor-multimode-seconds-for-pickup-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-multimode-seconds-for-pickup-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-multimode-seconds-for-pickup-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:42.665682877Z","duration":"162.285206147s","createdAt":"2019-12-27T04:26:42.298212125Z","updatedAt":"2019-12-27T04:29:24.950889147Z"}},{"id":{"nodeId":"trigger-anchor-pt-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-pt-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-anchor-pt-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:42.792947963Z","duration":"162.277260244s","createdAt":"2019-12-27T04:26:42.316992540Z","updatedAt":"2019-12-27T04:29:25.070208244Z"}},{"id":{"nodeId":"trigger-disco-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-disco-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-disco-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:43.053586219Z","duration":"162.174671406s","createdAt":"2019-12-27T04:26:42.813440496Z","updatedAt":"2019-12-27T04:29:25.228257406Z"}},{"id":{"nodeId":"trigger-marginal-cost-multimode-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-marginal-cost-multimode-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-marginal-cost-multimode-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:43.166123196Z","duration":"162.183791824s","createdAt":"2019-12-27T04:26:42.829270765Z","updatedAt":"2019-12-27T04:29:25.349914824Z"}},{"id":{"nodeId":"trigger-odtm-elasticity-training-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-odtm-elasticity-training-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-odtm-elasticity-training-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:20:40.138105783Z","duration":"164.945099532s","createdAt":"2019-12-27T04:20:39.982052333Z","updatedAt":"2019-12-27T04:23:25.083205532Z"}},{"id":{"nodeId":"trigger-otm-anchor-without-pt-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-otm-anchor-without-pt-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-otm-anchor-without-pt-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:20:40.313951021Z","duration":"164.880836952s","createdAt":"2019-12-27T04:20:39.998439683Z","updatedAt":"2019-12-27T04:23:25.194787952Z"}},{"id":{"nodeId":"trigger-shared-prices-bias-correction-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-shared-prices-bias-correction-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-shared-prices-bias-correction-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:26:43.308110852Z","duration":"162.151878194s","createdAt":"2019-12-27T04:26:42.843975972Z","updatedAt":"2019-12-27T04:29:25.459989194Z"}},{"id":{"nodeId":"trigger-shared-rides-multi-pax-fee-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-shared-rides-multi-pax-fee-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trigger-shared-rides-multi-pax-fee-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T04:15:00.949173213Z","duration":"144.605692590s","createdAt":"2019-12-27T04:15:00.025394325Z","updatedAt":"2019-12-27T04:17:25.554865590Z"}},{"id":{"nodeId":"trig-odtm-optimization-workflow-0","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trig-odtm-optimization-workflow-0/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/trig-odtm-optimization-workflow-0/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T06:44:40.705101199Z","duration":"163.899836253s","createdAt":"2019-12-27T06:44:40.198511474Z","updatedAt":"2019-12-27T06:47:24.604937253Z"}},{"id":{"nodeId":"validation-task","executionId":{"project":"priceoptimizeroffline","domain":"production","name":"eqwdb3jwg7"}},"inputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/validation-task/data/inputs.pb","closure":{"outputUri":"s3://lyft-modelbuilder/metadata/propeller/production/priceoptimizeroffline-production-eqwdb3jwg7/validation-task/data/outputs.pb","phase":"SUCCEEDED","startedAt":"2019-12-27T09:44:39.364123962Z","duration":"165.649058943s","createdAt":"2019-12-27T09:44:39.216343263Z","updatedAt":"2019-12-27T09:47:25.013182943Z"}}]} \ No newline at end of file diff --git a/cmd/timeline.go b/cmd/timeline.go deleted file mode 100644 index b62c6a16..00000000 --- a/cmd/timeline.go +++ /dev/null @@ -1,197 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "os" - "strconv" - "time" - - "github.com/wcharczuk/go-chart/drawing" - - "github.com/wcharczuk/go-chart" - - "github.com/lyft/flyteidl/gen/pb-go/flyteidl/service" - - "github.com/golang/protobuf/ptypes" - "github.com/golang/protobuf/ptypes/duration" - "github.com/lyft/flytestdlib/logger" - - adminIdl "github.com/lyft/flyteidl/gen/pb-go/flyteidl/admin" - coreIdl "github.com/lyft/flyteidl/gen/pb-go/flyteidl/core" - - "github.com/lyft/flyteidl/clients/go/admin" - "github.com/spf13/cobra" -) - -type timelineFlags struct { - persistentFlags - ExecutionName *string - OutputPath *string -} - -func newTimelineCmd(flags persistentFlags) *cobra.Command { - timelineFlags := timelineFlags{persistentFlags: flags} - timelineCmd := &cobra.Command{ - Use: "timeline", - Short: "Visualize workflow execution timeline.", - RunE: func(cmd *cobra.Command, args []string) error { - ctx := context.Background() - c := admin.InitializeAdminClient(ctx, *admin.GetConfig(ctx)) - return visualizeTimeline(ctx, c, timelineFlags) - }, - } - - timelineFlags.ExecutionName = timelineCmd.Flags().String("execution", "", "Specifies the name of the execution to visualize.") - timelineFlags.OutputPath = timelineCmd.Flags().String("output-path", "timeline.png", "Specifies the output image path.") - - return timelineCmd -} - -func getStartedAtTime(nodeExec *adminIdl.NodeExecution) time.Time { - if startedAt := nodeExec.Closure.StartedAt; startedAt != nil { - return time.Unix(startedAt.Seconds, int64(startedAt.Nanos)) - } else if createdAt := nodeExec.Closure.CreatedAt; createdAt != nil { - return time.Unix(createdAt.Seconds, int64(createdAt.Nanos)) - } else { - return time.Now() - } -} - -func getEndTime(startedAt time.Time, d *duration.Duration) time.Time { - if d == nil { - return startedAt - } - - goDuration, err := ptypes.Duration(d) - if err != nil { - logger.Errorf(context.TODO(), "Failed to parse duration [%v]. Error: %v", d, err) - return startedAt - } - - return startedAt.Add(goDuration) -} - -func visualizeTimeline(ctx context.Context, adminClient service.AdminServiceClient, flags timelineFlags) error { - chartTasks := make([]chart.StackedBar, 0, 10) - token := "" - firstTime := time.Now().Add(time.Hour * 10) - lastTime := time.Unix(0, 0) - barStyle := chart.Style{ - FillColor: drawing.ColorFromHex("c11313"), - StrokeColor: drawing.ColorFromHex("c11313"), - StrokeWidth: 0, - } - - noShowBarStyle := chart.Style{ - FillColor: drawing.ColorFromHex("ffffff"), - StrokeColor: drawing.ColorFromHex("ffffff"), - StrokeWidth: 0, - } - - allResp := make([]*adminIdl.NodeExecution, 0, 100) - - for { - resp, err := adminClient.ListNodeExecutions(ctx, &adminIdl.NodeExecutionListRequest{ - WorkflowExecutionId: &coreIdl.WorkflowExecutionIdentifier{ - Project: *flags.Project, - Domain: *flags.Domain, - Name: *flags.ExecutionName, - }, - Limit: 100, - Token: token, - }) - - if err != nil { - return err - } - - allResp = append(allResp, resp.NodeExecutions...) - - if len(resp.GetToken()) == 0 { - break - } - } - - for _, nodeExec := range allResp { - startedAt := getStartedAtTime(nodeExec) - finishedAt := getEndTime(startedAt, nodeExec.Closure.Duration) - if firstTime.After(startedAt) { - firstTime = startedAt - } - - if finishedAt.After(lastTime) { - lastTime = finishedAt - } - } - - for i, nodeExec := range allResp { - startedAt := getStartedAtTime(nodeExec) - finishedAt := getEndTime(startedAt, nodeExec.Closure.Duration) - chartTasks = append(chartTasks, chart.StackedBar{ - Name: strconv.Itoa(i), - Values: []chart.Value{ - { - Style: noShowBarStyle, - Value: startedAt.Sub(firstTime).Minutes(), - }, - { - Style: barStyle, - Value: finishedAt.Sub(startedAt).Minutes(), - }, - { - Style: noShowBarStyle, - Value: lastTime.Sub(finishedAt).Minutes(), - }, - }, - }) - } - - chartData := chart.StackedBarChart{ - Title: fmt.Sprintf("%v-%v-%v", *flags.Project, *flags.Domain, *flags.ExecutionName), - TitleStyle: chart.StyleShow(), - Background: chart.Style{ - Padding: chart.Box{ - Top: 40, - }, - }, - Width: 8096, - Height: 1024, - Bars: chartTasks, - XAxis: chart.StyleShow(), - YAxis: chart.StyleShow(), - //Height: (chartTasks[0].GetHeight() + 30) * len(chartTasks), - //BarSpacing: 30, - } - - if flags.OutputPath != nil { - f, err := os.Create(*flags.OutputPath) - if err != nil { - return err - } - - defer func() { - err = f.Close() - if err != nil { - panic(err) - } - }() - - return chartData.Render(chart.PNG, f) - } - - return nil - - //result := render.ProcessStructured(firstTime, chartData) - //logger.Print(ctx, result.Code) - // - //if result.Code > 0 { - // return fmt.Errorf(result.Message) - //} - // - ////save to file - //if flags.OutputPath != nil { - // return result.Context.SavePNG(*flags.OutputPath) - //} - // -} diff --git a/cmd/timeline_test.go b/cmd/timeline_test.go deleted file mode 100644 index 047d12be..00000000 --- a/cmd/timeline_test.go +++ /dev/null @@ -1,178 +0,0 @@ -package cmd - -import ( - "bytes" - "context" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/golang/protobuf/jsonpb" - - "github.com/stretchr/testify/mock" - - adminIdl "github.com/lyft/flyteidl/gen/pb-go/flyteidl/admin" - coreIdl "github.com/lyft/flyteidl/gen/pb-go/flyteidl/core" - - "github.com/lyft/flyteidl/clients/go/admin" - "github.com/lyft/flytestdlib/config" - "github.com/lyft/flytestdlib/config/viper" - - "github.com/lyft/flyteidl/clients/go/admin/mocks" - - "github.com/stretchr/testify/assert" -) - -var update = flag.Bool("update", false, "Updates testdata") - -func refStr(s string) *string { - return &s -} - -func copyFile(src, dst string) error { - sourceFileStat, err := os.Stat(src) - if err != nil { - return err - } - - if !sourceFileStat.Mode().IsRegular() { - return fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(src) - if err != nil { - return err - } - - destination, err := os.Create(dst) - if err != nil { - err2 := source.Close() - if err2 != nil { - return config.ErrorCollection([]error{err, err2}) - } - - return err - } - - errs := make([]error, 0) - _, err = io.Copy(destination, source) - if err != nil { - errs = append(errs, err) - } - - err2 := source.Close() - if err2 != nil { - errs = append(errs, err2) - } - - err3 := destination.Close() - if err3 != nil { - errs = append(errs, err3) - } - - if len(errs) > 0 { - return config.ErrorCollection(errs) - } - - return nil -} - -func Test_updateAdminResponse(t *testing.T) { - if !*update { - t.SkipNow() - } - - accessor := viper.NewAccessor(config.Options{ - SearchPaths: []string{filepath.Join("testdata", "config.yaml")}, - }) - - ctx := context.Background() - assert.NoError(t, accessor.UpdateConfig(ctx)) - c := admin.InitializeAdminClient(ctx, *admin.GetConfig(ctx)) - resp, err := c.ListNodeExecutions(ctx, &adminIdl.NodeExecutionListRequest{ - WorkflowExecutionId: &coreIdl.WorkflowExecutionIdentifier{ - Project: "priceoptimizeroffline", - Domain: "production", - Name: "eqwdb3jwg7", - }, - Limit: 100, - }) - - assert.NoError(t, err) - - if err != nil { - t.FailNow() - } - - m := &jsonpb.Marshaler{} - var buf bytes.Buffer - err = m.Marshal(&buf, resp) - assert.NoError(t, err) - assert.NoError(t, ioutil.WriteFile(filepath.Join("testdata", "listNodeExecutions.pb"), buf.Bytes(), os.ModePerm)) -} - -func Test_visualizeTimeline(t *testing.T) { - ctx := context.Background() - - respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "listNodeExecutions.pb")) - assert.NoError(t, err) - - resp := &adminIdl.NodeExecutionList{} - assert.NoError(t, jsonpb.Unmarshal(bytes.NewReader(respBytes), resp)) - - m := &mocks.AdminServiceClient{} - m.OnListNodeExecutionsMatch(mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - assert.NoError(t, visualizeTimeline(ctx, m, timelineFlags{ - persistentFlags: persistentFlags{ - Project: refStr("priceoptimizeroffline"), - Domain: refStr("production"), - }, - ExecutionName: refStr("eqwdb3jwg7"), - })) -} - -func Test_visualizeTimeline_test_output(t *testing.T) { - ctx := context.Background() - - respBytes, err := ioutil.ReadFile(filepath.Join("testdata", "listNodeExecutions.pb")) - assert.NoError(t, err) - - resp := &adminIdl.NodeExecutionList{} - assert.NoError(t, jsonpb.Unmarshal(bytes.NewReader(respBytes), resp)) - - tmpLoc, err := ioutil.TempFile(os.TempDir(), "visualize_time_line.png") - assert.NoError(t, err) - assert.NoError(t, tmpLoc.Close()) - - m := &mocks.AdminServiceClient{} - m.OnListNodeExecutionsMatch(mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - assert.NoError(t, visualizeTimeline(ctx, m, timelineFlags{ - persistentFlags: persistentFlags{ - Project: refStr("priceoptimizeroffline"), - Domain: refStr("production"), - }, - ExecutionName: refStr("eqwdb3jwg7"), - OutputPath: refStr(tmpLoc.Name()), - })) - - expectedPath := filepath.Join("testdata", "expected.png") - if *update { - assert.NoError(t, copyFile(tmpLoc.Name(), expectedPath)) - } - - expectedBytes, err := ioutil.ReadFile(expectedPath) - assert.NoError(t, err) - - actualBytes, err := ioutil.ReadFile(tmpLoc.Name()) - assert.NoError(t, err) - - if assert.Equal(t, expectedBytes, actualBytes) { - assert.NoError(t, os.Remove(tmpLoc.Name())) - } else { - t.Logf("Files are different, expected file [%v] vs actual file [%v]", expectedPath, tmpLoc.Name()) - } -} diff --git a/graph/timeline_chart.go b/graph/timeline_chart.go deleted file mode 100644 index 11da5973..00000000 --- a/graph/timeline_chart.go +++ /dev/null @@ -1,436 +0,0 @@ -package graph - -import ( - "errors" - "fmt" - "io" - "math" - - chart "github.com/wcharczuk/go-chart" - - "github.com/golang/freetype/truetype" - "github.com/wcharczuk/go-chart/seq" - util "github.com/wcharczuk/go-chart/util" -) - -type BarSection struct { - StartValue float64 - Length float64 -} - -// BarSections is an array of Value. -type BarSections []BarSection - -//// BarSections returns the values. -//func (vs BarSections) Values() []float64 { -// values := make([]float64, len(vs)) -// for index, v := range vs { -// values[index] = v.Length -// } -// -// return values -//} - -//// ValuesNormalized returns normalized values. -//func (vs BarSections) ValuesNormalized() []float64 { -// return util.Math.Normalize(vs.Values()...) -//} - -// Normalize returns the values normalized. -func (vs BarSections) Normalize(minValue, maxValue float64) []BarSection { - var output []BarSection - for _, v := range vs { - if v.Length > 0 { - output = append(output, BarSection{ - StartValue: util.Math.RoundUp((v.StartValue-minValue)/(maxValue-minValue), 0.0001), - Length: util.Math.RoundUp(v.Length/(maxValue-minValue), 0.0001), - }) - } - } - - return output -} - -// Bar is a bar within a TimelineChart. -type Bar struct { - Name string - Style chart.Style - Sections BarSections -} - -// GetHeight returns the width of the bar. -func (sb Bar) GetHeight() int { - return 15 -} - -// TimelineChart is a chart that draws sections of a bar based on percentages. -type TimelineChart struct { - Title string - TitleStyle chart.Style - - ColorPalette chart.ColorPalette - - Width int - Height int - DPI float64 - - Background chart.Style - Canvas chart.Style - - XAxis chart.Style - YAxis chart.Style - - BarSpacing int - - Font *truetype.Font - defaultFont *truetype.Font - - Bars []Bar - Elements []chart.Renderable -} - -// GetDPI returns the dpi for the chart. -func (sbc TimelineChart) GetDPI(defaults ...float64) float64 { - if sbc.DPI == 0 { - if len(defaults) > 0 { - return defaults[0] - } - return chart.DefaultDPI - } - return sbc.DPI -} - -// GetFont returns the text font. -func (sbc TimelineChart) GetFont() *truetype.Font { - if sbc.Font == nil { - return sbc.defaultFont - } - return sbc.Font -} - -// GetHeight returns the chart width or the default value. -func (sbc TimelineChart) GetWidth() int { - if sbc.Width == 0 { - return chart.DefaultChartWidth - } - return sbc.Width -} - -// GetHeight returns the chart height or the default value. -func (sbc TimelineChart) GetHeight() int { - if sbc.Height == 0 { - return chart.DefaultChartHeight - } - return sbc.Height -} - -// GetBarSpacing returns the spacing between bars. -func (sbc TimelineChart) GetBarSpacing() int { - if sbc.BarSpacing == 0 { - return 100 - } - return sbc.BarSpacing -} - -// Render renders the chart with the given renderer to the given io.Writer. -func (sbc TimelineChart) Render(rp chart.RendererProvider, w io.Writer) error { - if len(sbc.Bars) == 0 { - return errors.New("please provide at least one bar") - } - - r, err := rp(sbc.GetWidth(), sbc.GetHeight()) - if err != nil { - return err - } - - if sbc.Font == nil { - defaultFont, err := chart.GetDefaultFont() - if err != nil { - return err - } - sbc.defaultFont = defaultFont - } - r.SetDPI(sbc.GetDPI(chart.DefaultDPI)) - - canvasBox := sbc.getAdjustedCanvasBox(r, sbc.getDefaultCanvasBox()) - sbc.drawCanvas(r, canvasBox) - sbc.drawBars(r, canvasBox) - sbc.drawXAxis(r, canvasBox) - sbc.drawYAxis(r, canvasBox) - - sbc.drawTitle(r) - for _, a := range sbc.Elements { - a(r, canvasBox, sbc.styleDefaultsElements()) - } - - return r.Save(w) -} - -func (sbc TimelineChart) drawCanvas(r chart.Renderer, canvasBox chart.Box) { - chart.Draw.Box(r, canvasBox, sbc.getCanvasStyle()) -} - -func (sbc TimelineChart) drawBars(r chart.Renderer, canvasBox chart.Box) { - yoffset := canvasBox.Bottom - for _, bar := range sbc.Bars { - sbc.drawBar(r, canvasBox, sbc.getMinValue(), sbc.getMaxValue(), yoffset, bar) - yoffset -= sbc.GetBarSpacing() - bar.GetHeight() - } -} - -func (sbc TimelineChart) getMaxValue() float64 { - maxValue := float64(0) - for _, bar := range sbc.Bars { - for _, section := range bar.Sections { - maxValue = math.Max(maxValue, section.Length+section.StartValue) - } - } - - return maxValue -} - -func (sbc TimelineChart) getMinValue() float64 { - maxValue := math.MaxFloat64 - for _, bar := range sbc.Bars { - for _, section := range bar.Sections { - maxValue = math.Min(maxValue, section.StartValue) - } - } - - return maxValue -} - -func (sbc TimelineChart) drawBar(r chart.Renderer, canvasBox chart.Box, minValue, maxValue float64, yoffset int, bar Bar) int { - barSpacing2 := sbc.GetBarSpacing() >> 1 - bxl := yoffset + barSpacing2 - bxr := bxl + bar.GetHeight() - - normalizedBarComponents := bar.Sections.Normalize(minValue, maxValue) - for index, bv := range normalizedBarComponents { - barWidth := int(math.Ceil(bv.Length * float64(canvasBox.Width()))) - barStart := int(math.Ceil(bv.StartValue * float64(canvasBox.Width()))) - barBox := chart.Box{ - Top: bxl, - Left: util.Math.MinInt(barStart, canvasBox.Right-chart.DefaultStrokeWidth), - Right: util.Math.MinInt(barStart+barWidth, canvasBox.Right-chart.DefaultStrokeWidth), - Bottom: bxr, - } - - chart.Draw.Box(r, barBox, bar.Style.InheritFrom(sbc.styleDefaultsStackedBarValue(index))) - } - - return bxr -} - -func (sbc TimelineChart) drawXAxis(r chart.Renderer, canvasBox chart.Box) { - if sbc.XAxis.Show { - axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) - axisStyle.WriteToRenderer(r) - - r.MoveTo(canvasBox.Left, canvasBox.Bottom) - r.LineTo(canvasBox.Right, canvasBox.Bottom) - r.Stroke() - - r.MoveTo(canvasBox.Left, canvasBox.Bottom) - r.LineTo(canvasBox.Left, canvasBox.Bottom+chart.DefaultVerticalTickHeight) - r.Stroke() - - cursor := canvasBox.Left - for _, bar := range sbc.Bars { - - barLabelBox := chart.Box{ - Top: canvasBox.Bottom + chart.DefaultXAxisMargin, - Left: cursor, - Right: cursor + bar.GetHeight() + sbc.GetBarSpacing(), - Bottom: sbc.GetHeight(), - } - if len(bar.Name) > 0 { - chart.Draw.TextWithin(r, bar.Name, barLabelBox, axisStyle) - } - axisStyle.WriteToRenderer(r) - r.MoveTo(barLabelBox.Right, canvasBox.Bottom) - r.LineTo(barLabelBox.Right, canvasBox.Bottom+chart.DefaultVerticalTickHeight) - r.Stroke() - cursor += bar.GetHeight() + sbc.GetBarSpacing() - } - } -} - -func (sbc TimelineChart) drawXAxis(r chart.Renderer, canvasBox chart.Box) { - if sbc.XAxis.Show { - axisStyle := sbc.XAxis.InheritFrom(sbc.styleDefaultsAxes()) - axisStyle.WriteToRenderer(r) - r.MoveTo(canvasBox.Right, canvasBox.Top) - r.LineTo(canvasBox.Right, canvasBox.Bottom) - r.Stroke() - - r.MoveTo(canvasBox.Right, canvasBox.Bottom) - r.LineTo(canvasBox.Right+chart.DefaultHorizontalTickWidth, canvasBox.Bottom) - r.Stroke() - - ticks := seq.RangeWithStep(0.0, 1.0, 0.2) - for _, t := range ticks { - axisStyle.GetStrokeOptions().WriteToRenderer(r) - ty := canvasBox.Bottom - int(t*float64(canvasBox.Height())) - r.MoveTo(canvasBox.Right, ty) - r.LineTo(canvasBox.Right+chart.DefaultHorizontalTickWidth, ty) - r.Stroke() - - axisStyle.GetTextOptions().WriteToRenderer(r) - text := fmt.Sprintf("%0.0f%%", t*100) - - tb := r.MeasureText(text) - chart.Draw.Text(r, text, canvasBox.Right+chart.DefaultYAxisMargin+5, ty+(tb.Height()>>1), axisStyle) - } - - } -} - -func (sbc TimelineChart) drawTitle(r chart.Renderer) { - if len(sbc.Title) > 0 && sbc.TitleStyle.Show { - r.SetFont(sbc.TitleStyle.GetFont(sbc.GetFont())) - r.SetFontColor(sbc.TitleStyle.GetFontColor(sbc.GetColorPalette().TextColor())) - titleFontSize := sbc.TitleStyle.GetFontSize(chart.DefaultTitleFontSize) - r.SetFontSize(titleFontSize) - - textBox := r.MeasureText(sbc.Title) - - textWidth := textBox.Width() - textHeight := textBox.Height() - - titleX := (sbc.GetWidth() >> 1) - (textWidth >> 1) - titleY := sbc.TitleStyle.Padding.GetTop(chart.DefaultTitleTop) + textHeight - - r.Text(sbc.Title, titleX, titleY) - } -} - -func (sbc TimelineChart) getCanvasStyle() chart.Style { - return sbc.Canvas.InheritFrom(sbc.styleDefaultsCanvas()) -} - -func (sbc TimelineChart) styleDefaultsCanvas() chart.Style { - return chart.Style{ - FillColor: sbc.GetColorPalette().CanvasColor(), - StrokeColor: sbc.GetColorPalette().CanvasStrokeColor(), - StrokeWidth: chart.DefaultCanvasStrokeWidth, - } -} - -// GetColorPalette returns the color palette for the chart. -func (sbc TimelineChart) GetColorPalette() chart.ColorPalette { - if sbc.ColorPalette != nil { - return sbc.ColorPalette - } - return chart.AlternateColorPalette -} - -func (sbc TimelineChart) getDefaultCanvasBox() chart.Box { - return sbc.Box() -} - -func (sbc TimelineChart) getAdjustedCanvasBox(r chart.Renderer, canvasBox chart.Box) chart.Box { - var totalHeight int - for _, bar := range sbc.Bars { - totalHeight += bar.GetHeight() + sbc.GetBarSpacing() - } - // - //if sbc.YAxis.Show { - // yaxisHeight := chart.DefaultHorizontalTickWidth - // - // axisStyle := sbc.YAxis.InheritFrom(sbc.styleDefaultsAxes()) - // axisStyle.WriteToRenderer(r) - // - // cursor := canvasBox.Bottom - // for _, bar := range sbc.Bars { - // if len(bar.Name) > 0 { - // barLabelBox := chart.Box{ - // Top: cursor + bar.GetHeight() + sbc.GetBarSpacing(), //canvasBox.Bottom + chart.DefaultXAxisMargin, - // Left: canvasBox.Left, //cursor, - // Right: cursor + bar.GetHeight() + sbc.GetBarSpacing(), - // Bottom: sbc.GetHeight(), - // } - // lines := chart.Text.WrapFit(r, bar.Name, barLabelBox.Width(), axisStyle) - // linesBox := chart.Text.MeasureLines(r, lines, axisStyle) - // - // yaxisHeight = util.Math.MaxInt(linesBox.Height()+(2*chart.DefaultXAxisMargin), yaxisHeight) - // } - // } - // return chart.Box{ - // Top: canvasBox.Top, - // Left: canvasBox.Left, - // Right: canvasBox.Left + totalHeight, - // Bottom: sbc.GetHeight() - yaxisHeight, - // } - //} - return chart.Box{ - Top: canvasBox.Top, - Left: canvasBox.Left, - Right: canvasBox.Left + totalHeight, - Bottom: canvasBox.Bottom, - } - -} - -// Box returns the chart bounds as a box. -func (sbc TimelineChart) Box() chart.Box { - dpr := sbc.Background.Padding.GetRight(10) - dpb := sbc.Background.Padding.GetBottom(50) - - return chart.Box{ - Top: sbc.Background.Padding.GetTop(20), - Left: sbc.Background.Padding.GetLeft(20), - Right: sbc.GetWidth() - dpr, - Bottom: sbc.GetHeight() - dpb, - } -} - -func (sbc TimelineChart) styleDefaultsStackedBarValue(index int) chart.Style { - return chart.Style{ - StrokeColor: sbc.GetColorPalette().GetSeriesColor(index), - StrokeWidth: 3.0, - FillColor: sbc.GetColorPalette().GetSeriesColor(index), - } -} - -func (sbc TimelineChart) styleDefaultsTitle() chart.Style { - return sbc.TitleStyle.InheritFrom(chart.Style{ - FontColor: chart.DefaultTextColor, - Font: sbc.GetFont(), - FontSize: sbc.getTitleFontSize(), - TextHorizontalAlign: chart.TextHorizontalAlignCenter, - TextVerticalAlign: chart.TextVerticalAlignTop, - TextWrap: chart.TextWrapWord, - }) -} - -func (sbc TimelineChart) getTitleFontSize() float64 { - effectiveDimension := util.Math.MinInt(sbc.GetWidth(), sbc.GetHeight()) - if effectiveDimension >= 2048 { - return 48 - } else if effectiveDimension >= 1024 { - return 24 - } else if effectiveDimension >= 512 { - return 18 - } else if effectiveDimension >= 256 { - return 12 - } - return 10 -} - -func (sbc TimelineChart) styleDefaultsAxes() chart.Style { - return chart.Style{ - StrokeColor: chart.DefaultAxisColor, - Font: sbc.GetFont(), - FontSize: chart.DefaultAxisFontSize, - FontColor: chart.DefaultAxisColor, - TextHorizontalAlign: chart.TextHorizontalAlignCenter, - TextVerticalAlign: chart.TextVerticalAlignTop, - TextWrap: chart.TextWrapWord, - } -} -func (sbc TimelineChart) styleDefaultsElements() chart.Style { - return chart.Style{ - Font: sbc.GetFont(), - } -}