From 410b1e828dc2de98bf844cb48b54e690d1dbc477 Mon Sep 17 00:00:00 2001 From: Goel Biju Date: Thu, 25 Feb 2021 15:35:08 +0000 Subject: [PATCH 01/10] Add pareto-front visualisation and correct scaling (#6) --- .../frontend-common/src/state/state.types.tsx | 6 +- packages/frontend-plugin/src/App.tsx | 18 +- .../frontend-plugin/src/example.component.tsx | 23 -- .../src/visualisation.component.tsx | 213 ++++++++++++++++++ 4 files changed, 218 insertions(+), 42 deletions(-) delete mode 100644 packages/frontend-plugin/src/example.component.tsx create mode 100644 packages/frontend-plugin/src/visualisation.component.tsx diff --git a/packages/frontend-common/src/state/state.types.tsx b/packages/frontend-common/src/state/state.types.tsx index f35bad0..e646141 100644 --- a/packages/frontend-common/src/state/state.types.tsx +++ b/packages/frontend-common/src/state/state.types.tsx @@ -25,11 +25,11 @@ export interface Run { updatedAt: string; } -export interface Data { +export type Data = { generation: number; data: number[][]; time: Date; -} +} | null; export interface FrontendState { notifications: string[]; @@ -46,7 +46,7 @@ export interface FrontendState { runs: Run[]; selectedRun: Run | null; selectedVisualisation: string; - data: Data | null; + data: Data; // currentGeneration: number; } diff --git a/packages/frontend-plugin/src/App.tsx b/packages/frontend-plugin/src/App.tsx index 7475866..ce0daf5 100644 --- a/packages/frontend-plugin/src/App.tsx +++ b/packages/frontend-plugin/src/App.tsx @@ -10,7 +10,7 @@ import { AnyAction, Store } from "redux"; import { createLogger } from "redux-logger"; import thunk from "redux-thunk"; import "./App.css"; -import ExampleComponent from "./example.component"; +import VisualisationComponent from "./visualisation.component"; const history = createBrowserHistory(); const middleware = [thunk, routerMiddleware(history)]; @@ -83,21 +83,7 @@ class App extends React.Component { } else { return ( - {/* - - ) => } - /> - - */} - + ); } diff --git a/packages/frontend-plugin/src/example.component.tsx b/packages/frontend-plugin/src/example.component.tsx deleted file mode 100644 index 31238e5..0000000 --- a/packages/frontend-plugin/src/example.component.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { StateType } from "frontend-common"; -import React from "react"; -import { connect } from "react-redux"; - -interface ExampleComponentStateProps { - notifications: string[]; -} - -const ExampleComponent = ( - props: ExampleComponentStateProps -): React.ReactElement => ( -
-

{props.notifications}

-
-); - -const mapStateToProps = (state: StateType): ExampleComponentStateProps => { - return { - notifications: state.frontend.notifications, - }; -}; - -export default connect(mapStateToProps)(ExampleComponent); diff --git a/packages/frontend-plugin/src/visualisation.component.tsx b/packages/frontend-plugin/src/visualisation.component.tsx new file mode 100644 index 0000000..7269a3a --- /dev/null +++ b/packages/frontend-plugin/src/visualisation.component.tsx @@ -0,0 +1,213 @@ +import * as d3 from "d3"; +import { Data, StateType } from "frontend-common"; +import React from "react"; +import { connect } from "react-redux"; + +interface VCProps { + data: Data; +} + +// const sampleData = [ +// [34, 78], +// [109, 280], +// [310, 120], +// [79, 411], +// [420, 220], +// [233, 145], +// [333, 96], +// [222, 333], +// [78, 320], +// [21, 123], +// ]; + +const VisualisationComponent = (props: VCProps): React.ReactElement => { + const chartRef: React.RefObject = React.createRef(); + + const margin = { + top: 10, + right: 30, + bottom: 30, + left: 60, + }; + const width = 760 - margin.left - margin.right; + const height = 800 - margin.top - margin.bottom; + + const xScale: d3.ScaleLinear = d3 + .scaleLinear() + .range([0, width]); + const yScale: d3.ScaleLinear = d3 + .scaleLinear() + .range([height, 0]); + + const [builtChart, setBuiltChart] = React.useState(false); + + // public constructor(props: VCStateProps) { + // super(props); + + // // Create ref and configure D3 selection for svg chart. + // this.chartRef = React.createRef(); + + // // Initialise the chart sizes + // this.margin = { + // top: 10, + // right: 30, + // bottom: 30, + // left: 60, + // }; + // this.width = 760 - this.margin.left - this.margin.right; + // this.height = 800 - this.margin.top - this.margin.bottom; + + // // Add scales + // this.xScale = d3.scaleLinear().range([0, this.width]); + // this.yScale = d3.scaleLinear().range([this.height, 0]); + + // // Initialise the state + // // this.state = { + // // data: sampleData, + // // }; + // } + + // Get current chart a D3 selection + const currentChart = React.useCallback( + (): d3.Selection => + d3.select(chartRef.current), + [chartRef] + ); + + // public componentDidMount(): void { + // this.buildChart(); + + // // if (!this.state.chartConnected) { + // // // Start socket connections. + // // initiateSocket(); + + // // // Subscribe to the data feed. + // // subscribeToData((data) => { + // // // Add the new data point. + // // this.setState({ data: [...this.state.data, [data.x, data.y]] }); + // // }); + + // // // Set to being connected. + // // this.setState({ chartConnected: true }); + // // } + // } + + // public componentDidUpdate( + // prevProps: VCStateProps + // ): void { + // // console.log("Previous: ", prevState.data); + // // console.log("New: ", this.state.data); + + // this.updateChart(); + // } + + // Build the chart initially. + const buildChart = React.useCallback( + (data: number[][]) => { + // Adjust the svg. + const svg = currentChart() + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left}, ${margin.top})`); + + // Add x axis + const xAxis = xScale.domain([0, d3.max(data, (d) => d[0]) as number]); + svg + .append("g") + .attr("class", "scatter-chart-xaxis") + .attr("transform", `translate(0, ${height})`) + .call(d3.axisBottom(xAxis)); + + // Add y axis + const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); + svg + .append("g") + .attr("class", "scatter-chart-yaxis") + .call(d3.axisLeft(yAxis)); + + // Add dots + svg + .append("g") + .attr("class", "scatter-chart-points") + .selectAll("dot") + .data(data) + .enter() + .append("circle") + .attr("cx", (d) => d[0]) + .attr("cy", (d) => d[1]) + .attr("r", 3) + .style("fill", "#69b3a2"); + }, + [ + currentChart, + height, + margin.bottom, + margin.left, + margin.right, + margin.top, + width, + xScale, + yScale, + ] + ); + + // Update the chart when with new data. + const updateChart = React.useCallback( + (data: number[][]) => { + // Get the current SVG of the chart + const svg = currentChart(); + + // Update the x-axis + const xAxis = xScale.domain([0, d3.max(data, (d) => d[0]) as number]); + // NOTE: This is a shorthand to update the axis without using .call + // (using .call had type issues with .select). + d3.axisBottom(xAxis)(svg.select("g.scatter-chart-xaxis")); + + // Update the y-axis + const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); + d3.axisLeft(yAxis)(svg.select("g.scatter-chart-yaxis")); + + // Update points + svg + .select("g.scatter-chart-points") + .selectAll("dot") + .data(data) + .enter() + .append("circle") + .attr("cx", (d) => xScale(d[0]) as number) + .attr("cy", (d) => yScale(d[1]) as number) + .attr("r", 3) + .style("fill", "#69b3a2"); + }, + [currentChart, xScale, yScale] + ); + + // Build or update chart when data changes + React.useEffect(() => { + const data = props.data ? props.data.data : []; + console.log("Render: ", data); + + // Build chart initially otherwise update for data + if (!builtChart) { + buildChart(data); + setBuiltChart(true); + } else { + updateChart(data); + } + }, [buildChart, builtChart, props.data, updateChart]); + + return ( +
+ +
+ ); +}; + +const mapStateToProps = (state: StateType): VCProps => { + return { + data: state.frontend.data, + }; +}; + +export default connect(mapStateToProps)(VisualisationComponent); From c85e81b619ab0b1e7dd1d73fdc928424e03d2056 Mon Sep 17 00:00:00 2001 From: Goel Biju Date: Fri, 26 Feb 2021 18:16:53 +0000 Subject: [PATCH 02/10] Remove old circles before appending new ones (#16) --- documents/minutes/Project Meeting - 4.docx | Bin 15172 -> 16465 bytes documents/minutes/~$oject Meeting - 4.docx | Bin 0 -> 162 bytes .../src/visualisation.component.tsx | 102 +++++++++--------- .../src/components/routing.component.tsx | 29 ++--- 4 files changed, 58 insertions(+), 73 deletions(-) create mode 100644 documents/minutes/~$oject Meeting - 4.docx diff --git a/documents/minutes/Project Meeting - 4.docx b/documents/minutes/Project Meeting - 4.docx index 891e997b13f68559576be784b7071d28b253e8ef..05be19664b22d3ce3de80b82509be5602d32ab8c 100644 GIT binary patch delta 10793 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 V(EdUHN+7-@LVj{sKEZ!!{}<&>V=n*z delta 9486 zcmaJ{RajlilHRzxyW57~?(UG_?oNP>d)PsOySoQ>_W%Kc6Wl$xLvYwk&b{Z(nVE;_ zhx)p^s{dLKy{hW_tEWFh4S3c~z=P99MMQy!0Kj_|000XB0C<61E!iwV79NgPPVTJU zjt;-{S7TRsiF-^;Hv=!Vklm>x1pS1#>S!y`>Y(6N2)aw?RWyRzF1jdQqfmvMV+JL+w=2gz*4kEY8A4+A}r=tLQuso&!*%C;*Xi~sZEqSjtO+x zNW5tv9Bo?%(JAZ-N5c0!RV2Qz)jKQ&lD+;^OpDUd`fb1L2=zxt;Nh9Ym6TFLl%AS? zu;-$gf9%=Mf!=2REH34c4VKK93U%eCn@5WGz{8aH2(mN@`^_*aut>G7L3=MqdxJaD ziGDPY9jO6hgZHk$ZPHtUR`~jhHYTL4!U@nu&6#E8L*M$jT!;*HWb_9!ZIGH!NWXGk z%$7ZzlATit8FD9CwYPxNkImB|=II|34|hc*ePM^2s1Yk$A3oN&WP)J`s&L1uR(!l& z-VwXA3dP^j(JjbqbhnmA2cEmK3d^9S`1Mllw7wAd&^%g6OHtHOBb)P?ha>)Q^Gw7w z{qh`9Te$z((83gO1h_={ob)pg>f85Aebjf$er5e5@Ex*YvsID(BXeDpJnT?(FS%Mb^f4PtgM`QfTp_*sy7r_d)UX zws@dax0bQ&N+0YR;00U!BHc{k*Lm{(-4ExW+}75&9$C=0lTPRm4cyMr1Y`|9cQ>^_E_>f%eIg z$u`$rq{R>CS7XoqRDuV!Eq`b%u~IA940&xyGU;f9DtvHkz2g_iuXJPv&tRm5*Fa6( z`d#VB2@yQUSu0@Z$5-&g@3_skr~oJ%%1JL=ZsiV1R5kLMOvRCy&K8wm!j{HgIk_t#IDK@m*5MleXNju_@QSYSokE~ z3bm50mH*jl)M*B_<#aShVI%LA7OS@cH%#Ebqqq}{gA}jJ8alpVUBKnS_q#;mR~e#T zIx#6<%S*>?6&(d(wbKs&%3cCnzin-#h1bRWwKk!rK8FTb_ zgopH|WspnvvtYqR+X0*M=KJK~AWax=w;)|UmiJbTG>zW_2s?jdO_PISHnUp?YeSXk z)N*IRq;#jDSKQ1I{-H*`J=-guwb>1lt2u2~qF06`*NqF9D($_xtlWr>BNZarWQj43 zV*LT9j9x;T3w(}CfHNMUtQeEw-V-7mQ48g?(WBJ072`5X+etkpta3^`VJjH}r*6Fl zw4h1W$C(Q?IZoO+?D^%GO^juw!}{2I&@DDN^c>FR5VnHyiGDcNHnAL9+=9y^Jz1ha zeGwT3vB`ZlCa8|!Ntxy{m?`!f9`vqqswQG`66Y8F65{@BqA#+)%-3$*7XREah;_PO zUW#5qc?|su(Pn(|7GY8^TGvKId<+enXAUT|8v_#w&tsxH$JKz6i$OTap!s&mkvSBM zdHmP|r!~$|uyjccA~qVYH^C{GWhhhN*Q8Hg^m4P3#7mfs5ck(gjXbH6j%@5m_tDrS z|Nh(!6X=Vm7(v@*YaxgYJKWU zv{z93Te_hb34NzpFo6darxgp}#%N=g!mH+=U8`{wO+BmKKD4-8rkDi}Sf z%~v(vvziuNIX7WCf<*3jzJq=?Lyd9z-@1vj10K%3OBSPR@Gl~WS@hsE&QiZVkh)n> zJpUj{O2m6USkPQ8rK4klQ#9%pCClcb#~>$Q))Q;#6Ekc7ys*GIC*Bl+Jq_F$VMfpPkxkyzmXfPM7;QCPTHxx3pr*|_~x zSTyOZyRGnJg1==vQxN9HZGn0|`+R3rIRpY#7q>g(tH{t}` zO2oGo%bl6Uq{$bBuz%Cp7>e^*PGGS)eemgZIh<-m+O<{bBjY~=<7aCr`ISP<#*Pcp zOmE&9{*oKoS?GJufkBm3fJ`XcSqT`XYSB}qm6#!5D$z{)5^oM0iVjIFB4l-!ndZdW z$d4Yy(`Tbo3Os`93-Hx0=Uk(Tm5g}gl%cZCs*`q=W$p=I=M)Is>80{gLNWp#&o?j% zQ6}UQdOj2nKqWtKmVbuh;OoYgl_%{c4H{N>GsR*!9sbHG@Kht zBDud$8&oNL?6z3{9+&;wP{3Ri7%)5C{6czuZ~l1T62PSp7gy}(=V-hi#TG`?E7X!% zi)-P7^Y$bB(UBx5fb=xZD`@g&b?f5Alc%2G(omQ+deziv1AaP3qI8kj+%j(GYL{`7 z;^A&~nB$8fSQTAys%{OkTw*p9t{NpY^ovO|bo#=l+itr>atd=b6RunK*D9xl&2fr^ zcj$PuLM>Lku#LTLjQnPOX$<+2#Sho97vD(!Tu0@x$a_>ImRgh444dKco1lCo7ZHmY zP}444?rwBee6AyrM3FMvXEL6l{0pMk6K-mY@70<8U=2&tjGAdiF6XrXw<l6|xpINh6_VmCeV5Au)dcsgC;cKUONw+o~#Ov~rRtXkL` z)DQkY*-omp%u|P|pl8__HHBc#UDf^+yq-fgU>)wx5&^u4ayus(w~v`R)50>V5<2_r z)Mo`ono2U8?25}!aymQ^ymG{#r2<@pL0Gt>L1ak*^@xSwxH$H_?Qr=ApppV^Q4#SX z%lnA5HQYUxr1#!%_pdt0mspX1Fa?grX{4Xg zYG}NZvjE$UyKCa4SC-E*9d3-flht+|lvLVCmOry)Ka*`lC-kaCXcan9J;j~H{Ma=W zJWjvsJ?7sSI6*aBJ#34$$iQK)`;mJC@=>V7m}`B5O6tYNMQ+cZ97}@*0A7JfsMN&Z zRi|BE%uj4T!R)_t<&!P-co+2edzzR!qTqOC^6WBNBEpNsM_U(PGXf;VD!+YDZ?Y6v zfyGWbtuXqgi!I>B?KVu5i|BJ7@P20!-zAWUOn+;oUNBtNA@IoF_SHqK6S+E8p30~< zU5&d5U0(MB6aYC|clr}@V%xY+Y z7Xk9the5#PMM7YmmL?wMCZfEomc(%;)hD|4Ueu9l>f7=7E}*(?sOGY2aYr9mFQ_cr zkW6>Rpovjq6O5?a=toaCBk$Fr~VSfZuX13u0QRPEler4@VYvF9zaCv-d+nfxrOywd!4DpU1q1jhr^#^|m%Fwh&Cq&rB}O8l9znm-oUd2uRLNs>C zh(kid2hrtb>Db2=XxTjUiq#V}}IT5eLM0MT5r%wxz$7>yp z4MWWItGm)BZE$}$_QwN@3B;MKGUfOU+gjzHn=wepQd~iz6vNqrbe@VZ6J=KhdM=4! z+B(@awRc+C7eu_^m&Ka4{I7V69;gP7Ct8NJDR_%#M0dv^O&2pIX4XqIgNHHZNl}gN zW3nklbFI%4bXT^gL07-D4?rvta*fmO#2s+$gAJ*)SGK6x##^x-s37`?K#Mk0P-%o$ zu+Lr84ea%2h@YZ>!+ra@{?|3fk@j`%ucz&suWL@_>gTQC=QXElb@ZOq_D`xGKBhE) zmPX$$6a@e!6!QVjUrC8uE4Cq+3qGFLUke9qmw$=snE~$Ee(pE@s?f<0mt3_wqQyxTWupg(vDk}`_6MTo!;_^dFV=N#>YW>H0B6f5tXJ(5X- z9fq?&gz&+cbjN!>ZbyK}FL{Wf=A1L7F=LLlTCF%tph460ruMx0aSBo{22p#zBt4k- zkge6gGCuY~4>IPhJq4R+YU5AuB4`gtt{n&1c>~>r(romoWPMR=WPG#{7dl+bAhzq+ z1Yd9N+T{mq9rRfsm3&DgOf2B~C$NoI8k@;-Xc`NC3mGq4``r%2a!ULw`+^vMz&GEk zB2J6R3U!_sUGdj9Vj*~%ns*gwLe)2+8>0s%TK-c>cIAB8u?_}L6L~s(?H}4F(MpT% zTU1{6lS_BhX_2O~F|1J%E=^E!Us=2xeBNr=7B(kH>MJ;3@3%(=CZWN4w0U{vJ>1KZ zpo#S9Us*`;`?D!a+)m%$8iUUG6q^Rw__>(4R=^Q;>vB}+%F^4XjzW#T^%r*z`l|ch6NH%+}T) zMWQ=}a>87^Y%bW(&n;sP7L=+F6<1l95jq`eG&9N*<>>49g18 z$~nuKFjuDf_yk=ijl~rU6?qR{0xeS&LsLy2!4Pebsd_2P34M8Zq&78&A{rt|zmiokmyZ@zMqO{9F>~whwj|E7@VS@P%T3ah}cQ zigD7NJa=s|l-{p$7g|`iaS%EH`ouah#oB$up6}RDO+e{hq^=CbIK+<`HT+1P(7hl` z9bE16{heoZ+K6?ar-H_ma;4s~y}r`1)e2LRR(C$xIWJfnJlG~uugJG-wtK^?ZdYYYswDmu|6Spnv7vv_vaUSD@j>*=J2uAliKNO z{Y=hTR8%n9rD4if#gX)hj9&1|P9u26{Zg5rZ<%X)BNr=&yrB+7!a2TN0Z2uCWK}W@ zJHjz{4f!?2HFzBo>X~%&#F>+bgH_66v=D_34La(2D7(Svvmd^O47!?4qNrsYr^4=M z^RiSdfAbL*p8uqQHIh+qp=CEEw(_+mrsO-C2|d_hJCqHpD)Z^mbf2wn(rG*N;uJsD z-iD2q8F~8^nI~PYxs#dv7J|+OA(_T7-{Z z>9RRf<(w5KuOY)SlJucp)R@f7G&yw%Q{eGsfxVZ^?xahXV}FT{9`=OO*u8;j6I?Uk z?_$%f!syZVxq5{2!j0?|KGtTMH>Z~x0|##1h$TV;{PGn72b4$X+&K_%4fZm{u&?Z3io+6Z?3^#b>O<3X@fqw&R{;~i0a|p+=RM`k@MjR zpbztPu??-mjnS0+$!zs7tC+v}6XGeZN7WANX~DMU<(i15Scfc)AO@pG z+cDs@9%&W`C13DWcsK6Ch8!$BBolxYXwd4dMVsHYB|K5^bd~xV0{B30YEl%v@sN4M8n~si3 zX+T@6YiHSaNSRjcOBLjvI-A*pqItvH+x8?T0Fxt0a#>?8w|3sBrkfV}cJSan>Ria* z@TnIZ#>#Uqxpbin^->0+oV$$;ThA5%^p~|E*)93OPWtGs7bjzv0P=8GR z2jDzivq)3}9gm_J18FOw8?+08B?D$!c24hS`;iFrs#H=*(`MLaeY@DoRKw|)ke=g5 zq)%4AaX$F^a(J7)GB))G{6TNCFIPEsE_(Xoz-2yB$KJ4cj)|v6|2Uag=iTGiAuT0G z_1%iEyyf4M8o_ygKJ0Imhhj`+>dU`XF7xVH|HcVKuwwtED@H z-@gx(M6CCb4kynP6<%SN;BCdcQ>wrvqp(@I(i0d#JfKx{K9j|TcPrvcnq5B~2PYaU zg;jW_@{Km7t@6a58_cGH?H%QKahz|DtmXN3wZKjvn*^VYGLbf?ZNMMHCpjuac2BmR z>5)#43(lvM7sN73-r7d7(Yzf3SgX$Mt_SRsHv;;;c3Iz+&d)CMG_C z*@Ubb_pRMTDwJD)uS$d2QY3;`G?-cV>?9tCs%@MbbBHJfxLaEPyl=0Kk#tz(oo*lN7;4 z3bxlxL{Tt2gFG1td7zt|8m|~;J-ei>x%co0KEhJqu4JPcF>Z~EY z)w_s93`LZsDpu;=m}SN;$6g&U>ZU68gV9D~ph#Bz&n1$=d8<-T{Hg`zDt{787KLDz zQ51oNX_7_QgDY`{8IL$jiJs@iYYI~fe@m>?L_^?Y*FSF~v8UFZSu1#4A6!`*yXNX>bS zf`3>mq75Ys5}iGqdw=)3-4heCperJ_q2Bw3`gBV2S0N8Zso?cgga!b_kN^Pe|8$A1 zK~C|s}IaaUA?eEGtLoB)mn3~R$w(38s9m3Bf+I}Z3CJN7TmVm8AH<~+b19PYp z?x&_2`73dIMLDX6)>74TFlQH}!R|g-wM(ne2F;s=)6Twkr1hM-UbG+~qM%ulDLZ&r zE!+MuB@i2E6B-k!gkxVMFx%(V(rIpij85K-WoAxGlIp8HEs;UNT7xi+dvD!V3t zz$Fi*q24||@p@9oxG^oO|WmNfd_XEy-Uj1rCeDe2Q-2F zakRx9j-UP)7MNg*u{3rEneaF^TvdKjuy`Y*3*I@DDh#@~k)K%l;nc$v+3!}o+K?Y* zJC?Xc-=N-&qUtc(N)LV1%<=X{?%^Zk$@tBl(J}=OCi!bNooX9t18$y@$@2OXeEP?h z&Y{I|fXAq%*PW=hT0DYA{qlUxAEte?zg{!c0Nk_aV@-lE7X5Q_(^Bff(c4S4I_tU8 z2zhJP!t*NOoB1AJ9l?C(@Xn8wncE|#J!{Ub!1oMx4Gt+&+p{<3p$+K+w9zlVy3$1< z4>CQK+r9@=y*u1}Zv%|9E`&*uglNwL_&cogzyn>oqAVZ8#w+ml3h={&f0HAEP4qa+GX`F^3CPsPxS*{D0!z5!fh$>Xt&h| z@50RneP;X)H8a&#)JksaHRQR^+rS`$qV1uRQgqVmLH~fkZK6J&pN1g{@iCE=Fg-d= zR~`3h7z6RadVQ3lfg+qU}??EfB?gh^GRG^GFV&;L#Z|4{<~ zePl_P)VN7+qL`%r5I+C_#y=K+X5rLH%wkle|DB6r007+oW#G%2cYWy;@r sguzTuW}Q8Rar&W{mov|G#&75P4%@UdFic#;B^?9gi$c}iN`5{C00e^_4FCWD literal 0 HcmV?d00001 diff --git a/packages/frontend-plugin/src/visualisation.component.tsx b/packages/frontend-plugin/src/visualisation.component.tsx index 7269a3a..3d179e0 100644 --- a/packages/frontend-plugin/src/visualisation.component.tsx +++ b/packages/frontend-plugin/src/visualisation.component.tsx @@ -102,55 +102,52 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { // } // Build the chart initially. - const buildChart = React.useCallback( - (data: number[][]) => { - // Adjust the svg. - const svg = currentChart() - .attr("width", width + margin.left + margin.right) - .attr("height", height + margin.top + margin.bottom) - .append("g") - .attr("transform", `translate(${margin.left}, ${margin.top})`); - - // Add x axis - const xAxis = xScale.domain([0, d3.max(data, (d) => d[0]) as number]); - svg - .append("g") - .attr("class", "scatter-chart-xaxis") - .attr("transform", `translate(0, ${height})`) - .call(d3.axisBottom(xAxis)); - - // Add y axis - const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); - svg - .append("g") - .attr("class", "scatter-chart-yaxis") - .call(d3.axisLeft(yAxis)); - - // Add dots - svg - .append("g") - .attr("class", "scatter-chart-points") - .selectAll("dot") - .data(data) - .enter() - .append("circle") - .attr("cx", (d) => d[0]) - .attr("cy", (d) => d[1]) - .attr("r", 3) - .style("fill", "#69b3a2"); - }, - [ - currentChart, - height, - margin.bottom, - margin.left, - margin.right, - margin.top, - width, - xScale, - yScale, - ] - ); + const buildChart = React.useCallback(() => { + // Adjust the svg. + const svg = currentChart() + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", `translate(${margin.left}, ${margin.top})`); + + // Add x axis + // const xAxis = xScale.domain([0, d3.max(data, (d) => d[0]) as number]); + const xAxis = xScale.domain([0, 1.0]); + svg + .append("g") + .attr("class", "scatter-chart-xaxis") + .attr("transform", `translate(0, ${height})`) + .call(d3.axisBottom(xAxis)); + + // Add y axis + // const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); + const yAxis = yScale.domain([0, 1.0]); + svg + .append("g") + .attr("class", "scatter-chart-yaxis") + .call(d3.axisLeft(yAxis)); + + // Add dots + svg.append("g").attr("class", "scatter-chart-points"); + // .selectAll("dot") + // .data(data) + // .enter() + // .append("circle") + // .attr("cx", (d) => d[0]) + // .attr("cy", (d) => d[1]) + // .attr("r", 3) + // .style("fill", "#69b3a2"); + }, [ + currentChart, + height, + margin.bottom, + margin.left, + margin.right, + margin.top, + width, + xScale, + yScale, + ]); // Update the chart when with new data. const updateChart = React.useCallback( @@ -168,7 +165,10 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); d3.axisLeft(yAxis)(svg.select("g.scatter-chart-yaxis")); - // Update points + // Remove the old points + svg.selectAll("circle").remove(); + + // Append the new points svg .select("g.scatter-chart-points") .selectAll("dot") @@ -190,7 +190,7 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { // Build chart initially otherwise update for data if (!builtChart) { - buildChart(data); + buildChart(); // data setBuiltChart(true); } else { updateChart(data); diff --git a/packages/frontend/src/components/routing.component.tsx b/packages/frontend/src/components/routing.component.tsx index da9fd29..04a55fe 100644 --- a/packages/frontend/src/components/routing.component.tsx +++ b/packages/frontend/src/components/routing.component.tsx @@ -1,11 +1,9 @@ import { createStyles, makeStyles } from '@material-ui/core/styles'; -import { frontendNotification, RegisterRoutePayload, StateType } from 'frontend-common'; +import { RegisterRoutePayload, StateType } from 'frontend-common'; import { Location } from 'history'; import React from 'react'; import { connect } from 'react-redux'; import { Route, RouteComponentProps, Switch } from 'react-router'; -import { Action, AnyAction } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; import RunsPage from '../pages/runsPage.component'; import VisualisationContainer from '../pages/visualisationContainer.component'; import VisualisationsPage from '../pages/visualisationsPage.component'; @@ -18,13 +16,12 @@ const useStyles = makeStyles(() => }), ); -class PluginPlaceHolder extends React.Component<{ id: string; buttonClick: () => void }> { +class PluginPlaceHolder extends React.Component<{ id: string }> { public render(): React.ReactNode { - const { id, buttonClick } = this.props; + const { id } = this.props; return (
{id} failed to load correctly
-
); } @@ -35,22 +32,14 @@ interface RoutingProps { location: Location; } -interface RoutingDispatchProps { - sendNotification: (message: string) => Action; -} - -const Routing = (props: RoutingProps & RoutingDispatchProps): React.ReactElement => { +const Routing = (props: RoutingProps): React.ReactElement => { const classes = useStyles(); - const { plugins, location, sendNotification } = props; + const { plugins, location } = props; React.useEffect(() => { console.log('Changed location: ', location); }, [location]); - const clickedButton = () => { - sendNotification(`I clicked the button at ${new Date().toLocaleString()}`); - }; - return (
@@ -71,7 +60,7 @@ const Routing = (props: RoutingProps & RoutingDispatchProps): React.ReactElement // render={() => } render={({ match }: RouteComponentProps<{ runId: string; visualisationName: string }>) => ( - + )} /> @@ -81,13 +70,9 @@ const Routing = (props: RoutingProps & RoutingDispatchProps): React.ReactElement ); }; -const mapDispatchToProps = (dispatch: ThunkDispatch): RoutingDispatchProps => ({ - sendNotification: (message: string) => dispatch(frontendNotification(message)), -}); - const mapStateToProps = (state: StateType): RoutingProps => ({ plugins: state.frontend.configuration.plugins, location: state.router.location, }); -export default connect(mapStateToProps, mapDispatchToProps)(Routing); +export default connect(mapStateToProps)(Routing); From 3e08638aeb07808cfcf0879d0075a95485445f2b Mon Sep 17 00:00:00 2001 From: Goel Biju Date: Sat, 27 Feb 2021 15:47:32 +0000 Subject: [PATCH 03/10] Implement generation queue and add DTLZ2 script (#16) --- documents/minutes/Project Meeting - 4.docx | Bin 16465 -> 16500 bytes documents/minutes/~$oject Meeting - 4.docx | Bin 162 -> 0 bytes .../src/state/actions/action.types.tsx | 2 +- .../src/state/actions/socket.actions.tsx | 18 ++-- .../src/state/reducers/common.reducer.tsx | 26 +++-- .../frontend-common/src/state/state.types.tsx | 5 +- .../src/visualisation.component.tsx | 7 +- .../src/components/mainAppBar.component.tsx | 3 +- .../visualisationContainer.component.tsx | 90 +++++++++++++----- scripts/client.py | 3 - scripts/{sample_data.py => dtlz1.py} | 6 +- scripts/dtlz2.py | 51 ++++++++++ 12 files changed, 146 insertions(+), 65 deletions(-) delete mode 100644 documents/minutes/~$oject Meeting - 4.docx rename scripts/{sample_data.py => dtlz1.py} (87%) create mode 100644 scripts/dtlz2.py diff --git a/documents/minutes/Project Meeting - 4.docx b/documents/minutes/Project Meeting - 4.docx index 05be19664b22d3ce3de80b82509be5602d32ab8c..db53806fc7c1509db11c74c5f2e2dc12012697c9 100644 GIT binary patch delta 5601 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 delta 5556 zcmV;l6-(;$fC15f0kF*mf5O6rhu8}M0BK7A01*HH0C#V4WG`fIV|8t1ZgehqZEWmZ z>u%dP7XH7$z60T39bi&R)IFWJ&~>K3bT^oGF~I(#CC1i761BR-$^4pk*f-cmn}n#vmL>pae<6wDY;iUD*T>+( zoPe@IS%eaj;j765E+=nifB)MLtILRlb&9hJP!d^rxyr+<$+D{Q%jvWXmpDb`>og7v zQj(kMbx6`Fxw(l$JYA6@nnFpI_`AFyAudZgpNq0PR8AUl;k_8Os6Z<^m=RQ`;Sv=U zzHe#BClk@8*3{f3e*{MmqAP^*E-B??Qu>r}wL?f9MTl~>LrEJ)Y0u*Hag^XLC1V^V zWtWmUj?xaC(p`X)9A~uih7>8P=*MC)O;K@M=NFW4j;eSbCvo*aX-fKr42rYc5ggG` zTMDTXolL=)rX<3N5^boEt4UpCm(BQ#?Rdue<$8ScvKdi`f2@B!#A}=bU-`7a36%xO z%4M8yEj%4djdm_KMDC6);x0`#&#iJ<&|D6=CvPpvmY@jxjr`MO&G~VPvLv7pMq)cc zgoXZ`8%|P+!gk_OqUi`%7IYvR0&s_b9%4afILMp|)z$Gs3@MLyfkl%1vf%h0!bQJjWkN*NTs#=fb zb9IGbe|XSTZN9z{Jj~P~?MGbE35@0v?$UZh zM&-xi^Sd4)W`eA}???>GS9Mu;29Bip_a!=#+0e?U%O*E|!Bkj1Eq#X;FEu-9umBbQq8?f?a&sW)}Rqre+1kVB&z_*X^gm!(61=B#)X(@cS98$AJb zbUV=Oq12DnzQVdF=Pp7Z32sOMypKQsTOO8agp-uve?&7zZdgk> zJwZ_=#qb<=5J?o-u^b-;-#o-)c!(gbGF+A*L0Lo-%e)V!{d!S6SN04D2cFwdgFx3r z^rC2%zjp`!<>_U;)YJ9yKF;DaX7z;D+C@?bUA7&=F$aY|4SHr^=mNU=GFLr=z z)`!Z>Pf}qfsEDfvwwv4Tf7C<-YFd^`6WYE&p=^4l?|n6~&A+EPY(XWNRC&HN_P!nlUBINZ2NP zK#+PZ-W;KFIVY%ygz^>Dw_V%hNoLo9sh(jtDl?sfIu%ymT5jS+f3S{14yD@Hq~wQT zOnf2AbJ7V7;tb>%KxYw&UR3GfSaRR21Qae-S!z; zyXlYPX{PP7gjI}tF${MKKVLS6e_f&qFb%L^%+UMSacY_xNS@*if}o-Ww&O^Br*=}| z@fmf0P@lVpN`m@K(JcmX1yT~#G%FTR<}qFDxtue`jg0aFe@93mmPdVZO`WIq14vh8 zOE)-Fx&U%4S<~GXExR!Jk^!`>8L~uuTVd>Prv^e-J*b=7Q6Rlq(5>1!&EM|O80C~V zk-TC%;^CEtad<@_BsDwzp$_fQusg38LlK-F!8F;iJlz~#){s<3)!a5RvB?kDxB5LC zZ%q?l!eE~Oe_hFt4R*b-Go$Xxl#M`iMw4lG`*3$gF0-|O*v980=DQf7qwGMF6lZuq zDM8!RAWu_U#`@DY@b(nhGWXi!9&Vea=dj09tnHxfHj6vicJ~_vumAQfVZTl!Uw5HA zJfc#3*YG`~y+iR$Cnx#S-Fo?c>C3wBD@wplQM<(`f2b=KLXr@sEdEds(8$S0f6}Cyg^5#Q zq7LHwe_n)Vfq7FvTqi6SJYgAq;B^LXWr(T@4|DF*^91Oc&~f1)Y8$@dnu^ok*z85j zVMqKz5c6_^<1aW*&;w6EN%n>!s4j6drQLOk&%D?*I%1R+s?IN$=)Pt8HZOQ|Gc3t- zU5j5HZ$ShtRg;EyO}~Li7(^eEIKlycCtCv1|NMfv-mZBeS$qc zf4^YCO>=344y=W4C8(}#8>%v>!Wv58TBh3n28wJdI)8A-JM?@0BHrPVM6P(fN}I?| zU2%2WbIpNArZ|vn+v%5ezIK_ zT>e?RD^#+&eMnNuXPk-Lbs4n=evpOrkXE$~0BmyK$9>Dy({NnMn72yrDBK#aLKm?AfSb@m5e@f}r z(d-n5J3gf8;Y%*5qr2{{yXoa~FFyQEyH+%U$rk&e9BSXJ9%?G)dUU>ck2v=vr|qh` z4qD9o+mCZnRU~acC-|n*yTpj@(AZXn7u;?LsX8AQH7na}T@#gcNn0B@uxEI4M8Y2n z_S+AaNu1&9m^uXhmApob>m~BH3hN($n6|%>HTyr4K{^$)=n_l`3D$*RN3#V002Pxx z92$RZbJ{o%{(dv}9~eF##qx`InWi1UCZP>!FVO4Ur;M-#Xs|6;5&?2^|NU074J1{2 zNjv33Ebp`X>}s`=)_MJQpQVj^DkS6iMH9Vpnhl!AJYo6jqWNh)ZgratsYsrXl;`xK zd7!fS_Ra6Vz25aDRSMK(11NIY&*F>bTB(1c-)_rzO*0~2@q*?6F1g5vf>*I>XGCna zMJwi6K@?lCl&M48bsXHRX!u2QEAoCt(aKmXxa3QvP5OMfWHGHCwS{<&-_j)H@iwEm zG9KH4rVsxqw~mvml3#M5#X5aU&hg`IND%&&cl+I@!%s5J0%w3 z9}DJ;Kqe|#zlg=H^V-(7M3FU-$12i{^kqQ>N4i6H%8^W;Q#B=Y%@%@)a!*zzlg0hX zD(8YMQV0i1rvaPfkjR-nChcbYRVI;Bijo0{55~k2F5!I z&Rsrr#Nh-METMCq;+Z<6bPU1I*aIEp$-Avo3>eK>^%D_%F94cr&mW*D=7)lgDN$QU z(=T#nR$`p8V#p2MlU$3gyED-57RHZF~Dm+pUkylwG;ox3Hr7VnSShT_O~*f9B#tD z4>$9XH-+c)Vd~t36Lb?Mw@wX<6W>2`1@D#O<38G_JoT%8?H5YHWl9V_x}wxJ*4Dm_n-u`}ve5#;zI$La@l zJ?vRybKSrXhgJkTqtF_gi~Y!PtwS1G17BU;8<;7NxSwM9SmI93^rus1--~R zpHYO7y%GU&d)S7A4(>YEya5sotn&#y$BC?K8hWVLwMHEdPzQh6{X{qlt*ae%Q4d+? z6GZ`RZW}TlJH4T?X_pY3Pg!4oBzzS0NMG`eth5-BY#|tFO!a3(TWc)DX2|jyUr^}P z=s7amE^4sVDgl|1G#$frQv)V#SwCU22-W|Dg2S>%F;M<}^ zcY+k<-ixS+1^GR?FpfF{`a|=q}HoZJvC%7bZ`q^z8ZyE<@7$llUQ`O9y2J%*&$n;n($X;;3<>PFcr|RE3VrJgZ*JJwn<2$)qlUV)oPgx@^Ej?m($ymq?1FD2YWD9C^<%?7oh+oQ(VZ^7=0{e z;RprRQORkpB#hA!EShBZcPY~eQwH9Q(qNnb8+a#a6Q;*#?VL_9wrmXrwLMQrFD{iS zf2i}VSz%4tmaYKDQ8dH_ILfJ`xH1fN>xfR4oVAvEXY$&BGYmNtAf3geNAQJpUXj=H_@}$(g!PAuq?}7*{@MQKfzz~*Z2BYLm{iI7@#cWEOEl+kfr!i`pWKp zHo#o7-8Q~v2B=ded$5nei-J6uip{R3f6hvk`4*0)GTi#}oz7z%EHgqof0K=SXT#Gu zwQlaOb}4{QkJ;5n{)!cEU@xk7kq!Da()Q^oI}L54}6bREwIO+MWhn*9O*0RR6308mQD+Ju^ijRV8F&l z45HC!hV^~IeMfi0SH#x2*g?Z6HItJoJDu?l>)Q2R zTJo1oh)oRs86SM(Zqs)B@$fvEjr~dEe(R-5d-7qi$QK|h@hxScrHM{MnG*btt`JN* zfc~{RXiq)=7t%^Sjt^GUSF?(~VTVpR+JxyPiQfQ|S3Qsk!or1z*b4vvX_N3hPyxo1 zK|VcYWy;@r sguzTuW}Q8Rar&W{mov|G#&75P4%@UdFic#;B^?9gi$c}iN`5{C00e^_4FCWD diff --git a/packages/frontend-common/src/state/actions/action.types.tsx b/packages/frontend-common/src/state/actions/action.types.tsx index 7a114f8..79a8b04 100644 --- a/packages/frontend-common/src/state/actions/action.types.tsx +++ b/packages/frontend-common/src/state/actions/action.types.tsx @@ -16,12 +16,12 @@ export const FetchRunsType = `${microFrontendMessageId}:fetch_runs`; export const FetchRunsResultType = `${microFrontendMessageId}:fetch_runs_result`; export const FetchRunResultType = `${microFrontendMessageId}:fetch_run_result`; export const VisualisationNameType = `${microFrontendMessageId}:set_visualisation_name`; +export const DataRequestType = `${microFrontendMessageId}:data_request`; export const DataType = `${microFrontendMessageId}:set_data`; // Socket related actions export const InitiateSocketSuccessType = `${microFrontendMessageId}:initiate_socket_success`; export const DisconnectSocketSuccessType = `${microFrontendMessageId}:disconnect_socket_success`; -// export const RunGenerationSuccessType = `${microFrontendMessageId}:run_generation_success`; export const SubscribedType = `${microFrontendMessageId}:set_subscribed`; export interface RegisterRoutePayload { diff --git a/packages/frontend-common/src/state/actions/socket.actions.tsx b/packages/frontend-common/src/state/actions/socket.actions.tsx index 7898640..83055d0 100644 --- a/packages/frontend-common/src/state/actions/socket.actions.tsx +++ b/packages/frontend-common/src/state/actions/socket.actions.tsx @@ -2,6 +2,7 @@ import { Action } from "redux"; import ioclient from "socket.io-client"; import { ActionType, ThunkResult } from "../state.types"; import { + DataRequestType, DisconnectSocketSuccessType, InitiateSocketSuccessType, @@ -24,15 +25,6 @@ export const disconnectSocketSuccess = (): Action => ({ type: DisconnectSocketSuccessType, }); -// export const runGenerationSuccess = ( -// generation: number -// ): ActionType => ({ -// type: RunGenerationSuccessType, -// payload: { -// generation, -// }, -// }); - export const setSubscribed = ( subscribed: boolean ): ActionType => ({ @@ -90,6 +82,10 @@ export const subscribeToGenerations = ( }; }; +export const dataRequest = (): Action => ({ + type: DataRequestType, +}); + // Retrieve the data from a run given the ID and generation number export const fetchData = ( dataId: string, @@ -98,8 +94,10 @@ export const fetchData = ( return async (dispatch, getState) => { const { socket } = getState().frontend.configuration; - // socketConnected if (socket && socket.connected) { + // Dispatch the fetch data request + dispatch(dataRequest()); + // Emit the socket event socket.emit("data", { dataId, diff --git a/packages/frontend-common/src/state/reducers/common.reducer.tsx b/packages/frontend-common/src/state/reducers/common.reducer.tsx index 8571f53..c601d31 100644 --- a/packages/frontend-common/src/state/reducers/common.reducer.tsx +++ b/packages/frontend-common/src/state/reducers/common.reducer.tsx @@ -1,6 +1,7 @@ import * as log from "loglevel"; import { DataPayload, + DataRequestType, DataType, DisconnectSocketSuccessType, FetchRunResultType, @@ -33,14 +34,13 @@ export const initialState: FrontendState = { }, settingsLoaded: false, socket: null, - // socketConnected: false, subscribed: false, }, runs: [], selectedRun: null, selectedVisualisation: "", data: null, - // currentGeneration: 0, + fetchingData: false, }; const updatePlugins = ( @@ -143,7 +143,6 @@ export function handleInitiateSocket( configuration: { ...state.configuration, socket: payload.socket, - // socketConnected: true, }, }; } @@ -154,21 +153,10 @@ export function handleDisconnectSocket(state: FrontendState): FrontendState { configuration: { ...state.configuration, socket: null, - // socketConnected: false, }, }; } -// export function handleRunGeneration( -// state: FrontendState, -// payload: RunGenerationPayload -// ): FrontendState { -// return { -// ...state, -// currentGeneration: payload.generation, -// }; -// } - export function handleSubscribed( state: FrontendState, payload: SubscribedPayload @@ -182,6 +170,13 @@ export function handleSubscribed( }; } +export function handleDataRequest(state: FrontendState): FrontendState { + return { + ...state, + fetchingData: true, + }; +} + export function handleData( state: FrontendState, payload: DataPayload @@ -189,6 +184,7 @@ export function handleData( return { ...state, data: payload.data, + fetchingData: false, }; } @@ -201,9 +197,9 @@ const CommonReducer = createReducer(initialState, { [FetchRunResultType]: handleFetchRun, [InitiateSocketSuccessType]: handleInitiateSocket, [DisconnectSocketSuccessType]: handleDisconnectSocket, - // [RunGenerationSuccessType]: handleRunGeneration, [VisualisationNameType]: handleVisualisationName, [SubscribedType]: handleSubscribed, + [DataRequestType]: handleDataRequest, [DataType]: handleData, }); diff --git a/packages/frontend-common/src/state/state.types.tsx b/packages/frontend-common/src/state/state.types.tsx index e646141..098a7e4 100644 --- a/packages/frontend-common/src/state/state.types.tsx +++ b/packages/frontend-common/src/state/state.types.tsx @@ -38,16 +38,13 @@ export interface FrontendState { urls: SettingsUrls; settingsLoaded: boolean; socket: SocketIOClient.Socket | null; - // TODO: Add subscribed to state? - // TODO: Remove socketConnected - // socketConnected: boolean; subscribed: boolean; }; runs: Run[]; selectedRun: Run | null; selectedVisualisation: string; data: Data; - // currentGeneration: number; + fetchingData: boolean; } export interface StateType { diff --git a/packages/frontend-plugin/src/visualisation.component.tsx b/packages/frontend-plugin/src/visualisation.component.tsx index 3d179e0..e8638de 100644 --- a/packages/frontend-plugin/src/visualisation.component.tsx +++ b/packages/frontend-plugin/src/visualisation.component.tsx @@ -24,13 +24,14 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { const chartRef: React.RefObject = React.createRef(); const margin = { - top: 10, + top: 50, right: 30, bottom: 30, left: 60, }; + const width = 760 - margin.left - margin.right; - const height = 800 - margin.top - margin.bottom; + const height = 450 - margin.top - margin.bottom; const xScale: d3.ScaleLinear = d3 .scaleLinear() @@ -178,7 +179,7 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { .attr("cx", (d) => xScale(d[0]) as number) .attr("cy", (d) => yScale(d[1]) as number) .attr("r", 3) - .style("fill", "#69b3a2"); + .style("fill", "blue"); }, [currentChart, xScale, yScale] ); diff --git a/packages/frontend/src/components/mainAppBar.component.tsx b/packages/frontend/src/components/mainAppBar.component.tsx index 1088987..00652f9 100644 --- a/packages/frontend/src/components/mainAppBar.component.tsx +++ b/packages/frontend/src/components/mainAppBar.component.tsx @@ -1,7 +1,6 @@ import { AppBar, CssBaseline, Link, Toolbar, Typography } from '@material-ui/core'; import { createStyles, makeStyles } from '@material-ui/core/styles'; import React from 'react'; -import { Link as RouterLink } from 'react-router-dom'; const useStyles = makeStyles(() => createStyles({ @@ -20,7 +19,7 @@ const MainAppBar = (): React.ReactElement => { - + Visualising Optimisation Data diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index 619269e..d1cb86c 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -17,6 +17,8 @@ import { Action, AnyAction } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; // TODO: Use queue (in state?) +// TODO: Try to instead of having an object, store the list in the component +// then perform actions using setState on the list. // class GenerationQueue { // generations: number[]; @@ -25,13 +27,14 @@ import { ThunkDispatch } from 'redux-thunk'; // } // // Add a generation (enqueue) -// append(generation: number): void { +// push(generation: number): void { // this.generations.push(generation); // } // // Remove a generation (dequeue) // pop(): number | undefined { // if (!this.isEmpty()) { +// console.log('Returning number'); // return this.generations.shift(); // } else { // return -1; @@ -60,7 +63,6 @@ interface VCDispatchProps { initiateSocket: (runId: string) => Promise; subscribeToGenerations: (runId: string) => Promise; fetchData: (dataId: string, generation: number) => Promise; - // setCurrentGeneration: (generation: number) => Action; setSubscribed: (subscribed: boolean) => Action; setData: (data: Data) => Action; } @@ -69,9 +71,8 @@ interface VCStateProps { selectedRun: Run | null; selectedVisualisation: string; socket: SocketIOClient.Socket | null; - // socketConnected: boolean; - // currentGeneration: number; subscribed: boolean; + fetchingData: boolean; } type VCProps = VCViewProps & VCDispatchProps & VCStateProps; @@ -79,7 +80,6 @@ type VCProps = VCViewProps & VCDispatchProps & VCStateProps; const VisualisationContainer = (props: VCProps): React.ReactElement => { const { socket, - // socketConnected, initiateSocket, fetchRun, setVisualisationName, @@ -88,25 +88,52 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { selectedRun, selectedVisualisation, subscribeToGenerations, - // currentGeneration, fetchData, - // setCurrentGeneration, subscribed, setSubscribed, setData, + fetchingData, } = props; const [loadedRun, setLoadedRun] = React.useState(false); - // const [subscribed, setSubscribed] = React.useState(false); - const [currentGeneration, setCurrentGeneration] = React.useState(-1); + // Create a generation queue object in state + // const [generationQueue] = React.useState(new GenerationQueue()); + const [generationQueue, setGenerationQueue] = React.useState([]); + + const pushToGQ = (generation: number) => { + setGenerationQueue([...generationQueue, generation]); + }; + + // Remove a generation (dequeue) + const popFromGQ = (): number => { + if (!isEmpty()) { + const n = generationQueue.shift(); + console.log('Returning n: ', -1); + if (n) { + return n; + } else { + return -1; + } + } else { + console.log('Queue empty'); + return -1; + } + }; + + // Check if the queue is empty + const isEmpty = (): boolean => { + if (generationQueue.length === 0) { + return true; + } else { + return false; + } + }; + // TODO: * Be careful about where we place receiving data from sockets (prevent duplicate data) - // * Prevent multiple event handlers (subcribed to state, replace with socketConnected?) - // DONE: Remove unnecessary items from state e.g. socketConnected? (replace with socket.connected?) + // * Prevent multiple event handlers (subcribed to state) // TODO: * Wait until data has been received from server after firing request, before firing next request - // DONE: Remove/correct currentGeneration in state? - // DONE: currentGeneration should not be in state? Just in component. // Load run information React.useEffect(() => { @@ -150,12 +177,16 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { // When we receive "subscribed" from // server attach the callback function socket.on('generation', (generation: number) => { - // console.log('Generation received: ', generation); - setCurrentGeneration(generation); + // setCurrentGeneration(generation); + + // Add the generation to the queue. + // generationQueue.push(generation); + pushToGQ(generation); + console.log('Generation added to queue: ', generation); + console.log('Queue: ', generationQueue); }); // Handle the data response event - // eslint-disable-next-line @typescript-eslint/no-explicit-any socket.on('data', (data: Data) => { console.log('Data received for current generation: ', data); setData(data); @@ -169,18 +200,35 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { // TODO: Ensure this does not request multiple times // Handle fetching new data on generation changes React.useEffect(() => { + console.log('Got new update'); + // Fetch the data for the new generation if (selectedRun && socket && socket.connected) { if (currentGeneration < 0) { // Initialise current generation with current run information setCurrentGeneration(selectedRun.currentGeneration); console.log('Set current generation to: ', selectedRun.currentGeneration); + + pushToGQ(selectedRun.currentGeneration); + console.log('Queue: ', generationQueue); } else { - // console.log('Requesting data'); - fetchData(selectedRun.dataId, currentGeneration); + // Fetch data if there are currently no requests + if (!fetchingData) { + // Get the next generation to fetch + console.log(generationQueue); + const generation = popFromGQ(); + console.log('generation from queue: ', generation); + if (generation && generation !== -1) { + // TODO: Is this correct calling setCurrentGeneration here? + setCurrentGeneration(generation); + + console.log('Requesting data'); + fetchData(selectedRun.dataId, generation); + } + } } } - }, [selectedRun, socket, currentGeneration]); + }, [selectedRun, socket, currentGeneration, fetchingData, generationQueue]); return ( @@ -231,7 +279,6 @@ const mapDispatchToProps = (dispatch: ThunkDispatch) initiateSocket: (runId: string) => dispatch(initiateSocket(runId)), subscribeToGenerations: (runId: string) => dispatch(subscribeToGenerations(runId)), fetchData: (dataId: string, generation: number) => dispatch(fetchData(dataId, generation)), - // setCurrentGeneration: (generation: number) => dispatch(runGenerationSuccess(generation)), setSubscribed: (subscribed: boolean) => dispatch(setSubscribed(subscribed)), setData: (data: Data) => dispatch(setData(data)), }); @@ -239,11 +286,10 @@ const mapDispatchToProps = (dispatch: ThunkDispatch) const mapStateToProps = (state: StateType): VCStateProps => { return { socket: state.frontend.configuration.socket, - // socketConnected: state.frontend.configuration.socketConnected, selectedRun: state.frontend.selectedRun, selectedVisualisation: state.frontend.selectedVisualisation, - // currentGeneration: state.frontend.currentGeneration, subscribed: state.frontend.configuration.subscribed, + fetchingData: state.frontend.fetchingData, }; }; diff --git a/scripts/client.py b/scripts/client.py index 0d72e2d..108ee41 100644 --- a/scripts/client.py +++ b/scripts/client.py @@ -9,7 +9,6 @@ # http://opt-vis-backend.herokuapp.com/ # http://localhost:9000 BACKEND_URL = "http://localhost:9000" -# 40185 # PORT = "33585" # WEBSOCKET_URL = BACKEND_URL + ":" + PORT API_URL = BACKEND_URL + "/api" @@ -40,7 +39,6 @@ def on_disconnect(self): def on_save(self, data): # print(data) if (data['saved']): - # print("Saved data for generation: ", data['generation']) self.sending_data = False else: print("Unable to save data: ", data['message']) @@ -60,7 +58,6 @@ def process_queue(self): data = { "runId": self.run_id, "dataId": self.data_id, - # "generation": self.current_generation, "batch": batch } self.emit("data", data) diff --git a/scripts/sample_data.py b/scripts/dtlz1.py similarity index 87% rename from scripts/sample_data.py rename to scripts/dtlz1.py index f25c6c1..88062a6 100644 --- a/scripts/sample_data.py +++ b/scripts/dtlz1.py @@ -11,11 +11,7 @@ dtlz1_data = pickle.load(dtlz1_file) # Population size is 100 -# dtlz2_file = open('data/DTLZ2DataFile.pkl', 'rb') -# dtlz2_data = pickle.load(dtlz2_file) - print("Total population size: ", len(dtlz1_data)) -# print("Total population size: ", len(dtlz2_data)) populationSize = 100 totalGenerations = 282 @@ -50,7 +46,7 @@ else: # Add the data to the client queue to send # print("Sending generation: ", generation) - optimiserClient.addBatch(data_batch) # generation + optimiserClient.addBatch(data_batch) data_batch = [] # generation += 1 diff --git a/scripts/dtlz2.py b/scripts/dtlz2.py new file mode 100644 index 0000000..7cf39aa --- /dev/null +++ b/scripts/dtlz2.py @@ -0,0 +1,51 @@ +import os +import pickle +import time + +import client + +dirname = os.path.dirname(__file__) + +# Open the optimisation data file +dtlz2_file = open(os.path.join(dirname, 'data/DTLZ2DataFile.pkl'), 'rb') +dtlz2_data = pickle.load(dtlz2_file) + +# Population size is 100 +# print("Total population size: ", len(dtlz2_data)) + +populationSize = 100 +totalGenerations = 54 + +# Create an optimiser client +optimiserClient = client.OptimiserClient(populationSize) + +# Algorithm parameters +algorithmParameters = { + "Function Evaluations": 5000, + "sbxProb": 0.8, + "pmProb": 0.1 +} + +# Create the optimisation run +optimiserClient.createRun("Pareto front estimation of DTLZ2", "DTLZ2", + "NGSA-II", populationSize, totalGenerations, algorithmParameters, ["frontend-plugin"]) + +# Send generation data to the server +count = 1 +data_batch = [] +for values in dtlz2_data: + # Add the data to the batch to send + data_batch.append(values.tolist()) + + if (count < populationSize): + count += 1 + else: + # Add the data to the client queue to send + # print("Sending generation: ", generation) + optimiserClient.addBatch(data_batch) + data_batch = [] + + count = 1 + # print("Next Generation: ", generation) + +print("Completed adding items to queue, wait until queue has been processed") From f6cb4706c7918de309a78ef3f78075785db48232 Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Sat, 27 Feb 2021 16:41:10 +0000 Subject: [PATCH 04/10] Display algorithm parameters in visualisation container (#16) --- .../visualisationContainer.component.tsx | 19 +++++++++++++++++++ .../pages/visualisationsPage.component.tsx | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index d1cb86c..59ff769 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -266,6 +266,25 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { + {selectedRun && ( + + + {selectedRun.title} + + + (Algorithm - {selectedRun.algorithm}) + + {selectedRun.algorithmParameters && + Object.entries(selectedRun.algorithmParameters).map(([name, value], index) => ( +

+ {name}: {value} +

+ ))} +
+ )} + + {/* NOTE: Do not make this render based on any other variable (e.g. selectedRun), + otherwise the plugin may not load */} {props.children} diff --git a/packages/frontend/src/pages/visualisationsPage.component.tsx b/packages/frontend/src/pages/visualisationsPage.component.tsx index 56bb529..5799da3 100644 --- a/packages/frontend/src/pages/visualisationsPage.component.tsx +++ b/packages/frontend/src/pages/visualisationsPage.component.tsx @@ -11,6 +11,7 @@ const useStyles = makeStyles({ flexGrow: 1, padding: '15px', }, + label: { padding: '15px', textAlign: 'left' }, }); interface VisualisationsPageProps { @@ -46,6 +47,10 @@ const VisualisationsPage = (props: VisualisationsPageCombinedProps): React.React return (
+ + Visualisations + + {selectedRun ? ( plugins.length > 0 ? ( From 3884b20c4eb332ad206fc2be8011270b67e4ca1f Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Sun, 28 Feb 2021 18:13:20 +0000 Subject: [PATCH 05/10] Show content in a grid (#16) --- package.json | 5 +- .../visualisationContainer.component.tsx | 104 ++++++++---------- 2 files changed, 48 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 1b8afae..038bdbd 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,9 @@ "tsc": "lerna run tsc", "backend": "yarn workspace backend dev", "frontend": "yarn workspace frontend start", - "frontend-plugin": "yarn workspace frontend-plugin serve:build", + "plugins": "yarn workspace frontend-plugin serve:build", "lint:frontend": "yarn workspace frontend lint", - "test:frontend": "yarn workspace frontend test", - "stack": "yarn backend | yarn frontend | yarn frontend-plugin" + "test:frontend": "yarn workspace frontend test" }, "repository": { "type": "git", diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index 59ff769..f41fe5e 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -1,4 +1,5 @@ import { Grid, Paper, Typography } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; import { Data, fetchData, @@ -16,40 +17,15 @@ import { connect } from 'react-redux'; import { Action, AnyAction } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; -// TODO: Use queue (in state?) -// TODO: Try to instead of having an object, store the list in the component -// then perform actions using setState on the list. -// class GenerationQueue { -// generations: number[]; - -// constructor() { -// this.generations = []; -// } - -// // Add a generation (enqueue) -// push(generation: number): void { -// this.generations.push(generation); -// } - -// // Remove a generation (dequeue) -// pop(): number | undefined { -// if (!this.isEmpty()) { -// console.log('Returning number'); -// return this.generations.shift(); -// } else { -// return -1; -// } -// } - -// // Check if the queue is empty -// isEmpty(): boolean { -// if (this.generations.length === 0) { -// return true; -// } else { -// return false; -// } -// } -// } +// TODO: * Be careful about where we place receiving data from sockets (prevent duplicate data) +// * Prevent multiple event handlers (subcribed to state) +// TODO: * Wait until data has been received from server after firing request, before firing next request + +const useStyles = makeStyles({ + content: { + padding: '15px', + }, +}); interface VCViewProps { runId: string; @@ -78,6 +54,8 @@ interface VCStateProps { type VCProps = VCViewProps & VCDispatchProps & VCStateProps; const VisualisationContainer = (props: VCProps): React.ReactElement => { + const classes = useStyles(); + const { socket, initiateSocket, @@ -99,7 +77,6 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { const [currentGeneration, setCurrentGeneration] = React.useState(-1); // Create a generation queue object in state - // const [generationQueue] = React.useState(new GenerationQueue()); const [generationQueue, setGenerationQueue] = React.useState([]); const pushToGQ = (generation: number) => { @@ -131,10 +108,6 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { } }; - // TODO: * Be careful about where we place receiving data from sockets (prevent duplicate data) - // * Prevent multiple event handlers (subcribed to state) - // TODO: * Wait until data has been received from server after firing request, before firing next request - // Load run information React.useEffect(() => { // Fetch the run information @@ -266,27 +239,42 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { - {selectedRun && ( - - - {selectedRun.title} - - - (Algorithm - {selectedRun.algorithm}) - - {selectedRun.algorithmParameters && - Object.entries(selectedRun.algorithmParameters).map(([name, value], index) => ( -

- {name}: {value} -

- ))} + + + + {selectedRun && ( +
+ + Optimisation Run + + + (Algorithm - {selectedRun.algorithm}) + + {selectedRun.algorithmParameters && + Object.entries(selectedRun.algorithmParameters).map(([name, value], index) => ( +

+ {name}: {value} +

+ ))} +
+ )} +
- )} - {/* NOTE: Do not make this render based on any other variable (e.g. selectedRun), - otherwise the plugin may not load */} - - {props.children} + + + {selectedRun && ( +
+ + {selectedRun.title} + +
+ )} + {/* NOTE: Do not make this render based on any other variable (e.g. selectedRun), + otherwise the plugin may not load */} + {props.children} +
+
); From 0c016a95a3668dc35f36e2ce2ea9fb0f010a6eac Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Sun, 28 Feb 2021 18:53:09 +0000 Subject: [PATCH 06/10] Add further run details (#16) --- .../visualisationContainer.component.tsx | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index f41fe5e..8627055 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -25,6 +25,10 @@ const useStyles = makeStyles({ content: { padding: '15px', }, + informationContent: { + padding: '10px', + textAlign: 'left', + }, }); interface VCViewProps { @@ -245,17 +249,33 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { {selectedRun && (
- Optimisation Run + Run Details - (Algorithm - {selectedRun.algorithm}) - - {selectedRun.algorithmParameters && - Object.entries(selectedRun.algorithmParameters).map(([name, value], index) => ( -

- {name}: {value} -

- ))} +
+

+ Algorithm: {selectedRun.algorithm} +

+ +

+ Population Size: {selectedRun.populationSize} +

+ + {selectedRun.algorithmParameters && + Object.entries(selectedRun.algorithmParameters).map(([name, value], index) => ( +

+ {name}: {value} +

+ ))} + +

+ Created: {new Date(selectedRun.createdAt).toLocaleString()} +

+ +

+ Status: {!selectedRun.completed ? 'Running' : 'Completed'} +

+
)} From 856c69912f61805b3fc7f593d897da851e2f39ff Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Mon, 1 Mar 2021 18:33:07 +0000 Subject: [PATCH 07/10] Return completed property (#16) --- backend/src/sockets/namespaces.js | 39 ++++++++++++------- .../frontend-common/src/state/state.types.tsx | 1 + 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/backend/src/sockets/namespaces.js b/backend/src/sockets/namespaces.js index 1bd95b3..bd156a5 100644 --- a/backend/src/sockets/namespaces.js +++ b/backend/src/sockets/namespaces.js @@ -28,24 +28,33 @@ function setupNamespaces(io) { const { dataId, generation } = dataRequest; // Get the data based on the run ID - const data = await Data.findById(dataId).lean(); - if (data) { - const optimiserData = data.data; - if (optimiserData[generation]) { - // Send the generation data to the client - // console.log("Got data: ", optimiserData[generation]); - socket.emit("data", { - generation, - data: optimiserData[generation].values, - time: optimiserData[generation].time, - }); + console.log("Finding run with dataId: ", dataId); + const run = await Run.findOne({ dataId }).lean(); + console.log("Run: ", run); + if (run) { + console.log("Run found: ", run); + const data = await Data.findById(dataId).lean(); + if (data) { + const optimiserData = data.data; + if (optimiserData[generation]) { + // Send the generation data to the client + // console.log("Got data: ", optimiserData[generation]); + socket.emit("data", { + generation, + data: optimiserData[generation].values, + time: optimiserData[generation].time, + completed: run.completed, + }); + } else { + console.log("Generation not present: ", generation); + } } else { - console.log("Generation not present: ", generation); + console.log( + `Unable to find data for ${dataId} for generation ${generation}` + ); } } else { - console.log( - `Unable to find data for ${dataId} for generation ${generation}` - ); + console.log(`Unable to find run with data ID: `, dataId); } }); }); diff --git a/packages/frontend-common/src/state/state.types.tsx b/packages/frontend-common/src/state/state.types.tsx index 098a7e4..c100672 100644 --- a/packages/frontend-common/src/state/state.types.tsx +++ b/packages/frontend-common/src/state/state.types.tsx @@ -29,6 +29,7 @@ export type Data = { generation: number; data: number[][]; time: Date; + completed: boolean; } | null; export interface FrontendState { From 917c7f1b36f303086696e485f0a8417646f373ac Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Mon, 1 Mar 2021 19:46:04 +0000 Subject: [PATCH 08/10] Show the completed status (#16) --- .../pages/visualisationContainer.component.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index 8627055..e91020d 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -83,6 +83,9 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { // Create a generation queue object in state const [generationQueue, setGenerationQueue] = React.useState([]); + // All data has already been received from backend + const [dataComplete, setDataComplete] = React.useState(false); + const pushToGQ = (generation: number) => { setGenerationQueue([...generationQueue, generation]); }; @@ -167,6 +170,12 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { socket.on('data', (data: Data) => { console.log('Data received for current generation: ', data); setData(data); + + // If this the final data, then reload the run information + if (data && data.completed) { + setDataComplete(true); + setLoadedRun(false); + } }); setSubscribed(true); @@ -190,7 +199,7 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { console.log('Queue: ', generationQueue); } else { // Fetch data if there are currently no requests - if (!fetchingData) { + if (!fetchingData && !dataComplete) { // Get the next generation to fetch console.log(generationQueue); const generation = popFromGQ(); @@ -205,7 +214,7 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { } } } - }, [selectedRun, socket, currentGeneration, fetchingData, generationQueue]); + }, [selectedRun, socket, currentGeneration, fetchingData, dataComplete, generationQueue]); return ( @@ -219,7 +228,7 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { display: 'flex', }} > - + Visualisation Name: {selectedVisualisation} ({selectedRun._id}) From 2ec9312e774a6db1709f0d909b143d0071b9a534 Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Mon, 1 Mar 2021 20:26:22 +0000 Subject: [PATCH 09/10] Add in complete and elapsed time (#16) --- .../visualisationContainer.component.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/frontend/src/pages/visualisationContainer.component.tsx b/packages/frontend/src/pages/visualisationContainer.component.tsx index e91020d..9907ad9 100644 --- a/packages/frontend/src/pages/visualisationContainer.component.tsx +++ b/packages/frontend/src/pages/visualisationContainer.component.tsx @@ -216,6 +216,20 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { } }, [selectedRun, socket, currentGeneration, fetchingData, dataComplete, generationQueue]); + const secondsToDHMS = (seconds: number): string => { + const d = Math.floor(seconds / (3600 * 24)); + const h = Math.floor((seconds % (3600 * 24)) / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + + const dDisplay = d > 0 ? d + (d === 1 ? ' day' : ' days') + (h + m + s > 0 ? ', ' : '') : ''; + const hDisplay = h > 0 ? h + (h === 1 ? ' hour' : ' hours') + (m + s > 0 ? ', ' : '') : ''; + const mDisplay = m > 0 ? m + (s > 0 ? ' min, ' : ' min') : ''; + const sDisplay = s > 0 ? s + ' sec' : ''; + + return dDisplay + hDisplay + mDisplay + sDisplay || '< 1 second'; + }; + return ( {selectedRun && ( @@ -281,6 +295,23 @@ const VisualisationContainer = (props: VCProps): React.ReactElement => { Created: {new Date(selectedRun.createdAt).toLocaleString()}

+ {selectedRun.completed && ( +
+

+ Completed: {new Date(selectedRun.updatedAt).toLocaleString()} +

+ +

+ Run duration:{' '} + {secondsToDHMS( + (new Date(selectedRun.updatedAt).getTime() - + new Date(selectedRun.createdAt).getTime()) / + 1000, + )} +

+
+ )} +

Status: {!selectedRun.completed ? 'Running' : 'Completed'}

From 4fa3b3a9ee79fb964802f14be4cbbbcd2651739b Mon Sep 17 00:00:00 2001 From: Goel Biju <16869075+GoelBiju@users.noreply.github.com> Date: Mon, 1 Mar 2021 22:09:13 +0000 Subject: [PATCH 10/10] Remove old visualisation code (#16) --- .../src/visualisation.component.tsx | 78 +------------------ 1 file changed, 1 insertion(+), 77 deletions(-) diff --git a/packages/frontend-plugin/src/visualisation.component.tsx b/packages/frontend-plugin/src/visualisation.component.tsx index e8638de..f7a4f93 100644 --- a/packages/frontend-plugin/src/visualisation.component.tsx +++ b/packages/frontend-plugin/src/visualisation.component.tsx @@ -7,19 +7,6 @@ interface VCProps { data: Data; } -// const sampleData = [ -// [34, 78], -// [109, 280], -// [310, 120], -// [79, 411], -// [420, 220], -// [233, 145], -// [333, 96], -// [222, 333], -// [78, 320], -// [21, 123], -// ]; - const VisualisationComponent = (props: VCProps): React.ReactElement => { const chartRef: React.RefObject = React.createRef(); @@ -42,32 +29,6 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { const [builtChart, setBuiltChart] = React.useState(false); - // public constructor(props: VCStateProps) { - // super(props); - - // // Create ref and configure D3 selection for svg chart. - // this.chartRef = React.createRef(); - - // // Initialise the chart sizes - // this.margin = { - // top: 10, - // right: 30, - // bottom: 30, - // left: 60, - // }; - // this.width = 760 - this.margin.left - this.margin.right; - // this.height = 800 - this.margin.top - this.margin.bottom; - - // // Add scales - // this.xScale = d3.scaleLinear().range([0, this.width]); - // this.yScale = d3.scaleLinear().range([this.height, 0]); - - // // Initialise the state - // // this.state = { - // // data: sampleData, - // // }; - // } - // Get current chart a D3 selection const currentChart = React.useCallback( (): d3.Selection => @@ -75,33 +36,6 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { [chartRef] ); - // public componentDidMount(): void { - // this.buildChart(); - - // // if (!this.state.chartConnected) { - // // // Start socket connections. - // // initiateSocket(); - - // // // Subscribe to the data feed. - // // subscribeToData((data) => { - // // // Add the new data point. - // // this.setState({ data: [...this.state.data, [data.x, data.y]] }); - // // }); - - // // // Set to being connected. - // // this.setState({ chartConnected: true }); - // // } - // } - - // public componentDidUpdate( - // prevProps: VCStateProps - // ): void { - // // console.log("Previous: ", prevState.data); - // // console.log("New: ", this.state.data); - - // this.updateChart(); - // } - // Build the chart initially. const buildChart = React.useCallback(() => { // Adjust the svg. @@ -112,7 +46,6 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { .attr("transform", `translate(${margin.left}, ${margin.top})`); // Add x axis - // const xAxis = xScale.domain([0, d3.max(data, (d) => d[0]) as number]); const xAxis = xScale.domain([0, 1.0]); svg .append("g") @@ -121,7 +54,6 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { .call(d3.axisBottom(xAxis)); // Add y axis - // const yAxis = yScale.domain([0, d3.max(data, (d) => d[1]) as number]); const yAxis = yScale.domain([0, 1.0]); svg .append("g") @@ -130,14 +62,6 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { // Add dots svg.append("g").attr("class", "scatter-chart-points"); - // .selectAll("dot") - // .data(data) - // .enter() - // .append("circle") - // .attr("cx", (d) => d[0]) - // .attr("cy", (d) => d[1]) - // .attr("r", 3) - // .style("fill", "#69b3a2"); }, [ currentChart, height, @@ -191,7 +115,7 @@ const VisualisationComponent = (props: VCProps): React.ReactElement => { // Build chart initially otherwise update for data if (!builtChart) { - buildChart(); // data + buildChart(); setBuiltChart(true); } else { updateChart(data);