<commit_before>var Emitter = require('emitter-component'); var DataSet = require('../DataSet');
var DataView = require('../DataView');
var util = require('../util');
var Point3d = require('./Point3d');
var Point2d = require('./Point2d');
var Camera = require('./Camera');
var Filter = require('./Filter');
var Slider = require('./Slider');
var StepNumber = require('./StepNumber');

// -----------------------------------------------------------------------------
// Definitions private to module
// -----------------------------------------------------------------------------

/// enumerate the available styles
var STYLE = {
  BAR     : 0,
  BARCOLOR: 1,
  BARSIZE : 2,
  DOT     : 3,
  DOTLINE : 4,
  DOTCOLOR: 5,
  DOTSIZE : 6,
  GRID    : 7,
  LINE    : 8,
  SURFACE : 9
};


/**
 * Field names in the options hash which are of relevance to the user.
 *
 * Specifically, these are the fields which require no special handling,
 * and can be directly copied over.
 */
var OPTIONKEYS = [
  'width',
  'height',
  'filterLabel',
  'legendLabel',
  'xLabel',
  'yLabel',
  'zLabel',
  'xValueLabel',
  'yValueLabel',
  'zValueLabel',
  'showGrid',
  'showPerspective',
  'showShadow',
  'keepAspectRatio',
  'verticalRatio',
  'showAnimationControls',
  'animationInterval',
  'animationPreload',
  'animationAutoStart',
  'axisColor',
  'gridColor',
  'xCenter',
  'yCenter'
];


/**
 * Default values for certain option fields.
 *
 * These are the values used when a Graph3d instance is initialized
 * without custom settings.
 *
 * If a field is not in this list, a default value of 'undefined' can
 * be assumed. Of course, it does no harm to set a field explicitly to
 * 'undefined' here.
 *
 * A value of 'undefined' here normally means:
 *
 *     'derive from current data and graph style'
 *
 * In the code, this is indicated by the comment 'auto by default'.
 */
var DEFAULTS = {
  width            : '400px',
  height           : '400px',
  filterLabel      : 'time',
  legendLabel      : 'value',
  xLabel           : 'x',
  yLabel           : 'y',
  zLabel           : 'z',
  xValueLabel      : function(v) { return v; },
  yValueLabel      : function(v) { return v; },
  zValueLabel      : function(v) { return v; },
  showGrid         : true,
  showPerspective  : true,
  showShadow       : false,
  keepAspectRatio  : true,
  verticalRatio    : 0.5,           // 0.1 to 1.0, where 1.0 results in a 'cube'

  showAnimationControls: undefined, // auto by default
  animationInterval    : 1000,      // milliseconds
  animationPreload     : false,
  animationAutoStart   : undefined, // auto by default

  axisColor        : '#4D4D4D',
  gridColor        : '#D3D3D3',
  xCenter          : '55%',
  yCenter          : '50%',


  // Following not in OPTIONKEYS because they require special handling,

  style            : STYLE.DOT,     // Can't use Graph3d.STYLE here, not defined yet
  backgroundColor  : undefined,

  dataColor        : {
    fill       : '#7DC1FF',
    stroke     : '#3267D2',
    strokeWidth: 1                  // px
  },

  cameraPosition   : {
     horizontal: 1.0,
     vertical  : 0.5,
     distance  : 1.7
  }
};



/**
 * forcibly copy fields from src to dst in a controlled manner.
 *
 * A given field in dst will always be overwitten. If this field
 * is undefined or not present in src, the field in dst will 
 * be explicitly set to undefined.
 * 
 * The intention here is to be able to reset all option fields.
 * 
 * Only the fields mentioned in array 'fields' will be handled.
 *
 * @param fields  array with names of fields to copy
 */
function forceCopy(src, dst, fields) {
  for (var i in fields) {
    var field = fields[i];
    dst[field] = src[field];
  }
}


/**
 * Copy fields from src to dst in a safe and controlled manner.
 *
 * Only the fields mentioned in array 'fields' will be copied over,
 * and only if these are actually defined.
 *
 * @param fields  array with names of fields to copy
 */
function safeCopy(src, dst, fields) {
  for (var i in fields) {
    var field = fields[i];

    if (src[field] !== undefined) {
      dst[field] = src[field];
    }
  }
}



// -----------------------------------------------------------------------------
// Class Graph3d
// -----------------------------------------------------------------------------

/**
 * Enumerate the available styles.
 *
 * This definition retained for external compatibility
 * (It should be internal, but you never know)
 */
Graph3d.STYLE = STYLE;


/**
 * @constructor Graph3d
 * Graph3d displays data in 3d.
 *
 * Graph3d is developed in javascript as a Google Visualization Chart.
 *
 * @param {Element} container   The DOM element in which the Graph3d will
 *                              be created. Normally a div element.
 * @param {DataSet | DataView | Array} [data]
 * @param {Object} [options]
 */
function Graph3d(container, data, options) {
  if (!(this instanceof Graph3d)) {
    throw new SyntaxError('Constructor must be called with the new operator');
  }

  // create variables and set default values
  this.containerElement = container;

  this.dataTable = null;  // The original data table
  this.dataPoints = null; // The table with point objects

  // create a frame and canvas
  this.create();

  //
  // Start Settings
  //

  // Handle the defaults which can be simply copied over
  forceCopy(DEFAULTS, this, OPTIONKEYS);

  // Following are internal fields, not part of the user settings
  this.margin = 10; // px
  this.showGrayBottom = false; // TODO: this does not work correctly
  this.showTooltip = false;
  this.dotSizeRatio = 0.02; // size of the dots as a fraction of the graph width

  // Handle the more complex ('special') fields
  this._setSpecialSettings(DEFAULTS, this);

  // The rest of the fields.
  // These require special attention in some way
  // TODO: handle these

  this.showLegend  = undefined; // auto by default (based on graph style)

  this.eye = new Point3d(0, 0, -1);  // TODO: set eye.z about 3/4 of the width of the window?

  // the column indexes
  this.colX = undefined;
  this.colY = undefined;
  this.colZ = undefined;
  this.colValue = undefined;
  this.colFilter = undefined;

  this.xMin = 0;
  this.xStep = undefined; // auto by default
  this.xMax = 1;
  this.yMin = 0;
  this.yStep = undefined; // auto by default
  this.yMax = 1;
  this.zMin = 0;
  this.zStep = undefined; // auto by default
  this.zMax = 1;
  this.valueMin = 0;
  this.valueMax = 1;
  this.xBarWidth = 1;
  this.yBarWidth = 1;
  // TODO: customize axis range

  //
  // End Settings
  //

  // apply options (also when undefined)
  this.setOptions(options);

  // apply data
  if (data) {
    this.setData(data);
  }
}

// Extend Graph3d with an Emitter mixin
Emitter(Graph3d.prototype);

/**
 * Calculate the scaling values, dependent on the range in x, y, and z direction
 */
Graph3d.prototype._setScale = function() {
  this.scale = new Point3d(1 / (this.xMax - this.xMin),
    1 / (this.yMax - this.yMin),
    1 / (this.zMax - this.zMin));

  // keep aspect ration between x and y scale if desired
  if (this.keepAspectRatio) {
    if (this.scale.x < this.scale.y) {
      //noinspection JSSuspiciousNameCombination
      this.scale.y = this.scale.x;
    }
    else {
      //noinspection JSSuspiciousNameCombination
      this.scale.x = this.scale.y;
    }
  }

  // scale the vertical axis
  this.scale.z *= this.verticalRatio;
  // TODO: can this be automated? verticalRatio?

  // determine scale for (optional) value
  this.scale.value = 1 / (this.valueMax - this.valueMin);

  // position the camera arm
  var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  this.camera.setArmLocation(xCenter, yCenter, zCenter);
};


/**
 * Convert a 3D location to a 2D location on screen
 * http://en.wikipedia.org/wiki/3D_projection
 * @param {Point3d} point3d   A 3D point with parameters x, y, z
 * @return {Point2d} point2d  A 2D point with parameters x, y
 */
Graph3d.prototype._convert3Dto2D = function(point3d) {
  var translation = this._convertPointToTranslation(point3d);
  return this._convertTranslationToScreen(translation);
};

/**
 * Convert a 3D location its translation seen from the camera
 * http://en.wikipedia.org/wiki/3D_projection
 * @param {Point3d} point3d    A 3D point with parameters x, y, z
 * @return {Point3d} translation A 3D point with parameters x, y, z This is
 *                   the translation of the point, seen from the
 *                   camera
 */
Graph3d.prototype._convertPointToTranslation = function(point3d) {
  var ax = point3d.x * this.scale.x,
    ay = point3d.y * this.scale.y,
    az = point3d.z * this.scale.z,

    cx = this.camera.getCameraLocation().x,
    cy = this.camera.getCameraLocation().y,
    cz = this.camera.getCameraLocation().z,

  // calculate angles
    sinTx = Math.sin(this.camera.getCameraRotation().x),
    cosTx = Math.cos(this.camera.getCameraRotation().x),
    sinTy = Math.sin(this.camera.getCameraRotation().y),
    cosTy = Math.cos(this.camera.getCameraRotation().y),
    sinTz = Math.sin(this.camera.getCameraRotation().z),
    cosTz = Math.cos(this.camera.getCameraRotation().z),

  // calculate translation
    dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
    dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
    dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));

  return new Point3d(dx, dy, dz);
};

/**
 * Convert a translation point to a point on the screen
 * @param {Point3d} translation   A 3D point with parameters x, y, z This is
 *                    the translation of the point, seen from the
 *                    camera
 * @return {Point2d} point2d    A 2D point with parameters x, y
 */
Graph3d.prototype._convertTranslationToScreen = function(translation) {
  var ex = this.eye.x,
    ey = this.eye.y,
    ez = this.eye.z,
    dx = translation.x,
    dy = translation.y,
    dz = translation.z;

  // calculate position on screen from translation
  var bx;
  var by;
  if (this.showPerspective) {
    bx = (dx - ex) * (ez / dz);
    by = (dy - ey) * (ez / dz);
  }
  else {
    bx = dx * -(ez / this.camera.getArmLength());
    by = dy * -(ez / this.camera.getArmLength());
  }

  // shift and scale the point to the center of the screen
  // use the width of the graph to scale both horizontally and vertically.
  return new Point2d(
    this.currentXCenter + bx * this.frame.canvas.clientWidth,
    this.currentYCenter - by * this.frame.canvas.clientWidth);
};


/**
 * Calculate the translations and screen positions of all points
 */
Graph3d.prototype._calcTranslations = function(points, sort) {
  if (sort === undefined) {
    sort = true;
  }

  for (var i = 0; i < points.length; i++) {
    var point    = points[i];
    point.trans  = this._convertPointToTranslation(point.point);
    point.screen = this._convertTranslationToScreen(point.trans);

    // calculate the translation of the point at the bottom (needed for sorting)
    var transBottom = this._convertPointToTranslation(point.bottom);
    point.dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  }

  if (!sort) {
    return;
  }

  // sort the points on depth of their (x,y) position (not on z)
  var sortDepth = function (a, b) {
    return b.dist - a.dist;
  };
  points.sort(sortDepth);
};


// -----------------------------------------------------------------------------
//  Methods for handling settings
// -----------------------------------------------------------------------------


/**
 * Special handling for certain parameters
 *
 * 'Special' here means: setting requires more than a simple copy
 */
Graph3d.prototype._setSpecialSettings = function(src, dst) {
  if (src.backgroundColor !== undefined) {
    this._setBackgroundColor(src.backgroundColor, dst);
  }

  this._setDataColor(src.dataColor, dst);
  this._setStyle(src.style, dst);
  this._setCameraPosition(src.cameraPosition, dst);

/* TODO

  if (src.tooltip !== undefined) {
    dst.showTooltip = src.tooltip;
  }

End TODO */
}


Graph3d.prototype._setStyle = function(style, dst) {
  if (style === undefined) {
    return;   // Nothing to do
  }

  var styleNumber;

  if (typeof style === 'string') {
    styleNumber = this._getStyleNumber(style);

    if (styleNumber === -1 ) {
      throw new Error('Style \'' + style + '\' is invalid');
    }
  } else {
    // Do a pedantic check on style number value
    var valid = false;
    for (var n in STYLE) {
      if (STYLE[n] === style) {
        valid = true;
        break;
      }
    }<commit_msg>Adjusted STYLE enum to previous<commit_after>var Emitter = require('emitter-component'); var DataSet = require('../DataSet');
var DataView = require('../DataView');
var util = require('../util');
var Point3d = require('./Point3d');
var Point2d = require('./Point2d');
var Camera = require('./Camera');
var Filter = require('./Filter');
var Slider = require('./Slider');
var StepNumber = require('./StepNumber');

// -----------------------------------------------------------------------------
// Definitions private to module
// -----------------------------------------------------------------------------

/// enumerate the available styles
Graph3d.STYLE = {
  BAR     : 0,
  BARCOLOR: 1,
  BARSIZE : 2,
  DOT     : 3,
  DOTLINE : 4,
  DOTCOLOR: 5,
  DOTSIZE : 6,
  GRID    : 7,
  LINE    : 8,
  SURFACE : 9
};


/**
 * Field names in the options hash which are of relevance to the user.
 *
 * Specifically, these are the fields which require no special handling,
 * and can be directly copied over.
 */
var OPTIONKEYS = [
  'width',
  'height',
  'filterLabel',
  'legendLabel',
  'xLabel',
  'yLabel',
  'zLabel',
  'xValueLabel',
  'yValueLabel',
  'zValueLabel',
  'showGrid',
  'showPerspective',
  'showShadow',
  'keepAspectRatio',
  'verticalRatio',
  'showAnimationControls',
  'animationInterval',
  'animationPreload',
  'animationAutoStart',
  'axisColor',
  'gridColor',
  'xCenter',
  'yCenter'
];


/**
 * Default values for certain option fields.
 *
 * These are the values used when a Graph3d instance is initialized
 * without custom settings.
 *
 * If a field is not in this list, a default value of 'undefined' can
 * be assumed. Of course, it does no harm to set a field explicitly to
 * 'undefined' here.
 *
 * A value of 'undefined' here normally means:
 *
 *     'derive from current data and graph style'
 *
 * In the code, this is indicated by the comment 'auto by default'.
 */
var DEFAULTS = {
  width            : '400px',
  height           : '400px',
  filterLabel      : 'time',
  legendLabel      : 'value',
  xLabel           : 'x',
  yLabel           : 'y',
  zLabel           : 'z',
  xValueLabel      : function(v) { return v; },
  yValueLabel      : function(v) { return v; },
  zValueLabel      : function(v) { return v; },
  showGrid         : true,
  showPerspective  : true,
  showShadow       : false,
  keepAspectRatio  : true,
  verticalRatio    : 0.5,           // 0.1 to 1.0, where 1.0 results in a 'cube'

  showAnimationControls: undefined, // auto by default
  animationInterval    : 1000,      // milliseconds
  animationPreload     : false,
  animationAutoStart   : undefined, // auto by default

  axisColor        : '#4D4D4D',
  gridColor        : '#D3D3D3',
  xCenter          : '55%',
  yCenter          : '50%',


  // Following not in OPTIONKEYS because they require special handling,

  style            : Graph3d.STYLE.DOT,
  backgroundColor  : undefined,

  dataColor        : {
    fill       : '#7DC1FF',
    stroke     : '#3267D2',
    strokeWidth: 1                  // px
  },

  cameraPosition   : {
     horizontal: 1.0,
     vertical  : 0.5,
     distance  : 1.7
  }
};



/**
 * forcibly copy fields from src to dst in a controlled manner.
 *
 * A given field in dst will always be overwitten. If this field
 * is undefined or not present in src, the field in dst will 
 * be explicitly set to undefined.
 * 
 * The intention here is to be able to reset all option fields.
 * 
 * Only the fields mentioned in array 'fields' will be handled.
 *
 * @param fields  array with names of fields to copy
 */
function forceCopy(src, dst, fields) {
  for (var i in fields) {
    var field = fields[i];
    dst[field] = src[field];
  }
}


/**
 * Copy fields from src to dst in a safe and controlled manner.
 *
 * Only the fields mentioned in array 'fields' will be copied over,
 * and only if these are actually defined.
 *
 * @param fields  array with names of fields to copy
 */
function safeCopy(src, dst, fields) {
  for (var i in fields) {
    var field = fields[i];

    if (src[field] !== undefined) {
      dst[field] = src[field];
    }
  }
}



// -----------------------------------------------------------------------------
// Class Graph3d
// -----------------------------------------------------------------------------


/**
 * @constructor Graph3d
 * Graph3d displays data in 3d.
 *
 * Graph3d is developed in javascript as a Google Visualization Chart.
 *
 * @param {Element} container   The DOM element in which the Graph3d will
 *                              be created. Normally a div element.
 * @param {DataSet | DataView | Array} [data]
 * @param {Object} [options]
 */
function Graph3d(container, data, options) {
  if (!(this instanceof Graph3d)) {
    throw new SyntaxError('Constructor must be called with the new operator');
  }

  // create variables and set default values
  this.containerElement = container;

  this.dataTable = null;  // The original data table
  this.dataPoints = null; // The table with point objects

  // create a frame and canvas
  this.create();

  //
  // Start Settings
  //

  // Handle the defaults which can be simply copied over
  forceCopy(DEFAULTS, this, OPTIONKEYS);

  // Following are internal fields, not part of the user settings
  this.margin = 10; // px
  this.showGrayBottom = false; // TODO: this does not work correctly
  this.showTooltip = false;
  this.dotSizeRatio = 0.02; // size of the dots as a fraction of the graph width

  // Handle the more complex ('special') fields
  this._setSpecialSettings(DEFAULTS, this);

  // The rest of the fields.
  // These require special attention in some way
  // TODO: handle these

  this.showLegend  = undefined; // auto by default (based on graph style)

  this.eye = new Point3d(0, 0, -1);  // TODO: set eye.z about 3/4 of the width of the window?

  // the column indexes
  this.colX = undefined;
  this.colY = undefined;
  this.colZ = undefined;
  this.colValue = undefined;
  this.colFilter = undefined;

  this.xMin = 0;
  this.xStep = undefined; // auto by default
  this.xMax = 1;
  this.yMin = 0;
  this.yStep = undefined; // auto by default
  this.yMax = 1;
  this.zMin = 0;
  this.zStep = undefined; // auto by default
  this.zMax = 1;
  this.valueMin = 0;
  this.valueMax = 1;
  this.xBarWidth = 1;
  this.yBarWidth = 1;
  // TODO: customize axis range

  //
  // End Settings
  //

  // apply options (also when undefined)
  this.setOptions(options);

  // apply data
  if (data) {
    this.setData(data);
  }
}

// Extend Graph3d with an Emitter mixin
Emitter(Graph3d.prototype);

/**
 * Calculate the scaling values, dependent on the range in x, y, and z direction
 */
Graph3d.prototype._setScale = function() {
  this.scale = new Point3d(1 / (this.xMax - this.xMin),
    1 / (this.yMax - this.yMin),
    1 / (this.zMax - this.zMin));

  // keep aspect ration between x and y scale if desired
  if (this.keepAspectRatio) {
    if (this.scale.x < this.scale.y) {
      //noinspection JSSuspiciousNameCombination
      this.scale.y = this.scale.x;
    }
    else {
      //noinspection JSSuspiciousNameCombination
      this.scale.x = this.scale.y;
    }
  }

  // scale the vertical axis
  this.scale.z *= this.verticalRatio;
  // TODO: can this be automated? verticalRatio?

  // determine scale for (optional) value
  this.scale.value = 1 / (this.valueMax - this.valueMin);

  // position the camera arm
  var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  this.camera.setArmLocation(xCenter, yCenter, zCenter);
};


/**
 * Convert a 3D location to a 2D location on screen
 * http://en.wikipedia.org/wiki/3D_projection
 * @param {Point3d} point3d   A 3D point with parameters x, y, z
 * @return {Point2d} point2d  A 2D point with parameters x, y
 */
Graph3d.prototype._convert3Dto2D = function(point3d) {
  var translation = this._convertPointToTranslation(point3d);
  return this._convertTranslationToScreen(translation);
};

/**
 * Convert a 3D location its translation seen from the camera
 * http://en.wikipedia.org/wiki/3D_projection
 * @param {Point3d} point3d    A 3D point with parameters x, y, z
 * @return {Point3d} translation A 3D point with parameters x, y, z This is
 *                   the translation of the point, seen from the
 *                   camera
 */
Graph3d.prototype._convertPointToTranslation = function(point3d) {
  var ax = point3d.x * this.scale.x,
    ay = point3d.y * this.scale.y,
    az = point3d.z * this.scale.z,

    cx = this.camera.getCameraLocation().x,
    cy = this.camera.getCameraLocation().y,
    cz = this.camera.getCameraLocation().z,

  // calculate angles
    sinTx = Math.sin(this.camera.getCameraRotation().x),
    cosTx = Math.cos(this.camera.getCameraRotation().x),
    sinTy = Math.sin(this.camera.getCameraRotation().y),
    cosTy = Math.cos(this.camera.getCameraRotation().y),
    sinTz = Math.sin(this.camera.getCameraRotation().z),
    cosTz = Math.cos(this.camera.getCameraRotation().z),

  // calculate translation
    dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
    dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
    dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));

  return new Point3d(dx, dy, dz);
};

/**
 * Convert a translation point to a point on the screen
 * @param {Point3d} translation   A 3D point with parameters x, y, z This is
 *                    the translation of the point, seen from the
 *                    camera
 * @return {Point2d} point2d    A 2D point with parameters x, y
 */
Graph3d.prototype._convertTranslationToScreen = function(translation) {
  var ex = this.eye.x,
    ey = this.eye.y,
    ez = this.eye.z,
    dx = translation.x,
    dy = translation.y,
    dz = translation.z;

  // calculate position on screen from translation
  var bx;
  var by;
  if (this.showPerspective) {
    bx = (dx - ex) * (ez / dz);
    by = (dy - ey) * (ez / dz);
  }
  else {
    bx = dx * -(ez / this.camera.getArmLength());
    by = dy * -(ez / this.camera.getArmLength());
  }

  // shift and scale the point to the center of the screen
  // use the width of the graph to scale both horizontally and vertically.
  return new Point2d(
    this.currentXCenter + bx * this.frame.canvas.clientWidth,
    this.currentYCenter - by * this.frame.canvas.clientWidth);
};


/**
 * Calculate the translations and screen positions of all points
 */
Graph3d.prototype._calcTranslations = function(points, sort) {
  if (sort === undefined) {
    sort = true;
  }

  for (var i = 0; i < points.length; i++) {
    var point    = points[i];
    point.trans  = this._convertPointToTranslation(point.point);
    point.screen = this._convertTranslationToScreen(point.trans);

    // calculate the translation of the point at the bottom (needed for sorting)
    var transBottom = this._convertPointToTranslation(point.bottom);
    point.dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  }

  if (!sort) {
    return;
  }

  // sort the points on depth of their (x,y) position (not on z)
  var sortDepth = function (a, b) {
    return b.dist - a.dist;
  };
  points.sort(sortDepth);
};


// -----------------------------------------------------------------------------
//  Methods for handling settings
// -----------------------------------------------------------------------------


/**
 * Special handling for certain parameters
 *
 * 'Special' here means: setting requires more than a simple copy
 */
Graph3d.prototype._setSpecialSettings = function(src, dst) {
  if (src.backgroundColor !== undefined) {
    this._setBackgroundColor(src.backgroundColor, dst);
  }

  this._setDataColor(src.dataColor, dst);
  this._setStyle(src.style, dst);
  this._setCameraPosition(src.cameraPosition, dst);

/* TODO

  if (src.tooltip !== undefined) {
    dst.showTooltip = src.tooltip;
  }

End TODO */
}


Graph3d.prototype._setStyle = function(style, dst) {
  if (style === undefined) {
    return;   // Nothing to do
  }

  var styleNumber;

  if (typeof style === 'string') {
    styleNumber = this._getStyleNumber(style);

    if (styleNumber === -1 ) {
      throw new Error('Style \'' + style + '\' is invalid');
    }
  } else {
    // Do a pedantic check on style number value
    var valid = false;
    for (var n in Graph3d.STYLE) {
      if (Graph3d.STYLE[n] === style) {
        valid = true;
        break;
      }
    }
{'fraction_non_alphanumeric': 0.15626105064634002, 'fraction_numerical': 0.010922949982318965, 'mean_word_length': 2.356012658227848, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6502004', 'n_tokens_mistral': 8138, 'n_tokens_neox': 7821, 'n_words': 2508}