Skip to content

[Feature][Proposal] create_annotated_heatmap for JS #4116

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
mafar opened this issue Aug 9, 2019 · 2 comments
Closed

[Feature][Proposal] create_annotated_heatmap for JS #4116

mafar opened this issue Aug 9, 2019 · 2 comments

Comments

@mafar
Copy link
Contributor

mafar commented Aug 9, 2019

Dear Devs,

For Correlation matrix Graph, It is important that annotations are always displayed inside heatmap
like these
https://plot.ly/~cvillamil/4/annotated-heatmap/#/
https://codepen.io/plotly/pen/RPdwaz

Python Support:
Since there is nice function in python already, ff.create_annotated_heatmap(z)

import plotly.figure_factory as ff

z = [[.1, .3, .5, .7, .9],
     [1, .8, .6, .4, .2],
     [.2, 0, .5, .7, .9],
     [.9, .8, .4, .2, 0],
     [.3, .4, .5, .7, 1]]

fig = ff.create_annotated_heatmap(z)
fig.show()

Javascript Support:
Same should be supported in javascript , instead of workaround where one has to loop throught and create annotations manually

My suggestion would be to create a new Trace type Correlation which could be sub type of heatmap origin

@mafar mafar changed the title [Request][JS]Correlation matrix auto Annotated support [Request][JS]Correlation matrix, auto annotate support Aug 9, 2019
@mafar mafar changed the title [Request][JS]Correlation matrix, auto annotate support [Feature][Proposal]Correlation matrix, auto annotate support Aug 9, 2019
@mafar
Copy link
Contributor Author

mafar commented Aug 9, 2019

CodePen: https://codepen.io/mafar/pen/RXBpXO?editors=0010

Implementation:
standalone implementation of all functions from _annotated_heatmap.py for auto annotation.

  1. I used same variables & function names from _annotated_heatmap.py to make cross checking easy
  2. Only hexToRgb is a function which does not exist in _annotated_heatmap.py but it should be somewhere in plotly.js

I am sure it can be now easily integrated in plotly

'use strict';
// 
// https://github.com/plotly/plotly.js/issues/4116
// [Feature][Proposal] create_annotated_heatmap for JS
//
/*
   copied from _annotated_heatmap.py
   src: https://github.com/plotly/plotly.py/blob/master/packages/python/plotly/plotly/figure_factory/_annotated_heatmap.py
*/
//
// copied from _annotated_heatmap.py
//
function get_z_mid(z) {
  var z_max = Math.max.apply(
    null,
    z.map(function (row) {
      return Math.max.apply(Math, row);
    })
  );
  var z_min = Math.min.apply(
    null,
    z.map(function (row) {
      return Math.min.apply(Math, row);
    })
  );
  var z_mid = (z_max + z_min) / 2;
  return z_mid;
}
// custom function
function hexToRgb(hex) {
  var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  hex = hex.replace(shorthandRegex, function (m, r, g, b) {
    return r + r + g + g + b + b;
  });
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  var r = parseInt(result[1], 16);
  var g = parseInt(result[2], 16);
  var b = parseInt(result[3], 16);
  return result ? [r, g, b] : null;
}
//
// copied from _annotated_heatmap.py
//
function to_rgb_color_list(color_str, Default) {
  if (color_str.includes('rgb')) {
    return color_str.replace(/[^0-9.,]+/g, '').split(',');
  } else if (color_str.includes('#')) {
    return hexToRgb(color_str);
  } else {
    return Default;
  }
}
//
// copied from _annotated_heatmap.py
//
function should_use_black_text(background_color) {
  return (
    background_color[0] * 0.299 +
    background_color[1] * 0.587 +
    background_color[2] * 0.114
  ) > 186;
}
//
// copied from _annotated_heatmap.py
//
function get_text_color(colorscale, reversescale) {
  var white = '#FFFFFF';
  var black = '#000000';
  var colorscales = ['Greys', 'Greens', 'Blues', 'YIGnBu', 'YIOrRd', 'RdBu', 'Picnic', 'Jet', 'Hot', 'Blackbody', 'Earth', 'Electric', 'Viridis', 'Cividis'];
  var colorscales_reverse = ['Reds'];
  var min_text_color = black;
  var max_text_color = black;
  if ((colorscales.indexOf(colorscale) > -1) && reversescale) {
    min_text_color = black;
    max_text_color = white;
  } else if ((colorscales.indexOf(colorscale) > -1)) {
    min_text_color = white;
    max_text_color = black;
  } else if ((colorscales_reverse.indexOf(colorscale) > -1) && reversescale) {
    min_text_color = white;
    max_text_color = black;
  } else if ((colorscales_reverse.indexOf(colorscale) > -1)) {
    min_text_color = black;
    max_text_color = white;
  } else if (Array.isArray(colorscale)) {
    var _min_col = to_rgb_color_list(colorscale.slice(0)[0][1], [255, 255, 255]);
    var _max_col = to_rgb_color_list(colorscale.slice(-1)[0][1], [255, 255, 255]);
    var min_col = _min_col;
    var max_col = _max_col;
    if (reversescale) {
      // swap min max
      min_col = _max_col;
      max_col = _min_col;
    }
    if (should_use_black_text(min_col)) {
      min_text_color = black;
    } else {
      min_text_color = white;
    }
    if (should_use_black_text(max_col)) {
      max_text_color = black;
    } else {
      max_text_color = white;
    }
  }
  return [min_text_color, max_text_color];
}
//
// copied from _annotated_heatmap.py
//
function make_annotations(_x, _y, _z, colorscale) {
  var text_colors = get_text_color(colorscale);
  var min_text_color = text_colors[0];
  var max_text_color = text_colors[1];
  var z_mid = get_z_mid(_z);
  var annotations = [];
  for (var n = 0; n < _z.length; n++) {
    var row = _z[n];
    for (var m = 0; m < row.length; m++) {
      var val = row[m];
      var font_color = (val < z_mid) ? min_text_color : max_text_color;
      annotations.push({
        xref: 'x1',
        yref: 'y1',
        x: _x[m],
        y: _y[n],
        text: _z[n][m],
        font: {
          family: 'Arial',
          size: 12,
          color: font_color
        },
        showarrow: false
      });
    }
  }
  return annotations;
}
//
// Draw Plot
//
var xValues = ['A', 'B', 'C', 'D', 'E'];
var yValues = ['W', 'X', 'Y', 'Z'];
var zValues = [
  [0.0, 0.0, 0.75, 0.75, 0.0],
  [0.0, 0.0, 0.75, 0.75, 0.0],
  [0.75, 0.75, 0.75, 0.75, 0.75],
  [0.0, 0.0, 0.0, 0.75, 0.0]
];
var colorscale = [
  [0, '#3D9970'],
  [1, '#001f3f']
];
//var colorscale = 'Viridis';
var data = [{
  x: xValues,
  y: yValues,
  z: zValues,
  type: 'heatmap',
  colorscale: colorscale,
  showscale: false
}];
var layout = {
  title: 'Correlation Matrix auto_annotated',
  annotations: make_annotations(xValues, yValues, zValues, colorscale)
};
Plotly.newPlot('myDiv', data, layout);

@mafar mafar changed the title [Feature][Proposal]Correlation matrix, auto annotate support [Feature][Proposal] create_annotated_heatmap for JS Aug 9, 2019
@etpinard
Copy link
Contributor

etpinard commented Aug 9, 2019

duplicate of #1116

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants