Приключения с открытым исходным кодом: Эпизод 51: Полировка приложения «Русские потери

К моему приложению поступило несколько замечаний, в основном:

  • непонятно, что означает «База прогноза в днях», я заменил его на «Экстраполировать из последних N дней»
  • неясно, что означает график потерь личного состава, есть 3 цвета без объяснения (KIA, WIA, total).

Я также хочу сделать еще несколько вещей:

  • добавить нижний колонтитул с информацией об источниках
  • добавить сводку ожидаемых потерь личного состава на конец года
  • немного изменить минимальное/максимальное значение ползунков (значения по умолчанию могут остаться прежними).

Итак, давайте начнем!

Это просто статичный HTML:

<footer>
  Initial forces from <a href="https://www.iiss.org/publications/the-military-balance">IISS Military Balance 2022</a>.
  Daily losses from <a href="https://www.kaggle.com/datasets/piterfm/2022-ukraine-russian-war">Ukraine Armed Forces.</a>
  <a href="https://github.com/taw/open-source-adventures">Source code available on GitHub</a>.
</footer>

<style>
  footer {
    margin-top: 32px;
    color: #444;
  }
</style>
Вход в полноэкранный режим Выход из полноэкранного режима

SoldierLossesGraph.svelte

Этот компонент действительно становится довольно сложным, а итоги на конец года имеют довольно сложную логику, чтобы избежать неожиданного округления.

<script>
import * as d3 from "d3"
import SoldierGraph from "./SoldierGraph.svelte"
import { lossAdjustment, projectionBasis, wiaToKia, kiaRegular, futureIntensity } from "./stores"

export let lossData

let adjustRow = ({date, unit}, totalLossAdjustment, wiaToKia) => {
  let kia = Math.round(unit * totalLossAdjustment)
  let wia = Math.round(kia * wiaToKia / 100)
  let total = kia + wia
  return {date, kia, wia, total}
}
let adjust = (data, totalLossAdjustment, wiaToKia) => data.map(row => adjustRow(row, totalLossAdjustment, wiaToKia))

let at = (array, idx) => ((idx < 0) ? array[array.length + idx] : array[idx])

let round100 = (x) => Math.round(x / 100) * 100

let formatEoy = (x) => d3.format(".1f")(x / 1000.0) + "k"

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

$: adjustedData = adjust(lossData, ($kiaRegular/100) * (1 + $lossAdjustment / 100.0), $wiaToKia)
$: totalSoFar = d3.max(adjustedData, d => d.total)

$: timeInProjection = at(adjustedData, -$projectionBasis-1).date - at(adjustedData, -1).date
$: kiaInProjection = at(adjustedData, -$projectionBasis-1).kia - at(adjustedData, -1).kia
$: wiaInProjection = at(adjustedData, -$projectionBasis-1).wia - at(adjustedData, -1).wia
$: currentKiaRate = kiaInProjection / timeInProjection
$: currentWiaRate = wiaInProjection / timeInProjection

$: futureKiaRate = (currentKiaRate * $futureIntensity / 100.0)
$: futureWiaRate = (currentWiaRate * $futureIntensity / 100.0)
$: futureTotalRate = futureKiaRate + futureWiaRate

// Just hardcode as there's no obvious "finish date"
$: lastDate = new Date("2023-01-01")
$: graphTime = lastDate - maxDate

// How many KIA+WIA by lastDate
$: unitsMax = Math.round(graphTime * futureTotalRate) + totalSoFar

$: kiaSoFar = d3.max(adjustedData, d => d.kia)
$: wiaSoFar = d3.max(adjustedData, d => d.wia)
$: eoyKia = round100(graphTime * futureKiaRate + kiaSoFar)
$: eoyWia = round100(graphTime * futureWiaRate + wiaSoFar)
$: eoyTotal = eoyKia + eoyWia

$: eoyIKia = round100(eoyKia / $kiaRegular * (100 - $kiaRegular))
$: eoyIWia = round100(eoyWia / $kiaRegular * (100 - $kiaRegular))
$: eoyITotal = eoyIKia + eoyIWia

$: trendData = [
  adjustedData[0],
  at(adjustedData, -1),
  {
    date: lastDate,
    kia: Math.round(graphTime * futureKiaRate) + d3.max(adjustedData, d => d.kia),
    wia: Math.round(graphTime * futureWiaRate) + d3.max(adjustedData, d => d.wia),
    total: Math.round(graphTime * futureTotalRate) + d3.max(adjustedData, d => d.total),
  },
]

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

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

$: yAxis = d3
  .axisLeft()
  .scale(yScale)

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

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

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

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

$: kiaTrendData = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.kia))
  (trendData)

$: wiaTrendData = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.wia))
  (trendData)

$: totalTrendData = d3.line()
  .x(d => xScale(d.date))
  .y(d => yScale(d.total))
  (trendData)
</script>

<SoldierGraph {xAxis} {yAxis} {kiaData} {wiaData} {totalData} {kiaTrendData} {wiaTrendData} {totalTrendData} />
<div>
  <span class="box kia"></span> Killed
  <span class="box wia"></span> Wounded
  <span class="box total"></span> Total
</div>
<div>By end of the year, Russia will lose {formatEoy(eoyTotal)} ({formatEoy(eoyKia)} killed, {formatEoy(eoyWia)} wounded) regular soldiers.</div>
<div>As well as {formatEoy(eoyITotal)} ({formatEoy(eoyIKia)} killed, {formatEoy(eoyIWia)} wounded) irregulars (separatists, PMCs etc.)</div>

<style>
  .box {
    display: inline-block;
    height: 10px;
    width: 10px;
    border: 1px solid black;
  }
  .box.kia {
    background-color: red;
  }
  .box.wia {
    background-color: green;
  }
  .box.total {
    background-color: blue;
  }
</style>
Вход в полноэкранный режим Выход из полноэкранного режима

В результате получаются такие итоги, как:

К концу года Россия потеряет 208,6 тыс. (59,6 тыс. убитыми, 149,0 тыс. ранеными) солдат регулярной армии.
А также 52,2 тыс. (14,9 тыс. убитых, 37,3 тыс. раненых) нерегулярных (сепаратисты, ЧВК и т.д.).

История на данный момент

Весь код находится на GitHub.

Я развернул это на GitHub Pages, вы можете увидеть это здесь.

Оцените статью
Procodings.ru
Добавить комментарий