File

libs/d3/src/lib/d3-timeseries-graph/controls/d3-graph-hover-line/d3-graph-hover-line.component.ts

Extends

D3TimeseriesGraphControl

Metadata

encapsulation ViewEncapsulation.None
selector n52-d3-graph-hover-line
styleUrls ./d3-graph-hover-line.component.scss

Index

Properties
Methods

Constructor

constructor(graphId: D3GraphId, graphs: D3Graphs, graphHelper: D3GraphHelperService, timezoneSrvc: TimezoneService)
Parameters :
Name Type Optional
graphId D3GraphId No
graphs D3Graphs No
graphHelper D3GraphHelperService No
timezoneSrvc TimezoneService No

Methods

Public adjustBackground
adjustBackground(background: d3.Selection, graphExtent: D3GraphExtent, preparedData: InternalDataEntry[], graph: d3.Selection, timespan: Timespan)
Parameters :
Name Type Optional
background d3.Selection<SVGSVGElement | any | any | any> No
graphExtent D3GraphExtent No
preparedData InternalDataEntry[] No
graph d3.Selection<SVGSVGElement | any | any | any> No
timespan Timespan No
Returns : void
Private calcDist
calcDist(entry: DataEntry, x: number)
Parameters :
Name Type Optional
entry DataEntry No
x number No
Returns : any
Private checkLeftSide
checkLeftSide(itemCoord: number)

Function giving information if the mouse is on left side of the diagram.

Parameters :
Name Type Optional Description
itemCoord number No

x coordinate of the value (e.g. mouse) to be checked

Returns : boolean
Private createHoverLine
createHoverLine()
Returns : void
Private createLabel
createLabel(entry: InternalDataEntry)
Parameters :
Name Type Optional
entry InternalDataEntry No
Returns : void
Private displayLabel
displayLabel(label: Label, visible: boolean)

Function to change visibility of label and white rectangle inside graph (next to mouse-cursor line).

Parameters :
Name Type Optional Description
label Label No
visible boolean No

Boolean giving information about visibility of a label.

Returns : void
Public dragEndBackground
dragEndBackground()
Returns : void
Public dragStartBackground
dragStartBackground()
Returns : void
Private drawLineIndicator
drawLineIndicator(mouse: [number, number])
Parameters :
Name Type Optional
mouse [number, number] No
Returns : void
Private getItemForX
getItemForX(xCoord: number, data: DataEntry[])
Parameters :
Name Type Optional
xCoord number No
data DataEntry[] No
Returns : number
Public graphInitialized
graphInitialized(graph: D3TimeseriesGraphComponent)
Parameters :
Name Type Optional
graph D3TimeseriesGraphComponent No
Returns : void
Private hideHoverLineIndicator
hideHoverLineIndicator()
Returns : void
Private hideLabels
hideLabels()
Returns : void
Public mousemoveBackground
mousemoveBackground()
Returns : void
Public mouseoutBackground
mouseoutBackground()
Returns : void
Private moveHoverLineIndicator
moveHoverLineIndicator()
Returns : void
Private positionLabel
positionLabel(entry: InternalDataEntry, label: Label, item: DataEntry)

Function to show the labeling inside the graph.

Parameters :
Name Type Optional Description
entry InternalDataEntry No

Object containg the dataset.

label Label No
item DataEntry No

Object of the entry in the dataset.

Returns : void
Private showHoverLineIndicator
showHoverLineIndicator()
Returns : void
Private showLabel
showLabel(entry: InternalDataEntry, idx: number, xCoordMouse: number, entryIdx: number)
Parameters :
Name Type Optional
entry InternalDataEntry No
idx number No
xCoordMouse number No
entryIdx number No
Returns : void
Public zoomEndBackground
zoomEndBackground()
Returns : void
Public zoomStartBackground
zoomStartBackground()
Returns : void
Public Optional adjustYAxis
adjustYAxis(axis: YAxis)
Inherited from D3TimeseriesGraphControl
Parameters :
Name Type Optional
axis YAxis No
Returns : void
Public Abstract graphInitialized
graphInitialized(graph: D3TimeseriesGraphComponent)
Inherited from D3TimeseriesGraphControl
Parameters :
Name Type Optional
graph D3TimeseriesGraphComponent No
Returns : any
Public ngAfterViewInit
ngAfterViewInit()
Inherited from D3TimeseriesGraphControl
Returns : void
Public ngOnDestroy
ngOnDestroy()
Inherited from D3TimeseriesGraphControl
Returns : void

Properties

Private background
Type : d3.Selection<SVGSVGElement | any | any | any>
Private d3Graph
Type : D3TimeseriesGraphComponent
Private disableHovering
Type : boolean
Private drawLatency
Type : number
Default value : 20
Private drawLayer
Type : d3.Selection<SVGGElement | any | any | any>
Private graphExtent
Type : D3GraphExtent
Private labels
Type : Map<string | Label>
Default value : new Map()
Private lastDraw
Default value : new Date().getTime()
Private preparedData
Type : InternalDataEntry[]
import { Component, ViewEncapsulation } from '@angular/core';
import { Timespan, TimezoneService } from '@helgoland/core';
import * as d3 from 'd3';

import { D3GraphHelperService } from '../../../helper/d3-graph-helper.service';
import { D3GraphId } from '../../../helper/d3-graph-id.service';
import { D3Graphs } from '../../../helper/d3-graphs.service';
import { DataEntry, InternalDataEntry } from '../../../model/d3-general';
import { D3GraphExtent, D3TimeseriesGraphControl } from '../../d3-timeseries-graph-control';
import { D3TimeseriesGraphComponent } from '../../d3-timeseries-graph.component';

interface Label {
  text: d3.Selection<d3.BaseType, any, any, any>;
  rect: d3.Selection<d3.BaseType, any, any, any>;
}

const HOVERLINE_ID = 'hover-line';
const TIME_LABEL_ID = 'time-label';

@Component({
  selector: 'n52-d3-graph-hover-line',
  template: '',
  styleUrls: ['./d3-graph-hover-line.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class D3GraphHoverLineComponent extends D3TimeseriesGraphControl {

  private d3Graph: D3TimeseriesGraphComponent;
  private background: d3.Selection<SVGSVGElement, any, any, any>;
  private graphExtent: D3GraphExtent;
  private disableHovering: boolean;
  private lastDraw = new Date().getTime();
  private drawLatency = 20;
  private preparedData: InternalDataEntry[];

  private labels: Map<string, Label> = new Map();
  private drawLayer: d3.Selection<SVGGElement, any, any, any>;

  constructor(
    protected graphId: D3GraphId,
    protected graphs: D3Graphs,
    protected graphHelper: D3GraphHelperService,
    protected timezoneSrvc: TimezoneService
  ) {
    super(graphId, graphs, graphHelper);
  }

  public graphInitialized(graph: D3TimeseriesGraphComponent) {
    this.d3Graph = graph;
    this.d3Graph.redrawCompleteGraph();
  }

  public adjustBackground(
    background: d3.Selection<SVGSVGElement, any, any, any>,
    graphExtent: D3GraphExtent,
    preparedData: InternalDataEntry[],
    graph: d3.Selection<SVGSVGElement, any, any, any>,
    timespan: Timespan
  ) {
    if (!this.drawLayer) {
      this.drawLayer = this.d3Graph.getDrawingLayer('hovering-line-layer');
    }
    this.createHoverLine();
    this.background = background;
    this.graphExtent = graphExtent;
    this.preparedData = preparedData;
  }

  public mousemoveBackground() {
    if (!this.disableHovering) {
      this.moveHoverLineIndicator();
      this.showHoverLineIndicator();
    }
  }

  public mouseoutBackground() {
    if (!this.disableHovering) {
      this.hideHoverLineIndicator();
      this.hideLabels();
    }
  }

  public dragStartBackground() {
    this.hideHoverLineIndicator();
    this.hideLabels();
    this.disableHovering = true;
  }

  public zoomStartBackground() {
    this.hideHoverLineIndicator();
    this.hideLabels();
    this.disableHovering = true;
  }

  public dragEndBackground() {
    this.disableHovering = false;
  }

  public zoomEndBackground() {
    this.disableHovering = false;
  }

  private createHoverLine() {
    if (d3.select(`#${HOVERLINE_ID}`).empty()) {
      this.drawLayer.append('path')
        .attr('id', HOVERLINE_ID)
        .style('opacity', '0');
    }

    if (d3.select(`#${TIME_LABEL_ID}`).empty()) {
      this.drawLayer.append('svg:text')
        .attr('id', `${TIME_LABEL_ID}`)
        .style('pointer-events', 'none');
    }

  }

  private hideHoverLineIndicator(): void {
    d3.select(`#${HOVERLINE_ID}`).style('opacity', '0');
    d3.select(`#${TIME_LABEL_ID}`).style('opacity', '0');
  }

  private hideLabels() {
    this.labels.forEach(e => {
      e.rect.style('opacity', '0');
      e.text.style('opacity', '0');
    });
  }

  private showHoverLineIndicator(): void {
    d3.select(`#${HOVERLINE_ID}`).style('opacity', '1');
    d3.select(`#${TIME_LABEL_ID}`).style('opacity', '1');
  }

  private moveHoverLineIndicator(): void {
    const time = new Date().getTime();
    if (this.lastDraw + this.drawLatency < time) {
      const mouse = d3.mouse(this.background.node());
      this.drawLineIndicator(mouse);
      this.preparedData.forEach((entry, entryIdx) => {
        const idx = this.getItemForX(mouse[0] + this.graphExtent.leftOffset, entry.data);
        this.showLabel(entry, idx, mouse[0], entryIdx);
      });
      this.lastDraw = time;
    }
  }

  private drawLineIndicator(mouse: [number, number]) {
    const xPos = mouse[0] + this.graphExtent.leftOffset;

    d3.select(`#${HOVERLINE_ID}`)
      .attr('d', () => 'M' + (xPos) + ',' + this.graphExtent.height + ' ' + (xPos) + ',' + 0);

    const time = this.graphExtent.xScale.invert(xPos);

    // draw label
    d3.select(`#${TIME_LABEL_ID}`).text(this.timezoneSrvc.formatTzDate(time));
    const onLeftSide = this.checkLeftSide(xPos);
    const right = xPos + 2;
    const left = xPos - this.graphHelper.getDimensions(d3.select(`#${TIME_LABEL_ID}`).node()).w - 2;
    d3.select(`#${TIME_LABEL_ID}`)
      .attr('x', onLeftSide ? right : left)
      .attr('y', 13);
  }

  private getItemForX(xCoord: number, data: DataEntry[]): number {
    const PixelBuffer = 5;
    const time = this.graphExtent.xScale.invert(xCoord);
    const idx = d3.bisector((d: DataEntry) => d.timestamp).left(data, time);
    const distIdx = this.calcDist(data[idx], xCoord);
    if (idx > 0) {
      const distPrev = this.calcDist(data[idx - 1], xCoord);
      if (distPrev < distIdx) {
        if (distPrev <= PixelBuffer) {
          return idx - 1;
        }
      }
    }
    if (distIdx <= PixelBuffer) {
      return idx;
    }
  }

  private calcDist(entry: DataEntry, x: number) {
    return entry ? Math.abs(this.graphExtent.xScale(entry.timestamp) - x) : Infinity;
  }

  private showLabel(entry: InternalDataEntry, idx: number, xCoordMouse: number, entryIdx: number) {
    const item: DataEntry = entry.data[idx];

    if (!this.labels.has(entry.internalId)) {
      this.createLabel(entry);
    }
    const label = this.labels.get(entry.internalId);

    if (item !== undefined && item.yDiagCoord && item.value !== undefined) {
      this.positionLabel(entry, label, item);
      this.displayLabel(label, true);
    } else {
      this.displayLabel(label, false);
    }
  }

  /**
   * Function to change visibility of label and white rectangle inside graph (next to mouse-cursor line).
   * @param entry {DataEntry} Object containing the dataset.
   * @param visible {Boolean} Boolean giving information about visibility of a label.
   */
  private displayLabel(label: Label, visible: boolean): void {
    if (visible) {
      label.text.style('opacity', '1');
      label.rect.style('opacity', '1');
    } else {
      label.text.style('opacity', '0');
      label.rect.style('opacity', '0');
    }
  }

  private createLabel(entry: InternalDataEntry) {
    const rect = this.drawLayer.append('svg:rect')
      .attr('class', 'hoverline-label-rect')
      .style('fill', 'white')
      .style('stroke', 'none')
      .style('pointer-events', 'none');
    const text = this.drawLayer.append('svg:text')
      .attr('class', 'hoverline-label-text')
      .style('pointer-events', 'none')
      .style('fill', entry.options.color)
      .style('font-weight', 'lighter');
    this.labels.set(entry.internalId, { text, rect });
  }

  /**
   * Function to show the labeling inside the graph.
   * @param entry {DataEntry} Object containg the dataset.
   * @param item {DataEntry} Object of the entry in the dataset.
   */
  private positionLabel(entry: InternalDataEntry, label: Label, item: DataEntry): void {
    label.text.text(item.value + (entry.axisOptions.uom ? entry.axisOptions.uom : ''));

    const entryX: number = this.checkLeftSide(item.xDiagCoord) ?
      item.xDiagCoord + 4 : item.xDiagCoord - this.graphHelper.getDimensions(label.text.node()).w - 4;

    label.text
      .attr('x', entryX)
      .attr('y', item.yDiagCoord);
    label.rect
      .attr('x', entryX)
      .attr('y', item.yDiagCoord - this.graphHelper.getDimensions(label.rect.node()).h + 3)
      .attr('width', this.graphHelper.getDimensions(label.text.node()).w)
      .attr('height', this.graphHelper.getDimensions(label.text.node()).h);
  }

  /**
   * Function giving information if the mouse is on left side of the diagram.
   * @param itemCoord {number} x coordinate of the value (e.g. mouse) to be checked
   */
  private checkLeftSide(itemCoord: number): boolean {
    return ((this.background.node().getBBox().width + this.graphExtent.leftOffset) / 2 > itemCoord) ? true : false;
  }

}


./d3-graph-hover-line.component.scss

#hovering-line-layer {
    #hover-line {
        stroke: black;
        stroke-width: 1px;
    }
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""