Message Boards Message Boards

MapThread conflicting with DynamicModule

Posted 5 years ago

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]",
         uiPlotUnitFahrenheit, "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:

Here is the 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.

8 Replies

This version works great!!! Thanks for that

This version switches easily between celsius and fahrenheit:

DynamicModule[{userSystemWindow = {}, importedData = {{0, 0}, {1, 1}},
   calculatedList1, coeffs, calculatedList2 = {0, 0}, 
  temperatureUnits = "DegreesCelsius", uiPlotUnitFahrenheit},
 Dynamic@Column[{(*Load Data Button*)
    Button["Load Geometry",
     userSystemWindow = SystemDialogInput["FileOpen"];
     importedData = 
      If[ToString[userSystemWindow] != "$Canceled", 
       Flatten[Import[userSystemWindow, "XLSX"], 1] // Rest, {}], 
     Method -> "Queued"], 
    Grid[{(*Calculate Button*){Dynamic@
        Button["Calculate", 
         calculatedList1 = Map[Norm, Differences[importedData]];
         coeffs = Exp[-N[Pi]*0.345*5*calculatedList1/(10*2000)];
         calculatedList2 = FoldList[10 + (#1 - 10) #2 &, 170, coeffs],
          ImageSize -> {80, 27}, ContentPadding -> True, 
         Enabled -> (If[
            ToString[userSystemWindow] === "$Canceled" \[Or] 
             ToString[userSystemWindow] === "{}", False, True])], 
       Spacer[10],(*Clear Results Button*)
       Button["Clear Plot", calculatedList2 = {0, 0}]}}], 
    Row[{"Temperature Units", Spacer[10], 
      PopupMenu[
       Dynamic[temperatureUnits], {"DegreesCelsius" -> "C", 
        "DegreesFahrenheit" -> "F"}]}], Spacer[10],(*Graph Plot*)
    ListPlot[
     If[Length[importedData] != Length[calculatedList2], {}, 
      Transpose[{importedData[[All, 1]], 
        QuantityMagnitude@
         UnitConvert[QuantityArray[calculatedList2, "DegreesCelsius"],
           temperatureUnits]}]],
     Frame -> True, 
     FrameLabel -> {"Distance [m]", 
       If[temperatureUnits === "DegreesCelsius", "Temperature [C]", 
        "Temperature [F]"]}, ImageSize -> 500, Joined -> True]}, 
   Alignment -> Left]]
POSTED BY: Gianluca Gorni

My aim here is to plot temperatureUnits (i.e. celsius, fahrenheit) not calculatedList1 or calculatedList2. The problem is that celsius and fahrenheit which are the values of temperatureUnits (see PopupMenu) don't update dynamically as they supposed to.

The PopupMenu is linked to ListPlot, for example: if celsius with label "C" is selected from the list then ListPlot should print value of celsius and the same goes for fahrenheit. Every time different data is uploaded and button "Calculate" pressed celsius and fahrenheit should update dynamically.

I don't understand what your problem is. The following seems to work plausibly for me:

DynamicModule[{userSystemWindow = {}, importedData = {{0, 0}, {1, 1}},
   calculatedList1, coeffs, calculatedList2 = {1, 1}, 
  temperatureUnits = celsius, uiPlotUnitFahrenheit},
 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 = Map[Norm, Differences[importedData]]; 
          Table[1, {N[Length[importedData] - 1]}];
          coeffs = Exp[-N[Pi]*0.345*5*calculatedList1/(10*2000)];
          calculatedList2 = FoldList[10 + (#1 - 10) #2 &, 170, coeffs],
          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],
    Grid[{
      {"Temperature Units", 
       PopupMenu[Dynamic[temperatureUnits], {
         celsius -> "C",
         fahrenheit -> "F"}]}}], 
    Spacer[1],
    (*Graph Plot*) 
    Dynamic@
     ListPlot[
      If[Dimensions[importedData[[All, 1]]] === 
        Dimensions[calculatedList2], {calculatedList1, 
        calculatedList2}, {}],
       Frame -> True, FrameLabel -> {"Distance [m]",
         Switch[temperatureUnits, 
         celsius, "Temperature [C]",
          fahrenheit, "Temperature [F]"]},
       ImageSize -> 500, Joined -> True, 
      PlotLegends -> {"calculatedList1", "calculatedList2"}]
    }, Alignment -> Left],
 Initialization :> (
   celsius = 
    MapThread[List, {importedData[[All, 1]], calculatedList2}];
   fahrenheit = 
    MapThread[
     List, {importedData[[All, 1]], 
      QuantityMagnitude@
       UnitConvert[QuantityArray[calculatedList2, "DegreesCelsius"], 
        "DegreesFahrenheit"]}];
   ), 
 SynchronousInitialization -> False
 ]
POSTED BY: Gianluca Gorni

Sorry, It is my mistake with the Fahreheit. I missed this one when simplifying the code and shortening names for posting it here. The correct version is attached below

Attachments:

This version does something that looks plausible to me:

DynamicModule[{userSystemWindow = {}, importedData = {{0, 0}, {1, 1}},
   calculatedList1, calculatedList2 = {1, 1}, 
  temperatureUnits = celsius, uiPlotUnitFahrenheit},
 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], calculatedList2, {}],
      Frame -> True, FrameLabel -> {"Distance [m]",
        Switch[temperatureUnits, 
        celsius, "Temperature [C]",
         uiPlotUnitFahrenheit, "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
 ]

There remains a problem with Fahrenheit.

POSTED BY: Gianluca Gorni

This only helps when DynamicModule is first displayed. Try to import data from the attached spreadsheet above with the button "Load Geometry" and then click on button "Calculate", you will see that MapThread[List, {importedData[[All, 1]], calculatedList2}] is not updated and plotted. It evaluates only once using initial values. The graph should look like the one below when the button "Calculate" is pressed after the data is imported and it should update every time new data is imported and calculated.

enter image description here

If I give an initial value for importedData and calculatedList2 I don't get any error message:

DynamicModule[{userSystemWindow = {}, importedData = {{0}}, 
  calculatedList2 = {1}},
POSTED BY: Gianluca Gorni
Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract