chart.js

import * as ChartJS from 'chart.js';
import _ from 'lodash';
import Input from './input';
import Treemap from './_treemap';
import Map from './_map';
import {
  colors, settingDefualtChartOption, defineType, nonChartJsType,
} from './variable';
import Timeline from './timelime';

/**
 * GamifyJS에서 차트를 생성하는 클래스입니다.
 * <p>해당 클래스를 통해 차트를 생성하고, 제어할 수 있습니다.
 * @class
 * @alias Chart
 */
export default class Chart {
  /**
   * @description DOM의 크기를 계산해서 DOM의 크기에 딱 맞게 Gamify를 생성해준다.
   * @param  { object } option - Gamify를 통해 생설할 차트의 데이터
   * @param  { Gamify } parent - Gamify 클래스 ( 부모 클래스 ) 링크
   */
  constructor(option, parent) {
    // eslint-disable-next-line consistent-return
    return this.gamifyOptionToChartJsOption(option).then((objects) => {
      const value = objects.chartJsOption;

      if (_.includes(nonChartJsType, option.type)) {
        switch (option.type) {
          case 'treemap':
            new Treemap(option, value.data.datasets);
            document.addEventListener('contextmenu', event => event.preventDefault());
            break;
          case 'map':
            new Map(option, value.data.datasets);
            break;
          default:
            break;
        }
      } else {
        const chartDOM = document.createElement('canvas');
        chartDOM.width = option.rootSize.width;
        chartDOM.height = option.rootSize.height;
        chartDOM.style.position = 'absolute';
        chartDOM.style.zIndex = 1;
        document.getElementById(option.domId).appendChild(chartDOM);

        const context = chartDOM.getContext('2d');
        let _ChartJS;
        if (option.type === 'scatter') {
          _ChartJS = ChartJS.Scatter(context, value);
        } else {
          _ChartJS = new ChartJS(context, value);
        }
        // const
        /**
         * @description ChartJS 오브젝트
         * @type { ChartJS }
         */
        this.chartJsObject = _ChartJS;
        if (!_.isUndefined(objects.originalDatasets)) {
          parent.timeline = new Timeline({ chart: this.chartJsObject, parent, option }, objects.originalDatasets);
        }

        if (option.data.importType === 'api' && option.data.interval) {
          let start = new Date().getTime();
          setInterval(() => {
            let elapsed = Math.abs(new Date().getTime() - start);
            if (elapsed >= option.data.interval * 1000) {
              start = new Date().getTime();
              elapsed = 0;
            }

            this.chartJsObject.updateElapsedTime = elapsed;
          }, 1);

          setInterval(() => {
            Input.getDataFromAPI(option.data.url, option.data.params).then((result) => {
              const chartDataByUser = option.data.fn(result);
              if (option.data.colorMode === 'auto') { this.makeBackgroundColor(chartDataByUser); }
              for (let i = 0; i < this.chartJsObject.config.data.datasets.length; i++) {
                this.chartJsObject.config.data.datasets[i].data = chartDataByUser.datasets[i].data;
              }
              this.chartJsObject.update();
            });
          }, 1000 * option.data.interval);
        }

        return this.chartJsObject;
      }
    });
  }

  /**
   * Gamify의 일부차트는 ChartJS를 통해 생성하기 때문에 Gamify의 옵션을 Chartjs에 맞게 변형시켜주는 함수
   * <p><b> async 키워드를 사용한 Synchronous (동기) 함수</b>
   * @param {object} option Gamify 생성 시 입력한 옵션
   * @memberof Chart
   * @returns {object}
   * @instance
   */
  async gamifyOptionToChartJsOption(option) {
    const chartJsOption = {};
    let originalDatasets;
    chartJsOption.type = option.type;

    if (_.includes(Object.keys(defineType), chartJsOption.type)) {
      chartJsOption.type = defineType[chartJsOption.type];
    }

    if (option.data.timeline) {
      originalDatasets = _.cloneDeep(option.data.datasets);
      option.data.datasets = option.data.datasets.splice(0, 1);
    }

    chartJsOption.data = {};
    switch (option.data.importType) {
      case 'plane':
        chartJsOption.data.labels = option.data.labels;
        chartJsOption.data.datasets = option.data.datasets;
        break;
      case 'spreadsheet':
        await Input.getDataFromSpreadSheet(option.data).then((result) => {
          chartJsOption.data = { ...chartJsOption.data, ...result };
        });
        break;
      case 'api':
        await Input.getDataFromAPI(option.data.url, option.data.params).then((result) => {
          const chartDataByUser = option.data.fn(result);
          chartJsOption.data = { ...chartJsOption.data, ...chartDataByUser };
        });
        break;
      default:
        break;
    }

    chartJsOption.options = {
      responsive: true,
      scales: {
        yAxes: [{
          ticks: {
            beginAtZero: true,
          },
        }],
        xAxes: [{
          ticks: {
            beginAtZero: true,
          },
        }],
      },
    };

    chartJsOption.options = { ...chartJsOption.options, ...option.options };
    if (option.data.colorMode === 'auto') {
      this.makeBackgroundColor(chartJsOption.data);
    }
    if (settingDefualtChartOption[option.type] !== undefined) {
      settingDefualtChartOption[option.type](chartJsOption);
    }

    return { chartJsOption, originalDatasets };
  }

  /**
   * 차트의 배경색을 auto로 지정했을 때 자동으로 색을 선택해줌
   * @param {object} data 차트의 데이터
   * @memberof Chart
   * @instance
   */
  makeBackgroundColor(data) {
    let colorIndex = 0;
    const backgroundColorArray = [];
    const borderColorArray = [];
    if (data.datasets.length === 1) {
      for (let i = 0; i < data.labels.length; i++) {
        const backgroundColor = `rgba(${colors[colorIndex][0]},${colors[colorIndex][1]},${colors[colorIndex][2]},1)`;
        const borderColor = `rgba(${colors[colorIndex][0]},${colors[colorIndex][1]},${colors[colorIndex][2]},1)`;
        backgroundColorArray.push(backgroundColor);
        borderColorArray.push(borderColor);
        colorIndex += 6;

        if (colorIndex >= colors.length) {
          colorIndex -= colors.length;
        }
      }
      data.datasets[0].backgroundColor = backgroundColorArray;
      data.datasets[0].borderColor = borderColorArray;
      data.datasets[0].borderWidth = 1;
    } else {
      for (let i = 0; i < data.datasets.length; i++) {
        const backgroundColor = `rgba(${colors[colorIndex][0]},${colors[colorIndex][1]},${colors[colorIndex][2]},0.3)`;
        const borderColor = `rgba(${colors[colorIndex][0]},${colors[colorIndex][1]},${colors[colorIndex][2]},1)`;

        data.datasets[i].backgroundColor = backgroundColor;
        data.datasets[i].borderColor = borderColor;
        data.datasets[i].borderWidth = 1;

        colorIndex += 6;
        if (colorIndex >= colors.length) {
          colorIndex -= colors.length;
        }
      }
    }
  }
}