embed mutable values

How to embed mutable cell, the modified value ?

From Mutable Values / Mike Bostock / Observable,
if you ask to embed x after having incremented it, the cell is still
at its initial value.

How to embed cell a mutable cell ?

I also read from Demo: Mutable Variables and Multi-input Cells / Agnes Chang / Observable
that cells containing mutable array (or other complex data types such as sets and dictionaries) don’t react to mutable events.
I am confused.

In my real example WMS Leaflet map GCA (DEV) / Patrick Brockmann / Observable,
allmaps cannot be embeded, same for mapsArray that when embeded stays at its initial value (empty).

Hi Patrick.

Just to be clear: Are you running into this issue when trying to embed the entire notebook or specific cells one at a time? Before the cells will interact correctly with one another when embedded, they must all be part of the same notebook call.

You note that ‘allmaps’ cannot be embedded, but technically it can:

<div id="observablehq-allmaps-163e8c1f"></div>

<script type="module">
import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import define from "https://api.observablehq.com/@pbrockmann/wms-leaflet-map-gca-dev.js?v=3";
new Runtime().module(define, name => {
  if (name === "allmaps") return new Inspector(document.querySelector("#observablehq-allmaps-163e8c1f"));
});
</script>

However it won’t work interactively unless you embed it with all other cells on which it relies all one go, e.g.:

<div id="observablehq-allmaps-163e8c1f"></div>
<div id="observablehq-L-163e8c1f"></div>
<div id="observablehq-keyword_style-163e8c1f"></div>
<div id="observablehq-legend-163e8c1f"></div>
<div id="observablehq-palette-163e8c1f"></div>
<div id="observablehq-palettesArray-163e8c1f"></div>
<div id="observablehq-layersArray-163e8c1f"></div>
<div id="observablehq-rangeVariable-163e8c1f"></div>
<div id="observablehq-timeVariable-163e8c1f"></div>
<div id="observablehq-variablesArray-163e8c1f"></div>
<div id="observablehq-ressourcesArray-163e8c1f"></div>
<div id="observablehq-dirsArray-163e8c1f"></div>
<div id="observablehq-addMap-163e8c1f"></div>
<div id="observablehq-map-163e8c1f"></div>
<div id="observablehq-viewof-range-163e8c1f"></div>
<div id="observablehq-viewof-rangeFrom-163e8c1f"></div>
<div id="observablehq-viewof-variable-163e8c1f"></div>
<div id="observablehq-viewof-ressource-163e8c1f"></div>
<div id="observablehq-viewof-period-163e8c1f"></div>
<div id="observablehq-viewof-directory-163e8c1f"></div>
<div id="observablehq-viewof-numcolorbands-163e8c1f"></div>
<div id="observablehq-viewof-opacity-163e8c1f"></div>
<div id="observablehq-viewof-paletteInversed-163e8c1f"></div>
<div id="observablehq-viewof-palette0-163e8c1f"></div>
<div id="observablehq-viewof-layers-163e8c1f"></div>
<div id="observablehq-mapsArray-163e8c1f"></div>
<div id="observablehq-mapsArrayMutable-163e8c1f"></div>

<script type="module">
import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import define from "https://api.observablehq.com/@pbrockmann/wms-leaflet-map-gca-dev.js?v=3";
new Runtime().module(define, name => {
  if (name === "allmaps") return new Inspector(document.querySelector("#observablehq-allmaps-163e8c1f"));
  if (name === "L") return new Inspector(document.querySelector("#observablehq-L-163e8c1f"));
  if (name === "keyword_style") return new Inspector(document.querySelector("#observablehq-keyword_style-163e8c1f"));
  if (name === "legend") return new Inspector(document.querySelector("#observablehq-legend-163e8c1f"));
  if (name === "palette") return new Inspector(document.querySelector("#observablehq-palette-163e8c1f"));
  if (name === "palettesArray") return new Inspector(document.querySelector("#observablehq-palettesArray-163e8c1f"));
  if (name === "layersArray") return new Inspector(document.querySelector("#observablehq-layersArray-163e8c1f"));
  if (name === "rangeVariable") return new Inspector(document.querySelector("#observablehq-rangeVariable-163e8c1f"));
  if (name === "timeVariable") return new Inspector(document.querySelector("#observablehq-timeVariable-163e8c1f"));
  if (name === "variablesArray") return new Inspector(document.querySelector("#observablehq-variablesArray-163e8c1f"));
  if (name === "ressourcesArray") return new Inspector(document.querySelector("#observablehq-ressourcesArray-163e8c1f"));
  if (name === "dirsArray") return new Inspector(document.querySelector("#observablehq-dirsArray-163e8c1f"));
  if (name === "addMap") return new Inspector(document.querySelector("#observablehq-addMap-163e8c1f"));
  if (name === "map") return new Inspector(document.querySelector("#observablehq-map-163e8c1f"));
  if (name === "viewof range") return new Inspector(document.querySelector("#observablehq-viewof-range-163e8c1f"));
  if (name === "viewof rangeFrom") return new Inspector(document.querySelector("#observablehq-viewof-rangeFrom-163e8c1f"));
  if (name === "viewof variable") return new Inspector(document.querySelector("#observablehq-viewof-variable-163e8c1f"));
  if (name === "viewof ressource") return new Inspector(document.querySelector("#observablehq-viewof-ressource-163e8c1f"));
  if (name === "viewof period") return new Inspector(document.querySelector("#observablehq-viewof-period-163e8c1f"));
  if (name === "viewof directory") return new Inspector(document.querySelector("#observablehq-viewof-directory-163e8c1f"));
  if (name === "viewof numcolorbands") return new Inspector(document.querySelector("#observablehq-viewof-numcolorbands-163e8c1f"));
  if (name === "viewof opacity") return new Inspector(document.querySelector("#observablehq-viewof-opacity-163e8c1f"));
  if (name === "viewof paletteInversed") return new Inspector(document.querySelector("#observablehq-viewof-paletteInversed-163e8c1f"));
  if (name === "viewof palette0") return new Inspector(document.querySelector("#observablehq-viewof-palette0-163e8c1f"));
  if (name === "viewof layers") return new Inspector(document.querySelector("#observablehq-viewof-layers-163e8c1f"));
  if (name === "mapsArray") return new Inspector(document.querySelector("#observablehq-mapsArray-163e8c1f"));
  if (name === "mapsArrayMutable") return new Inspector(document.querySelector("#observablehq-mapsArrayMutable-163e8c1f"));
});
</script>

Separating your embeds will result in an apparent failure otherwise.

POC working embed here:

https://aaronkyle.github.io/concept/data-visualization/test-embed.html

Thanks Aaron for your interest in this question.
No, not the entire notebook. Only some cells.

In fact, I would like to let the user choose different variables and different parameters
he wants, fill a cart (list of paramters needed to draw a map) then export in a new thumbnail only the different maps, precisely the cell called ‘allmaps’.
To run this cell, the cell mapsArray must filled (after having used the “add” button) but this cell stays empy when embeded (set at its initial value).
That is the problem.
Am I clearer ?

Interesting. I see now what you mean that ‘mapsArray’ remains empty when embedded. This doesn’t make sense to me (but I am no expert!). I will play for a few minutes and see what I find. Thank you for raising this question! :slight_smile:

… but wait! mapsArray also doesn’t change in your notebook? it’s always empty? i see on the minimap that it connected to allMaps, but it doesn’t seem to change no matter the inputs.

I apologize, but I am still not sure what you’re expecting should happen?

Use Add button
It is like a shopping cart.

Ah, ok. i got it. your ‘add’ button isn’t named, so it won’t embed. give the cell a name and then it should work

Done, but not the expected behaviour.

The minimaps are well displayed in the notebook. It is when I want to export and see only allmaps cell that I have the problem du to the fact that the mutable mapsArray variable always stays at its initial value (empty array []).

I see that the array is populated after pressing add when embedded:

https://aaronkyle.github.io/concept/data-visualization/test-embed-2

Again, if you embed just one cell without the other cells that supply information to it, it’s not going to work. The only way cells can ‘listen’ to one another is if they are called with the same script.

Thus this what I would like to do.
Try with a static version of mapsArray, I have just coded.

Export just “allmaps” cell. That works nicelly.

So my problem is that I cannot built a mutable cell that is exportable
(mapsArray)

Sorry, but I still don’t get what you’re after.

mapsArray is static:

// A static version of mapsArray
mapsArray = [
  {
    directory: "Atlas/Flux_Transcom/Inversions-GCP2019",
    ressource:
      "fco2_CAMS-V18-2-2019_June2018-ext3_1979-2018_yearlymean-anom_XYT.nc",
    period: "yearlymean-anom",
    variable: "Terrestrial_flux",
    range: [-432, 291],
    palette: "div-RdYlBu",
    numcolorbands: 20,
    opacity: 100
  },
  {
    directory: "Atlas/Flux_Transcom/Inversions-GCP2019",
    ressource:
      "fco2_CAMS-V18-2-2019_June2018-ext3_1979-2018_yearlymean-anom_XYT.nc",
    period: "yearlymean-anom",
    variable: "Ocean_flux",
    range: [-29.9, 39.2],
    palette: "div-RdYlBu",
    numcolorbands: 20,
    opacity: 100
  }
]

mapsArray1 is mutable:

mapsArray1 = {
  var mapsArray = [];
  for (let i = 0; i < mapsArrayMutable.length; i++) {
    mapsArray.push(mapsArrayMutable[i]);
  }
  return mapsArray;
}

mapsArray1 has an initial value of 0. Before that value can increment, it listens to MapsArrayMutable, which is empty:

mutable mapsArrayMutable = []

for this value to change, it must be populated. the change comes from mapsSelectionButtons:

mapsSelectionButtons = Inputs.button(
  [
    [
      "Add",
      () => {
        mutable mapsArrayMutable = mapsArrayMutable.concat(addMap());
        //mutable mapsArrayMutable.push(addMap());
      }
    ],
    [
      "Remove last",
      () => {
        mutable mapsArrayMutable = mapsArrayMutable.slice(0, -1);
        //mutable mapsArrayMutable.splice(1, 1);
      }
    ],
    [
      "Reset",
      () => {
        mutable mapsArrayMutable = [];
      }
    ]
  ],
  { value: [], label: "List of maps" }
)

… so if you embed any of the other cells without mapsSelectionButtons, they will only reflect their initial value b/c there’s nothing else to trigger a change event.

You can only export a mutable cell alongside the thing that mutates it in order for it to function.

maybe you can build a frame of your desired output?

Very sorry not to be clear enough.

So back to the need. The application I have imagined.
Explore a map. Keep the different parameters that composed it.
Explore another map. Let’s say another variable. Keep the parameters
again. With a “Add button”
Then you have a mapsArray that have all is needed to draw multiple maps
synchronized in a empty thumbnail.

My problem comes from the fact that I have not been able to build this
list of parameters (mapsArray) without having used a mutable cell.

How to build this variable and then export it as a static variable ?
That is maybe a solution

Am I clearer ?

Please don’t take my lack of understanding as a statement about your clarity. I haven’t written into the forum for some time, but generally I am a novice and I expect most people know more than me.

From my reading, you have accomplished already what you want. I am only seeing a potential problem that your ‘storage’ value relies on inputs from other cells. I don’t know much about mutables, and maybe this is not the correct use case… but let’s put that aside…

In the full embed on Github, the embedded notebook works exactly like your Observable notebook. The mutable value captures the inputs after you press ‘add’. The only way I can understand this not to work is if you try to separate out cells, like when you try to embed them one at a time. Your list of parameters is supplied to the mutable cell by all other cells to it listens to. These changes are stored in state, and effectively written to the mutable array on the button click. Thus, without these other cells and the button all being present in the embedded notebook, there’s nothing for the mutable cell to listen to.

I may be at the end of my capacity to help, as I can only share the few conceptual things I understand. I was sorta hoping / expecting that you were simply separating cells, which led to the issue you’re experiencing… though this may not be the case…

Do no worry.
Always benefit to explain things.

Yes close to what I would like to build. Except the dynamic setting of the mapArray variable
that needs to be dynamic but then seen as static variable.

Best regards

1 Like

General note: cells downstream of a mutable cell do not run unless they are observed/imported/embedded, which is a big drawback of mutables because they complicate the dependency story.

I don’t see a link to the notebook so it very difficult for me to provide specific advice or understand the problem

Hi,
Ok I read your answer. Thanks for your interest in the question.

Here is the notebook:

I have tried to code a shopping cart mechanism to export then a selection
of maps (button Add).

@Fil proposed me to pass the mutable variable on the url (stringified).
Not as simple as I was expected.

I see. The problem is about exporting dynamic data over to the embedded version. Yeah you can serialize your array and pass it in URL params like fil said. Though there is a limit to how large you can fit in a request

URLs only accept some characters, so I usually serialize to JSON then base64

btoa(JSON.stringify(data))

I wrote that extremely quickly so its ropey and does some nasty things like throwing a parse exception unnecessarily. You can use the compare fork to see what changed easily

this is what I did too :slight_smile: => btoa(JSON.stringify(data)) in the url’s hash.

1 Like