오픈 소스 모험: 에피소드 41: 러시아 손실 앱용 투영 기준 슬라이더

24903 단어 d3sveltejavascript
Russian Losses 앱을 시작했을 때 특정 추세가 있는지 명확하지 않았기 때문에 단순 선형 적합을 사용했습니다.

그러나 지금 당장은 대략 몇 달에 해당하는 약 3단계로 적합이 더 복잡해질 것으로 보입니다.
  • 2월 - 모든 곳에서 러시아의 급속한 진격과 모든 곳에서 막대한 손실, 도중에 수백 대의 차량이 진흙 속에 버려져 모두가 앞으로 돌진합니다
  • 3월 - 키예프에서 우크라이나 주변의 Mikolaiv에 이르기까지 대부분 정적인 대규모 전선, 매일 큰 러시아 손실이 발생하지만 2월만큼 크지는 않음
  • 4월 - 북부 전선 붕괴, 훨씬 더 짧은 전선
  • 으로 인해 일일 러시아 손실이 훨씬 적음

    전선이 짧아지면 러시아군이 돈바스 포위 시도에 모든 부대를 투입하고 그곳에서 전투가 매우 치열할 것이라고 많은 사람들이 예상했지만 지금까지는 그렇지 않은 것 같습니다. 4월의 남부 전선은 3월의 남부 전선만큼 많은 전투가 발생하지만 북부 전선은 모두 사라졌기 때문에 전체 강도는 훨씬 낮습니다.

    어쨌든 이것은 전체 데이터 세트에 선형 맞춤을 수행하는 대신 마지막 N일에만 선형 맞춤을 원한다는 것을 의미합니다. 여기서 해당 N은 다른 슬라이더로 제어됩니다. 초반이 가장 강렬했기 때문에 러시아가 마지막 탱크를 잃을 것으로 예상되는 시간이 지연될 것입니다.

    슬라이더가 얼마나 멀리 갈 수 있습니까?



    먼저 App.svelte에서 데이터 로드를 수정하여 데이터가 얼마나 멀리 갈지 설정해야 합니다. 현재 53일(가능한 일수보다 1일 적음):

    import { dataDays } from "./stores"
    
    let loadData = async () => {
      let url = "./russia_losses_equipment.csv"
      let data = await d3.csv(url, parseRow)
      data.unshift({date: new Date("2022-02-24"), tank: 0, apc: 0, art: 0})
      $dataDays = data.length - 1
      return data
    }
    


    store.js



    매장이 하나 더 필요합니다 - dataDays :

    import { writable, derived } from "svelte/store"
    
    export let lossAdjustment = writable(0)
    export let projectionBasis = writable(30)
    export let futureIntensity = writable(100)
    
    export let dataDays = writable(0)
    
    export let activeTanks = writable(3417)
    export let activeArmored = writable(18543)
    export let activeArt = writable(5919)
    
    export let storageTanks = writable(10200)
    export let storageArmored = writable(15500)
    export let storageGood = writable(10)
    
    export let totalTanks = derived(
      [activeTanks, storageTanks, storageGood],
      ([$activeTanks, $storageTanks, $storageGood]) => Math.round($activeTanks + $storageTanks * $storageGood / 100.0)
    )
    export let totalArmored = derived(
      [activeArmored, storageArmored, storageGood],
      ([$activeArmored, $storageArmored, $storageGood]) => Math.round($activeArmored + $storageArmored * $storageGood / 100.0)
    )
    
    export let totalArt = activeArt
    


    CommonSliders.svelte



    이러한 양식 입력이 세 번 반복되므로 이를 공유 구성 요소로 추출했습니다.

    <script>
    import * as d3 from "d3"
    import Slider from "./Slider.svelte"
    import { lossAdjustment, projectionBasis, dataDays, futureIntensity } from "./stores"
    </script>
    
    <Slider label="Adjustment for losses data" min={-30} max={50} bind:value={$lossAdjustment} format={(v) => d3.format("+d")(v) + "%"} />
    <Slider label="Projection basis in days" min={1} max={$dataDays} bind:value={$projectionBasis} format={(v) => `${v} days`} />
    <Slider label="Predicted future war intensity" min={10} max={200} bind:value={$futureIntensity} format={(v) => `${v}%`} />
    


    LossesGraph.svelte



    이 파일에는 currentDestroyRate에 대한 새로운 계산이 필요했습니다. 또한 Safari에는 여전히 at가 없기 때문에 로컬Array.prototype.at 기능을 정의했으며 폴리필을 추가하는 것이 약간 번거롭습니다.

    <script>
    import * as d3 from "d3"
    import Graph from "./Graph.svelte"
    import { lossAdjustment, projectionBasis, futureIntensity } from "./stores"
    
    export let lossData, total, label
    
    let adjust = (data, adjustmentLoss) => data.map(({date, unit}) => ({date, unit: Math.round(unit * (1 + adjustmentLoss/100))}))
    let at = (array, idx) => ((idx < 0) ? array[array.length + idx] : array[idx])
    
    let [minDate, maxDate] = d3.extent(lossData, d => d.date)
    
    $: adjustedData = adjust(lossData, $lossAdjustment)
    $: alreadyDestroyed = d3.max(adjustedData, d => d.unit)
    $: unitsMax = Math.max(alreadyDestroyed, total)
    
    $: timeInProjection = at(adjustedData, -$projectionBasis-1).date - at(adjustedData, -1).date
    $: destroyedInProjection = at(adjustedData, -$projectionBasis-1).unit - at(adjustedData, -1).unit
    $: currentDestroyRate = destroyedInProjection / timeInProjection
    
    $: futureDestroyRate = (currentDestroyRate * $futureIntensity / 100.0)
    $: unitsTodo = total - alreadyDestroyed
    $: lastDestroyedDate = new Date(+maxDate + (unitsTodo / futureDestroyRate))
    
    $: xScale = d3.scaleTime()
      .domain([minDate, lastDestroyedDate])
      .range([0, 700])
    
    $: yScale = d3.scaleLinear()
      .domain([0, unitsMax])
      .nice()
      .range([500, 0])
    
    $: pathData = d3.line()
      .x(d => xScale(d.date))
      .y(d => yScale(d.unit))
      (adjustedData)
    
    $: trendPathData = d3.line()
      .x(d => xScale(d.date))
      .y(d => yScale(d.unit))
      ([adjustedData[0], at(adjustedData, -1), {unit: total, date: lastDestroyedDate}])
    
    $: totalPathData = d3.line()
      .x(xScale)
      .y(yScale(unitsMax))
      ([minDate, lastDestroyedDate])
    
    $: xAxis = d3.axisBottom()
      .scale(xScale)
      .tickFormat(d3.timeFormat("%e %b %Y"))
    
    $: yAxisL = d3
      .axisLeft()
      .scale(yScale)
    
    $: yScaleR = d3.scaleLinear()
      .domain([0, 100])
      .range([
        yScale(0),
        yScale(unitsMax)
      ])
    
    $: yAxisR =  d3
      .axisRight()
      .scale(yScaleR)
      .tickFormat(n => `${n}%`)
    </script>
    
    <Graph {pathData} {trendPathData} {totalPathData} {xAxis} {yAxisL} {yAxisR} />
    <div>Russia will lose its last {label} on {d3.timeFormat("%e %b %Y")(lastDestroyedDate)}</div>
    


    지금까지의 이야기



    All the code is on GitHub .

    저는 이것을 GitHub Pagesyou can see it here에 배포했습니다.

    다음에 온다



    다음 에피소드에서는 개인 손실에 대한 예측을 추가하려고 합니다.

    좋은 웹페이지 즐겨찾기