Source: plot/Plot.js

import {appendNewDiv, mergeOptions} from "Util";
import Axis from "axis/Axis";
import AxisDefs from "axis/AxisDefs";
import Element from "Element";
import cfg from "Config";

import PlotView from "plot/PlotView";
import PlotDataHandler from "plot/PlotDataHandler";
import PlotAxesHandler from "plot/PlotAxesHandler";

export default Plot;

/**
  * @constructor
  * @extends Element
  * @description
  * Generic base class for a plot which contains axes, data sources and series.
  *
  *  * ***axes***: Plots can have 2 or more axes. These can either be horizontal or vertical axes, and a multiple axes may
  *    exist for either direction. Axes for a plot are named. Their names are used in data series' options to
  *    identify which axes the series uses. Even though at least 2 axes definitions are required for scaling
  *    series, the axes may be hidden (set visible option to false).
  *
  *  * ***sources***:  Plots can have multiple data sources, of different types. These sources are named, and their names used
  *    in data series' options to which source the series uses.
  *
  *  * ***series***: Plots can have multiple data series. These extract values from their data source, and plot the values
  *    along their dependent and independent axes.
  *
  * @todo format description
  * @todo Right now it's weird that some setters receive options, but return instances. I think I should separate
  *       options out as the instance's serialization, and then let users set/get instances as expected, but then
  *       separately serialize/deserialize instances based on passed options. Initial construction would still take
  *       an optional options object as the initial serialization.
 */

function Plot(userOptions){
  let _ = this;

  /**
    * @typedef Plot.Options
    * @description
    * These are the options specific to this class. Other options provided by {@link Element.Options} can also be passed
    * to create a Plot.
    *
    * @property {Object} axes            An object of {@link Axis.Options} objects. Object keys are axis names which can be
    *                                    used for modifying the plot after construction.
    * @property {Object} sources         An object of {@link WebSocketJson.Options} objects. Object keys are source names which can
    *                                    be used for modifying data sources after construction.
    * @property {Object} series          An object of {@link Series.Options} objects. Object keys are series names which can
    *                                    be used for modifying data series after construction.
    * @property {PlotBody.Options} body  Options for the plot's body
    *
    **/
  let options = mergeOptions({
    title:"Untitled Plot",
    axes:{},
    sources:{},
    series:{},
    body: {},

    class:"pgPlot",
    container: null,
    style: {
      width:"300px",
      height:"300px",
      display:"flex",
      flexDirection:"column"
    },
  }, userOptions);

  Element.call(this, options);

  /**
   * @todo
   * it's not cool that the data handler accesses the view's private members (_plotBody and _axisContainers).
   * PlotBody should probably be pulled out into its own subelement, on the same level as view, and data and axes
   * handlers. Unfortunately, there will be some view sizing issues to work out.
   * I'm not sure how I'd refactor axisContainers right now, but there should be some consistency with plotBody
  */
  let view = new PlotView(mergeOptions({element: this.element}, options) );
  let dataHandler = new PlotDataHandler(options.series, options.sources, view._plotBody);
  let axesHandler = new PlotAxesHandler(options.axes, view._axisContainers);

  /* Update the data handler when the axes change, so that the data handler can resize the series that depend on axes
     which have been modified */
  axesHandler.onChange = function(axes) {
    dataHandler.handleAxesChange(axes);
  }

  this.add([view, axesHandler]);

  /**
   * @member title
   * @memberof Plot
   * @instance
   * @type String
   * @description Plot title
   **/
  Object.defineProperty(this, "title", {
    set: view.setTitle.bind(view),
    get: view.getTitle.bind(view)
  });

  /**
   * @member axes
   * @memberof Plot
   * @instance
   * @type {Axis.Options}
   * @description
   *
   * Set this value using an object of {@link Axis.Options} objects. Note that changing axes names may break some data
   * series, which would then need to be updated as well.
   *
   * Reading this value returns an object of Axis instances, instead of their options
   **/
  Object.defineProperty(this, "axes", {
    set: axesHandler.setAxes.bind(axesHandler),
    get: axesHandler.getAxes.bind(axesHandler)
  });

  /**
   * @member series
   * @memberof Plot
   * @instance
   * @type {Series.Options}
   * @description
   *
   * Set this value using an object of {@link Series.Options} objects.
   *
   * Reading this value returns an object of Series instances, instead of their options
   **/
  Object.defineProperty(this, "series", {
    set: dataHandler.setSeries.bind(dataHandler),
    get: dataHandler.getSeries.bind(dataHandler)
  });

  /**
   * @member sources
   * @memberof Plot
   * @instance
   * @type {WebSocketJson.Options}
   * @description
   * Set this value using an object of {@link WebSocketJson.Options} objects. Note that changing source names may break some
   * data series, which would then need to be updated as well.
   *
   * Reading this value returns an object of Source instances, instead of their options
   **/
  Object.defineProperty(this, "sources", {
    set: dataHandler.setSources.bind(dataHandler),
    get: dataHandler.getSources.bind(dataHandler)
  });

  /* Set initial values */
  this.sources = options.sources;
  this.series = options.series;
  this.axes = options.axes;
}

Plot.prototype = Object.assign(Object.create(Element.prototype), {});