distance: getItemDistance()

in client/src/components/special/timeline-chart/renderer/index.js [734:1080]


              distance: getItemDistance(item)
            });
          }
        }
      }
    });
    candidates.sort((a, b) => a.distance - b.distance);
    if (candidates.length > 0) {
      const x = candidates[0].item.x;
      return candidates
        .filter((item) => item.item.x === x)
        .map(({item, dataset}) => ({
          item,
          dataset
        }));
    }
    return [];
  }

  getItemsByMouseEvent = (event) => {
    const {
      x,
      valueX,
      y,
      hit
    } = this.getMousePosition(event);
    const result = [];
    if (hit) {
      switch (this.searchMode) {
        case SEARCH_MODE.closest:
          const item = this.searchClosestItem(x, y);
          if (item) {
            result.push(item);
          }
          break;
        case SEARCH_MODE.vertical:
        default:
          const items = this.searchItemsByXValue(valueX);
          result.push(...items);
          break;
      }
    }
    return result;
  };

  onResize () {
    this.xAxis.resize(this.width, false);
    this.yAxis.resize(this.height, false);
    this.requestRender();
  }

  registerDatasets (datasets = []) {
    const processed = datasets
      .slice()
      .map((dataset, index) => {
        const {
          data = [],
          key = index,
          ...rest
        } = dataset;
        return {
          ...rest,
          index,
          key,
          data: data
            .map((item) => {
              if (item === undefined || !isDate(item.date)) {
                return undefined;
              }
              const {
                value,
                date,
                ...itemRest
              } = item;
              const {
                unix: x,
                date: dateValue
              } = parseDate(date) || {};
              return {
                ...itemRest,
                value,
                y: Number(value),
                date,
                x,
                dateValue,
                hide: !isNumber(item.value)
              };
            })
            .filter((item) => item === undefined || item.x !== undefined)
        };
      });
    let minimum = Infinity;
    for (let processedDataset of processed) {
      for (let item of processedDataset.data) {
        if (item && minimum > item.x) {
          minimum = item.x;
        }
      }
    }
    processed.forEach((dataset) => {
      dataset.data.forEach((item, index, array) => {
        if (item) {
          item.x = item.x - minimum;
        }
      });
    });
    this.timelineStart = minimum;
    this.datasets = processed;
    this.xAxis.update(
      this.datasets,
      {
        shift: this.timelineStart
      }
    );
    this.yAxis.update(this.datasets);
    this.xAxis.setPixelsOffset(20 + this.yAxis.largestLabelSize, false);
    this.yAxis.setPixelsOffset(20 + this.xAxis.largestLabelSize, false);
    this.datasetBuffers = [];
    this.datasets.forEach((dataset) => {
      this.datasetBuffers.push({
        dataset,
        buffers: this.buildDatasetBuffers(dataset)
      });
    });
    this.coordinatesChanged = true;
    this.requestRender();
    this.adaptYAxisForVisibleElements();
    return this;
  }

  registerDatasetsOptions (datasetsOptions = []) {
    this.datasetsOptions = datasetsOptions.slice().map((datasetOptions, index) => {
      const {
        key = index,
        ...rest
      } = datasetOptions || {};
      return {
        ...rest,
        key,
        index
      };
    });
    this.requestRender();
    return this;
  }

  getDatasetOptions (dataset) {
    const {
      key
    } = dataset;
    return this.datasetsOptions.find((options) => options.key === key) || {};
  }

  buildDatasetBuffers (dataset) {
    const {
      data = []
    } = dataset;
    const sliced = data.reduce((result, item) => {
      let last = result.length > 0 ? result[result.length - 1] : undefined;
      if (item === undefined && last && last.length > 0) {
        return [...result, []];
      }
      if (item && !last) {
        last = [];
        result.push(last);
      }
      if (item) {
        last.push(item);
      }
      return result;
    }, []);
    const blocks = [];
    sliced.forEach((sliceItem) => {
      const maxItemsPerBlock = 1000;
      for (let i = 0; i < sliceItem.length; i++) {
        const item = sliceItem[i];
        let current = blocks.length > 0 ? blocks[blocks.length - 1] : undefined;
        if (!current || current.length >= maxItemsPerBlock) {
          const last = current ? current[current.length - 1] : undefined;
          current = [];
          if (last) {
            current.push({...last});
          }
          blocks.push(current);
        }
        current.push({...item});
      }
    });
    blocks.forEach((block) => {
      for (let i = 0; i < block.length; i++) {
        const currentIndex = (i < block.length - 1) ? (i) : (block.length - 2);
        const nextIndex = currentIndex + 1;
        const current = block[currentIndex];
        const next = block[nextIndex];
        block[i].vector = {
          x: next.x - current.x,
          y: next.y - current.y
        };
      }
    });
    return blocks.map((block) => {
      const filtered = block.filter(b => !b.hide);
      return buildPathVAO(filtered, this.pathProgram);
    });
  }

  draw () {
    if (!this.canvas) {
      return;
    }
    const gl = this.canvas.getContext('webgl2', {antialias: true});
    const {
      r, g, b
    } = parseColor(this.backgroundColor);
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    gl.clearColor(r / 255.0, g / 255.0, b / 255.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    this.drawCoordinates(gl);
    this.drawSelection(gl);
    this.drawLines(gl);
    this.drawPoints(gl);
    this.drawText();
  }

  prepareChartContext (gl) {
    if (!gl) {
      return undefined;
    }
    gl.viewport(
      this.xAxis.pixelsOffset * dpr,
      this.yAxis.pixelsOffset * dpr,
      this.xAxis.pixelsSize * dpr,
      this.yAxis.pixelsSize * dpr
    );
    const projection = new Float32Array(buildOrthoMatrix({
      top: this.yAxis.to,
      bottom: this.yAxis.from,
      left: this.xAxis.from,
      right: this.xAxis.to
    }));
    const pixelResolution = new Float32Array([
      2.0 / this.xAxis.pixelsSize,
      2.0 / this.yAxis.pixelsSize
    ]);
    return {
      projection,
      pixelResolution
    };
  }

  drawLines (gl) {
    const drawingContext = this.prepareChartContext(gl);
    if (!drawingContext) {
      return;
    }
    const {
      projection,
      pixelResolution
    } = drawingContext;
    // eslint-disable-next-line react-hooks/rules-of-hooks
    usePathProgram(this.pathProgram, projection, IDENTITY_MATRIX, pixelResolution);
    (this.datasetBuffers || []).forEach((datasetBuffer) => {
      const {
        dataset,
        buffers
      } = datasetBuffer;
      const {
        width = DEFAULT_LINE_WIDTH,
        color = DEFAULT_CHART_LINE_COLOR
      } = this.getDatasetOptions(dataset);
      buffers.forEach((buffer) => {
        drawPath(
          this.pathProgram,
          buffer,
          {
            width,
            color
          }
        );
      });
    });
  }

  drawPoints (gl) {
    const drawingContext = this.prepareChartContext(gl);
    if (!drawingContext) {
      return;
    }
    const {
      projection,
      pixelResolution
    } = drawingContext;
    // eslint-disable-next-line react-hooks/rules-of-hooks
    usePathProgram(this.pathProgram, projection, IDENTITY_MATRIX, pixelResolution);
    const pointsToDraw = [];
    const maxItemsToDrawPoints = this.xAxis.pixelsSize / 5.0;
    let total = 0;
    for (let d = 0; d < (this.datasetBuffers || []).length; d += 1) {
      const {
        dataset,
        buffers = []
      } = this.datasetBuffers[d];
      const datasetItemsToDraw = [];
      for (let i = 0; i < buffers.length; i += 1) {
        const {
          items = []
        } = buffers[i];
        const filtered = items.filter((item) => this.xAxis.valueFitsRange(item.x));
        total += filtered.length;
        if (total > maxItemsToDrawPoints) {
          break;
        }
        datasetItemsToDraw.push(...filtered);
      }
      if (total > maxItemsToDrawPoints) {
        break;
      }
      pointsToDraw.push({
        dataset,
        items: datasetItemsToDraw
      });
    }
    if ((this.hoveredItems || []).length || (total > 0 && total < maxItemsToDrawPoints)) {
      // eslint-disable-next-line react-hooks/rules-of-hooks
      useCircleProgram(this.circleProgram, projection, pixelResolution);
    }
    if (total > 0 && total < maxItemsToDrawPoints) {
      pointsToDraw.forEach((datasetPoints) => {
        const {
          dataset,
          items
        } = datasetPoints;
        const {
          width = DEFAULT_LINE_WIDTH,
          color: fill = DEFAULT_CHART_LINE_COLOR,
          pointRadius = width
        } = this.getDatasetOptions(dataset);
        if (pointRadius > 0) {
          items.forEach((item) => {
            drawCircle(
              this.circleProgram,
              this.circleBuffer,
              this.circleStrokeBuffer,
              [item.x, item.y],
              pointRadius,
              {