How to create standalone scale that is aware of Observable plot’s width, margins, etc

How can I create standalone scale that is aware of Observable plot’s width, margins, etc?

  • I need to create a standalone scale in order to use its scale.apply() method.
  • However, the resulting scale’s apply() function returns values in the range [0,1], not in terms of the plot’s dimensions (width, margin, etc).
  • Compare to plot.scale, which returns values in terms of the plot’s dimensions.
  • I cannot use plot.scale because the scale.apply() function is needed to define the marks that are part of the options when creating the plot. (Sort of a chicken-and-egg problem.)
  • I tried adding plot dimension options when creating a scale, but these are ignored.
  • Ideally, the scale could be accessed from the callback where it is used, but this does not seem possible.
  • Currently, my work-around is to create the plot twice: first to create plot.scale, then again to define the plot that uses plot.scale
  • Interestingly, if the (Svelte) component is used multiple times, only the first component needs the work-around above. So a “throw-away” component could be hidden.
  • Another work-around may be to first generate a simple plot for the scale, then use that for the “real” plot.

I have created a simple sample demonstrating the problem below:

const data = [{
    text: 'Weekend',
    utc: new Date('2024-06-16'), end: new Date('2024-06-17'),
  },{
    text: 'Weekdays',
    utc: new Date('2024-06-17'), end: new Date('2024-06-22'),
  }, {
    text: 'Weekend',
    utc: new Date('2024-06-22'), end: new Date('2024-06-23'),
  }]
  
  // Construct scale:
  const scale = Plot.scale({x: {domain: [new Date('2024-06-16'), new Date('2024-06-23')]}})
  
  // Simple plot:
  const plot = Plot.plot({
    height: 150,
    width: 800,
    marginRight: 12,
    marginLeft: 12,
    marks: [
      Plot.rectY(data, {
        x1: (d) => d.utc,
        x2: (d) => d.end,
        y: 1,
        fill: (d) => d.text == 'Weekend' ? 'lightgray': 'beige'
      }),
      Plot.text(data, {
        x: (d) => (Number(d.utc) + Number(d.end))/2,
        text: 'text'
      }),
    ],
  });
  
  function compareScales(value) {
    console.log({
      value,
      '     scale': scale.apply(value),
      'plot.scale': plot.scale('x').apply(value)
    })
  }
  
  compareScales(new Date('2024-06-16')) 
  compareScales(new Date('2024-06-17')) 
  compareScales(new Date('2024-06-23')) 
  

  const div = document.querySelector('#myplot');
  div.append(plot);
#myplot {
  height: 200px;
}
<!DOCTYPE html>
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
<script src="https://cdn.jsdelivr.net/npm/@observablehq/[email protected]"></script>

<div id="myplot"></div>