Skip to content

CircularHeatmaps

Lía Da Silva Rojas edited this page Feb 28, 2024 · 7 revisions

About the Queries

  • http://10.8.8.148/eds/tcsensors?telescope=clay
  • http://10.8.8.148/eds/tctemp?telescope=clay

diagram

  • End-Point: From where the data is received
  • Type of query: tctemp contains ['id' and 'temperature'] / tcsensors contains ['id', 'x', 'y', 'z'], through a data transform we join both queries.
  • Telescope: Either 'baade' or 'clay', no capital letters

Data transforms to join queries

data transforms

  1. Merge : Joins both queries by id, but does not group them, so there are repeated ids with different data.
  2. Convert Field Type : We need temperature values as numbers, for some reason that declaration does not work on the back-end parser.
  3. Group By : Groups all ids into one row. We must declare which columns are calculated and which one we are going to group by.
  4. Filter Data By Values : The data does not come clean or normalized, some sensors do not work, and should not be graphed, those are in the coordinates (0,0,0), so we filter them out, there are also sensors that mark '-999' as temperature values, those are also filtered out. We also filter out null values.

Plotly

The Plotly plugin is based on the "Plotly js" library. In the script section of the plugin, you must enter the js code that returns a dictionary with "data", "layout" and "config" (There are more options but those are used in all maps).

Objective

For the Mirror Graph purposes, 3 sections (Top, Middle and Bottom) must be shown in addition to 2 types (Relative and Absolute). For this we need 6 graphs, and in the technical sense we need the x and y coordinates of the sensors for the 3 layers, and for absolute and relative temperatures, x and y are the same obtained previously, but the z coordinate (the one that declares the heat) changes for the 6 graphs.

Obtaining Raw Data

//Obtain Raw Data
const x = data.series[0].fields[1].values;
const y = data.series[0].fields[2].values;
let layer = data.series[0].fields[3].values;
let temps = data.series[0].fields[4].values;
let ids = data.series[0].fields[0].values;
...

We obtain the data that grafana (and our queries provide), for example on the line const x= data.series[0].fields[2].values; the list named data is a reserved value, that stores all the data obtained by the data selector (our queries), then the sub-list series stores all the queries avaible on the data source, the sub-sub-list fields stores all the fields avaible on said query, finally the values stores a list with all the avaible values from the query.

Separating by Layers

Then we apply this function to obtain values from layers (we did 3 separate functions for each layer, but they can be fused into one).

function all_vals_layer(arr, layer){
  return arr.map(function(val,index){
    if (layer[index] == layer){
      return val
    }
  })
};

By applying it to each variable we will obtain "x", "y" and "z" for each layer. Furthermore, to obtain the relative temperature, the absolute temperature is "mapped" and the air temperature is subtracted from it, the air temperature is obtained through a global grafana variable.

var airTemp = variables.temperature.current.value

let z1a = all_vals_layer(temps,1);
const x1 = all_vals_layer(x,1);
const y1 = all_vals_layer(y,1);
let z1r = z1a.map(function(val, index){
  return val-airTemp});
let labels1 = all_vals_layer(ids,1);
...

Resizing the Graph

To make the heatmap the required dimensions, coordinates are added to each corner, along with NaN values ​​for z.

//Amplify size of heatmaps
x1.push(-7,7);
y1.push(-7,7);
z1r.push(NaN,NaN);
z1a.push(NaN,NaN);

Tooltip Generation

For the tooltip, it is necessary to unify the id with the temperature, because since they are at the same point, the program shows only one, making it non-readable.

//Text for the tooltips
var text1a = z1a.map(function(val, index){
  return `
    Temperature: ${Math.round(val*100)/100}
    Sensor: ${labels1[index]}
  `
});

The Scatter Graph

For each layer and type (Absolute and Relative), a Scatter-type graph is generated to illustrate the position of the sensors in the mirror, as the tooltip will go embedded to the heatmap, "showlegend" is indicated as false.

// Sensors Scatter Graph
var sensors1 = {
  name: 'sensor names',
  x: x1,
  y: y1,
  xaxis: 'x1',
  yaxis: 'y1',
  type: 'scatter',
  mode: 'markers',
  textposition: 'top center',
  textfont: {
    family:  'Raleway, sans-serif'
  },
  marker: { size: 3, color: 'black' },
  showlegend: false
};

Heatmap Graph

The heatmap of the "Relative" type is made in the contour type, the text for the tooltip is the one created with the temperature and the sensor id. As x and y are slightly offset, the map would interpret it as a separation and would not interpolate the values. To solve this issue the values ​​are rounded. The coloraxis and colorbar section indicate the scale that will be used in the map, colorbar is a variable declared at the beginning. axis and yaxis values must match those of the scattermap, so that they are modeled on top of each other.

// Colorbar declaration
var colorbarR = {
  len: 0.5,
  y: 1,
  yanchor: 'top',
  yref: 'paper'
};
// Obtain the maximum and minimum values
var contoursR = {
  start: -1,
  end: 1
};
//Relative Heatmaps
var heatmap1R = {
  type: 'contour',
  name: 'heat',
  text: text1r,
  hoverinfo: 'text',
  x: round(x1),
  y: round(y1),
  z: z1r,
  coloraxis: 'coloraxisR',
  colorbar: colorbarR,
  xaxis: 'x1',
  yaxis: 'y1',
  zsmooth: "best",
  zauto: false,
  connectgaps: true,
  xtype: 'scaled',
  contours: contoursR
};

Shape Generation

Since all shapes are repeated 6 times, we create a function that can generate them into a list.

Description of fields of dict_shape:

  • xref e yref: Shape position refferences, they must change for each shape, declaring to which graph they correspond.
  • xsizemode e ysizemode: The way the shape is graphed.
  • type: in this case is an SVG so we declare "path".
  • path: SVG code.
  • fillcolor: color of the figure fill.
  • line: line type of the shape.
//Get Shapes
function getShapes(){
  var all_shapes = []
  for(let i = 0; i < 6; i++){
    xref = 'x' + (i+1)
    yref = 'y' + (i+1)
    console.log(xref)
    dict_shape = 
    {
      xref: xref,
      yref: yref,
      xsizemode:'scale',
      ysizemode:'scale',
      type: 'path',
      path: 'M 5.779 0.0844 C 5.779 3.5854 3.1458 6.4234 -0.1025 6.4234 C -3.3507 6.4234 -5.9839 3.5854 -5.9839 0.0844 C -5.9839 -3.4165 -3.3507 -6.2546 -0.1025 -6.2546 C 3.1458 -6.2546 5.779 -3.4165 5.779 0.0844 C 5.779 0.0844 5.779 0.0844 5.779 0.0844 M -9.4279 -9.315 L 9.315 -9.315 L 9.315 9.315 L -9.4279 9.315 L -9.4279 -9.315',
      fillcolor: 'white',
      line: {
        color: 'white',
        width: 0
      }
    }
    all_shapes.push(dict_shape)
  }
  return all_shapes;
}

Layout declaration

Finally, the layout and config dictionaries are declared. The layout part is the longest, for the purposes of this section we will focus on the Relative graph of layer 1. Below are the descriptions of each variable in the dictionary:

  • annotations: Consists of the graph titles.
  • width y height: In this version they are hardcoded, but they can be configured as relative, however that might break the graph.
  • xaxis: It must be declared as numeric, the default value is date.
  • coloraxisR: It is a dictionary that contains the color scale and the numerical scale.
  • shapes: It is a list with all the figures in the graph, represented as a dictionary. We return the values from the previous function.
  • grid: The way graphs are distributed.
//Layout
var layout_circle ={
  annotations: annotations,
  width: '1110',
  height: '800',
  xaxis: {
    type: 'numeric'
  },
...
  coloraxisR: {
    colorscale: 'RdBu',
    showscale: true,
  },
  shapes: getShapes(),
  grid :{
    rows: 2,
    columns: 3,
    pattern: 'independent'
  }
}

Graph Configuration

The config section is used to remove unnecessary buttons.

config = {
  modeBarButtonsToRemove : ['zoom2d','pan2d','lasso2d','zoomIn2d','zoomOut2d','autoScale2d','resetScale2d','select2d']
}

The Final variables

Finally, the graphics you want to graph are declared, and the dictionary is returned with "data", "layout" and "config".

var data = [sensors1,sensors1A, heatmap1A, heatmap1R, sensors2,sensors2A,heatmap2A, heatmap2R, sensors3,sensors3A, heatmap3A, heatmap3R];
return{data: data, layout: layout_circle, config: config}

Results

Baade Telescope

  • mirror heatmap mirror heatmap
  • cell heatmap cell heatmap

Clay Telescope

  • mirror heatmap mirror heatmap
  • cell heatmap cell heatmap