<commit_before>import {ScaleBand, ScaleContinuousNumeric, ScaleOrdinal, scaleOrdinal} from "d3"
import { action, computed, makeObservable, observable } from "mobx"
import { createContext, useContext } from "react"
import { AxisPlace, AxisPlaces } from "./axis-model"

export type ScaleNumericBaseType = ScaleContinuousNumeric<number, number>
export type ScaleType = ScaleContinuousNumeric<number, number> | ScaleOrdinal<string, any> | ScaleBand<string>

export const kDefaultGraphWidth = 480
export const kDefaultGraphHeight = 0
export const kDefaultLegendHeight = 0
export const kDefaultPlotWidth = 0.8 * kDefaultGraphWidth
export const kDefaultPlotHeight = 0.8 * kDefaultGraphHeight

export interface Bounds {
  left: number
  top: number
  width: number
  height: number
}

export class GraphLayout {
  @observable graphWidth = kDefaultGraphWidth
  @observable graphHeight = kDefaultGraphHeight
  @observable legendHeight = kDefaultLegendHeight
  @observable margin = ({ top: 10, right: 30, bottom: 30, left: 60 })
  @observable axisBounds: Map<AxisPlace, Bounds> = new Map()
  axisScales: Map<AxisPlace, ScaleType> = new Map()

  constructor() {
    AxisPlaces.forEach(place => this.axisScales.set(place, scaleOrdinal()))
    makeObservable(this)
  }

  @computed get plotWidth() {
    return 0.8 * this.graphWidth
  }

  @computed get plotHeight() {
    return Math.max(0, 0.8 * this.graphHeight - this.legendHeight)
  }

  isHorizontal(place: AxisPlace) {
    return ["bottom", "top"].includes(place)
  }

  isVertical(place: AxisPlace) {
    return ["left", "right"].includes(place)
  }

  axisLength(place: AxisPlace) {
    return this.isVertical(place) ? this.plotHeight : this.plotWidth
  }

  getAxisBounds(place: AxisPlace) {
    return this.axisBounds.get(place)
  }

  @action setAxisBounds(place: AxisPlace, bounds: Bounds | undefined) {
    if (bounds) {
      // We allow the axis to draw gridlines for bivariate numeric plots. Unfortunately, the gridlines end up as
      // part of the axis dom element so that we get in here with bounds that span the entire width or height of
      // the plot. We tried work arounds to get gridlines that were _not_ part of the axis element with the result
      // that the gridlines got out of synch with axis tick marks during drag. So we have this inelegant solution
      // that shouldn't affect the top and right axes when we get them but it may be worthwhile to
      // (TODO) figure out if there's a better way to render gridlines on background (or plot) so this isn't necessary.

      // given state of the graph, we may need to adjust the drop areas' bounds
      const newBounds = bounds

      if (place === "bottom"){
        newBounds.top = this.plotHeight
      }

      if (place === "left" && bounds.width > this.plotWidth){
         newBounds.width -= this.plotWidth
      }

      this.axisBounds.set(place, newBounds)
    }
    else {
      this.axisBounds.delete(place)
    }
  }

  axisScale(place: AxisPlace) {
    return this.axisScales.get(place)
  }

  @action setAxisScale(place: AxisPlace, scale: ScaleType) {
    scale.range(this.isVertical(place) ? [this.plotHeight, 0] : [0, this.plotWidth])
    this.axisScales.set(place, scale)
  }

  updateScaleRanges(plotWidth:number, plotHeight:number) {
    AxisPlaces.forEach(place => {
      const range = this.isVertical(place) ? [plotHeight, 0] : [0, plotWidth]
      this.axisScale(place)?.range(range)
    })
  }

  @action setGraphExtent(width: number, height: number) {
    const plotWidth = 0.8 * width<commit_msg>restore height computations for upcoming legend work<commit_after>import {ScaleBand, ScaleContinuousNumeric, ScaleOrdinal, scaleOrdinal} from "d3"
import { action, computed, makeObservable, observable } from "mobx"
import { createContext, useContext } from "react"
import { AxisPlace, AxisPlaces } from "./axis-model"

export type ScaleNumericBaseType = ScaleContinuousNumeric<number, number>
export type ScaleType = ScaleContinuousNumeric<number, number> | ScaleOrdinal<string, any> | ScaleBand<string>

export const kDefaultGraphWidth = 480
export const kDefaultGraphHeight = 0
export const kDefaultLegendHeight = 0
export const kDefaultPlotWidth = 0.8 * kDefaultGraphWidth
export const kDefaultPlotHeight = 0.8 * kDefaultGraphHeight

export interface Bounds {
  left: number
  top: number
  width: number
  height: number
}

export class GraphLayout {
  @observable graphWidth = kDefaultGraphWidth
  @observable graphHeight = kDefaultGraphHeight
  @observable legendHeight = kDefaultLegendHeight
  @observable margin = ({ top: 10, right: 30, bottom: 30, left: 60 })
  @observable axisBounds: Map<AxisPlace, Bounds> = new Map()
  axisScales: Map<AxisPlace, ScaleType> = new Map()

  constructor() {
    AxisPlaces.forEach(place => this.axisScales.set(place, scaleOrdinal()))
    makeObservable(this)
  }

  @computed get plotWidth() {
    return 0.8 * this.graphWidth
  }

  @computed get plotHeight() {
    return Math.max(0, 0.8 * this.graphHeight - this.legendHeight)
  }

  isHorizontal(place: AxisPlace) {
    return ["bottom", "top"].includes(place)
  }

  isVertical(place: AxisPlace) {
    return ["left", "right"].includes(place)
  }

  axisLength(place: AxisPlace) {
    return this.isVertical(place) ? this.plotHeight : this.plotWidth
  }

  getAxisBounds(place: AxisPlace) {
    return this.axisBounds.get(place)
  }

  @action setAxisBounds(place: AxisPlace, bounds: Bounds | undefined) {
    if (bounds) {
      // We allow the axis to draw gridlines for bivariate numeric plots. Unfortunately, the gridlines end up as
      // part of the axis dom element so that we get in here with bounds that span the entire width or height of
      // the plot. We tried work arounds to get gridlines that were _not_ part of the axis element with the result
      // that the gridlines got out of synch with axis tick marks during drag. So we have this inelegant solution
      // that shouldn't affect the top and right axes when we get them but it may be worthwhile to
      // (TODO) figure out if there's a better way to render gridlines on background (or plot) so this isn't necessary.

      // given state of the graph, we may need to adjust the drop areas' bounds
      const newBounds = bounds

      if (place === "bottom"){
        newBounds.height = Math.min(bounds.height, this.graphHeight - this.axisLength('left') - this.legendHeight)
        newBounds.top = this.plotHeight
      }

      if (place === "left" && bounds.width > this.plotWidth){
        newBounds.height = Math.min(bounds.height, this.graphHeight - this.legendHeight)
        newBounds.width -= this.plotWidth
      }

      this.axisBounds.set(place, newBounds)
    }
    else {
      this.axisBounds.delete(place)
    }
  }

  axisScale(place: AxisPlace) {
    return this.axisScales.get(place)
  }

  @action setAxisScale(place: AxisPlace, scale: ScaleType) {
    scale.range(this.isVertical(place) ? [this.plotHeight, 0] : [0, this.plotWidth])
    this.axisScales.set(place, scale)
  }

  updateScaleRanges(plotWidth:number, plotHeight:number) {
    AxisPlaces.forEach(place => {
      const range = this.isVertical(place) ? [plotHeight, 0] : [0, plotWidth]
      this.axisScale(place)?.range(range)
    })
  }

  @action setGraphExtent(width: number, height: number) {
    const plotWidth = 0.8 * width
{'fraction_non_alphanumeric': 0.0988292948543425, 'fraction_numerical': 0.007895453307922679, 'mean_word_length': 3.7035851472471193, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 21, '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': '406400', 'n_tokens_mistral': 2302, 'n_tokens_neox': 2174, 'n_words': 682}