1
$\begingroup$

Cross-posted: https://community.wolfram.com/groups/-/m/t/1625808

Answer posted on Wolfram Community

I'm creating an interactive UI which allows the user to import data from a spreadsheet, perform calculation and plot the results. The issue I'm having is with MapThread, which is evaluated at the same time as the DynamicModule, giving an error message that dimensions of objects are incompatible. I noticed that outputs of dynamically displayed objects such as buttons in this case are retained in the memory and used as initial values every time DynamicModule is re-evaluated (specifying initial values didn't help), and because dimensions of these objects are different at the point of DynamicModule re-evaluation further output of ListPlot is suppressed before any action is taken. I'm struggling to get around this problem. Any help will be greatly appreciated.

DynamicModule[{userSystemWindow = {}},
 Dynamic @ Column[{
    Row[(*Load Data Button*)
     {Button[
       "Load Geometry",
       userSystemWindow = SystemDialogInput["FileOpen"];
       importedData = 
        If[ToString[userSystemWindow] != "$Canceled", 
         Flatten[Import[userSystemWindow, "XLSX"], 1] // Rest, {}],
        Method -> "Queued"] }],
    Spacer[1], Style[
     Dynamic@Grid[{(*Calculate Button*)
        {Button[
          "Calculate", 
          calculatedList1 = Table[1, {N[Length[importedData] - 1]}];

      For[l = 2, l <= Length[importedData], l++, 
       calculatedList1[[l - 1]] = 
        Sqrt[(importedData[[l, 1]] - 
             importedData[[l - 1, 1]])^2 + (importedData[[l, 2]] -
              importedData[[l - 1, 2]])^2]]; 
      PrependTo[calculatedList1, 0];
      calculatedList2 = Table[1, Length[importedData]];

      For[t = 2; calculatedList2[[1]] = 170, 
       t <= Length[importedData], t++, 
       calculatedList2[[t]] = 

        10 + (calculatedList2[[t - 1]] - 10)*
          Exp[-N[Pi]*0.345*5*calculatedList1[[t]]/(10*2000)]],
      Method -> "Queued", ImageSize -> {75, 27}, 
      ContentPadding -> True, 

      Enabled -> (If[
         ToString[userSystemWindow] === "$Canceled" \[Or] 
          ToString[userSystemWindow] === "{}", False, True])], 
     (*Clear Results Button*)
     Button[
      "Clear Plot", calculatedList2 = {0}]}}]],
Spacer[1],
Dynamic@Grid[{
   {"Temperature Units", 
    PopupMenu[Dynamic[temperatureUnits], {
      celsius -> "C",
      fahrenheit -> "F"}]}}], 
Spacer[1],
(*Graph Plot*) 
ListPlot[
 If[Dimensions[importedData[[All, 1]]] === 
   Dimensions[calculatedList2], temperatureUnits, {}],
  Frame -> True, FrameLabel -> {"Distance [m]",
    Switch[temperatureUnits, 
    celsius, "Temperature [C]",
     fahrenheit, "Temperature [F]"]},
  ImageSize -> 500, Joined -> True]
}, Alignment -> Left],
 Initialization :> (
   celsius = 
    MapThread[List, {importedData[[All, 1]], calculatedList2}];
   fahrenheit = 
    MapThread[
     List, {importedData[[All, 1]], 
      QuantityMagnitude@
       UnitConvert[QuantityArray[calculatedList2, "DegreesCelsius"], 
        "DegreesFahrenheit"]}];
   ), 
 SynchronousInitialization -> False
 ]

Edit:

This a description of what the UI should calculate.

At first the UI should display:

  • 3 buttons: "Load Data", "Calculate Data" and "Clear Plot" (the last 2 should initially be disabled);
  • popup menu which is linked to the graph allowing the user to change the temperature unit of plotted data;
  • graph which initially should be blank just showing frames and labels

Button "Load Data" upon mouse-click should open system dialog window, allowing the user to select the excel file with data which upon file selection are imported in the form {{x1, y1}, {x2, y2}, ...}. In case the user clicks on button "Load Data" but decides to cancel the selection and close the system dialog window the button “Calculate” should remain disabled with a graph being blank.

Button “Calculate” should become enable as soon as data is imported, which upon mouse-click should solve couple of equations using imported data and inputs from InputField’s (removed from the code posted here) returning result of final equation in the form {z1, z2, …}.

When the data is ready to be plotted ListPlot should automatically plot data in the form {{x1, z1}, {x2, z2}, …} with temperature unit DegreesCelsius. This is why I need to use MapTherad to combine the first column from imported data with the calculated data set of z’s. Then every time the user decides to change the input data or import new data and press the button “Calculate” ListPlot should update accordingly and plot new set of data by adding the new curve to the old one i.e. {list1, list2, ….} where each list corresponds to mouse-click, so the user can compare different results.

Button “Clear Plot” is there to bring the plot to the blank state. I hope all that makes sense.

$\endgroup$
  • $\begingroup$ Comments are not for extended discussion; this conversation has been moved to chat. $\endgroup$ – Kuba Mar 7 at 11:04
5
$\begingroup$

Generally, you have to be a bit careful when mixing synchronous and asynchronous dynamics in one application. Synchronous dynamics will interrupt asynchronous ones, resulting in behavior that's difficult to anticipate. Here's an idiom I frequently use for initializing DynamicModules that need a non-trivial amount of time to get themselves ready:

DynamicModule[{
  initQ = False
 },
 Dynamic[
  If[TrueQ[initQ],
   Dynamic[content],
   ProgressIndicator[Appearance -> "Necklace"]
   ],
  TrackedSymbols :> {initQ}
  ],
 SynchronousInitialization -> False,
 Initialization :> (
   initQ = False;
   Pause[6]; (* any pre-computation that takes significant time goes here *)
   initQ = True
  )
]

In this case, Dynamic[content] is the thing you actually want to show, but it will only appear after the initialisation has finished.

$\endgroup$
4
$\begingroup$

Updating answer (meaning replacing it completely).

Right now your Initialization depends on values that aren't set until the body executes, but the body doesn't execute until after the initialization, and the initialization only executes once. So you need to have those set a priori. Putting this in up front, along with setting importedData lets it start up without a bunch of errors.

calculatedList2 = Table[1, Length[importedData]];

Then you can call your DynamicModule[ ].

$\endgroup$
  • $\begingroup$ MikeY your suggestion unfortunately doesn't work, I'm still getting an error message such as: MapThread: Incompatible dimensions of objects at positions {2, 1} and {2, 2} of MapThread[List,{{},{0}}]; dimensions are 0 and 1. The problem is that MapThread evaluates as soon as DynamicModule is displayed, using last output of dynamic objects as initial values. Another problem is that MapThread evaluates only once and it doesn't update when the data is imported and button "Calculate" is clicked, at which point output of the button should be plotted. $\endgroup$ – Kamila Mar 5 at 19:15
  • $\begingroup$ I tried to use Dynamic with it but ListPlot doesn't accepts Dynamic inside. Maybe there is other way to combine 2 lists without using MapThread that wouldn't result in such error? $\endgroup$ – Kamila Mar 5 at 19:20
  • $\begingroup$ Yeah, did a little more digging...not working. It looks like the fundamental problem is your Initialization depends on calculatedList2, but the Initialization is guaranteed to get computed before the body, and you first compute calculatedList2 in the body. $\endgroup$ – MikeY Mar 5 at 19:43
  • $\begingroup$ Anything you use in the Initialization gets called exactly once, and needs to be set in advance. $\endgroup$ – MikeY Mar 5 at 19:44
  • 2
    $\begingroup$ Think you need to start with a clean sheet on your question. Describe what you want the UI to calculate. We can probably help you from there. $\endgroup$ – MikeY Mar 5 at 22:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.