From 9517ba3681555d09b6d5fcd140f8ee74e897e618 Mon Sep 17 00:00:00 2001 From: lempinen Date: Wed, 30 Nov 2011 10:23:45 +0000 Subject: [PATCH] Chart update: chart working in trend view git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@23373 ac1ea38d-2e2b-0410-8846-a27921b304fc --- org.simantics.jfreechart.ontology/graph.tg | Bin 6046 -> 5255 bytes .../graph/JFreeChart.pgraph | 119 +--- .../simantics/sysdyn/JFreeChartResource.java | 285 ++++----- .../simantics/modelica/ModelicaManager.java | 94 ++- org.simantics.sysdyn.ontology/graph.tg | Bin 76823 -> 78031 bytes .../sysdyn/ui/browser/nodes/ChartNode.java | 31 +- .../newComponents/NewChartHandler.java | 21 +- .../simantics/sysdyn/ui/trend/TrendToPng.java | 18 +- .../simantics/sysdyn/ui/trend/TrendView.java | 306 ++++++---- .../sysdyn/ui/trend/chart/ChartComposite.java | 1 + .../sysdyn/ui/trend/chart/ChartUtils.java | 118 ++++ .../sysdyn/ui/trend/chart/JFreeChart.java | 2 +- .../sysdyn/ui/trend/chart/NumberAxis.java | 14 +- .../sysdyn/ui/trend/chart/TextTitle.java | 4 +- .../sysdyn/ui/trend/chart/XYDataset.java | 196 ++++-- .../sysdyn/ui/trend/chart/XYPlot.java | 26 +- .../chart/graphexplorer/AxisChildRule.java | 11 +- .../trend/chart/graphexplorer/DropAction.java | 27 +- .../graphexplorer/VariableChildRule.java | 15 +- .../properties/AxisHidePropertyComposite.java | 16 +- .../properties/AxisPropertyComposite.java | 8 +- .../properties/ChartAxisAndVariablesTab.java | 69 +-- .../trend/chart/properties/ColorPicker.java | 2 +- .../properties/GeneralChartPropertiesTab.java | 24 +- .../ui/trend/chart/properties/RVIFactory.java | 2 +- .../trend/chart/properties/RVIModifier.java | 2 +- .../properties/SeriesPropertyComposite.java | 4 +- .../properties/VariableProposalProvider.java | 7 +- .../simantics/sysdyn/ui/values/ValueView.java | 8 + .../SysdynDatasetSelectionListener.java | 577 +++++++++++------- .../sysdyn/adapter/HistoryVariable.java | 76 ++- .../adapter/SysdynVariableProperties.java | 7 +- .../simantics/sysdyn/manager/SysdynModel.java | 40 +- 33 files changed, 1347 insertions(+), 783 deletions(-) create mode 100644 org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartUtils.java diff --git a/org.simantics.jfreechart.ontology/graph.tg b/org.simantics.jfreechart.ontology/graph.tg index a07c775b3669b17dc2b8df3908ea0f8b6418113e..208c3be8761684b1014203c87be37fc57f2635fe 100644 GIT binary patch literal 5255 zcmb7|2U8nK5QPnB6#;>B&Net!g?cUQbRoirrYX$kbgRTa){4v;F^9?68gZsSmTyDw?w& z(a=!_%u5nS zKT+_am>MHY={Igva{UHs*?=;VTK4x<$i;ofDH$!SZBuxgrF^-)r@~M*UF;DDk{rk` z`?NQHO|iDX8g&?}h6>4@Vj;ce$3Zh_bVw<5ZN)I44&x5n6l*z}*}NOO6{;pBo@FsP zAGGQC5zlqG9EYBd1rIV)opw~{#StloSs5y~tG`V&!}18$ZBA~}jbQkJNJgyxD5oA? zZ2PtDSt#ihQ=Z4M9B%{;9MAkTi>cWlXe!sItTYP)zB(WnW=PKmE!XRA+72rjtdvDc zy2#S#a7w~Rj?~X8IpUn1=n1I-qTAH5MRX+(K;mpyFqXYvS z&vg&Rrp|E&U>bhA7%DZlPzm-3mKhQSwT;x9O!=x?+iiq&ebmh)*AciWD)E{#vqew!271a`swX^y z@H89I0Z@@X<=>$_6Ft4)q~VskR|)vw&;0aI{3`SpBJnvV<~gxj9i8bS@LXh`hv204 zy~yjH%`$}ZB4eC061szjpz%SSUvR6cNy0a%4~6Lgr{#4Tyamt4(f^9T1Ce>3Ge>l0 z#jEYExZy5V^#zgpBK01pj*3(sAGx0qx+ij{ICr4s;Us?qOo`~Uj=Dc7@Ch(2qIV%U zDJod88KBlrSjeunTU8b6jd;acevU5FMi6#-m3+*B%sOsVoqLU?;kls2$4{9vnPc7I zQ%l2~1LlUz-qO9}*GVZsJ}2gPg7b3KAgrs<9J@7$Cq&k%-+G(nWJ)PQJ|*(b$lf-* zdb~pk?;)NMS=S*r3B4h=LDz>uT)y4n^0mAP`L=M~;=G(*(NgGPJsS{jiL5cs%GUWB z(OXu5HYSp8a8l;ikQDv^>4wO;3c(pE(iIh#Yi?5&%pShwh*RKJn-YFD`%oYqxH$3#DmIA_ot zCcOuXtfq^-D{F?8WSMUX>SG=}lWEesWhz&PG%xzT!=7J#>sigRn%j3n58sJO>&s-T zL7WxdNzQU=`u^PlPWIyc{}7|3hv~sz*aAH@i>ns97Js++o5f!({$lZGi$7WX(c&$O zH!a?p!psB#)DcTp8S_S^!uo0Q~x8xv-BAMoMz+4*+5+S!O}lNJWLO+A;ctOY{`?F z<@{fhX80+!Us?Rp;upkbzT?yeoB38X%lRTM=lh&k`oYGJvksPiu=GD8HvZ$(1{?o( z&C-v!^ka=?zYvp*u_R;eV6#7{_Y=Wb|6_|ES^SXL%=a|4!DhaAcfoSLU^(9h#L^Eo ze!S~o=?6>yePZbc8~+z-gQXuV{r8BaA8h=esSTEXu=Gz88~<5qgN^@_X6Z*<`lpDc zA8h;|sSTEXu=Gz78~;gagN+||8(8|m(tnrO)c-oQCoH~W@oi!=-f?P!&3L#w!E!vX z9Pbvf@#DOJjURU{So*=zf0Nkwv0kw8uWOcm#HIg+$p1iXupKx3@6)8ksD+GA zTa2?J<2ZYgah4_H-2e~LgZmrr;}|`1K47!nijK?mA}-g1nwt3|CK+p&jJ=YKy_bwy zg5~_K2_B{PRg14!eA(hl7GEGX^S?-KuvtIO%lSXB*^jc0%l$yU>F)@&&sl!3@t1U5 z`Vp7@Gs2Je9c=vXHA_F@(tnCrt_N)VMQVekA1wVRh>aiTA8h>it^k&Pu=F1zHh#Rn zVB=raEd7W}KWc2w6ZTdzY6X_(BTsC`Lkw)jThT1XLtKuRBR1n9CK+>+jI~O}-bqF+ zB%`)qIo~X?nGe3NIP_%b`9Tk2U=vSKyHD%mWAxuD@xQC66V*HZMDPC)=3g|Ff9OBz CIq1Ux literal 6046 zcmb7|iE|Uj62@b!+qVsbkU&n31aP=Q?gzGHK*U#U#hCY!ysV|Mys@+^c2~BQlU#py z@_oHCR8)2CIW1d{#bxnclTr%jT zz0%TAVxqw7{9|OH=S8SjoS!>A4C3A#oq%ih35 z*RS1}zYaV_+lg|`^i0r-*8;nVeaJqmj!N(Qy#_p`PO z|G4-uJ9{SblNgq!SjzR_z(lbT(V0~A(2F;`pv|9}Md+7BGEIqFP8~01z8~o+ zD`_#!wiYB|Cv3Cm|D=|lEfJPou~b~qQpGD`E8-EWm|}=dieX~2zZ02`$CoX-m($p_ zsAVG4+BB5j7u!rder&w`&R~Oj(`tlpy(_LUq;*e&@mpmy2Y%S+NInf(_$O0oXqTr;2US5lm3n~Xi99lzHL;yiHE>Nn4UC&SF<|naCZT7Jio}09mI40|Hy0}cx9LVF>#m?EOd5PD+#zfxJ zVw}C-51Q!~Q^T!^uecb79piC@ZlvO>7qxusMl92XWF?G~8}Pj-zB02*jW8{z@!YO$ z6Tz@5hB;_d3bzh_rE^tH?lV!3$pCqb$RKhwjuao6U9W9i*pk?$5VqyVKDPX_w_`d8{GN^_ z6+I@(yo>~{w*`yqzFF^t+#$EcP(Y>?pRetJd|M^BE`o6gWb*AGV_L5(**THPb_!#; z2Oy}6akLTk09OV0LT$|rzq!BSMf;qz9-UWJzPCi3vsHd1RNj8V)fmgxz96Pqq+9fw z`|Sv~Sj){}m%a-s+b1HfVA#V1AB%0OiW8w7MuVYaRveSe9P%)6S@&q4RaxH`;ke6S zr{=6!%Q&8JHg^n1w@=eq6?sQAGV9(Ee23&aO7NKo@*948k8kOQ;M9uOYlP{Ru@6h= zxS;|cin6d6CP~%Zby(Ks)_3LHSr~Q_Sn-L-J6T3*#R{&L;o*5XB~63#7~T~6yIq2m{57u$+hRSm}*_y>AcR1J4U%GurWBA;KdHn!@}9MOJP3Fk#P z0pWA6FQR!~>8^Y)H_d>AcGFrf4RsVYkOb$A{TF zV$Y@TaP%soen2~k_7fVvt$d&1?=s9U6I#zLrJ0wa=7S9LtC{vE8Rpz;KQ9l>PczKp zPW$%Ua`i|CWJo~-r%Q~C?&leM0#_5W4+^XQYco~-pxl>Qw0WUVJ_{kGE2p-CJz47;NmloU$hscxK^M=njqKu= zE$evtb^JZBi|1KLcJWoqI-Y(V&pqklkI*N(_(jVv;@{uVZd%sypTf3=_6yoKXqvqY z^X#Et&)+ACng8Pqf0W@56~BZ22N`}p!*{^CUa}kSA^K!JKV&!l_pINIpU=CErt^_q zz60ym`RLdA$U5IGu+B$z`TFRSbw0Ao$9l*vALoxv&oBSarSp?r{seup&QI3)$vQuO zlyLDe`edyqJN;YMub)2;n~RUoC+m2!jwkDQ-We|bDf(m=&;3u<<00$#t6&%3L!YeU z$vU2_<9VmJc;4+~9Z%NrWF60U=Jv~1=xgR(rkQskc^vH-+63C$XpT9*mot1R!@Sd6 zy#e}UT`yVJOV;&X0K51u`eYYhwXEam*YT{;jhA<$X5Ou2Hy)1fO~o&v|3-#i&+uy* zel^3qb6x!%^vSONnq^%-{kr~hitnROcJaIm&MN;amfd(>w(Oq259XNMd_2i8zYEas z;yE94XfB@L{mAb5JfFzAezLBA7VP5rU6JhKtCn><{W^XI?Bd(#lU+RLkF4X#I(}O5 zDfG!MpRugt>38`#7i4!HjG@ow=C6_AFEYH9;l~-?wyfv70-LLc_g@~ZjP@fMePrh^ fqCaBomoMQ*T=kP~JcwI^V1D>plKYVq`WOBJ5(v5J diff --git a/org.simantics.jfreechart.ontology/graph/JFreeChart.pgraph b/org.simantics.jfreechart.ontology/graph/JFreeChart.pgraph index 716030f9..d6f115d9 100644 --- a/org.simantics.jfreechart.ontology/graph/JFreeChart.pgraph +++ b/org.simantics.jfreechart.ontology/graph/JFreeChart.pgraph @@ -14,24 +14,19 @@ JFREE = : L0.Ontology // Charts //##################################################################### JFREE.Chart -- JFREE.title --> JFREE.Title -- JFREE.subtitles --> L0.List -- JFREE.Chart.borderColor --> G2D.Color -- JFREE.Chart.visibleBorder --> L0.Boolean -- JFREE.Chart.borderWidth --> L0.Double -- JFREE.Chart.visibleLegend --> L0.Boolean -- JFREE.visible --> L0.Boolean -- JFREE.Title.position --> L0.Boolean -- JFREE.Plot.domainAxis --> JFREE.Axis -- JFREE.Plot.rangeAxis --> JFREE.Axis -- JFREE.backgroundColor --> G2D.Color -- JFREE.Plot.visibleGrid --> L0.Boolean -- JFREE.Plot.rangeAxisList --> L0.List -- JFREE.color --> G2D.Color -- JFREE.Axis.min --> L0.Double -- JFREE.Axis.max --> L0.Double -- JFREE.Axis.visibleTickLabels --> L0.Boolean -- JFREE.Axis.visibleTickMarks --> L0.Boolean -- JFREE.Axis.visibleAxisLine --> L0.Boolean -- JFREE.Axis.visibleLabel --> L0.Boolean -- JFREE.Dataset.seriesList --> L0.List -- JFREE.Dataset.mapToDomainAxis --> JFREE.Axis -- JFREE.Dataset.mapToRangeAxis --> JFREE.Axis -- JFREE.variableRVI --> L0.String -- JFREE.Series.lineWidth --> L0.Integer -- JFREE.color -JFREE.HasVariableRVI inits, String additionalScript) throws IOException { System.out.println(simulationDir.getAbsolutePath()); modelName = modelName.replace(" ", ""); @@ -216,11 +256,21 @@ public class ModelicaManager { ); } + /** + * Builds a model with omc. The location of required files is + * defined in simulationLocation. + * + * @param simulationLocation Location of model files + * @param monitor Monitor for printing build process output + * @throws ModelicaException + */ public static void buildModel(SimulationLocation simulationLocation, IModelicaMonitor monitor) throws ModelicaException { try { + // Find OMC File openModelicaHome = getModelicaHome(); + // Create the build process ProcessBuilder processBuilder = new ProcessBuilder( openModelicaHome.getAbsolutePath() + "\\bin\\omc.exe", simulationLocation.inputFile.getAbsolutePath() @@ -228,7 +278,7 @@ public class ModelicaManager { .directory(simulationLocation.simulationDir.getAbsoluteFile()) .redirectErrorStream(true); - + // Set environment variables so that OMC can find itself Map env = processBuilder.environment(); env.put("OPENMODELICAHOME", openModelicaHome.getAbsolutePath()); @@ -236,32 +286,48 @@ public class ModelicaManager { env.put("OMPATH", openModelicaHome.getAbsolutePath() + "\\bin"); env.put("Path", env.get("Path") + System.getProperty("path.separator") + openModelicaHome.getAbsolutePath() + "\\MinGW\\lib"); + // Start the building process Process process = processBuilder.start(); + + // Print process output printProcessOutput(process, monitor); if(!simulationLocation.exeFile.isFile()) + // If .exe file was not created, something went wrong throw new ModelicaException(".exe file not created\nSee log at " + simulationLocation.simulationDir.getAbsolutePath()); } catch(IOException e) { - e.printStackTrace(); + throw new ModelicaException(".exe file not created: " + e.getMessage()); } } + /** + * Runs a model that has been built to simulationLocation. + * Some initial values can be set for a compiled model without + * re-compiling it. + * + * @param simulationLocation Location of a built model + * @param monitor Monitor for printing process output + * @param inits Initial values for a simulation run + * @return Simulation process + * @throws IOException + */ public static Process runModelica(SimulationLocation simulationLocation, IModelicaMonitor monitor, HashMap inits) throws IOException { - - try { + // Write new initial values (parameters) writeInits(simulationLocation, inits); + // Create simulation proecss ProcessBuilder processBuilder = new ProcessBuilder( simulationLocation.exeFile.getAbsolutePath() ) .directory(new File(simulationLocation.simulationDir.getAbsolutePath())) .redirectErrorStream(true); + // Set environment variables for the process Map env = processBuilder.environment(); File openModelicaHome = getModelicaHome(); env.put("OPENMODELICAHOME", openModelicaHome.getAbsolutePath()); @@ -270,7 +336,7 @@ public class ModelicaManager { env.put("Path", env.get("Path") + System.getProperty("path.separator") + openModelicaHome.getAbsolutePath() + "\\bin" + System.getProperty("path.separator") + openModelicaHome.getAbsolutePath() + "\\MinGW\\lib"); - + // Simulate model Process process = processBuilder.start(); return process; @@ -280,6 +346,12 @@ public class ModelicaManager { return null; } + /** + * Write initial values to inits-file. File can be either + * xml-file or the old txt-file + * @param simulationLocation Location of the model + * @param inits Map containing inits that are changed + */ private static void writeInits(SimulationLocation simulationLocation, HashMap inits) { /* How the correct input file format should be investigated: try { diff --git a/org.simantics.sysdyn.ontology/graph.tg b/org.simantics.sysdyn.ontology/graph.tg index 66dd2706d43e3d3f76d073a87ea62b68e77bd67f..bea7e85b23ba70a194317ca208d322ceaf8b6004 100644 GIT binary patch literal 78031 zcmce<37A|(^~QZ~-|l2)vajsW>=Q^r681fl4FqEGt-ka%uEm6JtP59R6ub9 z1Vj)N6ctol5CIpYT>w#JQxsfr#T5~ELH*wMRGqqYD}(>|@O@vN=hF9A=bZPPs#~}2 z(w#6iGJoRMTDiYmEA{-Z>+tgcnhDfc$&Xn8SV^VE~8wR4Lrs(s~w zqBWxVCE8wWrAACU-*| z7mJ~V;Y+GRnCQ7*OH0y!5L5n2fhiv~(0d@u{H6lkt`eBGqO`d(I5HU2@*U&ZcOZx7 zDFqJCw-q=%-%{Z4JgLCpc|w7CA6MY;Jf^@R4iwm7b+ulv;LP=_=o}gzX~5-Pahcd% z87y~Km*5}|RHV;`O8c&(4Q6+>Q5w+rW#Tu*&Jt$m802aI>F30h^dHEM{ZfG)`-K8K z_HzYx>}Lw>*iRMMv7acgW48;)F+$Z^Be>1)?FshcB`{4(;tFgk#;+4mZ774jDq2otqf~0pI{gVL;TWAmLR8Ic$zI` z;JBo3KF4B-0>@&J0>`35fn%}1zyxY8;D78qJw|;vH75wjDc@hk8jYrfYUdD65hyU6 zxQn2i(+k`YoR<19?JU6=qs^>*cVRoP6cZl8R@Ev6LIeH81KFgeE_gT|^vM&QU-y6u;(!9fH9hLGCxr4Y&lS7c5 zLNM+w#z{+uMh3a8aBh|BJSAqNQS-y7goikOX=h5x5yNyDWm!HJUP;HOwdv%YFD}zZ z8)U?;7i-eD00Wtvt$>kC&QicoCTA*OER!=7Fqp~d3K-4gGzAQ2!o?uz7>~22Z=|qUqJto0{zzrOkt0gagOSM!*r_g>s>%Uo(W0Ec4m?4Fefsc&CN$0wiCo* z{K|5}Z(S*^a`wxBb~UQK=VFXsMtl5H8`GLG#*vAoZBj7!Fti%yrJIM#HJs{P+2X&m z?Zs)mxMH5&R~{}8;iji|%h|X^Li?Y^nC~uaKxH|0G5+(!j=sUlP-lHzMHXVTuafr3 zwtZQ(ccjjnD>(l};@eVPc(Z@;iv{t<-roc#1^Hq4hlyn`y0#4VR0q)ZS82=l4^%fn zDKeq1pXwIHazim?)22;x>y^P0ZsL0DbE~xtxY-y>Q{1|^I#8|6nLih+OO%(Uadv$~ zBMwv4&Bpu`dlc&{aaUF!E^+B8tf#4aZ0FGVD-3oMIwp53(G&XPx1Z!8u zx~Nv&ROkJPwf;%0V{I&i*BEz~&{klMRk33viF`_uA#L?Q@J4F*y|UZfKt%odZSjVaL(m7Nb^`UcjF!x4OLOx zae1YVn?~H|j3!-&Pki+_cB_n$m-8|Hs5G{CEgxgcM+#GDSt@fT83*?oc+AbjZaUNF!1$N zIO7uzXp|eJWB%hGR8B(dyU*evCAoyJ|k6Fsc zDP&G=7angq)>py33pV zD5b{k;>?y|dXpH&bs_oChK^dT#9n<$8pnHwnJdZO(mHl|rKeV^(d{y6;8=B+aleMX zuP)WFzTnA>1I|g7T$WZ8{lUm~;v5Ax=+Q^jxosPH^;ZCw^q{lyy1uky5XO$z&2X3kHEv6CD z>6}ZRM46 zkTXhpmp8U>W8m9Z)nmPy;pPK--}oZ>=J)EYK5PXrkGk<3Yx;6MlN9BE!J-FUpNsQXP@|j%cNDGE~NGLSz=q7O3m0 zdltgEN%X+Sk!7%PFPkemltm=)f)CfG>z@81!=clyYayewu}R>l4yiSX3G6gHxef z5G%_fK7ev%NgkJ`sWuU&%(_yoVs0o4YsEHJE-p+GeOVf&^G13T2br}Y#9Su|atZ&k zcc8y0?i09{imSbRJ*LfWC6;L=lmA5AEMnaqlH29zE8@w;yQ9%4^={<7ReIV+y#I+# zl#ZpgA=B|DY-<}{(!7!s$~0k9 zt&1(w9FtfsN`<|*3g=|?tFSjY7f1!NDw>3LQm>YFoF8^pc3177V1TxWZIY(9_ZJxP zlf*DXk4*R&@@AOhlO~|I{wfulv72EYth_O|l^iFcyQE_@%Gf|x=2OAt4jzzllSE~V zIeHlRHNvKU962Wn+d{GJ?PZj)8+AzGZf4Vrbg0BzpA+M;qSn^yjz12doWX@%wpR3} z2?kp3lMAhQ&#!9HYebFf-J(*xY*)dkg{^U$>9jo$NsKL!XwmCAeO@wl+N6 zBCcyo9q+2`iF69v+Gcj)+EDJ(vp9V|iTPS7YMZdseqFGvA`5Y}S(Qb zQ5&yxxL+I@;GG0tzC?ddm(Q5*8l$$cX7=z-jW4>Rwn_#x1|b}aCvTJ)^v^F#5$Hwme3VQ;qm&~y5UIE|NYTS^0X z0vh3S72n@QKk?lD!-h8APnq6vY~aVY%F%y3zvsmdFWzxu-iwQw_udQcb7_6vbLUHQ zGib}J)pJLN;jWsUTprB=l4hP+YXXuo#`#2 zj^)_5O&+ce;QpeqWx0E$9`l7()TS?V!nJZEtnsQD*|V^|OmC+K#5@xtVBUD*jYebf z#>zk+R|G`z{bRgpl8#Ioc#aq*n8398TxsSt74f>*#3xJZG}FrE*iXTAB3|Vg<&oIt zUnj=Ni$*E~jmnTK13mr|EI65QF#Ht+2ZFDeqd$2Ef^XO{7Z(pyNBXp+=ssy|!M#MO zG9dR)*GS8>PP|hzx9g4cRxVyGMjd3c?qP|t@5MI38m}H}nsIW(7qMZ}_AASqvIFm) ztK-*-$M{u9#aDtoI3W?j#V_780deVAq2)EwJQdA40;$l2VjJh#5O9GsPDEp7Nr=uw zd(_5NANRqvhF#0}rZqa#8?j@>ZE`2B*)=>i@>q7`HXp**ip8GS`abH{(ztWi#;Tu= z1G%|#NFD^ZVDR03^lLARq-`6w3eN&V_-HK|hLD~`ZN z=y^>6h4Z2ve@Jkr(MB+xXCDx!=^6FZ`U$a4wi0eEAbyuvrmft1G-14(#=FL!Zyz-I zdIhJ~VQjtIJH0+=GzRoFb>;-PU5t~0c?^XkV8C6Qw``cZ3we-%=^3$4b64S10Y-jZ z6tzvp^R2zfuOC;d@;)P$dy%$@%kbcv4ltaLk%)AqPAVU`w(1<^+a=vXk?bP){c?Q zm4S-9_>4}H$f;%x#A{6RVqH$O=wY!H@LF8%%XTI{oDO~TMiXm$m3a(}j~3gswOF;> zX~LCKI`<_p%6AR8EJ5`VY2X{JrRR;gO*il6~=Ycn;JNe`{Nikc@2R6salxw;^w562g64RtXpLEn3y{3a{gv=!uZvO$Jmu=u*0S2(f1$8L z;2Znz`+6mq7uRmQn#Olr@&(*@w)T4UzbJ0FG9e{iFz947x3kgb#X8MRSX_G3X?nRB z`H-C+Q;w0`dS9OAh|K|=Dz{B@Dnmt0!am;vk+@^>I{x5kFk|^DE%)S4>s)dgc77m~ zbak!-{M5kWRb7DR1#-)2__~SdI%!Qe=w6|=&Sl-lFI~BGdDl|3@z*)=J!4j^TC#Te zD(vzu8d)H9Ja)LAproaztj6bWjj6k1B~9Nk)YZL|wug*k$0An1dwgiD=~zJ%&l=Dv zrtTlxuuc*Aq6u3ey?9)puzIbjj7J-}kq)06c)*H|Q}D@!Ebho}uJm1io#TbZ;p@P1 zjW^(YXBD3)jg!2_Wg`Q8U9{B1ULdj2%b=)js?JCpH}`FJ#F2pwhqIGEzr!;@dXDc( z+c(og8u5N7YMW>#Mt6GR3`qNQ)1I!a_O;BDV&#H@A4Hvssj?>OWfyT%7QjHf(Pu+t_$^8Xn6D+*Vjq*|3pspLvUf#}x$G+|)G|LW(?d1I}wIid%o4G%6R6&zkCEnB?~+g_R{xoetR z?Rm;#WtXcemMTD>wD4_8R~3smzmb*Yzt0=tt4x;kK;ZcSe$&`|Rq7W{5x+5us*Oe! zU+~9=3ePnC#N&=RHtwZNJV)%4bzx1fHw8wc?+YJvfi{{AklH8QtGQ#$(k&CS%@* z&yn-RlD#o@UlGKKSb6sG>D)V;{wjv-7g0A#TfzPYD|&KA0puA zU~W13mo$!X3*4~qbX>~PxBIqpNOOr;TXdk&av57L$=kR5(h=_m39-3d+}>RRG}4_B z(f34>U*YlB2@QXhj^%3B)^>cgR=EJLd*n6VvEsko*0-DX);Th(&nWk!>GRDyMT3`4 zxvVW6!VD-(XX87)qw<7!$+fvM#H;rnY|xRFt3Xw2xng z!-jD`AAdiB2{(!3zTRUZjyrxB(=);sP{?CSNh^@AsN1;~x!($!8V+X-E7KZs@*9tK zx&Oz_zy82Ye#q9&-^`gu+x+EPs=R68D8vh-Df2aHPQL7HFUWVIsQWx|8JnI6^23St zvFW#I=ZnX7uh27mRsEzm@WPPZChFOMnbQSx;@uhM$-u?VD&+iVS_Nn;2Nv2MAB9`A)w&RyP?yFyZ zMI7g+$QSl_%ZR_-AYa(?G3o#jn!ctw(kRPMY0^PO=1#G)d-ebpIFoaoY4-}N#08aP zPYM~%`C=Q#3x9fmFY2~_ShcM#!M2opz*wwIy4NKFcdHJzWJMJO) zt4_WzR*{-1G9sTnSULH{NIUL_%=aFc20SZr%TwVusNBUURm@6g!5&v#g}mCN<;w-{8-{4I*jv3zbNo7Knp%=$ot;wooy9hT zPig}+1>cwO$3{^yrDlJA=c(p*F(&=i-Z$+EQ+^k@8rVRH+$8=p)9+ccl_c+lJ-LZN zt7{cppZHtF!f9-LZ+cvk_lcK3cFD~y&>N=h&=@6?@M~z?w)^9=gBWJ^xvzV(WyJDZ zi)0GEYOqH$JsvD3N_N_6FnHUn-#|rsh||>7__bg6#@a|1?i9;4^X*SGLu`}v*m>pd zCYEV9uW=l>p1CC)DT_4j#*y(Nc9%w;8}_RuSy2=Gr3y1K_!`yk_XNo>A-K^?ZejC; z9U${c2PT%E1WXv;iKqJBfswv)UnhP67{5UR<+nJa%&WpNV#Z&2lMpO0k<6C~Gm&Z3 zUdCrh1RtbjDrLtYma9Rsz27@rtt6QB*6^)MX^!Op0b#ypeu4G=A9yOKc&iC6{HnY{V2Bd zr&B$aUnV6}dTq4Vv1qCY+u7~O=4BC0Tt-=O%>2kNK}>$Jw+@Y5FUg_e5+-5c`9o8;xMq@Q z@4cSPS&yRd7kMIF;|gbE!(V(X;P0Sh7=H~V!}yyh89pfB0|Qy=`F23Qc^)(W z0Rhhqcuv5x1KvO7>4oI-1sry&H?Y}F~@g%kLkZ%$`OuTVP?uQ9y0=-9`Lk)rv^MFWf}j;DNDXd0Z&X> z@=pkOe8Bvh$U1-6pSFPc_jG;x*p#K7kjLP3c{6;ziZk&N=z*j0|G^)$Uv605`P~hB z1zZU1;QU+6##q{8o^f}Eo9H1^jhqSO4n` zXX0UZ`gr8$jcdjZcKYz8-Ra{MAiL}IUuQTI54+PxJ$a0qZznr_YJq+I{J%Z@i?9#Q z#Dkswd9eR0u!B`!w7^auuNm_%W7Fe%q2Y`^>`ou!lYc4D2RnUgfiwF5M4Rekki>UkLaQ0YCqL@b8gR`?nDL;7tF(u76m| z^1lo0VAnrtfwg}*9x1!gKrA*i`=*!x??poj#B6 zuL6Cr)29|#^>duh&tX&jqYY>DVR!mGSAQAkgPlILz)qj_`~sWmA7wbB54-Af>C68- z&<8txYJoHQKSP`9ABlZ%Mjx#DInK|Y26nL1rxsZC6V~$+Y^r~R;fy})s-NImmwz_U z2RnUgfmL7X`7zp5|8VSsGx}iF&*5Ck|0uA7oj$d|PM^o?huBpAFvA&r*quJ-!w&*| zu+yg&*y*#L?_*Q_Lkws1VR!nh=X-%Z*y&RXtojM=4f4-mQ~iSsXY^rr`dCZyPY3#7 zr%x?#M*q8LQ~d?l2WRxbsxS3?C$NK^KDEHApZhzQYo6-kUYDHF2RnV3^4|{ZV5d(l za7O=IXj6UME9b!(eX!FX1N)PK9qja}1$O#>1wVmJ^=BK-=)L(n}2e7Gr(Qrl|cGb^45BpaGeX!G~7C58-6||}TuGj}> z^ubP_>)V$DJJ{({3+(jS-!EZP{hbYG^kH}UFTmam^ubP_T41No@wp$H>hEYcqYu04 z%l!XhpbvKX)B-zw)_)&1)!*K5Mjv*k-vav=0)4R4rxrM)|9P~j{!HwHGx}hspNIW( zfgSAhsRhpHe->@3KOOtvj6PWPbH9WA-oOra`qTnDefIYr`1j+v=4?E#aeciz;Likn zSHPbR_|AYo74Rnm{zSkZ5BQFNKNj#u1O7z&gIUGr=FiruuI)oY9Bf>C^v%fj-#jQw!|$tKeI)ss39GXY^rr`lrMGfj}SZ z^r;1Q`h(#2V^e*6k~8{Xr++T&w*+>u)29~L>6gJbV^jUh3}^IVcls6B-xuhEoj$d| z8U6R7P4zFqJ~*QfcKWR6roaw%`qTnD{dM3Qv8nzW3}^IVcltcO?+Nt5PM=y}r_b@c z0h{VyY&fG2yVGa=*9ZDwr%x@g(`P;J#-{qOHJs6h-RYNLzb?=RJAG<_Gy3mBo9e#? z`{0Z|*y(S8{o23|cKXx;XY{W@o9bVHeQ-t}?DRSRuMX^Br%x@g)8~A9CpOjJY&fG2 zyVK|RUlr(soj$d|PM`H$iB0v-H=NOj-RWbE&R-GegPlILz!`n6X{z7AJ~*QfcKRIu zw+D8x)29|Vqt7)>_0PjTIHM1C`s-kSYhVXEeQJT7KI_4nkhwlx8Zgf3jQx!PC+$V{6A*d`seH5 zErCDS`G4Bj6JH@CQ5pPr;w;>*Jit=!2a<^~t{eNZ=24{&&Ei z?Cay4%IJfgKgWmc>*E~B_=BB4ukU2_?*|VD{$TZwcs(PlKhKw;z#pvsF|YMx^{4(| z;15=R(I=}v^#=lfu=>Ziw$twXsgG+|roUkI7k#p?-;0=2?R>m zmM^o+`3HZ^7jt~b3oxIl!}YfZo39Uc`Z!l;kF9=$b3oYFhdrZDR{g8s&ugTw56Klb%u&*+m?|4R6;5A?wqeX@?v8OA@O4|_(Rtom2LpVt__ez4QWT0y(&%lMGB ze#~{+Gx}uJ=k?>PKp&jZC;Q_=_VvLTeX{Dm9olCG`rwQ{*&iRWuMf`XlU4t1(B_!> z;{$g3y#FApKIXQtuMc}hpRD?Cg+K2Je0^|6pRD?CHvXCVVbAE3RiDoXrv>_8r;oLk zcBg-iWvw4;AMF`^vg%(9JI7Q!`MFt{@Nef$eQmQ%YMG2Q++w#j!Iebfn7fM=E<6`iv1(Ke6*1@ zACDK=&v%5!?BC%jOFpp62VdG?LdG@jNhy2fO%_jXk#UvGtENp5u+RQ2U?X89YB_sRyij^rhYDF`jJoBCOr{ zdDwQf^=&Zh^mhRt5a@%QK4Zz+U(R2$*308HH;tEi=cFw4f}I|G^JMKm^ z(FZ$y#*$T^^^>iB#C-b%`rwTI-YJVd*y%Httoqa=`}%tY`rwTIo+*ny*y%Ht?CXv(Yf?UcqR zw;9&wj~HtM?fU!?Jq+H_^Z%sbi8T(YQUw_C`0)McphxL=Se$JoCfj`*!vwpJgKgqMResHF~ ziJqPHgERICV88y5#|QpkR}bqaYdu`A#|8di=g<1dzJHr%XZ_$zeXX9I^@B6^7O-D` z$YTS4u&amlleHeq&HR|aAME^DKiT&$cy`tg&eWIp?5rQ0u_s`^{*ZHlKiJj7`pH@k z=4L(){K3wj^^<*nxVw5j2|G~F*BHlESkKpcVf&BgkM)M^)*Bv6^6faEPr>=fWA|@t zPk_4-hw&l1e2;_w<=HVl&w9wd|MP)A z*!gpx+sEzojrzY2{K3xucgCLaN1HodoR9Re@0X+BShn*q`Zf5so!hxR8e`~Jw0@n=r;e**ht-~U&EKiK&{j(xK4|6Je?cK%#P$iDwC1Anmd ze+2tv-~ShZKiK&{ihZ*0|MS2fobf07{yz)+!Os7i*eCn`KMnlB&Y$&@egB^X{@{#1 z+4p}o@CRr7$-e)O1Anmd{}%Si>d*7*M}a@s`G3jSoj>RQ4+DR&^MBCTGyXpa{K3xu z>)0pz^?X0@2Rnb(Lsoy*^S!_y?EF~|+4p}Y@CQ5php|ug{hto}!Onj+_Q~qc@%(P! z4|e`Mf6404`o9DJew?poWB(~^-}d5peu3Tf@NUaIk6wl!*ApC5cRtgf#~A0aTtDx! z?E63J`E$JhyZSzieavyEM?2@dt`7?=`}JdvJAc;0Ij`&6t(I>`J*U9$1K@82{$S_- zeq-0`b95OPbHwSh9?lu9=N8L;JvfHWpY`yVYCZ3>to2a;Yk@!5`M=lLT|GB}AM*TJ z57^anrDd&$^8s_v)kA-<^XL2_`~D9E{$S_N{AAz%tDZmm3wHJ2VA=2QR|0>q^M5z? z$-e)W1Anmde;4-2zCX^hZc9`(&-3^S>GRgPlLehphe_&-(*^u=Bs#*faiL z4E({)|DD(;`}+3<{$S^S752%#KhEh)e|e5;e>oq>zW?Vve;zNe8_%~|*71A`_;a2= z=MUKFb3Dnu{$~S!u=9U2_Q}5gJ%K;i`SbiBt3Qty)*#nk*3UIb&#$^=t-l8TOyCc8 z{$*o#^QRAtHOuLx$oV+LSC#yg6k$wLgQjXWd4%YWi(G<)2K8yKi*Y{7+j;H!HzXb>q*`OzRqLL?{|4j`?Vg^evQYpU+ppNSQ9e(IOiSHj&s^EfonB(u9)Hn;J);M9dL{V3A+0w$2mE?$y|!NXl3jiCEk6u9 z=RRV^uKkG(XYwK5nS5ZE?`+s#o9aov zi_oU|;G2Lm`N*0Nv0~SJ|1zA(2Y;9EEZAR@>Pfx}(Wd#}n}9R<$eItaV%K~x8P4Q` zzsq+f>=&eZl5Y#zG#`8ua3&vF^C4F3n(rTmGx^}}@|^+u=2TDeZ9<#ogKq-P{mpPDAN*au(_ue9)suW9Xw!V~O~9FaWX*?Iv1`7+7|!H_zsq+T?2S|}N3LVj zeDEc^e7hLVor3e>1neW0_T0(f)!0838_r$z7rpb)rh3!{t3BSsu+u{X_*yNhkgMGc9_y+9c2eCbXedZ^B3EO?x z=X@_=-}fi0KaVF_{TaU=tp4{~w*EQJPukUg85nDN#-Hr_lhvR3uqJ2x$yyJ`jBNeo z{KA@=@hAKKWYuRqSQ9h;Wc6qN$m-90Skp58WZ$2x{;VHsP{yC^`;*n5^9RSk`M(E@ zwaM|JV9Z^|Pa96w;Trm9?Bi4HkKowvf_=oY&G}zpxgU0pGv^cSO|v--)kCgiEo^#x znICh_G4q3yF694{`4qe6M=Wj5pZUptesa@nP6KVSPi>63i@(s{h8>jfu^QSTqjr`WY##L||jm)!52Kjfy_oQCRA8|So(XT4;t z7k*?{FZOv2aQ?3YF9bh?e3(1@w-1Ev`6$l^u;+dQ+wY;wOw{IQ-M z;)H!Y*j0~Ov}g2aSG`}sevGH5?CZhq^bkw`j2`W(_Z;j;dwRdL?CZhq^bkw`j2`W( z$M~Z>y`Nk5^%s2y5KI4z9_^~fdXDh)p0(`j!S3`B zOaF`>?W*@P*bn#geq`C#gWc&Nmi`$%+EtJJJIvGjfn{G0cBh9}`e*cLSG}LWj&sn> z?{mP1q#W~pf&4J$>(0hkSm*!uj6I_df2aSfvAcSZJ2^Pjm* ztob;;WX<<&!-1c9##ilLJzH>EGOxB_CPy!8ajmz9$W5^1<%%aek4tf2bvy zlj=*p*(pmtvgU(tLe_kb8_wi|-R1kfvAcZ8o$R0LOTPV5mV9K*2j7IO`5rZ#$p^d3 z$N5YC4bGA0v5#7kS*gC{YfoA7ku@KD6SC%e#Be4b>@MFk#_sYVcd~D)FZuRKS@MxJ zAAA$C=KH$gOg`9MKAtaR?H_7M_D=OB-(D$8KCNqgKGlSBWphR zCS=XmG@Qu?yUWM*hpgj?T9TboeaW|D%94+)`QV$7HQyHvXY#@B@^SqkYd+*oc1ZOl z-}Wg>KC2fNGngt5DP$enDL>PxA3QFN7rnNWMGx%spp}rdKEw*E9`DCm z(|FNqNm=y3P7hiMS@jSrta|r?$ENY3HzsA#13NuvC1lk@tgz}`3ofMbqL)uu^uSIJ zS_xV85G$;DSAml>Ui5M)%lk^O(}NcOO&6z!SYg$>3mm8Mq6c*A-@dQ|ogTD=wLZiO ztKKK!`=2zPdbxk6%=*YqkJ@Cb$5>(2TMYhJ8ZUY;r!0D4)stAV>LE5qR=wM?|56$+ zdjCwB?`QdM=s7*cl2s3}!m9T{@ITUc(ffPKq6c<*)FP`MVue-j7VzKFc+vZ7%AyB$ zdekDT9%6-6?3itSf{KUw|nvUc@n{Bwan zS^arFlGUH{|CfP3S^d?n{#+k^5%`mRf3o_268=9A{K@LCcJ=4^^RvL8to~|O|8K(o zr-46N{aG(r{dqq9B=9GzzuMKGF_*CB!&*qreHHI- z?lGU-cWiC0zAKE)|Gt2}QPXUqfi=SUUkiWo<;XwT>Vx(B0orn3_Wap@9viI>=NdWJ zM16OgPqAx%5KEi0TVEH?dSTDhOSYS6;v91Rte33y!jJ6g<^C5vf7W}S$E+8e`vU6y zjQJG1){9u$GWC$PzpNMR;;ojemztmV{F(oA9y32#^TC&#`z-3=xra|~nSA7a9G|oC z`INOcv5(De zb#{&q*{_$}G@H{@eda>HUHtWywO;tq?&{?}>T>?9_fC&lA2|0Z)Nen3!@kzb{2%q0`N6r5AU|uxr`R<=Vx>*(*4ODVKWv%&N#2C2PIZ0=s&-f2-%udOzSX>jUTBkNSD+@F{k!7qM(}`B*Qx-#g#QO|v--w8=iT zZ-Gt6m-UjhUigt+z1+Xq^Jl&9^O*I5bMJ*d<`F-|uJt08Z7v_{CHwV~n`UzwXp?f^bCPqNc{#yY+SXSubpvddW?* ziRM+<_vY78&;NB`BA@C~r zaD4##@1G^U20JuzOR>E@)#v-|+}l!?d|;Q4xoOvYh!xg+Tt8sX@-bfXEyVt%slMd9BxT75cKH}fyXHfz zu;%0X1bZeQ<2Bzg*neZHFZte(vg8B1e2k@C^C4DP^Kt!xJ(G{|n(t`rzdqHMd>5xI z`M@q8V`#^`(EWNm=rNT|UOruK5rvtogY9!Jf&-c+GbN_AgBJCEo=pOFpp6$5`4mA7X_y zAJ<3NGx->=`3}SWmQ-KzZBAM8fn7ev(ysXsE3EmrUc#Qq$9TPvBH{<>nZG+e2mw83$R~L z^(EhVDN8=E%g0#SH6LPyH6Pbg*faSUuleR-e>l~b_tVvs<^43+n%AyB$dekDT9%6-6Z(neK8ZUa~l=*#M4(#-(MOHn; z3aj31a9!Kx6;{1nz&&Za=#^3yJ+RZG7FqQWE3A4ufY+z-qIY)6 zq6c<*)FP`MVue+22KcNrUi8jPIsOOiV5dhdvg#pLSoNlX&q(7%@AQ;K5A5`)MOHn; z3aj1}@TqCM=&kb@yE(AaqZV295G$;D6ToZJc=j!(vuVJo1Gv1Glz=1v43hpiKKXoxTQcrdlb zVIyOc`(4|2jhFAMVAJ~LebOlGzOTYqP%CPhO*EHbU+>p%gq?g7^3TNnBZl4mI&8dW zj=pZkPx(Q#Jpdc$1MRAJzvVBX?LO@9f^CTxzu2GO|e zm;ZX}e>wUy8T)r&TNLCcYySPL-Oo?E=I8wE0Q+{b#&bO(tN$$ULa^^o_WjA~&-L6z{dls*?*RWJy!Znx`|-5<@nntP7yjt8n?LM7Hyv-wF8*5Uuk+^% z;KRK9^e1b69zU|5pRD;ge>i5EkNeo1KacMrU_HLHlU1MbWM7}G`W#=5sp@kdoAams zL15LVo$TwAeSNa(&wxM2T=lt+&G}P*0a*2EC;R$jU!Sb{JU;WmdVXV{?DAQ5@z+{^ zdwi00;CYDG{PZVle(IC`{AA6~^XCB1&hej%Z4TDo%giU)#XoKA(IZ%2cELVk>FexQ zSnh}YY_vaRxru#jPE+-n3&+sKvwvirfAAx_@!&q!Og$dl$L8XBJh-N*o%`4_cAoQU z=RUTKopW04+{c!&b4=CFeQX)~J|1&C_xAW#hI4yhEH1@9KE?h8j8`Z25u4k~^Jn~? z9y1>7;-M{ejYq6#s=oDg^*v|ond>Kfd2HnPu%_Lye+%l_&T5f${@jeVqQ|UnH*CA2 zKbM$Kvh#n&*xh_VEPb87jdlKKj#bia@0H7YI9!d@!|f?om&EWuJar0;;ojemzq0x@vN7u^)eRh>gE3Som_Tw@FTl=xj)_W zXT8(FSmO%4#wKj_;+Kux#ltr@HHe?$#lw~(`|)5G&wU(swMWc9!HegdAlvv{Y}m!a zmK&eiC4QV24;$}EH9mSBxXokcYxVqL6V~zjE4an;KL`8r^~V)*^3*Wn5zl zeQ5t67$1@HZHD82!hX8(6}ydRp7=l5{)PU%)_js(y%-O&>)%D-e|!Fn1!w%h&i^&W z*V)m3=JNYHDmUSS&DEs!F*oM4_Mh+eg|)xvfBX{qImX_34Pt^bIV4MT4e(HmrKgWx#{#dW#zXbkZ=l@P)cm7v_|Lpni z4Sq4@_)p*$Qs(&`{~_S#J%(>go{j#Njj!+@!3P+v`1hVa^?n!dZ#||SS@qy6ta{Lj zf8+U6@7Dq2n(xMsdSumuudwPtE5XB6szQU>pt@!7jKlOeV@J~Ia z9$EF^E3A6ZihttyQ}5Y;f9x^!$f^flVbz0H{3FkwdOr;K2Od+8ta|VjRy}CN-}n5f z_q~8|4R+&8J+kV-S6KC+6+i9yQ}4S0f5&6$kyQ`A!m0TEGu^Og*yd!B<%IpcOyp`BU$KfWPW7^~kCRUt!gQR{Ryu zpL$;o_)8vBkF0v|6;?fH#ZAwjdiMwXMUSaRRz3I%t6md)pXX1#F9iH~kEusiJ@^W% z9<<`mdH&S@xgDXz2eRjq?|*cK-hdg%@D{^f|K}E8oW{%g0UxKw z^Y@~FUlTCagp5Ddj0|H<$nd6=<#^znQoH2GIh0|{@eE_mI-Y_0aIR$-Yh;G8uEksr zkHTjMHn7^`GMMY6u8->t>%W&tU)uHG%Z%4q_Wu7d{C~h`e8hTANjW|hd~(XsWH2-` z{@or=fE^sKLA}S|bFJk!nCCA!mb6>WpO&@fPY)QiiJtmn4b1Ra0kf~Z{`!DR0rv#V zId>8^t-s5#>o5BUd$fz`5C7j<8Rq)!+c^e4^Z5C^DBxuQcLuyX;8g*m?uhXAzRS#P6ah^Z*Fb6Vv zOFgC@S@k$y$f^gec!}pvy~P1z&N+SRkyQ`A!m0-?t~t*CFW`k>u1A9@Tan5D@(Z>uQ67azRW6rq#VD05^c-;NpPKM)o=-)#4-EY`k zZ*g4`|4+b2Xfs9B}nu?H6vM4O@Z7kM;`ovDV4;g{Ppf z`%yp7KaSUE+=LCA)PObH#dEy&0PA=mR@fge+I74TD|UapXjkTV`Mk`q8?PI|yoT(C zS|*rJva5&V2X^Bz9$Ykj;?G!c#vknb#~EK|=lCF(%LiZXXU1zZZo&tf%{?Sh4H)aDLIQ%<=M>;{|r(^&af+41dkf@d3wJNAfM^lkECG7HwFw zGX7k{)St0n=Rd~SoIl44zM1hEjhpbn=4w!V=E9oi^f+Ev6Ww^hPuL$X+I76RUXpcu zI9@(;ynN<(f!%mri~a4q{2VW^>u=6*JOi~S*vDs#HOWVy@D&* zjo)?F{!!SEfZu<>Q&YW|=PcGV)fb)|@T7nz1`G|2m-_{rnvZ&9Ki?Rz9uf+%#KZ zwq?z){(in+!N;!`?D|K4@<%bAT>n_hbJ&_@E9`Gs z7htXTJJ=^{f7w5>`g1;#n`SG_vaI8E4fy9?f9UHobNkG``OLBLnPclSj|JF`AM5>@ zmyb4Z#{N^!P8&F5{|Q+8&+~!YG+UwFviAQG@Uz%_e{$1og?;h)EH>Z&M_~2GdL5CQ zW-IJt+4uh;+SH%*keg;J>}}ci|32FE_;NhRoVz?GjP;qheSS7z_WgU_cyX-28U1HG zJN>~K`_rENS=hiCJ8E?Eo#VlpwSKN2-@@kipWHNCVK01=wf`Hi|0Fi`KgV(tpV$g} zTK4^)K%4JRhOhYVflspU|2Ww9CpXPj*d3o_-~TbN?@w-;tx&`#+4p}Gto}USM2XXN>PpZkmn1Y2$D4GWC4JJ3gF0K6Czn-SPSw_P>rz z^)IpPuOGB)J@;C>t{3-%AI9eUlbdGa-)R-Q>ht*0u6jKF52nofpXh;cCO)|_*O#!_{E`nEZa(u|@R{e1&ph{h=6MRvjOTq| zt1tDF{rbs%{ba5GtJo*|^^==+W9^^!>gD<7GtX79tB32&J=pa4eAcqPe-s<-S}%|9 z-O&Fh#_xFSGdEcC%e^XD{ds&ogH84CG_21*{C`P`{o~N&I)TsVE>EAw4(#+}%f3D} z?fMeuu@83F*O$Pb!sh1(yZl^hXxIF#l{F|^n~VR1wQGJ}kLlkuoAlvh*w=hK-*3Zq z2gYkHKIuzd0Pe*neaW_dz8}i)M*?1E*ga2u5d2|mI$wToxrt9~g&i$xy?+2B#?9w% z2K=aDSI?#3Td`?>evADNVAFicYTsz>nvdr%{hMYhY;RfT`_16@W7Bw!*Dcso|2fNQ zr#|hf{|?Jdd}8Bon8ohv-;6dLU#<^e*MF{E<(#*7shIxnAAm zG3z5Y%~qIcS^J~@T0h6<@w8!j4SWMOf4r}UzaF2j!baBkX#r2S zto>(w^jBtmU{~KY0kgh$WApQ0=jHzjY-F21`B%XIwygP>pZ?0s56d^^{7pLwnGxfw98Pq4fEoF7+X z^Un`*(`J95)({5ufBy54a--;7Q5 zereg)qutjdH_cX$BXuU_@0eVu5YNxoxh(q?8b-l4fgy- z`~5K2cAt6e@cBcA-Tb^K;Lis9Iq(~?>G60y_TLcj>pZ>~`##hEwb*q0_Q(EakGEL9 zz}{aDgGaD!vdmbY_p_{e=Y#9mUSs_)vaI$C!PIP6JGIE#pYsg6@x2>73JLQ?aT33hbW}@X289&pPa{4YAvabib`imY}^{BNZwafc%)}VIU$eM4lWz=5aJY0s2eNg*C?6U@+ zj{)oPIU4&%1&rhD{Ex&wkNaiNKLelFSiTgDd6irZKHT%)8~cZN%<(ud-~+%1Bc8eG zOFjr2uPyso*8Vb>4P*TA2w{?n}8_um<9%I9Ezr+|0#cpvP~^mqpLrw2U606({{AYtKHwX`ZP->}K9umubCs<1 zas7B6-yv~+v>f}{>-h&U^8X-dxj*Fw?DQFP>m~nx5npRN z7<}fK`^;myZMg=TaE&4_7mwAp#h!jKL!*WE6Mg1!-L}~Niy0a%ESXQl^0;qXtc_W? zril>#NqBs>Eq2|-42TxmPsFwYw=K3?LL)El*lmvG6%v0Iu3sph$Xme9w&m6NaMHyZ zn{0Eej&nMf;%$!YElR%h_~!nz&9QwF8ZG1%O8(^jg^z9PudUUKeQBFx&v{m|rSn<- z-x1rESLcV z{xz~^e+c`<-7dUv-t2=fntkECxd+ca5TEmBV-KGTeRl)XPQOpyWOe^}Y8RENyO|H@irm z#nr*#fpVi#9iosFF{gn&cK04Q=lDBGdHxwwD31)3YiRnkw#&bREFL-Dbx)nL)zHEF zHds0qw!5mV2B1pS^>JbB#YwS3a3H7q^n~&#UH>gXeKX zuRyzZ>6L#<)aSPb_R9l)Q^1!7{KkM^AMon}#&BgH4vvRp%@V2cIz3buXUoquh z&HLYC)75wYQri}5SK)I?P9> zLcY6P;&Bt@4{5vSvJs7*w(K@EU)rv|}9nozvsI8T@ z{%Wn4;^Wb>-AuNusP>fy7FUP*D;q{?vc|#8=b3SBOE(XfYgkN&8ffC>v}GaIUGqo& zi4Ok~TR{7`HYyfH5>G&TqG?RQUdZI%7+Achh(8rx{BUh74HP#YIiZbB)scp4y8vya zfeqDKrLl3)m@gk zFK(V&G@hM9#X)wt(p$2M#mZ=L8#WFWOGAD1nLiJC=+#rgWpn;Ks|*jUX!36zELr?2 z`qd~6ZL}Tx9XhsPes{;p<45h;#w|D?Jyq0RM9>Bt7}GUW@f$?Gs?mj@E#lXZ+GlSR zF=`EA;^*M~s=D7>t!wvx39b3_7O(31Kf8}0+}*g||NPbV9(uO-KZDfXd!dF=dmrTe z$*b)=dZC?v7Fq|)?dtr$kA0@=KSJ26I`B}lUH>7Z_S^#%jN0`~*6+XCtmVzaHtY8w zHFk_SIWdu+2Hto>6r4{R%asiq8+Nhq){lClJk0yOtu2k>(M8_4{tpWbZUY;|V~#GW zsJXv3sqKy5+<9DozKa^yc6W6zUEOZ1vt3NE9^qECh$n{1fZQihSc;OBMeg2fcHuHQ zTu+|D)^UF>7=Zdfby)k7>3^x#7<~ZB=1%y392uq^efc#<$cz!XYxBw&8Ix4ZAv~&R zbDIH7t@MzN;F+hrfqQ!=Gs~{d^x(tEE5xX-A8A~(L2{J7jiZFC#F~y3tJ@_jkCH8W zww9=g^#YPTT<{K#mk8C1Bg30YwLW`jQB~lzu{zLKFZNc4kZ`EMqxvL@`NSO&`xhR* zt&h;AN?&8+*3JKQN>h3C1D8%~c8AxrbnIspk2<<2>CCa3kNIjI4$Vw|0%gDE!qsb+ zcP(v~!JBQogK5q%G2_v!!^_c6MP zGovOWTIbL`h8j*iB^bHV*2B|do3`a%0=EF|rP;+Ext(|v2|5<8ShZyB@>QnQ%qg7{ ze8j1h2TPTqKGd~|58Wl!+*dij(ub1qsM9mIh)N%U(5t!@*3YXo?6SyL9{catb<&#d z(O2X1MoKl5RjsD?01qSNRb2{7J$e=V8l>jT?pQSXRS41*=(lR=54FHiTM8Vw%5HarvL`5gr&O2ocJYk+V}AC zc0aPsJ75Lw17zUs((IoN^}*5r-u>|Yt0ZT54@dXD|F5b%X0$55fNTfMUD0vMD^@vB zs%_Y+$j>3{+N+~FrVadUGGldy$BWuQ7 zr7(h@MR6ys?p#*vK7Q%S;!>`lbWeC3$!rO1xwGnGZ zUvb&UP_MkqT3+d?m1=kcB>w=(l*ywQu0-IdhAUpl@D3PSTT}|#Zx0*Z7B=vm*!Xcv zheig=`Uo1oWt6r{(Kd0ys$nd^e9-KvR?mh0#b_Nn1`nNh9SP(crIxV<@wO?hf~QOl zx=zCo3~jAiC|Z&lTDI-M-@YPWcy<#Z{KJ2CAm4xMq;y`%^X*7+53`1tm9o9RQmZ#` zv<5~7hj{ihN_g!%Ymcqxofu9(Vs`O}GmCo&6dToIPuW~QhpPjnM!7E=*NH1D=X(Y6 z#XW)?|0UXAEVi&-T%c_Nd#_@P7vVrxYRCJ%2HuckYME8dRbu<4JXG8R$GFFwleOxm zx*G!Ri5ax{HqOujpSEOdTWE9d3T*ZvzAUI$E-1IlxXmukpJf$rq73nd$GkZ=weo;% zVT2j+`LkvG&a@HP+sXs>D|VGjwcd^CUCe$5q%x2np}bR?f2N%6sfWxCCevQ9A)CXw zXt&wGKbtmI25|VXWaIV&>&Qs)7@MhrE}n^(64l{sCPr15ty0`SlaU7Aw2GwdDcktR z2F%F>(7DnN&olwX;<$3dn~r=t-dAdrivEy{%uk0_%Jj00Ebwfsklpa?Te7j7eHruC z;h_s^__Y{X

ggUFeRfjSOhXPyy^)2n>Mus4A{_;PWc)V2&s zw%_g*ZdOsm64-BgPc%vut=`e&Q;yHPFKzV)J9vHCikaSnrGn2{n4UdYKe%h=d@Ga5 z%heLTR}*P7XH$ddrxR~6ikvb1lu>S5as4|P#<62?j%?tSINzPcld`xnizjCB_zcGF zz%hlcMzt4L{QSaHw$c$WtWEqi0ji)fp?MyTQGvoWS#9l`tiR$PxOSZb@dUrDaRyZLM2%#Lk` ztu3-%*pFHx-xv8JPmK;qq=l3CM>-r$*|cfX+$R zgqG>jJQmH}W(X&W#E#1=cwIJxxip%V{EOOg+w#(ua%~=JnFpaMjLWrl)a&IMZ*M`{ zOVc=LFTs_`>W`7ewoZ9D(_3EE567v}HXe?vafgiW1{yq=QB(y@hRKTQT|cct-3Ae?DjbIqYbPkTMt9Rp0 zp~5-SQZJv+f!s2#uUzk~Rff$`O0KovIL(0TG;EV2=Lv$k3FrQ_C>zT*`SV|GaXxHnt9XFOznw02mDcw_CnqMf#;+lPw)=BKM}^m#sl4I?X>yKiRw=T zE_`g$iN+&KR{g1#b)r$<{*mA8ZVyPt)<@(#E7TiX2FisEwd%+)ny%OZL#;G8XMrq`W~R+Nj)ixz5BNz; zKW^lg;&C1i-jPha!uX)IP1c1L95V@opP7Yh5f!xP@o2NhV;tHz{{D&T9}iyH?4RVF z*x?1kTw(GX@oS-cqf{9v;9Esrx|6FgryqpFF4%U$HsSwi?rMAEI;yafh1LzhO+r+f zCP=rNma5?0o%a_fP95^rASfYq8Y%(8*juOI>{_--LWNMpHxNR6=Of~O^r!JWXYNg| zub0IFp>n0WzUSPTGc#wNbI#1%nFM=X%PTll#0;bb7v((hS#o{i=+MEdDNXchxL-9>X5yv{^(1rc2|k#2$)M@78bXe8Z6 zJLxuhX#wr@q#LwTHtA;6M&#F5?=N`x7wE;(>Iu4N7RCE(Gtv9X5%kV+mL-aDvV%P` zlkM7F7JvNG4>xvq#Sw@f?zq$vv}7P(!b6E!WYBG*db38{uAo@Htu6hU;hYH>>_dF?!V;eYngk_oCmxn0rbeUPF%}U zYx+71$&B*UKJk3W~cXu|{#$IH83@~r)Atwt*}J&bY8~P6`?z0Sl0|_E zTYj6}R^j z@Gf2CEK0WX%BtvhFwNS+TmxHD4lu9mQA6*1d)D^jyWD*{I-Hz`T3`Cd-(SD_-m}L$ z6&7i@KEB$=x>_F}9X6+1Xx?i7!TRk%>T{#dWNTvwXJc0TlWG6GYggaA`J)@_AK$xG z?LXPB?mhV45ANQm`h(Hvp}jF0-CVy}efZIx3JZp#(X9{qRlkMur0^KtPiC~%De_#% z$~i|~_9-W?!L%+r?ZfiG4s|rjCJUy)W6&rE@1yY{8ZcE--ug(UPevnK1wp7sawrFt z3qBZ^!HglJCa94eW@A!NOc_*+I_l+tqa+X!Oy_m5N|+j3(38jn!#HZf4K-Se<|C!{ z(2hmv(3I9HgQ#kcakUbR4o8Z%uIE>F6Egpu^E3qeF^dq62eKM8VKx7X5*DiO*Z>cGMuQ%$E5eqtZcYQD z;5F$SYLqRy1iYZVk-W}2LJ?)52Q8aL^(XXRk(UkZhw@Zb+KWK#U;vhiLKk}neK0A| zZGjwcRfP745y;YLyP*QL@<0&bR134zmvEyJv1mkO4XB_&9HLiX6265Qst7;X9ZtFzvib_Eu6l(*_1odIl#0eNh=o2VPg(R|_MzfOS1WLR}gPp+wrO#mI zR(i)3FIY;eKF~*5@h>oy*9O@Zz(6HM(|nw2d1+ z{u*N2j}hC-IYf&m9+-J{05*2uEL>TUgPXT`%%ur_asco}qz`Hlr%eC= literal 76823 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 implements IDropTargetNode /** - * Adds a variable to this chart + * Adds a variable to this chart and map it to the first rangeAxis * @param variable */ private void addVariableToChart(final Variable variable) { @@ -79,14 +79,25 @@ public class ChartNode extends AbstractNode implements IDropTargetNode JFreeChartResource jfree = JFreeChartResource.getInstance(graph); Resource plot = graph.syncRequest(new SingleObjectWithType(data, l0.ConsistsOf, jfree.Plot)); + if(plot == null) + return; - // FIXME: order of the datasets varies in this way - Resource dataset = graph.syncRequest(new ObjectsWithType(plot, l0.ConsistsOf, jfree.Dataset)).iterator().next(); - + Resource rangeAxis = null; + Resource dataset = null; + Resource rangeAxisList = graph.getPossibleObject(plot, jfree.Plot_rangeAxisList); + if(rangeAxisList == null || ListUtils.toList(graph, rangeAxisList).isEmpty()) { + // No range axis -> Create a new one + rangeAxis = ChartUtils.createNumberRangeAxis(graph, plot); + Resource domainAxis = graph.getPossibleObject(plot, jfree.Plot_domainAxis); + dataset = ChartUtils.createXYDataset(graph, plot, domainAxis, rangeAxis); + } else { + rangeAxis = ListUtils.toList(graph, rangeAxisList).get(0); + dataset = graph.getPossibleObject(rangeAxis, jfree.Dataset_mapToRangeAxis_Inverse); + } + + // Create the series and attach it to the dataset String rvi = Variables.getRVI(graph, variable); - GraphUtils.create2(graph, jfree.Series, - jfree.HasVariableRVI, rvi, - l0.PartOf, dataset); + ChartUtils.createSeries(graph, dataset, rvi); } }); } @@ -103,7 +114,7 @@ public class ChartNode extends AbstractNode implements IDropTargetNode } catch (DatabaseException e) { ExceptionUtils.logAndShowError(e); } - + } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewChartHandler.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewChartHandler.java index e84dadb2..3bf1b9a7 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewChartHandler.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/handlers/newComponents/NewChartHandler.java @@ -11,6 +11,8 @@ *******************************************************************************/ package org.simantics.sysdyn.ui.handlers.newComponents; +import java.util.ArrayList; +import java.util.Collections; import java.util.UUID; import org.eclipse.core.commands.AbstractHandler; @@ -21,6 +23,7 @@ import org.eclipse.ui.handlers.HandlerUtil; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.diagram.stubs.G2DResource; @@ -63,14 +66,14 @@ public class NewChartHandler extends AbstractHandler { l0.HasName, "Chart" + UUID.randomUUID().toString(), l0.HasLabel, NameUtils.findFreshLabel(g, "Chart", model), l0.PartOf, model, - jfree.HasVisibleBorder, true, - jfree.HasBorderWidth, 3); - g.claimLiteral(jfreechart, jfree.HasBorderColor, g2d.Color, new float[] {0,0,0,1}); + jfree.Chart_visibleBorder, true, + jfree.Chart_borderWidth, 3); + g.claimLiteral(jfreechart, jfree.Chart_borderColor, g2d.Color, new float[] {0,0,0,1}); GraphUtils.create2(g, jfree.TextTitle, l0.HasName, "TextTitle" + UUID.randomUUID().toString(), l0.HasLabel, "Chart Title", - jfree.HasPosition, jfree.Top, + jfree.Title_position, jfree.Top, l0.PartOf, jfreechart); Resource domainAxis = GraphUtils.create2(g, jfree.NumberAxis, @@ -82,14 +85,16 @@ public class NewChartHandler extends AbstractHandler { Resource dataset = GraphUtils.create2(g, jfree.XYDataset, l0.HasName, "XYDataset" + UUID.randomUUID().toString(), - jfree.MapToDomainAxis, domainAxis, - jfree.MapToRangeAxis, rangeAxis); + jfree.Dataset_mapToDomainAxis, domainAxis, + jfree.Dataset_mapToRangeAxis, rangeAxis, + jfree.Dataset_seriesList, ListUtils.create(g, new ArrayList())); GraphUtils.create2(g, jfree.XYPlot, l0.HasName, "XYPlot" + UUID.randomUUID().toString(), l0.PartOf, jfreechart, - jfree.HasDomainAxis, domainAxis, - jfree.HasRangeAxis, rangeAxis, + jfree.Plot_domainAxis, domainAxis, + jfree.Plot_rangeAxis, rangeAxis, + jfree.Plot_rangeAxisList, ListUtils.create(g, Collections.singletonList(rangeAxis)), l0.ConsistsOf, dataset, l0.ConsistsOf, domainAxis, l0.ConsistsOf, rangeAxis); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendToPng.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendToPng.java index ba1b10e5..e0555cae 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendToPng.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendToPng.java @@ -1,3 +1,14 @@ +/******************************************************************************* + * Copyright (c) 2007, 2011 Association for Decentralized Information Management in + * Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ package org.simantics.sysdyn.ui.trend; import java.io.File; @@ -13,6 +24,11 @@ import org.eclipse.ui.handlers.HandlerUtil; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; +/** + * Exports the current chart in TrendView + * @author Teemu Lempinen + * + */ public class TrendToPng extends AbstractHandler { @Override @@ -31,7 +47,7 @@ public class TrendToPng extends AbstractHandler { if (!(selected == null)){ File file = new File(selected); - JFreeChart chart = TrendView.chart; + JFreeChart chart = TrendView.panel.getChart(); try { ChartUtilities.saveChartAsPNG(file, chart, width, height, null, true, compressionLevel); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendView.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendView.java index 2dbe6609..f0d36edf 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendView.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/TrendView.java @@ -26,7 +26,13 @@ import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.data.xy.AbstractXYDataset; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.procedure.Listener; +import org.simantics.db.request.Read; import org.simantics.sysdyn.manager.SysdynDataSet; +import org.simantics.sysdyn.ui.trend.chart.IJFreeChart; import org.simantics.sysdyn.ui.viewUtils.SysdynDatasetSelectionListener; /** @@ -37,117 +43,195 @@ import org.simantics.sysdyn.ui.viewUtils.SysdynDatasetSelectionListener; */ public class TrendView extends ViewPart { - Frame frame; - public static ChartPanel panel; - SysdynDatasets sysdynDatasets = new SysdynDatasets(); - SysdynDatasetSelectionListener sysdynDatasetSelectionListener; - public static JFreeChart chart; - - /** - * Dataset for jFreeChart - * - * @author Teemu Lempinen - * - */ - @SuppressWarnings("serial") - class SysdynDatasets extends AbstractXYDataset { - - SysdynDataSet[] sets = new SysdynDataSet[0]; - - public void setDatasets(SysdynDataSet[] sets) { - this.sets = sets; - fireDatasetChanged(); - } - - @Override - public Number getY(int series, int item) { - return sets[series].values[item]; - } - - @Override - public Number getX(int series, int item) { - return sets[series].times[item]; - } - - @Override - public int getItemCount(int series) { - return sets[series].times.length; - } - - @Override - public Comparable getSeriesKey(int series) { - SysdynDataSet sds = sets[series]; - if(sds.result == null) - return sds.name; - else - return sds.name + " : " + sds.result; - } - - @Override - public int getSeriesCount() { - return sets.length; - } - - } - - @Override - public void createPartControl(Composite parent) { - - final Composite composite = new Composite(parent, - SWT.NO_BACKGROUND | SWT.EMBEDDED); - frame = SWT_AWT.new_Frame(composite); - - // Create the chart - SwingUtilities.invokeLater(new Runnable() { - - @Override - public void run() { - XYPlot plot = new XYPlot( - sysdynDatasets, - new NumberAxis("time"), - new NumberAxis("x"), - new XYLineAndShapeRenderer(true, false) - ); - - chart = new JFreeChart(plot); - panel = new ChartPanel(chart); - frame.add(panel); - panel.requestFocus(); - } - - }); - - // Add a dataset listener that updates datasets for the chart according to current selection - sysdynDatasetSelectionListener = new SysdynDatasetSelectionListener() { - - @Override - protected void selectionChanged(final Collection activeDatasets) { - SwingUtilities.invokeLater(new Runnable() { - - @Override - public void run() { - sysdynDatasets.setDatasets(activeDatasets.toArray(new SysdynDataSet[activeDatasets.size()])); - } - - }); - } - }; - getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(sysdynDatasetSelectionListener); - - } - - @Override - public void dispose() { - super.dispose(); - getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(sysdynDatasetSelectionListener); - } - - @Override - public void setFocus() { - if(panel != null) - panel.requestFocus(); - } - + private Frame frame; + public static ChartPanel panel; + private SysdynDatasets sysdynDatasets = new SysdynDatasets(); + private SysdynDatasetSelectionListener sysdynDatasetSelectionListener; + public static JFreeChart defaultchart; + private Composite composite; + private CustomChartListener listener; + + /** + * Dataset for jFreeChart + * + * @author Teemu Lempinen + * + */ + @SuppressWarnings("serial") + class SysdynDatasets extends AbstractXYDataset { + + SysdynDataSet[] sets = new SysdynDataSet[0]; + + public void setDatasets(SysdynDataSet[] sets) { + this.sets = sets; + fireDatasetChanged(); + } + + @Override + public Number getY(int series, int item) { + return sets[series].values[item]; + } + + @Override + public Number getX(int series, int item) { + return sets[series].times[item]; + } + + @Override + public int getItemCount(int series) { + return sets[series].times.length; + } + + @Override + public Comparable getSeriesKey(int series) { + SysdynDataSet sds = sets[series]; + if(sds.result == null) + return sds.name; + else + return sds.name + " : " + sds.result; + } + + @Override + public int getSeriesCount() { + return sets.length; + } + + } + + @Override + public void createPartControl(Composite parent) { + + composite = new Composite(parent, + SWT.NO_BACKGROUND | SWT.EMBEDDED); + frame = SWT_AWT.new_Frame(composite); + + // Create the chart + displayDefaultChart(); + + // Add a dataset listener that updates datasets for the chart according to current selection + sysdynDatasetSelectionListener = new SysdynDatasetSelectionListener() { + + @Override + protected void selectionChanged(final Collection activeDatasets) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + if(listener != null) { + listener.dispose(); + } + sysdynDatasets.setDatasets(activeDatasets.toArray(new SysdynDataSet[activeDatasets.size()])); + displayDefaultChart(); + } + + }); + } + + @Override + protected void selectionChanged(ReadGraph graph, final Resource chartResource) { + + if(listener != null) { + listener.dispose(); + } + + listener = new CustomChartListener(); + + graph.asyncRequest(new Read() { + + @Override + public JFreeChart perform(ReadGraph graph) throws DatabaseException { + if(graph.hasStatement(chartResource)) { + IJFreeChart chart = graph.adapt(chartResource, IJFreeChart.class); + if(chart != null) { + return chart.getChart(); + } + } + return null; + } + }, listener); + } + }; + + getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(sysdynDatasetSelectionListener); + + } + + private class CustomChartListener implements Listener { + + private boolean disposed = false; + + @Override + public void execute(JFreeChart result) { + if(!disposed) + displayChart(result); + } + + @Override + public void exception(Throwable t) { + t.printStackTrace(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + public void dispose() { + this.disposed = true; + } + + } + + /** + * Displays jFreeChart + * @param jFreeChart + */ + private void displayChart(final JFreeChart jFreeChart) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + frame.removeAll(); + if(jFreeChart != null) { + panel = new ChartPanel(jFreeChart); + frame.add(panel); + } + frame.repaint(); + frame.validate(); + panel.requestFocus(); + } + + }); + } + + /** + * displays a default chart + */ + private void displayDefaultChart() { + if(defaultchart == null) { + XYPlot plot = new XYPlot( + sysdynDatasets, + new NumberAxis("time"), + new NumberAxis(""), + new XYLineAndShapeRenderer(true, false) + ); + defaultchart = new JFreeChart(plot); + } + displayChart(defaultchart); + } + + @Override + public void dispose() { + super.dispose(); + getSite().getWorkbenchWindow().getSelectionService().removePostSelectionListener(sysdynDatasetSelectionListener); + } + + @Override + public void setFocus() { + if(panel != null) + panel.requestFocus(); + } + diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartComposite.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartComposite.java index 0ce473af..d6fe5445 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartComposite.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartComposite.java @@ -126,6 +126,7 @@ public class ChartComposite extends Composite { composite.layout(); panel = new ChartPanel((JFreeChart)getObject(), false, true, true, true, true); frame.add(panel); + frame.repaint(); frame.validate(); } }); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartUtils.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartUtils.java new file mode 100644 index 00000000..73f08545 --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/ChartUtils.java @@ -0,0 +1,118 @@ +package org.simantics.sysdyn.ui.trend.chart; + +import java.util.ArrayList; +import java.util.UUID; + +import org.simantics.db.Resource; +import org.simantics.db.WriteGraph; +import org.simantics.db.common.utils.ListUtils; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; +import org.simantics.layer0.utils.direct.GraphUtils; +import org.simantics.sysdyn.JFreeChartResource; + +/** + * Utilities for handling charts + * + * @author Teemu Lempinen + * + */ +public class ChartUtils { + + /** + * Creates a new range axis of type jfree.NumberAxis to a plot + * + * @param graph WriteGraph + * @param plot Plot resource + * @return Created number axis, null if not successful + * @throws DatabaseException + */ + public static Resource createNumberRangeAxis(WriteGraph graph, Resource plot) throws DatabaseException { + Resource axis = null; + JFreeChartResource jfree = JFreeChartResource.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + + if(plot != null) { + // Create range axis + axis = GraphUtils.create2(graph, jfree.NumberAxis, + l0.HasName, "NumberAxis" + UUID.randomUUID().toString(), + l0.HasLabel, NameUtils.findFreshLabel(graph, "range", plot), + jfree.Plot_rangeAxis_Inverse, plot, + l0.PartOf, plot); + + // Add range axis to the plot's range axis list + Resource axisList = graph.getPossibleObject(plot, jfree.Plot_rangeAxisList); + ArrayList list = new ArrayList(); + list.add(axis); + if(axisList == null) { + axisList = ListUtils.create(graph, list); + graph.claim(plot, jfree.Plot_rangeAxisList, axisList); + } else { + ListUtils.insertBack(graph, axisList, list); + } + } + + return axis; + + } + + /** + * Create a XYDataset and map it to axis + * @param graph WriteGraph + * @param plot Plot resource containing the dataset + * @param domainAxis Mapped domain axis for the dataset + * @param rangeAxis Mapped range axis for the dataset + * @return created dataset or null if not successful + * @throws DatabaseException + */ + public static Resource createXYDataset(WriteGraph graph, Resource plot, Resource domainAxis, Resource rangeAxis) throws DatabaseException { + if(plot == null || domainAxis == null || rangeAxis == null) + return null; + + JFreeChartResource jfree = JFreeChartResource.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + + // Create a dataset for the axis + Resource dataset = GraphUtils.create2(graph, jfree.XYDataset, + l0.HasName, "XYDataset" + UUID.randomUUID().toString(), + jfree.Dataset_mapToDomainAxis, domainAxis, + jfree.Dataset_mapToRangeAxis, rangeAxis, + l0.PartOf, plot); + + return dataset; + } + + /** + * Creates a new series to a dataset + * @param graph WriteGraph + * @param dataset Dataset for the new series + * @return created series or null if unsuccessful + * @throws DatabaseException + */ + public static Resource createSeries(WriteGraph graph, Resource dataset, String rvi) throws DatabaseException { + if(dataset == null) return null; + + JFreeChartResource jfree = JFreeChartResource.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + // Create series + Resource series = GraphUtils.create2(graph, jfree.Series, + l0.HasName, "Series" + UUID.randomUUID().toString(), + jfree.variableRVI, rvi == null ? " " : rvi, + l0.PartOf, dataset); + + // Add series to the dataset's series list + Resource seriesList = graph.getPossibleObject(dataset, jfree.Dataset_seriesList); + ArrayList list = new ArrayList(); + list.add(series); + if(seriesList == null) { + seriesList = ListUtils.create(graph, list); + graph.claim(dataset, jfree.Dataset_seriesList, seriesList); + } else { + ListUtils.insertBack(graph, seriesList, list); + } + + return series; + } + +} diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/JFreeChart.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/JFreeChart.java index e25fd2c3..87661ceb 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/JFreeChart.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/JFreeChart.java @@ -55,7 +55,7 @@ public class JFreeChart implements IJFreeChart { Resource titleResource = graph.syncRequest(new PossibleObjectWithType(chartResource, l0.ConsistsOf, jfree.Title)); title = graph.adapt(titleResource, ITitle.class); - legendVisible = graph.getPossibleRelatedValue(chartResource, jfree.HasVisibleLegend, Bindings.BOOLEAN); + legendVisible = graph.getPossibleRelatedValue(chartResource, jfree.Chart_visibleLegend, Bindings.BOOLEAN); plots = new HashMap(); for(Resource plotResource : graph.syncRequest(new ObjectsWithType(chartResource, l0.ConsistsOf, jfree.Plot))) { diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/NumberAxis.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/NumberAxis.java index 762be849..57bb9721 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/NumberAxis.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/NumberAxis.java @@ -49,14 +49,14 @@ public class NumberAxis implements IAxis { Layer0 l0 = Layer0.getInstance(graph); JFreeChartResource jfree = JFreeChartResource.getInstance(graph); label = graph.getPossibleRelatedValue(axisResource, l0.HasLabel); - tMarksVisible = graph.getPossibleRelatedValue(axisResource, jfree.HasVisibleTickMarks, Bindings.BOOLEAN); - tLabelsVisible = graph.getPossibleRelatedValue(axisResource, jfree.HasVisibleTickLabels, Bindings.BOOLEAN); - labelVisible = graph.getPossibleRelatedValue(axisResource, jfree.HasVisibleLabel, Bindings.BOOLEAN); - lineVisible = graph.getPossibleRelatedValue(axisResource, jfree.HasVisibleAxisLine, Bindings.BOOLEAN); - Resource c = graph.getPossibleObject(axisResource, jfree.HasColor); + tMarksVisible = graph.getPossibleRelatedValue(axisResource, jfree.Axis_visibleTickMarks, Bindings.BOOLEAN); + tLabelsVisible = graph.getPossibleRelatedValue(axisResource, jfree.Axis_visibleTickLabels, Bindings.BOOLEAN); + labelVisible = graph.getPossibleRelatedValue(axisResource, jfree.Axis_visibleLabel, Bindings.BOOLEAN); + lineVisible = graph.getPossibleRelatedValue(axisResource, jfree.Axis_visibleAxisLine, Bindings.BOOLEAN); + Resource c = graph.getPossibleObject(axisResource, jfree.color); color = c == null ? null : G2DUtils.getColor(graph, c); - min = graph.getPossibleRelatedValue(axisResource, jfree.HasMin, Bindings.DOUBLE); - max = graph.getPossibleRelatedValue(axisResource, jfree.HasMax, Bindings.DOUBLE); + min = graph.getPossibleRelatedValue(axisResource, jfree.Axis_min, Bindings.DOUBLE); + max = graph.getPossibleRelatedValue(axisResource, jfree.Axis_max, Bindings.DOUBLE); } catch (DatabaseException e) { e.printStackTrace(); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/TextTitle.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/TextTitle.java index a00fc62f..3fd7ede1 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/TextTitle.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/TextTitle.java @@ -38,9 +38,9 @@ public class TextTitle implements ITitle { JFreeChartResource jfree = JFreeChartResource.getInstance(graph); try { text = graph.getPossibleRelatedValue(titleResource, l0.HasLabel, Bindings.STRING); - Resource pos = graph.getPossibleObject(titleResource, jfree.HasPosition); + Resource pos = graph.getPossibleObject(titleResource, jfree.Title_position); position = getRectangleEdgePosition(graph, pos); - visible = graph.getPossibleRelatedValue(titleResource, jfree.IsVisible, Bindings.BOOLEAN); + visible = graph.getPossibleRelatedValue(titleResource, jfree.visible, Bindings.BOOLEAN); } catch (DatabaseException e) { e.printStackTrace(); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYDataset.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYDataset.java index 9d70bea0..339bdc7f 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYDataset.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYDataset.java @@ -14,6 +14,7 @@ package org.simantics.sysdyn.ui.trend.chart; import java.awt.BasicStroke; import java.awt.Color; import java.util.ArrayList; +import java.util.List; import org.jfree.chart.renderer.AbstractRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; @@ -22,18 +23,21 @@ import org.jfree.data.xy.DefaultXYDataset; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; -import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.exception.MissingVariableException; import org.simantics.db.layer0.request.PossibleActiveExperiment; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; +import org.simantics.db.procedure.Listener; +import org.simantics.db.request.Read; import org.simantics.diagram.G2DUtils; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingResources; import org.simantics.operation.Layer0X; import org.simantics.sysdyn.JFreeChartResource; import org.simantics.sysdyn.adapter.SysdynVariableProperties; +import org.simantics.ui.SimanticsUI; /** * Class representing a JFreeChart.XYDataset @@ -43,12 +47,15 @@ import org.simantics.sysdyn.adapter.SysdynVariableProperties; */ public class XYDataset implements IDataset { - private DefaultXYDataset dataset; private boolean disposed; - private ArrayList series; private XYLineAndShapeRenderer renderer; + private double[] domainValues; + private List seriesList; + private String realizationURI; + private Resource resource; public XYDataset(ReadGraph graph, final Resource datasetResource) { + this.resource = datasetResource; try { Layer0 l0 = Layer0.getInstance(graph); @@ -62,7 +69,7 @@ public class XYDataset implements IDataset { } while(model != null && !graph.isInstanceOf(model, mr.StructuralModel)); // Find the variable realization of the current experiment - String realizationURI = null; + realizationURI = null; Resource realization = graph.syncRequest(new PossibleActiveExperiment(model)); if (realization == null) { Layer0X L0X = Layer0X.getInstance(graph); @@ -76,10 +83,10 @@ public class XYDataset implements IDataset { // Get a variable for the x-axis (if not time) - double[] domainValues = null; - Resource domainAxis = graph.getPossibleObject(datasetResource, jfree.MapToDomainAxis); + domainValues = null; + Resource domainAxis = graph.getPossibleObject(datasetResource, jfree.Dataset_mapToDomainAxis); if(domainAxis != null) { - String rvi = graph.getPossibleRelatedValue(domainAxis, jfree.HasVariableRVI); + String rvi = graph.getPossibleRelatedValue(domainAxis, jfree.variableRVI); if(rvi != null && !rvi.isEmpty()) { try { Variable domainVariable = Variables.getVariable(graph, realizationURI + rvi); @@ -90,58 +97,9 @@ public class XYDataset implements IDataset { } } - series = new ArrayList(); - // Get properties for all series - for(Resource r : graph.syncRequest(new ObjectsWithType(datasetResource, l0.ConsistsOf, jfree.Series))) { - String rvi = graph.getRelatedValue(r, jfree.HasVariableRVI); - - // Get label. If there is no label, rvi is used - String label = graph.getPossibleRelatedValue(r, l0.HasLabel); - if(label == null || label.isEmpty()) label = rvi.substring(1).replace('/', '.'); - try { - // Get visual properties - Integer width = graph.getPossibleRelatedValue(r, jfree.HasLineWidth, Bindings.INTEGER); - Resource c = graph.getPossibleObject(r, jfree.HasColor); - Color color = c == null ? null : G2DUtils.getColor(graph, c); - - // Get a variable for the series - Variable v = Variables.getVariable(graph, realizationURI + rvi); - - if(width == null) width = 1; - // Get values - double[] va = v.getPossiblePropertyValue(graph, SysdynVariableProperties.VALUES , Bindings.DOUBLE_ARRAY); - // Get domain axis values (time OR other variable) - double[] ta; - if(domainValues != null) { - ta = domainValues; - - // If domainAxis is other than time, parameter values size is different. - if(domainValues.length > va.length) { - double value = va[0]; - va = new double[domainValues.length]; - for(int i = 0; i < domainValues.length; i++) - va[i] = value; - } - - // If domainAxis is a parameter, the domainValues array is too short - if(domainValues.length < va.length && domainValues.length == 2 && domainValues[0] == domainValues[1]) { - double value = domainValues[0]; - ta = new double[va.length]; - for(int i = 0; i < va.length; i++) - ta[i] = value; - } - - } else { - ta = v.getPossiblePropertyValue(graph, SysdynVariableProperties.TIMES , Bindings.DOUBLE_ARRAY); - } - - if(ta!=null && va!=null && (va.length == ta.length)) - // Add series if everything OK - series.add(new TempSeries(label, new double[][] {ta, va}, width, color)); - } catch (MissingVariableException e) { - // Do nothing, if variable was not found. Move on to the next series - } - + Resource seriesList = graph.getPossibleObject(resource, jfree.Dataset_seriesList); + if(seriesList != null) { + this.seriesList = ListUtils.toList(graph, seriesList); } } catch (DatabaseException e) { @@ -149,17 +107,121 @@ public class XYDataset implements IDataset { } } + DefaultXYDataset dataset; + @Override public Dataset getDataset() { - dataset = new DefaultXYDataset(); - for(int i = 0; i < series.size(); i++) { - TempSeries s = series.get(i); - dataset.addSeries(s.name, s.values); - getRenderer().setSeriesStroke(i, new BasicStroke((float)s.width)); - if(s.color != null) - getRenderer().setSeriesPaint(i, s.color); - } + if(seriesList == null || seriesList.isEmpty()) + return null; + + if(dataset == null) { + dataset = new DefaultXYDataset(); + SimanticsUI.getSession().asyncRequest(new Read>() { + + @Override + public ArrayList perform(ReadGraph graph) throws DatabaseException { + Layer0 l0 = Layer0.getInstance(graph); + JFreeChartResource jfree = JFreeChartResource.getInstance(graph); + + ArrayList series = new ArrayList(); + // Get properties for all series + if(seriesList != null) { + for(Resource r : seriesList) { + String rvi = graph.getPossibleRelatedValue(r, jfree.variableRVI); + if(rvi == null) + continue; + + // Get label. If there is no label, rvi is used + String label = graph.getPossibleRelatedValue(r, l0.HasLabel); + if(label == null || label.isEmpty()) label = rvi.substring(1).replace('/', '.'); + try { + // Get visual properties + Integer width = graph.getPossibleRelatedValue(r, jfree.Series_lineWidth, Bindings.INTEGER); + Resource c = graph.getPossibleObject(r, jfree.color); + Color color = c == null ? null : G2DUtils.getColor(graph, c); + + // Get a variable for the series + Variable v = Variables.getVariable(graph, realizationURI + rvi); + + if(width == null) width = 1; + // Get values + double[] va = v.getPossiblePropertyValue(graph, SysdynVariableProperties.VALUES , Bindings.DOUBLE_ARRAY); + + +// Object o = v.getPossiblePropertyValue(graph, SysdynVariableProperties.ACTIVE_DATASETS , Bindings.VARIANT); +// System.out.println(o); + + // Get domain axis values (time OR other variable) + double[] ta; + if(domainValues != null) { + ta = domainValues; + + // If domainAxis is other than time, parameter values size is different. + if(domainValues.length > va.length) { + double value = va[0]; + va = new double[domainValues.length]; + for(int i = 0; i < domainValues.length; i++) + va[i] = value; + } + + // If domainAxis is a parameter, the domainValues array is too short + if(domainValues.length < va.length && domainValues.length == 2 && domainValues[0] == domainValues[1]) { + double value = domainValues[0]; + ta = new double[va.length]; + for(int i = 0; i < va.length; i++) + ta[i] = value; + } + + } else { + ta = v.getPossiblePropertyValue(graph, SysdynVariableProperties.TIMES , Bindings.DOUBLE_ARRAY); + } + + if(ta!=null && va!=null && (va.length == ta.length)) { + // Add series if everything OK + series.add(new TempSeries(label, new double[][] {ta, va}, width, color)); + } + } catch (MissingVariableException e) { + // Do nothing, if variable was not found. Move on to the next series + } + } + } + return series; + } + + }, new Listener>() { + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public void execute(ArrayList series) { + // Remove all series + for(int i = dataset.getSeriesCount() - 1; i >= 0; i-- ) { + dataset.removeSeries(dataset.getSeriesKey(i)); + } + + // Add found series + for(int i = 0; i < series.size(); i++) { + TempSeries s = series.get(i); + dataset.removeSeries(s.name); + dataset.addSeries(s.name, s.values); + getRenderer().setSeriesStroke(i, new BasicStroke((float)s.width)); + if(s.color != null) + getRenderer().setSeriesPaint(i, s.color); + } + } + + @Override + public void exception(Throwable t) { + t.printStackTrace(); + } + + }); + + } return dataset; } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYPlot.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYPlot.java index 3e5cc129..59978204 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYPlot.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/XYPlot.java @@ -21,6 +21,7 @@ import org.jfree.data.xy.XYDataset; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.layer0.Layer0; import org.simantics.sysdyn.JFreeChartResource; @@ -50,20 +51,23 @@ public class XYPlot implements IPlot { HashMap axisMap = new HashMap(); ranges = new ArrayList(); - + // Get all range axis - for(Resource axisResource : graph.syncRequest(new ObjectsWithType(plotResource, jfree.HasRangeAxis, jfree.Axis))) { - IAxis axis = graph.adapt(axisResource, IAxis.class); - if(axis.getAxis() instanceof ValueAxis) { - ranges.add(axis); - axisMap.put(axisResource, axis); + Resource rangeList = graph.getPossibleObject(plotResource, jfree.Plot_rangeAxisList); + if(rangeList != null) { + for(Resource axisResource : ListUtils.toList(graph, rangeList)) { + IAxis axis = graph.adapt(axisResource, IAxis.class); + if(axis.getAxis() instanceof ValueAxis) { + ranges.add(axis); + axisMap.put(axisResource, axis); + } } } // Get all domain axis // There usually is only one domain axis, but this supports also multiple domain axis domains = new ArrayList(); - for(Resource axisResource : graph.syncRequest(new ObjectsWithType(plotResource, jfree.HasDomainAxis, jfree.Axis))) { + for(Resource axisResource : graph.syncRequest(new ObjectsWithType(plotResource, jfree.Plot_domainAxis, jfree.Axis))) { IAxis axis = graph.adapt(axisResource, IAxis.class); if(axis.getAxis() instanceof ValueAxis) { domains.add(axis); @@ -77,16 +81,16 @@ public class XYPlot implements IPlot { domainMappings = new HashMap(); for(Resource datasetResource : graph.syncRequest(new ObjectsWithType(plotResource, l0.ConsistsOf, jfree.Dataset))) { IDataset dataset = graph.adapt(datasetResource, IDataset.class); - if(dataset.getDataset() instanceof XYDataset) { + if(dataset.getDataset() != null && dataset.getDataset() instanceof XYDataset) { datasets.add(dataset); - Resource axisResource = graph.getPossibleObject(datasetResource, jfree.MapToRangeAxis); + Resource axisResource = graph.getPossibleObject(datasetResource, jfree.Dataset_mapToRangeAxis); IAxis axis; if(axisMap.containsKey(axisResource)) { axis = axisMap.get(axisResource); rangeMappings.put(dataset, axis); } - axisResource = graph.getPossibleObject(datasetResource, jfree.MapToDomainAxis); + axisResource = graph.getPossibleObject(datasetResource, jfree.Dataset_mapToDomainAxis); if(axisMap.containsKey(axisResource)) { axis = axisMap.get(axisResource); domainMappings.put(dataset, axis); @@ -95,7 +99,7 @@ public class XYPlot implements IPlot { } // Visual properties - visibleGrid = graph.getPossibleRelatedValue(plotResource, jfree.HasVisibleGrid); + visibleGrid = graph.getPossibleRelatedValue(plotResource, jfree.Plot_visibleGrid); } catch (DatabaseException e) { e.printStackTrace(); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/AxisChildRule.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/AxisChildRule.java index 2ab462e1..5b8c44a0 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/AxisChildRule.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/AxisChildRule.java @@ -18,6 +18,7 @@ import org.simantics.browsing.ui.model.children.ChildRule; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.layer0.Layer0; import org.simantics.sysdyn.JFreeChartResource; @@ -47,11 +48,15 @@ public class AxisChildRule implements ChildRule { * 2. plot may have multiple axis */ for(Resource plot : graph.syncRequest(new ObjectsWithType((Resource)parent, l0.ConsistsOf, jfree.Plot))) { - for(Resource axis : graph.syncRequest(new ObjectsWithType(plot, jfree.HasRangeAxis, jfree.Axis))) { - result.add(axis); - } + Resource rangeAxisList = graph.getPossibleObject(plot, jfree.Plot_rangeAxisList); + if(rangeAxisList != null) + for(Resource axis : ListUtils.toList(graph, rangeAxisList)) { + result.add(axis); + } } return result; + + } @Override diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/DropAction.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/DropAction.java index 85f77a5a..7cb33bf4 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/DropAction.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/DropAction.java @@ -11,11 +11,14 @@ *******************************************************************************/ package org.simantics.sysdyn.ui.trend.chart.graphexplorer; +import java.util.Collections; + import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.PossibleObjectWithType; import org.simantics.db.common.request.WriteRequest; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.adapter.DropActionFactory; import org.simantics.layer0.Layer0; @@ -59,6 +62,7 @@ public class DropAction implements DropActionFactory { @Override public void perform(WriteGraph graph) throws DatabaseException { + if(t == null || s == null) return; JFreeChartResource jfree = JFreeChartResource.getInstance(graph); Layer0 l0 = Layer0.getInstance(graph); Resource target = t; @@ -73,18 +77,35 @@ public class DropAction implements DropActionFactory { // Dropped a series over an axis -> get target dataset if(graph.isInstanceOf(target, jfree.Axis) && graph.isInstanceOf(source, jfree.Series)) { - Resource dataset = graph.syncRequest(new PossibleObjectWithType(target, jfree.MapToRangeAxis_Inverse, jfree.Dataset)); + Resource dataset = graph.syncRequest(new PossibleObjectWithType(target, jfree.Dataset_mapToRangeAxis_Inverse, jfree.Dataset)); if(dataset != null) target = dataset; } // Move series to a dataset if(graph.isInstanceOf(target, jfree.Dataset) && graph.isInstanceOf(source, jfree.Series)) { + // Remove from old dataset + Resource sourceDataset = graph.getPossibleObject(source, l0.PartOf); + if(sourceDataset != null) { + Resource sourceSeriesList = graph.getPossibleObject(sourceDataset, jfree.Dataset_seriesList); + if(sourceSeriesList != null) + ListUtils.removeElement(graph, sourceSeriesList, source); + } graph.deny(source, l0.PartOf); + + // Add to new dataset + Resource targetSeriesList = graph.getPossibleObject(target, jfree.Dataset_seriesList); + if(targetSeriesList == null) { + targetSeriesList = ListUtils.create(graph, Collections.emptyList()); + graph.claim(target, jfree.Dataset_seriesList, targetSeriesList); + } + + ListUtils.insertBack(graph, targetSeriesList, Collections.singleton(source)); + graph.claim(target, l0.ConsistsOf, source); } - - // TODO: Change the order of axis and series. Implement when they are in ordered sets + + // TODO: Change the order of axis and series } }); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/VariableChildRule.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/VariableChildRule.java index 871710e4..f2cf79f7 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/VariableChildRule.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/graphexplorer/VariableChildRule.java @@ -18,6 +18,7 @@ import org.simantics.browsing.ui.model.children.ChildRule; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.layer0.Layer0; import org.simantics.sysdyn.JFreeChartResource; @@ -49,15 +50,17 @@ public class VariableChildRule implements ChildRule { * 3. Dataset is mapped to a single range axis * 3. Dataset may have multiple seires */ - Resource plot = graph.getPossibleObject(axis, jfree.HasRangeAxis_Inverse); + Resource plot = graph.getPossibleObject(axis, jfree.Plot_rangeAxis_Inverse); if(plot == null) return result; - + for(Resource dataset : graph.syncRequest(new ObjectsWithType(plot, l0.ConsistsOf, jfree.Dataset))) { - if(graph.hasStatement(dataset, jfree.MapToRangeAxis, axis)) { - for(Resource series : graph.syncRequest(new ObjectsWithType(dataset, l0.ConsistsOf, jfree.Series))) { - result.add(series); - } + if(graph.hasStatement(dataset, jfree.Dataset_mapToRangeAxis, axis)) { + Resource seriesList = graph.getPossibleObject(dataset, jfree.Dataset_seriesList); + if(seriesList != null) + for(Resource series : ListUtils.toList(graph, seriesList)) { + result.add(series); + } } } return result; diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisHidePropertyComposite.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisHidePropertyComposite.java index c0105d5a..f8334c71 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisHidePropertyComposite.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisHidePropertyComposite.java @@ -41,26 +41,26 @@ public class AxisHidePropertyComposite extends Composite { Button label = new Button(hideGroup, support, SWT.CHECK); label.setText("Label"); - label.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.HasVisibleLabel, true)); - label.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.HasVisibleLabel)); + label.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.Axis_visibleLabel, true)); + label.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Axis_visibleLabel)); GridDataFactory.fillDefaults().applyTo(label.getWidget()); Button tmarks = new Button(hideGroup, support, SWT.CHECK); tmarks.setText("Tick marks"); - tmarks.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.HasVisibleTickMarks, true)); - tmarks.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.HasVisibleTickMarks)); + tmarks.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.Axis_visibleTickMarks, true)); + tmarks.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Axis_visibleTickMarks)); GridDataFactory.fillDefaults().applyTo(tmarks.getWidget()); Button axisLine = new Button(hideGroup, support, SWT.CHECK); axisLine.setText("Axis line"); - axisLine.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.HasVisibleAxisLine, true)); - axisLine.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.HasVisibleAxisLine)); + axisLine.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.Axis_visibleAxisLine, true)); + axisLine.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Axis_visibleAxisLine)); GridDataFactory.fillDefaults().applyTo(axisLine.getWidget()); Button tlabels = new Button(hideGroup, support, SWT.CHECK); tlabels.setText("Tick labels"); - tlabels.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.HasVisibleTickLabels, true)); - tlabels.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.HasVisibleTickLabels)); + tlabels.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.Axis_visibleTickLabels, true)); + tlabels.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Axis_visibleTickLabels)); GridDataFactory.fillDefaults().applyTo(tlabels.getWidget()); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisPropertyComposite.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisPropertyComposite.java index fe4c0aea..882dd10b 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisPropertyComposite.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/AxisPropertyComposite.java @@ -65,16 +65,16 @@ public class AxisPropertyComposite extends Composite { GridLayoutFactory.fillDefaults().numColumns(3).applyTo(minmax); min = new TrackedText(minmax, support, SWT.BORDER); min.setColorProvider(new JFreeChartPropertyColorProvider(min.getResourceManager())); - min.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.HasMin)); - min.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.HasMin)); + min.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.Axis_min)); + min.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.Axis_min)); min.setInputValidator(new DoubleValidator()); label = new Label(minmax, SWT.NONE); label.setText("Max:"); max = new TrackedText(minmax, support, SWT.BORDER); max.setColorProvider(new JFreeChartPropertyColorProvider(max.getResourceManager())); - max.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.HasMax)); - max.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.HasMax)); + max.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.Axis_max)); + max.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.Axis_max)); max.setInputValidator(new DoubleValidator()); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ChartAxisAndVariablesTab.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ChartAxisAndVariablesTab.java index 1e818b6a..7c33faaa 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ChartAxisAndVariablesTab.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ChartAxisAndVariablesTab.java @@ -11,8 +11,6 @@ *******************************************************************************/ package org.simantics.sysdyn.ui.trend.chart.properties; -import java.util.UUID; - import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.viewers.ISelectionProvider; @@ -36,16 +34,16 @@ import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.PossibleObjectWithType; -import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.common.utils.ListUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.util.RemoverUtil; import org.simantics.db.management.ISessionContext; import org.simantics.db.request.Read; import org.simantics.layer0.Layer0; -import org.simantics.layer0.utils.direct.GraphUtils; import org.simantics.sysdyn.JFreeChartResource; import org.simantics.sysdyn.SysdynResource; import org.simantics.sysdyn.ui.properties.LabelPropertyTabContributor; +import org.simantics.sysdyn.ui.trend.chart.ChartUtils; import org.simantics.ui.SimanticsUI; import org.simantics.ui.utils.AdaptionUtils; import org.simantics.utils.datastructures.ArrayMap; @@ -183,18 +181,15 @@ public class ChartAxisAndVariablesTab extends LabelPropertyTabContributor { public void apply(WriteGraph graph, Resource chart) throws DatabaseException { JFreeChartResource jfree = JFreeChartResource.getInstance(graph); Layer0 l0 = Layer0.getInstance(graph); - Resource plot = graph.syncRequest(new PossibleObjectWithType(chart, l0.ConsistsOf, jfree.Plot)); - if(plot != null) { - GraphUtils.create2(graph, jfree.NumberAxis, - l0.HasName, "NumberAxis" + UUID.randomUUID().toString(), - l0.HasLabel, NameUtils.findFreshLabel(graph, "range", plot), - jfree.HasRangeAxis_Inverse, plot, - l0.PartOf, plot); + Resource rangeAxis = ChartUtils.createNumberRangeAxis(graph, plot); + if(rangeAxis != null) { + Resource domainAxis = graph.getPossibleObject(plot, jfree.Plot_domainAxis); + ChartUtils.createXYDataset(graph, plot, domainAxis, rangeAxis); + } } } - } @@ -221,30 +216,21 @@ public class ChartAxisAndVariablesTab extends LabelPropertyTabContributor { dataset = graph.getPossibleObject(input, l0.PartOf); } else { // Selected resource is axis. Find the dataset it is mapped to or create dataset if not created already - dataset = graph.getPossibleObject(input, jfree.MapToRangeAxis_Inverse); + dataset = graph.getPossibleObject(input, jfree.Dataset_mapToRangeAxis_Inverse); if(dataset == null) { Resource plot = graph.getPossibleObject(input, l0.PartOf); if(plot == null) return; - Resource domainAxis = graph.getPossibleObject(plot, jfree.HasDomainAxis); + Resource domainAxis = graph.getPossibleObject(plot, jfree.Plot_domainAxis); if(domainAxis == null) return; - - dataset = GraphUtils.create2(graph, jfree.XYDataset, - l0.HasName, "XYDataset" + UUID.randomUUID().toString(), - jfree.MapToDomainAxis, domainAxis, - jfree.MapToRangeAxis, input, - l0.PartOf, plot); + ChartUtils.createXYDataset(graph, plot, domainAxis, input); } } - if(dataset != null) - GraphUtils.create2(graph, jfree.Series, - l0.HasName, "Series" + UUID.randomUUID().toString(), - l0.HasLabel, "", - jfree.HasVariableRVI, "", - l0.PartOf, dataset); - + if(dataset != null) { + // Create series with no rvi + ChartUtils.createSeries(graph, dataset, null); + } } - } @@ -264,23 +250,34 @@ public class ChartAxisAndVariablesTab extends LabelPropertyTabContributor { */ @Override public void apply(WriteGraph graph, Resource input) throws DatabaseException { + if(input == null) + return; + JFreeChartResource jfree = JFreeChartResource.getInstance(graph); + Layer0 l0 = Layer0.getInstance(graph); + Resource list = null; if(graph.isInstanceOf(input, jfree.Series)) { - // Remove series - RemoverUtil.remove(graph, input); + // Remove series from dataset and seriesList + Resource dataset = graph.getPossibleObject(input, l0.PartOf); + if(dataset != null) + list = graph.getPossibleObject(dataset, jfree.Dataset_seriesList); } else { // Remove associated dataset - Resource dataset = graph.getPossibleObject(input, jfree.MapToRangeAxis_Inverse); + Resource dataset = graph.getPossibleObject(input, jfree.Dataset_mapToRangeAxis_Inverse); if(dataset != null) { - graph.deny(dataset, jfree.MapToDomainAxis); - graph.deny(dataset, jfree.MapToRangeAxis); + graph.deny(dataset, jfree.Dataset_mapToDomainAxis); + graph.deny(dataset, jfree.Dataset_mapToRangeAxis); RemoverUtil.remove(graph, dataset); } - // Remove axis - RemoverUtil.remove(graph, input); + // Remove axis from plot and rangeAxisList + Resource plot = graph.getPossibleObject(input, l0.PartOf); + if(plot != null) + list = graph.getPossibleObject(plot, jfree.Plot_rangeAxisList); } - + if(list != null) + ListUtils.removeElement(graph, list, input); + RemoverUtil.remove(graph, input); } } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ColorPicker.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ColorPicker.java index de914e37..33b1fe95 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ColorPicker.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/ColorPicker.java @@ -111,7 +111,7 @@ public class ColorPicker extends Composite implements Widget { */ protected Resource getColorRelation(ReadGraph graph) throws DatabaseException { JFreeChartResource jfree = JFreeChartResource.getInstance(graph); - return jfree.HasColor; + return jfree.color; } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/GeneralChartPropertiesTab.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/GeneralChartPropertiesTab.java index 095e0b85..25d486c3 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/GeneralChartPropertiesTab.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/GeneralChartPropertiesTab.java @@ -127,16 +127,16 @@ public class GeneralChartPropertiesTab extends LabelPropertyTabContributor imple hgrid = new Button(hideGroup, support, SWT.CHECK); hgrid.setText("Grid"); - hgrid.setSelectionFactory(new BooleanPropertyFactory(JFreeChartResource.URIs.Plot, JFreeChartResource.URIs.HasVisibleGrid, true)); - hgrid.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Plot, JFreeChartResource.URIs.HasVisibleGrid)); + hgrid.setSelectionFactory(new BooleanPropertyFactory(JFreeChartResource.URIs.Plot, JFreeChartResource.URIs.Plot_visibleGrid, true)); + hgrid.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.Plot, JFreeChartResource.URIs.Plot_visibleGrid)); htitle = new Button(hideGroup, support, SWT.CHECK); htitle.setText("Title"); - htitle.setSelectionFactory(new BooleanPropertyFactory(JFreeChartResource.URIs.TextTitle, JFreeChartResource.URIs.IsVisible, true)); - htitle.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.TextTitle, JFreeChartResource.URIs.IsVisible)); + htitle.setSelectionFactory(new BooleanPropertyFactory(JFreeChartResource.URIs.TextTitle, JFreeChartResource.URIs.visible, true)); + htitle.addSelectionListener(new BooleanSelectionListener(context, JFreeChartResource.URIs.TextTitle, JFreeChartResource.URIs.visible)); hlegend = new Button(hideGroup, support, SWT.CHECK); hlegend.setText("Legend"); - hlegend.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.HasVisibleLegend, true)); - hlegend.addSelectionListener(new BooleanSelectionListener(context, null, JFreeChartResource.URIs.HasVisibleLegend)); + hlegend.setSelectionFactory(new BooleanPropertyFactory(null, JFreeChartResource.URIs.Chart_visibleLegend, true)); + hlegend.addSelectionListener(new BooleanSelectionListener(context, null, JFreeChartResource.URIs.Chart_visibleLegend)); // X-Axis properties @@ -180,16 +180,16 @@ public class GeneralChartPropertiesTab extends LabelPropertyTabContributor imple GridLayoutFactory.fillDefaults().numColumns(3).applyTo(minmax); xmin = new TrackedText(minmax, domainAxisSupport, SWT.BORDER); xmin.setColorProvider(new JFreeChartPropertyColorProvider(xmin.getResourceManager())); - xmin.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.HasMin)); - xmin.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.HasMin)); + xmin.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.Axis_min)); + xmin.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.Axis_min)); xmin.setInputValidator(new DoubleValidator()); label = new Label(minmax, SWT.NONE); label.setText("Max:"); xmax = new TrackedText(minmax, domainAxisSupport, SWT.BORDER); xmax.setColorProvider(new JFreeChartPropertyColorProvider(xmax.getResourceManager())); - xmax.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.HasMax)); - xmax.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.HasMax)); + xmax.setTextFactory(new DoublePropertyFactory(JFreeChartResource.URIs.Axis_max)); + xmax.addModifyListener(new DoublePropertyModifier(context, JFreeChartResource.URIs.Axis_max)); xmax.setInputValidator(new DoubleValidator()); @@ -212,7 +212,7 @@ public class GeneralChartPropertiesTab extends LabelPropertyTabContributor imple JFreeChartResource jfree = JFreeChartResource.getInstance(graph); Resource plot = graph.syncRequest(new PossibleObjectWithType(chart, l0.ConsistsOf, jfree.Plot)); if(plot == null) return; - Resource domainAxis = graph.getPossibleObject(plot, jfree.HasDomainAxis); + Resource domainAxis = graph.getPossibleObject(plot, jfree.Plot_domainAxis); if(domainAxis == null) return; domainAxisSupport.fireInput(context, new StructuredSelection(domainAxis)); } @@ -253,7 +253,7 @@ public class GeneralChartPropertiesTab extends LabelPropertyTabContributor imple Resource title = graph.syncRequest(new PossibleObjectWithType(chart, l0.ConsistsOf, jfree.TextTitle)); if(title == null) { title = GraphUtils.create2(graph, jfree.TextTitle, - jfree.HasPosition, jfree.Top); + jfree.Title_position, jfree.Top); } graph.claimLiteral(title, l0.HasLabel, text); } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIFactory.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIFactory.java index 463b2c42..d029827b 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIFactory.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIFactory.java @@ -26,7 +26,7 @@ public class RVIFactory extends ReadFactoryImpl { @Override public String perform(ReadGraph graph, Resource input) throws DatabaseException { - String value = graph.getPossibleRelatedValue(input, JFreeChartResource.getInstance(graph).HasVariableRVI); + String value = graph.getPossibleRelatedValue(input, JFreeChartResource.getInstance(graph).variableRVI); return value != null ? value = value.substring(1).replace('/', '.') : ""; } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIModifier.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIModifier.java index 7061001a..b17a4935 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIModifier.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/RVIModifier.java @@ -101,7 +101,7 @@ public class RVIModifier extends TextModifyListenerImpl { public void applyText(WriteGraph graph, Resource issue, String text) throws DatabaseException { if(active) { text = "/" + text.replace('.', '/'); - graph.claimLiteral(issue, JFreeChartResource.getInstance(graph).HasVariableRVI, text, Bindings.STRING); + graph.claimLiteral(issue, JFreeChartResource.getInstance(graph).variableRVI, text, Bindings.STRING); } } diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/SeriesPropertyComposite.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/SeriesPropertyComposite.java index 1276908c..b1f94d34 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/SeriesPropertyComposite.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/SeriesPropertyComposite.java @@ -128,7 +128,7 @@ public class SeriesPropertyComposite extends Composite { try { // usually reliable, since the spinner does all the checks Integer value = Integer.parseInt(textValue); - graph.claimLiteral(series, jfree.HasLineWidth, value, Bindings.INTEGER); + graph.claimLiteral(series, jfree.Series_lineWidth, value, Bindings.INTEGER); } catch (NumberFormatException e) { e.printStackTrace(); } @@ -158,7 +158,7 @@ public class SeriesPropertyComposite extends Composite { @Override public Integer perform(ReadGraph graph, Resource axis) throws DatabaseException { JFreeChartResource jfree = JFreeChartResource.getInstance(graph); - Integer width = graph.getPossibleRelatedValue(axis, jfree.HasLineWidth); + Integer width = graph.getPossibleRelatedValue(axis, jfree.Series_lineWidth); if(width == null) // Default width == 1 width = 1; diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/VariableProposalProvider.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/VariableProposalProvider.java index 45c88218..f4382101 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/VariableProposalProvider.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/trend/chart/properties/VariableProposalProvider.java @@ -78,7 +78,7 @@ public class VariableProposalProvider extends SimpleContentProposalProvider impl // Find the model of this resource Resource model = resource; - while(!graph.isInstanceOf(model, sr.SysdynModel) && model != null) + while(model != null && !graph.isInstanceOf(model, sr.SysdynModel)) model = graph.getPossibleObject(model, l0.PartOf); if(model == null) @@ -138,6 +138,11 @@ public class VariableProposalProvider extends SimpleContentProposalProvider impl name = path + NameUtils.getSafeName(graph, resource); items.add(name); } + + for(Resource resource : graph.syncRequest(new ObjectsWithType(configuration, l0.ConsistsOf, sr.Input))) { + name = path + NameUtils.getSafeName(graph, resource); + items.add(name); + } for(Resource module : graph.syncRequest(new ObjectsWithType(configuration, l0.ConsistsOf, sr.Module))) { Resource instanceOf = graph.getPossibleObject(module, l0.InstanceOf); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/values/ValueView.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/values/ValueView.java index 5ea14eea..5cfca79e 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/values/ValueView.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/values/ValueView.java @@ -32,6 +32,8 @@ import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.part.ViewPart; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; import org.simantics.sysdyn.manager.SysdynDataSet; import org.simantics.sysdyn.ui.viewUtils.SysdynDatasetSelectionListener; @@ -83,6 +85,12 @@ public class ValueView extends ViewPart { } }); } + + @Override + protected void selectionChanged(ReadGraph graph, Resource resource) { + // Do nothing + } + }; getSite().getWorkbenchWindow().getSelectionService().addPostSelectionListener(selectionListener); diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/viewUtils/SysdynDatasetSelectionListener.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/viewUtils/SysdynDatasetSelectionListener.java index 23aee37e..0359d533 100644 --- a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/viewUtils/SysdynDatasetSelectionListener.java +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/viewUtils/SysdynDatasetSelectionListener.java @@ -39,6 +39,7 @@ import org.simantics.diagram.stubs.DiagramResource; import org.simantics.layer0.Layer0; import org.simantics.modeling.ModelingUtils; import org.simantics.simulation.ontology.SimulationResource; +import org.simantics.sysdyn.JFreeChartResource; import org.simantics.sysdyn.SysdynResource; import org.simantics.sysdyn.manager.SysdynDataSet; import org.simantics.sysdyn.manager.SysdynModel; @@ -48,228 +49,360 @@ import org.simantics.sysdyn.ui.trend.PinTrend; import org.simantics.ui.SimanticsUI; import org.simantics.utils.ui.ISelectionUtils; +/** + * Selection listener for listening datasets of the selected variables. Selections can come + * from both diagram and model browser. Listener provides the active datasets + * of the selected variable(s) or the JFreeChart of a selected chart definition. + * + * @author Teemu Lempinen + * + */ public abstract class SysdynDatasetSelectionListener implements ISelectionListener { - - protected abstract void selectionChanged(Collection activeDatasets); - - HashMap resultListeners = new HashMap(); - - @Override - public void selectionChanged(IWorkbenchPart part, final ISelection selection) { - if(selection.isEmpty() || Boolean.TRUE.equals(PinTrend.getState())) - return; - if(selection instanceof IStructuredSelection) { - if(!resultListeners.isEmpty()) { - for(SysdynModel model : resultListeners.keySet()) - model.removeResultListener(resultListeners.get(model)); - resultListeners.clear(); - } - - Session session = SimanticsUI.peekSession(); - if (session == null) - return; - - session.asyncRequest(new ReadRequest() { - @Override - public void run(ReadGraph graph) throws DatabaseException { - - Collection vars = ISelectionUtils.filterSetSelection(selection, Variable.class); - - if(vars.isEmpty()) { - Set ress = ISelectionUtils.filterSetSelection(selection, Resource.class); - List runtimes = ISelectionUtils.getPossibleKeys(selection, SelectionHints.KEY_VARIABLE_RESOURCE, Resource.class); - if(runtimes.isEmpty()) - return; - Resource runtime = runtimes.get(0); - - for(Resource resource : ress) { - Variable variable = getVariable(graph, resource, runtime); - if(variable != null) - vars.add(variable); - } - } - - updateDatasets(graph, vars); - addResultListeners(graph, vars); - } - }); - } - } - - - private void addResultListeners(ReadGraph graph, final Collection variables) throws DatabaseException { - if(variables.size() < 1) return; - - HashSet models = new HashSet(); - for(Variable variable : variables) { - Resource model = Variables.getModel(graph, variable); - if(model != null) { - models.add(getSysdynModel(graph, model)); - } - } - - Runnable listener = new Runnable() { - @Override - public void run() { - Session session = SimanticsUI.peekSession(); - if (session == null) - return; - - session.asyncRequest(new ReadRequest() { - - @Override - public void run(ReadGraph graph) throws DatabaseException { - updateDatasets(graph, variables); - } - }); - } - }; - - for(SysdynModel model : models) { - model.addResultListener(listener); - resultListeners.put(model, listener); - } - } - - private void updateDatasets(ReadGraph graph, Collection variables) throws DatabaseException { - - ArrayList datasets = new ArrayList(); - for(Variable variable : variables) { - Collection activeDataSets = loadAllActive(graph, variable); - if(activeDataSets != null && !activeDataSets.isEmpty()) - datasets.addAll(activeDataSets); - } - - for(SysdynDataSet dataset : datasets) - System.out.println(dataset.name); - selectionChanged(datasets); - - } - - - protected Collection loadAllActive(ReadGraph g, Variable variable) throws DatabaseException { - ArrayList dataSets = new ArrayList(); - HashMap rvis = new LinkedHashMap(); - - String rvi = Variables.getRVI(g, variable).replace("/", "."); - if(rvi.length() > 1) - rvi = rvi.substring(1); - - Resource r = variable.getPropertyValue(g, Variables.REPRESENTS); - - SysdynResource sr = SysdynResource.getInstance(g); - Resource arrayIndexes = g.getPossibleObject(r, sr.HasArrayIndexes); - if(arrayIndexes == null) { - rvis.put(rvi, rvi); - } else { - List arrayIndexList= OrderedSetUtils.toList(g, arrayIndexes); - resolveActiveArrayIndexes(g, variable, arrayIndexList); - - if(arrayIndexList.size() > 0) - traverseIndexes(g, rvi, rvis, arrayIndexList); - else - rvis.put(rvi, rvi); - } - - Resource modelResource = Variables.getModel(g, variable); - SysdynModel model = getSysdynModel(g, modelResource); - - if(model == null) - return dataSets; - - Collection activeResults = model.getActiveResults(g); - for(SysdynResult sysdynResult : activeResults) { - for(String currvi : rvis.keySet()) { - SysdynDataSet sds = sysdynResult.getDataSet(currvi); - if(sds != null) { - sds.name = rvis.get(currvi); - dataSets.add(sds); - } - } - } - return dataSets; - - } - - private static List resolveActiveArrayIndexes(ReadGraph graph, Variable variable, List enumerations) { - try { - Layer0 l0 = Layer0.getInstance(graph); - SysdynResource sr = SysdynResource.getInstance(graph); - String uri = variable.getURI(graph); - uri = uri.substring(0, uri.lastIndexOf("/")); - Variable v = Variables.getPossibleVariable(graph, uri); - if(v != null) { - Variable rp = (Variable)v.getProperty(graph, Variables.REPRESENTS); - Resource module = rp.getValue(graph); - if(module != null && graph.isInheritedFrom(graph.getSingleObject(module, l0.InstanceOf), sr.Module)) { - - boolean somethingIsReplaced = false; - for(Resource redeclaration : graph.syncRequest(new ObjectsWithType(module, sr.HasRedeclaration, sr.Redeclaration))) { - Resource replaced = graph.getSingleObject(redeclaration, sr.ReplacedEnumeration); - if(enumerations.contains(replaced)) { - enumerations.add(enumerations.indexOf(replaced), graph.getSingleObject(redeclaration, sr.ReplacingEnumeration)); - enumerations.remove(replaced); - somethingIsReplaced = true; - } - } - - if(somethingIsReplaced) { - resolveActiveArrayIndexes(graph, v, enumerations); - } - } - } - } catch (DatabaseException e) { - e.printStackTrace(); - - } - return enumerations; - } - - private void traverseIndexes(ReadGraph g, String rvi, HashMap rvis, List arrayIndexes) throws DatabaseException { - traverseIndexes(g, rvi, rvis, arrayIndexes, arrayIndexes.get(0), "", ""); - } - - private void traverseIndexes(ReadGraph g, String rvi, HashMap rvis, List arrayIndexes, Resource currentEnumeration, String indexesSoFar, String indexNamesSoFar) throws DatabaseException { - SysdynResource sr = SysdynResource.getInstance(g); - Resource enumerationIndexes = g.getPossibleObject(currentEnumeration, sr.HasEnumerationIndexes); - if(enumerationIndexes == null) - return; - List indexes = OrderedSetUtils.toList(g, enumerationIndexes); - for(int i = 0; i < indexes.size(); i++) { - Boolean b = g.getPossibleRelatedValue(indexes.get(i), sr.ShowEnumerationIndexInCharts, Bindings.BOOLEAN); - if(Boolean.TRUE.equals(b)) { - int arrayIndex = arrayIndexes.indexOf(currentEnumeration); - String name = g.getRelatedValue(indexes.get(i), Layer0.getInstance(g).HasName); - if(arrayIndex < arrayIndexes.size() - 1) - traverseIndexes(g, rvi, rvis, arrayIndexes, arrayIndexes.get(arrayIndex + 1), - indexesSoFar + (i + 1) +",", indexNamesSoFar + (name) +","); - else { - rvis.put( - rvi + "[" + indexesSoFar + (i + 1) + "]", - rvi + "[" + indexNamesSoFar + (name) + "]"); - } - } - } - } - - private Variable getVariable(ReadGraph g, Resource element, Resource runtime) throws DatabaseException { - SysdynResource sr = SysdynResource.getInstance(g); - DiagramResource dr = DiagramResource.getInstance(g); - if(runtime == null) return null; - Resource resource = ModelingUtils.getPossibleElementCorrespondendence(g, element); - if(resource == null || !g.isInstanceOf(resource, sr.Variable)) return null; - String variableURI = g.getPossibleRelatedValue(runtime, dr.RuntimeDiagram_HasVariable); - try { - Variable compositeVariable = Variables.getVariable(g, variableURI); - return compositeVariable.browsePossible(g, resource); - } catch (MissingVariableException e) { - return null; - } - } - - private SysdynModel getSysdynModel(ReadGraph g, Resource model) throws DatabaseException { - Resource configuration = g.getPossibleObject(model, SimulationResource.getInstance(g).HasConfiguration); - return SysdynModelManager.getInstance(g.getSession()).getModel(g, configuration); - } + + /** + * Triggered after a variable is selected from diagram or model browser + * Subclasses implement. + * @param activeDatasets Active dataset(s) of the selected variable(s) + */ + protected abstract void selectionChanged(Collection activeDatasets); + + /** + * Triggered after a chart definition is selected from model browser + * Subclasses implement + * + * @param graph ReadGraph + * @param resource Chart definition resource + */ + protected abstract void selectionChanged(ReadGraph graph, Resource resource); + + HashMap resultListeners = new HashMap(); + + @Override + public void selectionChanged(IWorkbenchPart part, final ISelection selection) { + // Empty selection or pinned trend -> Do nothing + if(selection.isEmpty() || Boolean.TRUE.equals(PinTrend.getState())) + return; + + if(selection instanceof IStructuredSelection) { + // Remove all previously added result listeners from model + if(!resultListeners.isEmpty()) { + for(SysdynModel model : resultListeners.keySet()) + model.removeResultListener(resultListeners.get(model)); + resultListeners.clear(); + } + + Session session = SimanticsUI.peekSession(); + if (session == null) + return; + + session.asyncRequest(new ReadRequest() { + @Override + public void run(ReadGraph graph) throws DatabaseException { + + // Model browser provides variables + Collection vars = ISelectionUtils.filterSetSelection(selection, Variable.class); + + if(vars.isEmpty()) { + // Selection did not contain variables + Set ress = ISelectionUtils.filterSetSelection(selection, Resource.class); + List runtimes = ISelectionUtils.getPossibleKeys(selection, SelectionHints.KEY_VARIABLE_RESOURCE, Resource.class); + if(!runtimes.isEmpty()) { + // Selection is most probably in a diagram + Resource runtime = runtimes.get(0); + + // Get variables for selected resources + for(Resource resource : ress) { + Variable variable = getVariable(graph, resource, runtime); + if(variable != null) + vars.add(variable); + } + } else { + // Selection is a jfreechart + if(ress.size() == 1) { + Resource r = ress.iterator().next(); + if(graph.isInstanceOf(r, JFreeChartResource.getInstance(graph).Chart)) { + selectionChanged(graph, r); + return; + } + } + } + } + + // Update datasets and add result listeners to models + updateDatasets(graph, vars); + addResultListeners(graph, vars); + } + }); + } + } + + + /** + * Adds result listeners to models. In model browser it is possible to select + * variables from different models. This method adds listeners to all of those. + * + * @param graph ReadGraph graph + * @param variables Selected variables + * @throws DatabaseException + */ + private void addResultListeners(ReadGraph graph, final Collection variables) throws DatabaseException { + if(variables.size() < 1) return; + + // Get models + HashSet models = new HashSet(); + for(Variable variable : variables) { + Resource model = Variables.getModel(graph, variable); + if(model != null) { + models.add(getSysdynModel(graph, model)); + } + } + + // Create a result listener + Runnable listener = new Runnable() { + @Override + public void run() { + Session session = SimanticsUI.peekSession(); + if (session == null) + return; + + session.asyncRequest(new ReadRequest() { + + @Override + public void run(ReadGraph graph) throws DatabaseException { + updateDatasets(graph, variables); + } + }); + } + }; + + // Add the result listener to all models and to resultListeners list + for(SysdynModel model : models) { + model.addResultListener(listener); + resultListeners.put(model, listener); + } + } + + /** + * Updates datasets for the selected variables + * @param graph ReadGraph + * @param variables Selected variables + * @throws DatabaseException + */ + private void updateDatasets(ReadGraph graph, Collection variables) throws DatabaseException { + + ArrayList datasets = new ArrayList(); + for(Variable variable : variables) { + // Get all active datasets for the variable and add them to the result + Collection activeDataSets = loadAllActive(graph, variable); + if(activeDataSets != null && !activeDataSets.isEmpty()) + datasets.addAll(activeDataSets); + } + + selectionChanged(datasets); + } + + + /** + * Get all active datasets for a variable (and all of its dimensions) + * @param g ReadGraph + * @param variable Selected variable + * @return Active datasets for the selected variable + * @throws DatabaseException + */ + protected Collection loadAllActive(ReadGraph g, Variable variable) throws DatabaseException { + ArrayList dataSets = new ArrayList(); + HashMap rvis = new LinkedHashMap(); // HashMap + + // Get variable's rvi and change it to modelica format + String rvi; + try { + rvi = Variables.getRVI(g, variable).replace("/", "."); + } catch (DatabaseException e) { + return dataSets; + } + + // Remove the first '.' + if(rvi.length() > 1) + rvi = rvi.substring(1); + + Resource r = variable.getPropertyValue(g, Variables.REPRESENTS); + + SysdynResource sr = SysdynResource.getInstance(g); + Resource arrayIndexes = g.getPossibleObject(r, sr.HasArrayIndexes); + if(arrayIndexes == null) { + // If variable is single-dimensional, use the same rvi + rvis.put(rvi, rvi); + } else { + // If variable is multidimensional, get all indexes that are active and add them to rvis-map + List arrayIndexList= OrderedSetUtils.toList(g, arrayIndexes); + resolveActiveArrayIndexes(g, variable, arrayIndexList); + + if(arrayIndexList.size() > 0) + traverseIndexes(g, rvi, rvis, arrayIndexList); + else + rvis.put(rvi, rvi); + } + + Resource modelResource = Variables.getModel(g, variable); + SysdynModel model = getSysdynModel(g, modelResource); + + if(model == null) + return dataSets; + + // Finally, find all active datasets for the found rvis + Collection activeResults = model.getAndUpdateActiveResults(g); + for(SysdynResult sysdynResult : activeResults) { + for(String currvi : rvis.keySet()) { + SysdynDataSet sds = sysdynResult.getDataSet(currvi); + if(sds != null) { + sds.name = rvis.get(currvi); + dataSets.add(sds); + } + } + } + return dataSets; + + } + + /** + * Resolves and replaces all overridden enumerations in enumerations -list + * + * @param graph ReadGraph + * @param variable Selected variable + * @param enumerations List of array indexes of the variable + * @return + */ + private static List resolveActiveArrayIndexes(ReadGraph graph, Variable variable, List enumerations) { + try { + Layer0 l0 = Layer0.getInstance(graph); + SysdynResource sr = SysdynResource.getInstance(graph); + String uri = variable.getURI(graph); + uri = uri.substring(0, uri.lastIndexOf("/")); + // The parent configuration or module + Variable v = Variables.getPossibleVariable(graph, uri); + if(v != null) { + Variable rp = (Variable)v.getProperty(graph, Variables.REPRESENTS); + Resource module = rp.getValue(graph); + if(module != null && graph.isInheritedFrom(graph.getSingleObject(module, l0.InstanceOf), sr.Module)) { + // If the variable is located in a module, it might have overridden (redeclared) enumerations + boolean somethingIsReplaced = false; + // Find all redeclarations + for(Resource redeclaration : graph.syncRequest(new ObjectsWithType(module, sr.HasRedeclaration, sr.Redeclaration))) { + Resource replaced = graph.getSingleObject(redeclaration, sr.ReplacedEnumeration); + if(enumerations.contains(replaced)) { + // Replace the redelcared enumeration in enumerations -list with the replacing enumeration + enumerations.add(enumerations.indexOf(replaced), graph.getSingleObject(redeclaration, sr.ReplacingEnumeration)); + enumerations.remove(replaced); + somethingIsReplaced = true; + } + } + + if(somethingIsReplaced) { + // If something was replaced, do the same again for the parent configuration. The + // enumerations may be replaced throughout the whole model hierarchy + resolveActiveArrayIndexes(graph, v, enumerations); + } + } + } + } catch (DatabaseException e) { + e.printStackTrace(); + + } + return enumerations; + } + + /** + * Recursive function for finding number format and label for the set of rvis. (rvis.put("Variable[1]", "Variable[index1]"); + * At the end of the recursive calls, rvis contains all possible combinations of enumeration indexes that are + * set to be shown in charts + * + * @param g ReadGraph + * @param rvi RVI of the variable + * @param rvis RVI map containing the full rvi with enumerations. Key contains numerical + * value of the enumeration and value contains the name of the enumeration (e.g. ["Variable[1]", "Variable[index1]"]) + * @param arrayIndexes ArrayIndex resources, Enumerations. + * @throws DatabaseException + */ + private void traverseIndexes(ReadGraph g, String rvi, HashMap rvis, List arrayIndexes) throws DatabaseException { + traverseIndexes(g, rvi, rvis, arrayIndexes, arrayIndexes.get(0), "", ""); + } + + /** + * Recursive function for finding number format and label for the set of rvis. (rvis.put("Variable[1]", "Variable[index1]"); + * At the end of the recursive calls, rvis contains all possible combinations of enumeration indexes that are + * set to be shown in charts + * + * @param g ReadGraph + * @param rvi RVI of the variable + * @param rvis RVI map containing the full rvi with enumerations. Key contains numerical + * @param arrayIndexes ArrayIndex resources, Enumerations. + * @param currentEnumeration Currently evaluated enumeration index (in arrayIndexes list) + * @param indexesSoFar String representation of the indexes so far in numerical format + * @param indexNamesSoFar String representation of the indexes so far in name format + * @throws DatabaseException + */ + private void traverseIndexes(ReadGraph g, String rvi, HashMap rvis, List arrayIndexes, Resource currentEnumeration, String indexesSoFar, String indexNamesSoFar) throws DatabaseException { + SysdynResource sr = SysdynResource.getInstance(g); + // Enumeration indexes of the current enumeration (e.g. the first EnumIndexes in Var[EnumIndexes, EnumIndexes, EnumIndexes]) + Resource enumerationIndexes = g.getPossibleObject(currentEnumeration, sr.HasEnumerationIndexes); + if(enumerationIndexes == null) + return; + List indexes = OrderedSetUtils.toList(g, enumerationIndexes); + for(int i = 0; i < indexes.size(); i++) { + Boolean b = g.getPossibleRelatedValue(indexes.get(i), sr.ShowEnumerationIndexInCharts, Bindings.BOOLEAN); + // If this index is not wanted to be shown in charts, the recursion does not go any further and rvis.put() is not called for this enumeration + if(Boolean.TRUE.equals(b)) { + int arrayIndex = arrayIndexes.indexOf(currentEnumeration); + // Get the name of the index + String name = g.getRelatedValue(indexes.get(i), Layer0.getInstance(g).HasName); + if(arrayIndex < arrayIndexes.size() - 1) + // If there are still more EnumIndexes, recursively call the function and add current index to indexesSoFar and indexNamesSoFar + traverseIndexes(g, rvi, rvis, arrayIndexes, arrayIndexes.get(arrayIndex + 1), + indexesSoFar + (i + 1) +",", indexNamesSoFar + (name) +","); + else { + // The last enumeration. Add [rvi[1, 1, 1] = rvi[index1, index1, index1]}and so on to the rvis map + rvis.put( + rvi + "[" + indexesSoFar + (i + 1) + "]", + rvi + "[" + indexNamesSoFar + (name) + "]"); + } + } + } + } + + /** + * Find a variable representing element + * + * @param g ReadGraph + * @param element Element resource + * @param runtime runtime resource + * @return Variable representing element + * @throws DatabaseException + */ + private Variable getVariable(ReadGraph g, Resource element, Resource runtime) throws DatabaseException { + SysdynResource sr = SysdynResource.getInstance(g); + DiagramResource dr = DiagramResource.getInstance(g); + if(runtime == null) return null; + Resource resource = ModelingUtils.getPossibleElementCorrespondendence(g, element); + if(resource == null || !g.isInstanceOf(resource, sr.Variable)) return null; + String variableURI = g.getPossibleRelatedValue(runtime, dr.RuntimeDiagram_HasVariable); + try { + Variable compositeVariable = Variables.getVariable(g, variableURI); + return compositeVariable.browsePossible(g, resource); + } catch (MissingVariableException e) { + return null; + } + } + + /** + * Get SysdynModel representing the model resource + * @param g ReadGraph + * @param model Model resource + * @return SysdynModel representing model + * @throws DatabaseException + */ + private SysdynModel getSysdynModel(ReadGraph g, Resource model) throws DatabaseException { + Resource configuration = g.getPossibleObject(model, SimulationResource.getInstance(g).HasConfiguration); + return SysdynModelManager.getInstance(g.getSession()).getModel(g, configuration); + } } diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/HistoryVariable.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/HistoryVariable.java index f2b5f45f..150d6b32 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/HistoryVariable.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/HistoryVariable.java @@ -36,6 +36,7 @@ import org.simantics.db.layer0.variable.Variables; import org.simantics.db.procedure.Listener; import org.simantics.db.request.ExternalRead; import org.simantics.layer0.Layer0; +import org.simantics.modelica.data.DataSet; import org.simantics.simulation.experiment.ExperimentState; import org.simantics.simulation.ontology.SimulationResource; import org.simantics.structural.stubs.StructuralResource2; @@ -49,9 +50,10 @@ import org.simantics.sysdyn.manager.SysdynResult; /** * + * Variable implementation for Sysdyn variables. History + * variables are used when there is an active experiment. * * @author Teemu Lempinen - * */ public class HistoryVariable extends ChildVariable implements PropertyProvider { @@ -61,6 +63,12 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { SysdynModel model = null; String rvi = null; + /** + * Variable representing a variable with some history (active experiment) + * + * @param parent Parent variable + * @param resource Resource that the variable represents + */ public HistoryVariable(Variable parent, Resource resource) { super(parent, resource); experiment = SysdynExperiment.INSTANCE; @@ -70,6 +78,10 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { @SuppressWarnings("unchecked") @Override public T getInterface(ReadGraph graph, Class clazz) throws DatabaseException { + /* + * Implementation for OperatingInterfaces (and Tacton integration?) + * Should get rid of this and use only properties. + */ if(RecordAccessor.class.equals(clazz) || Accessor.class.equals(clazz)) { SimulationResource SIMU = SimulationResource.getInstance(graph); Resource model = Variables.getModel(graph, this); @@ -129,16 +141,19 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { @Override public Variable getChild(ReadGraph graph, String name) throws DatabaseException { + // If the variable represents a module, it may have children SysdynResource sr = SysdynResource.getInstance(graph); Layer0 l0 = Layer0.getInstance(graph); Resource instanceOf = graph.getSingleObject(this.resource, l0.InstanceOf); if(graph.isInheritedFrom(instanceOf, sr.Module)) { + // Find the configuration of the module StructuralResource2 sr2 = StructuralResource2.getInstance(graph); if(instanceOf == null) throw new MissingVariableException("No instanceof for resource " + NameUtils.getSafeName(graph, resource)); Resource configuration = graph.getPossibleObject(instanceOf, sr2.IsDefinedBy); if(configuration == null) throw new MissingVariableException("No configuration for " + NameUtils.getSafeName(graph, instanceOf)); + // Get the components in the configuration and find the child Map children = graph.syncRequest(new UnescapedChildMapOfResource(configuration)); Resource child = children.get(name); return graph.adaptContextual(child, this, Variable.class, Variable.class); @@ -150,6 +165,7 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { @Override public Collection browseChildren(ReadGraph graph) throws DatabaseException { + // If the variable represents a module, it may have children SysdynResource sr = SysdynResource.getInstance(graph); StructuralResource2 sr2 = StructuralResource2.getInstance(graph); Layer0 l0 = Layer0.getInstance(graph); @@ -158,10 +174,11 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { ArrayList result = new ArrayList(); if(instanceOf == null) return result; + // Find the configuration of the model Resource configuration = graph.getPossibleObject(instanceOf, sr2.IsDefinedBy); if(configuration == null) return result; - + // Add all children to the result for(Resource child : graph.syncRequest(new UnescapedChildMapOfResource(configuration)).values()) result.add(graph.adaptContextual(child, this, Variable.class, Variable.class)); return result; @@ -170,69 +187,93 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { } } - @Override public Variable getPossibleExtraProperty(ReadGraph graph, String name) throws DatabaseException { if(SysdynVariableProperties.TIME.equals(name)) { + // Get current time return graph.syncRequest(new PropertyRequest(this, name)); - } else if(SysdynVariableProperties.VALUES.equals(name) || SysdynVariableProperties.TIMES.equals(name)) { + } else if(SysdynVariableProperties.VALUES.equals(name) || SysdynVariableProperties.TIMES.equals(name) || + SysdynVariableProperties.ACTIVE_DATASETS.equals(name)) { + // For these requests, we need to make sure that model and rvi exist if(model == null) { SimulationResource SIMU = SimulationResource.getInstance(graph); Resource modelResource = Variables.getModel(graph, this); Resource configuration = graph.getPossibleObject(modelResource, SIMU.HasConfiguration); model = SysdynModelManager.getInstance(graph.getSession()).getModel(graph, configuration); + // Update active results + model.getAndUpdateActiveResults(graph); } if(rvi == null) { rvi = Variables.getRVI(graph, this).substring(1).replace("/", "."); } if(SysdynVariableProperties.TIMES.equals(name)) + // If times were requested, just return them without listening return getProperty(name); else + // Otherwise make a request and listen for changes in the result return graph.syncRequest(new PropertyRequest(this, name)); } return super.getPossibleExtraProperty(graph, name); } + /** + * Register a property subscription + * + * @param request PropertyRequest + * @param procedure + * @param property Name of the requested property + * @return + */ protected VariableValueSubscription registerSubscription(ExternalRead request, Listener procedure, String property) { if(SysdynVariableProperties.TIME.equals(property) && experiment instanceof SysdynPlaybackExperiment) { + // Current time is requested from experiment VariableValueSubscription subscription = new VariableValueSubscription(request, this, property, procedure); experiment.addVariableValueSubscription(subscription); subscription.update(); return subscription; - } else if(SysdynVariableProperties.VALUES.equals(property) && model != null) { + } else if(model != null && ( + SysdynVariableProperties.VALUES.equals(property) || + SysdynVariableProperties.TIMES.equals(property) || + SysdynVariableProperties.ACTIVE_DATASETS.equals(property))) { + // Other properties are requested from model (they listen to new simulation results) VariableValueSubscription subscription = new VariableValueSubscription(request, this, property, procedure); model.addVariableValueSubscription(subscription); subscription.update(); return subscription; - } else if(SysdynVariableProperties.TIMES.equals(property) && model != null) { - VariableValueSubscription subscription = new VariableValueSubscription(request, this, property, procedure); - model.addVariableValueSubscription(subscription); - subscription.update(); - return subscription; } else { return null; } } + /** + * Unregisters a subscription + * @param subscription + */ protected void unregisterSubscription(VariableValueSubscription subscription) { subscription.setListener(null); if(SysdynVariableProperties.TIME.equals(subscription.property)) + // Time was registered from experiment experiment.removeVariableValueSubscription(subscription); else if(SysdynVariableProperties.TIMES.equals(subscription.property) - || SysdynVariableProperties.VALUES.equals(subscription.property)) + || SysdynVariableProperties.VALUES.equals(subscription.property) + || SysdynVariableProperties.ACTIVE_DATASETS.equals(subscription.property)) + // Other properties were requested from model model.removeVariableValueSubscription(subscription); } @Override public Variable getProperty(String name) { if(SysdynVariableProperties.TIME.equals(name)){ + // Get current time from experiment if(experiment instanceof SysdynPlaybackExperiment) { SysdynPlaybackExperiment exp = (SysdynPlaybackExperiment) experiment; return new ConstantPropertyVariable(this, name, exp.getTime(), Datatypes.DOUBLE); } else { + // Experiment is not compatible, return time = 0 return new ConstantPropertyVariable(this, name, 0, Datatypes.DOUBLE); } } else if(SysdynVariableProperties.VALUES.equals(name)) { + // Get the values of this variable from the currently active experiment SysdynResult sr = new SysdynResult(model.getSimulationResult()); SysdynDataSet ds = sr.getDataSet(rvi); if(ds == null) @@ -240,12 +281,23 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { else return new ConstantPropertyVariable(this, name, ds.values, Datatypes.DOUBLE_ARRAY); } else if(SysdynVariableProperties.TIMES.equals(name)) { + // Get the times of this variable from the currently active experiment SysdynResult sr = new SysdynResult(model.getSimulationResult()); SysdynDataSet ds = sr.getDataSet(rvi); if(ds == null) return new ConstantPropertyVariable(this, name, new double[0], Datatypes.DOUBLE_ARRAY); else return new ConstantPropertyVariable(this, name, ds.times, Datatypes.DOUBLE_ARRAY); + } else if(SysdynVariableProperties.ACTIVE_DATASETS.equals(name)) { + // Get all active datasets for this variable (currentyl active experiment and all active saved results) + Collection activeResults = model.getActiveResults(); + ArrayList result = new ArrayList(); + for(SysdynResult sr : activeResults) { + DataSet ds = sr.getDataSet(rvi); + if(ds != null) + result.add(ds); + } + return new ConstantPropertyVariable(this, name, result, Datatypes.VARIANT); } return null; } @@ -254,7 +306,7 @@ public class HistoryVariable extends ChildVariable implements PropertyProvider { * Class for supporting requests with different property parameters. Equals-method has been modified * from ParametrizedPrivimiteRead to check also the property value. * - * @author tlteemu + * @author Teemu Lempinen * */ class PropertyRequest extends ParametrizedPrimitiveRead { diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/SysdynVariableProperties.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/SysdynVariableProperties.java index c21b4a9c..84dae6e2 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/SysdynVariableProperties.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/adapter/SysdynVariableProperties.java @@ -11,11 +11,16 @@ *******************************************************************************/ package org.simantics.sysdyn.adapter; +/** + * Properties that can be requested from Sysdyn variables + * @author Teemu Lempinen + * + */ public class SysdynVariableProperties { - final public static String VALUE = "VALUE"; final public static String VALUES = "VALUES"; final public static String TIME = "TIME"; final public static String TIMES = "TIMES"; + final public static String ACTIVE_DATASETS = "ACTIVE_DATASETS"; } diff --git a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java index 25bff4f8..a195a5fb 100644 --- a/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java +++ b/org.simantics.sysdyn/src/org/simantics/sysdyn/manager/SysdynModel.java @@ -32,6 +32,7 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; +import org.simantics.db.common.request.ObjectsWithType; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ManyObjectsForFunctionalRelationException; import org.simantics.db.exception.ServiceException; @@ -90,6 +91,7 @@ public class SysdynModel implements IMappingListener, IModel { private boolean canceled; private SimulationResult result; private SysdynResult sysdynResult; + private ArrayList activeResults; private CopyOnWriteArrayList modificationListeners = new CopyOnWriteArrayList(); @@ -598,7 +600,7 @@ public class SysdynModel implements IMappingListener, IModel { } else { return null; } - + ((SysdynExperiment)exp).init(g); ExperimentRuns.createRun(g.getSession(), experiment, exp, listener, null); @@ -612,30 +614,33 @@ public class SysdynModel implements IMappingListener, IModel { } } + /** * Get all active results for this model including the current result and - * all saved results that are active + * all saved results that are active. Saves the active results to be obtained + * with getActiveResults + * * @param graph ReadGraph - * @return all active SysdynResults + * @return Active SysdynResults */ - public Collection getActiveResults(ReadGraph graph) { - ArrayList results = new ArrayList(); - + public Collection getAndUpdateActiveResults(ReadGraph graph) { + activeResults = new ArrayList(); + // TODO: this can be done automatically with a listener try { // Find all active saved results Layer0 l0 = Layer0.getInstance(graph); SysdynResource sr = SysdynResource.getInstance(graph); SimulationResource SIMU = SimulationResource.getInstance(graph); Resource model = graph.getSingleObject(configurationResource, SIMU.IsConfigurationOf); - Collection experiments = graph.getObjects(model, l0.ConsistsOf); + Collection experiments = graph.syncRequest(new ObjectsWithType(model, l0.ConsistsOf, sr.Experiment)); for(Resource experiment : experiments) { Collection experimentResults = graph.getObjects(experiment, sr.HasResult); for(Resource result : experimentResults) { - if(graph.hasStatement(result, SIMU.IsActive)) { + if(graph.hasStatement(result, sr.ShowResult)) { SysdynResult sysdynResult = new SysdynResult( (String) graph.getPossibleRelatedValue(result, l0.HasLabel), (String) graph.getPossibleRelatedValue(result, sr.HasResultFile)); - results.add(sysdynResult); + activeResults.add(sysdynResult); } } } @@ -645,9 +650,22 @@ public class SysdynModel implements IMappingListener, IModel { // Add the current result if there is one if(getSysdynResult() != null) - results.add(0, getSysdynResult() ); + activeResults.add(0, getSysdynResult() ); + + return activeResults; + } - return results; + + /** + * Get active results. To update the active results, use getAndUpdateActiveResults() + * @param graph ReadGraph + * @return all active SysdynResults (last update with getAndUpdateActiveResults()) + */ + public Collection getActiveResults() { + if(activeResults == null) + return new ArrayList(); + else + return activeResults; } /** -- 2.47.1