<commit_before>import * as d3 from 'd3';
import { BaseD3, RenderOptions as BaseRenderOptions } from './base.d3';
import * as topojson from 'topojson';
import { Observable, Subject } from 'rxjs';
import { GeoDatum } from '../datasets/queries/geo.query';
import * as GeoJSON from 'geojson';
import { GeometryCollection } from 'topojson-specification';
import { isNotNullish, linearScale, linearSquaredScale } from '../utils/misc';
import { City, Territory, TerritoryLevel, World } from '../datasets/geo.types';
import * as chroma from 'chroma-js';
import { SECOND } from '../utils/timeUnits';
import { easing } from 'transition-timing';

export interface RenderOptions extends BaseRenderOptions {
  world: World;
  data$: Observable<GeoDatum[]>;
  filteringTerritory$: Subject<Territory | null>;
}

const { CONTINENT, SUBCONTINENT, COUNTRY, CITY } = TerritoryLevel;

export class GeoMapD3 extends BaseD3<RenderOptions> {
  static animFPS = 30;
  static animDuration = SECOND;
  static animTimingFunction = easing('easeInOut');
  static colorBorder = '#FFFFFF';
  static minOpacity = .2;
  static maxOpacity = .8;
  static minRadius = 1;
  static maxRadius = 30;
  static latitudeBounds = [-84, 84];
  static paddingScale = .9;
  static rawProjection = d3.geoMercator()
    .scale(1)
    .translate([0, 0]);
  static rawGeoPath = d3.geoPath(GeoMapD3.rawProjection);

  protected zoom: d3.ZoomBehavior<SVGSVGElement, unknown>;
  private animationId?: number;
  private projection: d3.GeoProjection;
  private geoPath: d3.GeoPath;
  private centerY: number;
  private lastTransform: d3.ZoomTransform | null;
  private landPath: d3.Selection<SVGPathElement, GeoJSON.FeatureCollection<GeoJSON.Geometry, {}>, null, undefined>;
  private boundaryPath: d3.Selection<SVGPathElement, GeoJSON.MultiLineString, null, undefined>;
  private dataG: d3.Selection<SVGGElement, unknown, null, undefined>;
  private territoryPaths: d3.Selection<SVGPathElement, any, null, undefined>[];
  private territoryCircles: d3.Selection<SVGCircleElement, any, null, undefined>[];

  private get geoTransform() {
    return datum => `translate(${this.projection([datum.lng, datum.lat])})`;
  }

  private static accessValue(datum: GeoDatum) {
    return datum.values.activeUsers;
  }

  private static getColor(valueRatio: number) {
    const { colorPrimary, colorBorder, minOpacity, maxOpacity } = GeoMapD3;
    const opacity = linearScale(valueRatio, minOpacity, maxOpacity);
    return chroma.scale([colorBorder, colorPrimary])(opacity).hex('rgb');
  }

  render() {
    super.render();

    const { data$, filteringTerritory$ } = this.renderOptions;

    this.renderMap();
    this.renderData();

    data$
      .pipe(this.takeUntilCleared())
      .subscribe(data => {
        this.updateData(data);
      });

    filteringTerritory$
      .pipe(this.takeUntilCleared())
      .subscribe(filteringTerritory => {
        this.fit(filteringTerritory);
      });
  }

  private renderMap() {
    const { height, width, world } = this.renderOptions;

    this.landPath = this.svg
      .append('path')
      .attr('class', 'geo_map-land')
      .datum(topojson.feature(world.topology, world.topology.objects.land));

    const countryGeometryCollection: GeometryCollection = {
      type: 'GeometryCollection',
      geometries: Object.values(world[COUNTRY]).map(country => country.geometry).filter(isNotNullish),
    };

    this.boundaryPath = this.svg
      .append('path')
      .attr('class', 'geo_map-boundary')
      .datum(topojson.mesh(world.topology, countryGeometryCollection, (a, b) => a !== b));

    this.projection = d3.geoMercator()
      .scale(1)
      .translate([width / 2, height / 2]);

    const { minX, maxX, minY, maxY } = this.getProjectionBounds();
    const minScale = width / (maxX - minX);
    this.centerY = (minY + maxY) / 2;

    this.lastTransform = null;

    this.zoom = d3.zoom<SVGSVGElement, unknown>().scaleExtent([minScale, 50 * minScale]);
    this.zoom.scaleTo(this.svg, minScale);
    this.zoom.on('zoom', this.handleZoomAndPan.bind(this));
    this.svg.call(this.zoom);

    this.geoPath = d3.geoPath(this.projection);
    this.fit(null);
  }

  private getProjectionBounds() {
    const { latitudeBounds } = GeoMapD3;

    const [longitude] = this.projection.rotate();
    const hemisphere = 180.0 - Number.EPSILON;

    const [minX, minY] = this.projection([-longitude - hemisphere, latitudeBounds[1]])!;
    const [maxX, maxY] = this.projection([-longitude + hemisphere, latitudeBounds[0]])!;
<commit_msg>Disable double click to zoom<commit_after>import * as d3 from 'd3';
import { BaseD3, RenderOptions as BaseRenderOptions } from './base.d3';
import * as topojson from 'topojson';
import { Observable, Subject } from 'rxjs';
import { GeoDatum } from '../datasets/queries/geo.query';
import * as GeoJSON from 'geojson';
import { GeometryCollection } from 'topojson-specification';
import { isNotNullish, linearScale, linearSquaredScale } from '../utils/misc';
import { City, Territory, TerritoryLevel, World } from '../datasets/geo.types';
import * as chroma from 'chroma-js';
import { SECOND } from '../utils/timeUnits';
import { easing } from 'transition-timing';

export interface RenderOptions extends BaseRenderOptions {
  world: World;
  data$: Observable<GeoDatum[]>;
  filteringTerritory$: Subject<Territory | null>;
}

const { CONTINENT, SUBCONTINENT, COUNTRY, CITY } = TerritoryLevel;

export class GeoMapD3 extends BaseD3<RenderOptions> {
  static animFPS = 30;
  static animDuration = SECOND;
  static animTimingFunction = easing('easeInOut');
  static colorBorder = '#FFFFFF';
  static minOpacity = .2;
  static maxOpacity = .8;
  static minRadius = 1;
  static maxRadius = 30;
  static latitudeBounds = [-84, 84];
  static paddingScale = .9;
  static rawProjection = d3.geoMercator()
    .scale(1)
    .translate([0, 0]);
  static rawGeoPath = d3.geoPath(GeoMapD3.rawProjection);

  protected zoom: d3.ZoomBehavior<SVGSVGElement, unknown>;
  private animationId?: number;
  private projection: d3.GeoProjection;
  private geoPath: d3.GeoPath;
  private centerY: number;
  private lastTransform: d3.ZoomTransform | null;
  private landPath: d3.Selection<SVGPathElement, GeoJSON.FeatureCollection<GeoJSON.Geometry, {}>, null, undefined>;
  private boundaryPath: d3.Selection<SVGPathElement, GeoJSON.MultiLineString, null, undefined>;
  private dataG: d3.Selection<SVGGElement, unknown, null, undefined>;
  private territoryPaths: d3.Selection<SVGPathElement, any, null, undefined>[];
  private territoryCircles: d3.Selection<SVGCircleElement, any, null, undefined>[];

  private get geoTransform() {
    return datum => `translate(${this.projection([datum.lng, datum.lat])})`;
  }

  private static accessValue(datum: GeoDatum) {
    return datum.values.activeUsers;
  }

  private static getColor(valueRatio: number) {
    const { colorPrimary, colorBorder, minOpacity, maxOpacity } = GeoMapD3;
    const opacity = linearScale(valueRatio, minOpacity, maxOpacity);
    return chroma.scale([colorBorder, colorPrimary])(opacity).hex('rgb');
  }

  render() {
    super.render();

    const { data$, filteringTerritory$ } = this.renderOptions;

    this.renderMap();
    this.renderData();

    data$
      .pipe(this.takeUntilCleared())
      .subscribe(data => {
        this.updateData(data);
      });

    filteringTerritory$
      .pipe(this.takeUntilCleared())
      .subscribe(filteringTerritory => {
        this.fit(filteringTerritory);
      });
  }

  private renderMap() {
    const { height, width, world } = this.renderOptions;

    this.landPath = this.svg
      .append('path')
      .attr('class', 'geo_map-land')
      .datum(topojson.feature(world.topology, world.topology.objects.land));

    const countryGeometryCollection: GeometryCollection = {
      type: 'GeometryCollection',
      geometries: Object.values(world[COUNTRY]).map(country => country.geometry).filter(isNotNullish),
    };

    this.boundaryPath = this.svg
      .append('path')
      .attr('class', 'geo_map-boundary')
      .datum(topojson.mesh(world.topology, countryGeometryCollection, (a, b) => a !== b));

    this.projection = d3.geoMercator()
      .scale(1)
      .translate([width / 2, height / 2]);

    const { minX, maxX, minY, maxY } = this.getProjectionBounds();
    const minScale = width / (maxX - minX);
    this.centerY = (minY + maxY) / 2;

    this.lastTransform = null;

    this.zoom = d3.zoom<SVGSVGElement, unknown>().scaleExtent([minScale, 50 * minScale]);
    this.zoom.scaleTo(this.svg, minScale);
    this.zoom.on('zoom', this.handleZoomAndPan.bind(this));
    this.svg
      .call(this.zoom)
      .on('dblclick.zoom', null);

    this.geoPath = d3.geoPath(this.projection);
    this.fit(null);
  }

  private getProjectionBounds() {
    const { latitudeBounds } = GeoMapD3;

    const [longitude] = this.projection.rotate();
    const hemisphere = 180.0 - Number.EPSILON;

    const [minX, minY] = this.projection([-longitude - hemisphere, latitudeBounds[1]])!;
    const [maxX, maxY] = this.projection([-longitude + hemisphere, latitudeBounds[0]])!;

{'fraction_non_alphanumeric': 0.13613888583672124, 'fraction_numerical': 0.01098780353807274, 'mean_word_length': 4.039867109634551, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5750200', 'n_tokens_mistral': 3028, 'n_tokens_neox': 2879, 'n_words': 637}