오픈 소스 모험: 에피소드 39: 러시아 손실 앱의 손실 비율 축

18842 단어 d3sveltejavascript
Russian Losses 앱에 추가하고 싶은 몇 가지 기능이 있는데 첫 번째는 % 손실 축입니다.

이것은 때때로 사용되는 시각적 디자인 패턴입니다. 두 개의 Y축이 있습니다. 왼쪽의 Y축은 손실의 절대값이고 오른쪽의 Y축은 손실률입니다.

약간의 주의가 필요합니다. 예를 들어 총 장갑 차량 수가 20093인 경우 다음 라운드 수(22000)로 반올림해야 하므로 왼쪽 축은 0에서 20093으로 이동합니다. 그러나 오른쪽 축은 0%에서 100%로 이동해야 합니다. , 여기서 100%는 22000이 아니라 20093과 일치합니다!

우리가 원하는 것은 다음과 같습니다.

[영상]

Graph.svelte



그래프 구성 요소는 전달한 내용만 표시합니다. 우리는 yAxisLyAxisR를 전달하고 구성 요소는 그것에 대해 많이 생각하지 않고 적절한 위치에 표시합니다.

<script>
import Axis from "./Axis.svelte"
export let pathData, trendPathData, totalPathData, xAxis, yAxisL, yAxisR
</script>

<svg viewBox="0 0 800 600">
  <g class="graph">
    <path class="data" d={pathData}/>
    <path class="trendline" d={trendPathData}/>
    <path class="total" d={totalPathData}/>
  </g>
  <g class="x-axis"><Axis axis={xAxis}/></g>
  <g class="y-axis-left"><Axis axis={yAxisL}/></g>
  <g class="y-axis-right"><Axis axis={yAxisR}/></g>
</svg>

<style>
svg {
  width: 800px;
  max-width: 100vw;
  display: block;
}
.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.total {
  stroke: blue;
  stroke-width: 1.5;
}
.x-axis {
  transform: translate(50px, 520px);
}
.y-axis-left {
  transform: translate(50px, 20px);
}
.y-axis-right {
  transform: translate(750px, 20px);
}
</style>


LossesGraph.svelte



모든 논리는 LossesGraph 구성 요소에 있습니다.

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

export let lossData, total, adjustmentLoss, futureIntensity, label

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

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

$: adjustedData = adjust(lossData, adjustmentLoss)
$: alreadyDestroyed = d3.max(adjustedData, d => d.unit)
$: unitsMax = Math.max(alreadyDestroyed, total)

$: currentDestroyRate = alreadyDestroyed / (maxDate - minDate)
$: 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], adjustedData[adjustedData.length - 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>


가장 중요한 부분은 yScaleR 정의, 특히 pre-nice-ified range 로 정의되는 yScale 입니다. 따라서 0%yScale(0) 와 정렬되어야 하지만 100%yScale(unitsMax) 와 정렬되어야 합니다(이 예에서는 yScale(20093) ).
yScale(22000)는 특별한 작업을 수행하지 않는 모든 틱에 yAxisR를 추가하는 것 외에는 일반적인 백분율 척도입니다.

지금까지의 이야기



All the code is on GitHub .

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

다음에 온다



다음 몇 편의 에피소드에서는 Russian Losses 앱에 추가하고 싶은 기능이 몇 가지 더 있습니다.

좋은 웹페이지 즐겨찾기