update value of another input when an input is triggered in a multi input single object

I have a notebook Clear Mountain Monastery – Org. Chart / Cittadhammo | Observable where I would like that when the darkMode checkbox is checked, it changes the value of some of the other inputs, for example the colorSeed range.

If the input are in different cells, I can use a ternary operator (see version 1062):

viewof colorSeed = Inputs.range([0, 100], {
    label: "colorSeed",
    step: 1,
    value: darkMode ? 50 : 2
  }),

Alas, as I am using a single multi input statement with a objet p now, I run into a ciruclar error: p.darkMode, p.colorSeed…

I have tried using () => this.darkMode or Custom Input Example / Toph Tucker | Observable or Synchronized Inputs / Observable | Observable, but unsuccessfully so far.

What would be the best way to proceed?

Thank you for your help.

I would first try to approach this from a conceptual point of view. From what I can tell your Dark Mode toggle acts more like a preset, since I can change the affected sliders afterwards without the toggle getting deselected, in which case I would make it a button (and maybe even move it above the form).

If on the other hand it is in fact a modifier then I would recommend to not change the sliders, but apply the changes in your final calculations.

If you still need to include the button or toggle in your form, then I would recommend to use Inputs.form() with the template option instead for this particular scenario:

viewof mySettings = Inputs.form({
  optionA: Inputs.number([0, 100], {label: "Option A", value: 10}),
  optionB: Inputs.range([20, 60], {label: "Option B"})
}, {
  template: ({optionA, optionB}) => {
    const set = (input, value) => {
      input.value = value;
      input.dispatchEvent(new Event("input", {bubbles: true}));
    };
    const button = Inputs.button([["Set option B", () => set(optionB, 55)]]);
    return htl.html`<div style="display:flex;gap:32px">
      <div>${optionA}</div>
      <div>${[button, optionB]}</div>`;
  }
})

1 Like

Thank you very much!, I have simplify the input by separating into two blocks as suggested.

Another thing that would be nice is to enable the input scroll Zoom (p.scrollZoom) when fullscreen is enable (I don’t want user to be stuck when scrolling down the page and the chart start zooming instead). I have tried the following function:

fullscreen = {
  const button = html`<button> Go Full Screen`;
  button.onclick = () => {
    button.parentElement.nextElementSibling.requestFullscreen();
  };
  return button;
}

But it fails with a simple redefinition of the p.scrollZoom variable. I think that using input.dispatchEvent would be the way forward, but it is pretty obscure how this works.

The current fullscreen option is imported from Fullscreen / Fil | Observable

Thanks for your help

From what I can tell d3-zoom offers no method to toggle zooming or ignore a zoom event - the internal transform will always get updated even if you choose to not apply it.

My recommendation would be to conditionally remove the event handlers on fullscreen change:

const zoom = d3.zoom().on("zoom", zoomed);

const toggleZoom = enabled => enabled
  // Attach zoom handlers
  ? svg.call(zoom)
  // Remove all *.zoom event handlers
  : svg.on(".zoom", null);
// Initialize
toggleZoom();
  
svg.on("fullscreenchange", (e) => {
  // Enable zooming if svg.node() is the fullscreen element
  toggleZoom(e.target === document.fullscreenElement)
});
1 Like

Ok, interesting, I have tried to add:

 function zoomed({ transform }) {
    svg.attr("transform", transform);
  }

  // --- alternative zoom from https://talk.observablehq.com/t/update-value-of-another-input-when-an-input-is-triggered-in-a-multi-input-single-object/8532/3 independant of toogle and allow zoom only on fullscreen --- //

  document.addEventListener("fullscreenchange", function () {
    console.log("fullscreenchange event fired!");
  });

  const zoom = d3.zoom().scaleExtent(0.1, 8).on("zoom", zoomed);

  const toggleZoom = (enabled) =>
    enabled
      ? // Attach zoom handlers
        svgOrigin.call(zoom)
      : // Remove all *.zoom event handlers
        svgOrigin.on(".zoom", null);
  // Initialize
  toggleZoom();

  document.addEventListener("fullscreenchange", (e) => {
    // Enable zooming if svg.node() is the fullscreen element
    console.log(e);
    console.log(document.fullscreenElement);
    console.log(e.target === document.fullscreenElement);
    toggleZoom(e.target === document.fullscreenElement);
  });

at the end of the code of the chart cell, with a few testing log and the log says it works and toggleZoom() is supposed to be ON, but there is no zoom or pan in fullscreen…

Establish a baseline by testing directly with

svgOrigin.call(zoom)

before you start testing with toggleZoom(). You’ll notice that zooming is already broken here, and if you log transform in zoomed you’ll see several NaN values because scaleExtent expects an array:

  const zoom = d3.zoom().scaleExtent([0.1, 8]).on("zoom", zoomed);

Also keep an eye on your JS console. There are several errors that you may want to take care of first.

1 Like

Very good ! you were right, I made a mistake in the argument of the function scaleExtent(). It all works nicely now. Thank you very much for your help.

If you have more patience, I have been trying to reset the position of the chart to (0,0) and scale 1 when exiting the full screen mode. I have tried using the translateExtent() function. It does the job but I wonder if there is a better solution (it is a bit complicated as there is a toggle p.scollZoom that allow to zoom and pan in no fullscreen mode) :

const zoom = d3.zoom().scaleExtent([0.5, 8]).on("zoom", zoomed);
const zoomNoFull = d3
    .zoom()
    .scaleExtent(p.scrollZoom ? [0.5, 8] : [1, 1])
    .translateExtent(
      p.scrollZoom
        ? [
            [-(p.width / p.zoom), -(p.width / p.zoom)],
            [p.width / p.zoom, p.width / p.zoom]
          ]
        : [
            [0, 0],
            [0, 0]
          ]
    )
    .on("zoom", zoomed);

const toggleZoom = (enabled) => {
    if (enabled) {
      // Attach zoom handlers
      svgOrigin.call(zoom);
    } else {
      // Remove all *.zoom event handlers
      svgOrigin.on(".zoom", null);
      svgOrigin.call(zoomNoFull);
    }
  };

Anyway, thank you for your help, awesome. I wish there would be a template for basic chart display with options, fulscreen mode, dowload high resolution, etc.

Best regards

Be sure to take a look at the d3-zoom docs. There you’ll find an example to reset a zoom transform, which you need to apply before you remove the handlers so that your SVG can still update:

    } else {
      svgOrigin.call(zoom.transform, d3.zoomIdentity);
      // Remove all *.zoom event handlers
      svgOrigin.on(".zoom", null);
    }
1 Like

That is much better, thank you !