오픈 소스 모험: 에피소드 33: D3를 사용하여 러시아가 언제 마지막 탱크를 잃을지 파악

24178 단어 svelted3javascript
자, 이제 앱의 주요 지점인 러시아가 언제 탱크가 바닥날지 알아내겠습니다.

러시아 손실에 대한 데이터 소스



우크라이나 군대 데이터 또는 Oryx 데이터를 가져와서 조정을 추가할 수 있습니다. 숫자는 실제로 매우 유사하므로 지금은 우크라이나 군대 데이터만 사용하겠습니다.

실제 손실이라고 생각하는 조정 슬라이더가 있습니다. 이를 위해 -30%에서 +50% 범위로 설정하겠습니다. -30%는 우크라이나가 파괴했다고 말하는 것의 70%에 대한 사진 증거가 있기 때문에 합리적으로 갈 수 있는 것보다 낮습니다.

Oryx는 처리되지 않은 러시아 손실 사진의 큰 백로그를 보유하고 있음을 공개합니다. 그들의 트윗에 따르면 백로그만 해도 지금까지 총 손실의 약 10%인 것 같습니다. 그것만으로도 Oryx와 우크라이나 데이터의 차이가 공식 우크라이나 데이터보다 -30%에서 -23%로 줄어듭니다.

손실의 77%가 이미 Oryx로 전송된 사진 증거를 가지고 있다고 가정한다면 - 23%는 사진이 찍히지 않은 것, 아직 사진이 찍히지 않은 것, 사진이 찍혔지만 아직 Oryx에 도착하지 않았거나 완전히 파괴되어 무엇인지 식별할 수 없는 것입니다. 사진에서 Oryx 데이터는 공식 우크라이나 데이터와 동일합니다.

모든 사람이 적의 손실을 과대평가하는 것은 자연스러운 가정이므로 큰 할인으로 시작하는 것이 당연하겠지만 -10% 미만의 조정 값은 Oryx 데이터를 기준으로 정당화하기 어렵습니다. 추가 증거 없이 +0%를 초과하는 조정도 정당화하기 어렵습니다. 하지만 기본값을 +0%로 두겠습니다.

다른 종류의 지상 차량에 대한 우크라이나 및 Oryx 데이터도 대략적으로 일치합니다.

공기 손실에 관해서는 그러한 합의가 없으며 우크라이나 숫자는 Oryx 숫자보다 몇 배 더 높습니다. 격추된 비행기와 헬리콥터는 탱크보다 지상에 훨씬 적은 증거를 남길 것이므로 Oryx는 분명히 그것들을 과소 계산하고 있지만 심지어 우크라이나 숫자가 너무 높게 보인다고 가정합니다.

사상자에 관해서는 독립적인 정보가 없습니다.

궁금한 점이 있을 경우를 대비해 러시아 데이터는 모두 순수한 거짓말입니다. 그 프로파간다를 쓰는 러시아 사람들은 실제 데이터에 접근할 수조차 없고 그냥 지어낸 것일 가능성이 높습니다. 여기에는 대칭이 없습니다.

어쨌든, 현재 이 그래프는 러시아 전차 손실에 대한 질문에 답하려고만 하므로 이에 대해 알아보겠습니다!

러시아 전차 인벤토리의 데이터 소스



다른 질문은 러시아가 얼마나 많은 탱크를 보유하고 있는지입니다. According to sources like Wikipedia 약 2,800대의 탱크가 운용 중이고 약 10,000대가 보관 중입니다.

얼마나 많은 탱크가 "저장소에"가동될 수 있는지는 불확실합니다. 대부분은 완전히 녹슬었거나 예비 부품으로만 사용할 수 있습니다. 슬라이더는 0%에서 100% 사이이며 기본값은 10%입니다.

미래 예측



마지막 정보는 지금까지 일어난 일과 비교하여 미래에 러시아가 얼마나 빨리 탱크를 잃을 것으로 예상하는지입니다. 슬라이더는 50%에서 200% 사이이며 기본값은 100%입니다.

그럼 시작하겠습니다!

src/Form.svelte



양식 구성 요소는 레이블과 텍스트가 있는 5개의 슬라이더입니다. 활성/스토리지/스토리지 상품을 내보내지 않고 총계만 내보냅니다. 이러한 분류가 실제로 필요하지 않기 때문입니다.

<script>
import * as d3 from "d3"

export let adjustmentLoss = 0
export let futureIntensity = 100
let activeTanks = 2800
let storageTanks = 10000
let storageGood = 10

export let totalTanks

$: totalTanks = Math.round(activeTanks + storageTanks * storageGood / 100.0)

</script>

<form>
  <div>
    <label for="loss">Adjustment for losses data:</label>
    <input type="range" min="-30" max="50" bind:value={adjustmentLoss} id="loss" />
    <span>{d3.format("+d")(adjustmentLoss)}%</span>
  </div>

  <div>
    <label for="intensity">Predicted future war intensity</label>
    <input type="range" min="50" max="200" bind:value={futureIntensity} id="intensity" />
    <span>{futureIntensity}%</span>
  </div>

  <div>
    <label for="active">Russian tanks at start of war</label>
    <input type="range" min="2500" max="3000" bind:value={activeTanks} id="active" />
    <span>{activeTanks}</span>
  </div>

  <div>
    <label for="storage">Russian tanks in storage</label>
    <input type="range" min="8000" max="12000" bind:value={storageTanks} id="active" />
    <span>{storageTanks}</span>
  </div>

  <div>
    <label for="good">Usable tanks in storage</label>
    <input type="range" min="0" max="100" bind:value={storageGood} id="good" />
    <span>{storageGood}%</span>
  </div>

  <div>
    <span>Total usable tanks</span>
    <span></span>
    <span>{totalTanks}</span>
  </div>
</form>

<style>
form {
  display: grid;
  grid-template-columns: auto auto auto;
}
form > div {
  display: contents;
}
</style>


src/Graph.svelte



여기에 반응형$: 주석이 많이 있습니다. Svelte는 무엇이 무엇에 의존하는지 파악하고 올바른 업데이트를 자동으로 수행합니다.

이것이 바로 Svelte가 적합한 종류의 앱입니다. React 버전에는 많은 반응성 주석과 Form에서 Graph 구성 요소로의 콜백이 필요합니다.

이 구성 요소는 약간 크며 계산 및 그래프 표시를 별도의 구성 요소로 분할할 수 있습니다.

<script>
import * as d3 from "d3"
import Axis from "./Axis.svelte"
import Form from "./Form.svelte"

export let data

let adjust = (data, adjustmentLoss) => data.map(({date, tank}) => ({date, tank: Math.round(tank * (1 + adjustmentLoss/100))}))

// put some dummy data to avoid issues with initialization order
let adjustmentLoss = 0, futureIntensity = 100, totalTanks = 0

let [minDate, maxDate] = d3.extent(data, d => d.date)

$: adjustedData = adjust(data, adjustmentLoss)
$: alreadyDestroyedTanks = d3.max(adjustedData, d => d.tank)
$: tanksMax = Math.max(alreadyDestroyedTanks, totalTanks)

$: currentTankRate = alreadyDestroyedTanks / (maxDate - minDate)
$: futureTankRate = (currentTankRate * futureIntensity / 100.0)
$: tanksTodo = totalTanks - alreadyDestroyedTanks
$: lastTankDate = new Date(+maxDate + (tanksTodo / futureTankRate))

$: xScale = d3.scaleTime()
  .domain([minDate, lastTankDate])
  .range([0, 700])

$: yScale = d3.scaleLinear()
  .domain([0, tanksMax])
  .nice()
  .range([500, 0])

$: pathData = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.tank))
  (adjustedData)

$: trendPathData = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.tank))
  ([adjustedData[0], adjustedData.at(-1), {tank: totalTanks, date: lastTankDate}])

$: tankTotalPathData = d3.line()
  .x(xScale)
  .y(yScale(tanksMax))
  ([minDate, lastTankDate])

$: xAxis = d3.axisBottom()
  .scale(xScale)
  .tickFormat(d3.timeFormat("%e %b %Y"))

$: yAxis = d3
  .axisLeft()
  .scale(yScale)
</script>

<h1>Russian Tank Losses</h1>
<svg>
  <g class="graph">
    <path class="data" d={pathData}/>
    <path class="trendline" d={trendPathData}/>
    <path class="tanktotal" d={tankTotalPathData}/>
  </g>
  <g class="x-axis"><Axis axis={xAxis}/></g>
  <g class="y-axis"><Axis axis={yAxis}/></g>
</svg>

<Form bind:adjustmentLoss bind:futureIntensity bind:totalTanks />
<div>Russia will lose its last tank on {d3.timeFormat("%e %b %Y")(lastTankDate)}</div>

<style>
svg {
  height: 600px;
  width: 800px;
}
.graph {
  transform: translate(50px, 20px);
}
path {
  fill: none;
}
path.data {
  stroke: red;
  stroke-width: 1.5;
}
path.trendline {
  stroke: red;
  stroke-width: 1.5;
  stroke-dasharray: 3px;
}
path.tanktotal {
  stroke: blue;
  stroke-width: 1.5;
}
.x-axis {
  transform: translate(50px, 520px);
}
.y-axis {
  transform: translate(50px, 20px);
}
</style>


결과



기본 설정으로 러시아는 2022년 10월 9일에 마지막 탱크를 잃게 됩니다. 물론 이 앱의 요점은 변화하는 가정이 결과를 어떻게 바꾸는지 확인하는 것입니다.

After that date Russian occupiers will use machine guns mounted on Ural trucks or something .

지금까지의 이야기



All the code is on GitHub .

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

다음에 온다



다음화에서는 앱으로 몇 가지 더 해보도록 하겠습니다.

좋은 웹페이지 즐겨찾기