Black Lives Matter. Please consider donating to Black Girls Code today.
Dash HoloViews is now available! Check out the docs.

Attempting to understand the co-ordiante frame for scene.camera.center, and scene.camera.eye

I’m tring to control the direction of view and zoom of a plotly 3D image. I need to place the camera and the centre point of the image in the same Cartesian co-ordinates as the data itself. I see that layout.scene.camera gives the position of the centre of the image and layout.scene.eye gives the position of the camera both in a 3D catesian frame (x,y,z) but the units of x,y and z are not obvious and do not line up with the data co-ordinates. The following example code produces three lines one running from [0,0,0] to [0,0,10] is blue another from [50,0,0] to [50,0,10] is red and a third is yellow and runs from [0,0,10] to [50,50,10]. I can move the center and eye positions around so that the tips of different lines line up and thus establish a mapping between the data positions and the eye/center positions. When I do this I establish that eye and center co-ordinates correspond to the line/data co-ordinates in the following way

x of 0/50 in data = x of +/-0.805 when using eye/center
y of 0/50 in data = y of +/-0.805 when using eye/center
z of 0/10 in data = z of +/-0.16 when using eye/center

I have then moved the second point of the yellow line to [50,25,10] thus halving the y extent of the data and I find

x of 0/50 in data = x of +/-1 when using eye/center
y of 0/25 in data = y of +/-0.5 when using eye/center
z of 0/10 in data = z of +/-0.2 when using eye/center

The origin of the co-ordinates system for camera/eye funcitons appears to be the centre of the plotted data and the aspect ratio is maintained the same as the data, but why is the scaling changing? and how can I map these co-ordinate systems to each other correctly. Thank you for any help, The following code is for the first case ie larger y extent with the eye and center lining up the tops of the red and blue lines.

    // set image layout
    var layout = {
       scene : {
            aspectmode: "data",
            camera: {
                    center: {x: -0.805, y: -0.805, z: 0.16 },
                    eye: {x: 1.805, y: -0.805, z: 0.16 },
                    up: {x: 0, y: 0, z: 1 }
                    },
            xaxis: {
                    visible: true
            },
            yaxis: {
                    visible: true
            },
            zaxis: {
                    visible: true
            },
            margin: {
                    l: 0,
                    r: 0,
                    b: 0,
                    t: 0
            },
            showlegend: true,
            legend: {
                   "x": "0",
                  "margin.r": "120"
           }
       }
    }
   
    // Plot yellow line 
    x=[0.0, 25.0, 50.0]
    y=[0.0, 25.0, 50.0]
    z=[10.0, 10.0, 10.0]
    Plotly.plot('tester', [{
             type: 'scatter3d',
            mode: 'lines',
            x: x,
            y: y,
            z: z,
            opacity: 0.7,
           line: {
                   width: 10,
                  color: 'yellow'}
           }], layout);

    //plot red line
    x=[50, 50]
    y=[0, 0]
    z=[0.0, 10.0]
    Plotly.plot('tester', [{
         type: 'scatter3d',
         mode: 'lines',
         x: x,
         y: y,
         z: z,
        opacity: 1,
        line: {
               width: 5,
               color: 'red'}
    }],layout);

    // Plot blue line
    x=[0.0, 0.0]
    y=[0.0, 0.0]
    z=[0.0, 10.0]
   Plotly.plot('tester', [{
       type: 'scatter3d',
      mode: 'lines',
      x: x,
      y: y,
     z: z,
    opacity: 0.7,
    line: {
      width: 10,
      color: 'blue'}
    }],layout);

edited original post to include code

Have you tried setting aspectratio?
Here is a demo.

Thank you very much for your response @archmoj and sorry to have been quiet myself (I’ve been sick).

I need the lines to maintain their real world aspects thus why I was using

aspectmode: “data”,

Of course I can still use the manual mode as you propose in which case I can use
aspectratio: {
x: 1,
y: 1,
z: 0.2
},
to maintain the correct projection. I’ve tried this as you suggest and then tried to line up the top of my red line with the top of my blue line while the viewer stands behinds the red line, using the original yellow line case again to achieve this I need to set
camera: {
center: {x: -0.4705, y: -0.4705, z:0.0941 },
eye: {x: 1.4705, y: -0.4705, z: 0.0941 },
up: {x: 0, y: 0, z: 1 }
},

And I get the following image:

The tops of the lines line up perfectly and as expected 0.0941 is 1/5th of 0.4705 so I can accurately position my camera so long as I know that 0 in x for the data corresponds to -0.4705 for the camera and 50 in x for the data 0.4705 for the camera but where does this strange 0.4705 number come from? I’m very confused and open to any help. The javascript code for this new case is below:

TESTER = document.getElementById(‘tester’);

    // set image layout
    var layout = {
       scene : {

aspectratio: {
x: 1,
y: 1,
z: 0.2
},

          // aspectmode: "data",
            camera: {
                    center: {x: -0.4705, y: -0.4705, z:0.0941 },
                    eye: {x: 1.4705, y: -0.4705, z: 0.0941 },
                    up: {x: 0, y: 0, z: 1 }
                    },
            xaxis: {
                    visible: true
            },
            yaxis: {
                    visible: true
            },
            zaxis: {
                    visible: true
            },
            margin: {
                    l: 0,
                    r: 0,
                    b: 0,
                    t: 0
            },
            showlegend: true,
            legend: {
                   "x": "0",
                  "margin.r": "120"
           }
       }
    }

    // Plot yellow line
    x=[0.0, 25.0, 50.0]
    y=[0.0, 25.0, 50.0]
    z=[10.0, 10.0, 10.0]
    Plotly.plot('tester', [{
             type: 'scatter3d',
            mode: 'lines',
            x: x,
            y: y,
            z: z,
            opacity: 0.7,
           line: {
                   width: 10,
                  color: 'yellow'}
           }], layout);

    //plot red line
    x=[50, 50]
    y=[0, 0]
    z=[0.0, 10.0]
    Plotly.plot('tester', [{
         type: 'scatter3d',
         mode: 'lines',
         x: x,
         y: y,
         z: z,
        opacity: 1,
        line: {
               width: 5,
               color: 'red'}
    }],layout);

    // Plot blue line
    x=[0.0, 0.0]
    y=[0.0, 0.0]
    z=[0.0, 10.0]
   Plotly.plot('tester', [{
       type: 'scatter3d',
      mode: 'lines',
      x: x,
      y: y,
     z: z,
    opacity: 0.7,
    line: {
      width: 10,
      color: 'blue'}
    }],layout);

The scaling is happening here:

1 Like

Thanks for the tip @archmoj, and for the suggestion I dig into the underlying code I’ll do so during the week and write back here if I have any success.

I’ve found the answer thanks to the bread crumbs @archmoj gave me. So to position the camera and the centre of the image using the same coordinates as the data that was plotted one must first convert from the data co-ordinate system to the camera co-ordinates. This conversion doesn’t seem to be published (at least I couldn’t find it). But the code segment @archmoj points to above is the appropriate code. The following assumes a 3D plot with aspectMode set to “data”, note there is a padding variable along with others which is unset in my but which might change the below in other cases.

(0,0,0) in the camera space is the centre of the data space ie ((xmin+xmax)/2, (ymin+ymax)/2,(zmin+zmax)/2) for each of the three dimensions the maximum and minimum extents of the data are

+/-(r1r2r3)^(-1/3)ri0.5*32/34

where r1->r3 are the ranges of the data in each dimension and ri is the range in the dimension for which you wish to have the maximum extents.