Before we answer the question when Russian will run out of tanks, let's refactor and improve our code a bit.
We want to achieve the following:
- put awkward axis code in separate component
- extend tank axis slightly so it ends up on a round number
- change ticks on date axis, so they start on Feb 24
- add trend line for overall data
- reduce dead space around the graph
src/Axis.svelte
This component doesn't do any logic, it just bridges D3 DOM manipulation with Svelte, so we have less awkward code in the main component:
<script>
import * as d3 from "d3"
export let axis
let axisNode
$: {
d3.select(axisNode).selectAll("*").remove()
d3.select(axisNode).call(axis)
}
</script>
<g bind:this={axisNode}></g>
Rounded numbers on Y scale
We don't need any special code for this, as D3 already comes with a convenient .nice()
function that pads domain on both sides to nearest round number. As 0 is already as round as it gets, it will only pad a bit on the top, in this case to 700:
let yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.tank))
.nice()
.range([500, 0])
Date formatting on X scale
This is a bit more complicated. The scale is fine, and we do not want to pad it with extra days. It should start Feb 24, and end at the last day we have data for.
We can solve that by manually telling D3 where to place ticks (with .ticks(d3.timeThursday)
- as Feb 24 is Thursday), and then how to format them (with .tickFormat(d3.timeFormat("%d %b"))
)
This isn't perfect, as today doesn't have its own tick, but it will do for now.
let xAxis = d3.axisBottom()
.scale(xScale)
.ticks(d3.timeThursday)
.tickFormat(d3.timeFormat("%b %d"))
Trend line
Nothing fancy here, just connect first and last datapoint with dashed line.
let trendPathData = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.tank))
([data[0], data.at(-1)])
src/Graph.svelte
And here's the complete src/Graph.svelte
. All other files are unchanged from previous episode:
<script>
import * as d3 from "d3"
import Axis from "./Axis.svelte"
export let data
let xScale = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([0, 700])
let yScale = d3.scaleLinear()
.domain(d3.extent(data, d => d.tank))
.nice()
.range([500, 0])
let pathData = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.tank))
(data)
let trendPathData = d3.line()
.x(d => xScale(d.date))
.y(d => yScale(d.tank))
([data[0], data.at(-1)])
let xAxis = d3.axisBottom()
.scale(xScale)
.ticks(d3.timeThursday)
.tickFormat(d3.timeFormat("%b %d"))
let yAxis = d3
.axisLeft()
.scale(yScale)
</script>
<h1>Russian Tank Losses</h1>
<svg>
<g class="graph"><path d={pathData}/></g>
<g class="trendline"><path d={trendPathData}/></g>
<g class="x-axis"><Axis axis={xAxis}/></g>
<g class="y-axis"><Axis axis={yAxis}/></g>
</svg>
<style>
svg {
height: 600px;
width: 800px;
}
.graph {
transform: translate(50px, 20px);
}
.graph path {
fill: none;
stroke: red;
stroke-width: 1.5;
}
.trendline {
transform: translate(50px, 20px);
}
.trendline path {
fill: none;
stroke: red;
stroke-width: 1.5;
stroke-dasharray: 3px;
}
.x-axis {
transform: translate(50px, 520px);
}
.y-axis {
transform: translate(50px, 20px);
}
</style>
Story so far
I deployed this on GitHub Pages, you can see it here.
Coming next
In the next episode, we'll add some functionality to the app. The end goal is to try to figure out how long until Russia runs out of tanks, but that might take longer than an episode.
Top comments (0)