text: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  import React, { Component } from 'react';
  import { Chart, Axis, Tooltip, Geom } from 'bizcharts';
  import autoHeight from '../autoHeight';
  import styles from '../index.less';
  
  const yTickOffset = 20;
  
  @autoHeight()
  class HeatMap extends Component {
    getScaleMap = maxResponseTimeOffset => {
      const scaleMap = [];
      const remainder = (maxResponseTimeOffset + 1) % (yTickOffset + 1);
      const times = ~~((maxResponseTimeOffset + 1) / (yTickOffset + 1));
      if (remainder > 0) {
        scaleMap.push([0, remainder - 1, times + 1]);
        scaleMap.push([remainder, yTickOffset, times]);
      } else {
        scaleMap.push([0, yTickOffset, times]);
      }
      return scaleMap;
    };
  
    reduceData = (scaleMap, reducer, init = 0) => {
      const result = [];
      for (let i = 0; i < scaleMap.length; i += 1) {
        const scale = scaleMap[i];
        for (let j = scale[0]; j <= scale[1]; j += 1) {
          let item = init;
          for (let k = 0; k < scale[2]; k += 1) {
            item = reducer(item, k);
          }
          result.push(item);
        }
      }
      return result;
    };
  
    mapXAxisData = (reducedData, datetime) =>
      reducedData.map((count, i) => ({ datetime, responseTime: i, count }));
  
    handlePlotClick = (dtStart, dtEnd, responseTime) => {
      const removedUnit = responseTime.slice(0, responseTime.indexOf('ms'));
      let min;
      let max;
      if (removedUnit.indexOf('>') === 0) {
        min = parseInt(removedUnit.slice(1), 10);
      } else {
        const value = parseInt(removedUnit, 10);
        min = value - 100 < 0 ? 0 : value - 100;
        max = value;
      }
      this.props.onClick({ start: dtStart, end: dtEnd }, { min, max });
    };
  
    render() {
      const {
        height,
        data: { nodes, responseTimeStep },
        duration,
      } = this.props;
  
      if (!nodes || nodes.length < 1) {
        return <span style={{ display: 'none' }} />;
      }
      const {
        display: { range },
        raw: { range: rawRange },
      } = duration;
  
      const source = [];
      let maxResponseTimeOffset = 0;
      for (let i = 0; i < nodes.length; i += 1) {
        const item = nodes[i];
        if (item[0] >= range.length) {
          break;
        }
        maxResponseTimeOffset = maxResponseTimeOffset > item[1] ? maxResponseTimeOffset : item[1];
        source.push({
          datetime: item[0],
          responseTime: item[1],
          count: item[2],
        });
      }
      const mergeSource = [];
      let responseTimeAxis = [];
      if (maxResponseTimeOffset > yTickOffset) {
        const scaleMap = this.getScaleMap(maxResponseTimeOffset);
        let data = 0;
        const xAxisStepArray = this.reduceData(scaleMap, time => time + responseTimeStep);
        responseTimeAxis = xAxisStepArray.map((_, i) => {
          data += _;
          return `${i === xAxisStepArray.length - 1 ? '>' : ''}${
            i === xAxisStepArray.length - 1 ? data - _ : data
          }ms`;
        });
        let datetime = 0;
        while (source.length > 0) {
          const reducedData = this.reduceData(scaleMap, count => {
            const item = source.shift();
            return item ? item.count + count : count;
          });
          mergeSource.push(...this.mapXAxisData(reducedData, datetime));
          datetime += 1;
        }
      } else {
        for (let i = 0; i < maxResponseTimeOffset + 1; i += 1) {
          responseTimeAxis.push(
            `${
              i === maxResponseTimeOffset ? `>${i * responseTimeStep}` : (i + 1) * responseTimeStep
            }ms`
          );
        }
        mergeSource.push(...source);
      }
      const cols = {
        datetime: {
          type: 'cat',
          values: range,
          tickCount: 5,
        },
        responseTime: {
          type: 'cat',
          values: responseTimeAxis,
          tickCount: 5,
        },
      };
      return (
        <div className={styles.chart} style={{ height }}>
          <div>
            <Chart
              data={mergeSource}
              scale={cols}
              forceFit
              height={height * 1.4}
              onPlotClick={({
                data: {
                  _origin: { datetime, responseTime },
                },
              }) =>
                this.handlePlotClick(
                  rawRange[datetime],
                  rawRange[datetime + 1],
                  responseTimeAxis[responseTime]
                )
              }
            >
              <Axis
                name="datetime"
                grid={{
                  align: 'center',
                  lineStyle: {
                    lineWidth: 1,
                    lineDash: null,
                    stroke: '#f0f0f0',
                  },
                  showFirstLine: true,
                }}
              />
              <Axis
                name="responseTime"
                grid={{
                  align: 'center',
                  lineStyle: {
                    lineWidth: 1,
                    lineDash: null,
                    stroke: '#f0f0f0',
                  },
                }}
              />
              <Tooltip />
              <Geom
                type="polygon"
                position="datetime*responseTime"
                color={['count', '#EBEDF0-#BAE7FF-#1890FF-#0050B3']}
                style={{ stroke: '#fff', lineWidth: 1 }}
                tooltip={[
                  'datetime*responseTime*count',
                  (datetime, responseTime, count) => {
                    return {
                      name: range[datetime],
                      title: `${responseTime > 0 ? responseTimeAxis[responseTime - 1] : 0}~${
                        responseTimeAxis[responseTime]
                      }`,
                      value: count,
                    };
                  },
                ]}
              />
            </Chart>
          </div>
        </div>
      );
    }
  }
  
  export default HeatMap;
  