Let's define precisely what is a line chart when it's best used and the dos and don'ts of this type of chart.
If you want to dive directly into practice, have a look at the How-to do a line chart? guide.
A line chart (or line plot or line graph or curve chart) displays data values as points (markers or symbols) that are connected using line segments, curves, or even steps.
The most standard usage of a line chart is to show the trend of a value over a continuous time span which is represented along the X-axis.
The measurement points are ordered, typically by their X-axis value.
//run height=400 from=100 to=114
import io.data2viz.charts.*
import io.data2viz.charts.core.Padding
import io.data2viz.charts.core.*
import io.data2viz.charts.dimension.*
import io.data2viz.charts.chart.*
import io.data2viz.charts.chart.mark.*
import io.data2viz.charts.viz.*
import io.data2viz.charts.layout.*
import io.data2viz.charts.config.configs.*
import io.data2viz.math.*
import io.data2viz.color.*
import io.data2viz.geom.*
import io.data2viz.shape.Symbols
import io.data2viz.dsv.Dsv
import org.w3c.fetch.Response
import kotlinx.browser.window
import kotlin.js.Promise
val width = 600.0
val height = 400.0
// The dataset holds 2016 & 2017
private val year = 2017
// The "Weather" class
data class Weather(
val city: String,
val year: Int,
val month: Int,
val highTemp: Double,
val avgTemp: Double,
val lowTemp: Double,
val precip: Double)
// This function transform a CSV line to a "Weather" instance
private fun parseWeather(row: List<String>) = Weather(
row[0],
row[1].toInt(),
row[2].toInt(),
row[3].toDouble(),
row[4].toDouble(),
row[5].toDouble(),
row[6].toDouble()
)
// Just use a simple list of months label for the X axis
private val months = listOf("Jan.", "Feb.", "Mar.", "Apr.", "May", "Jun.",
"Jul.", "Aug.", "Sep.", "Oct.", "Nov.", "Dec.")
fun main() {
// Creating and sizing the VizContainer
val vc = newVizContainer().apply {
size = Size(width, height)
}
// original taken from <https://vincentarelbundock.github.io/Rdatasets/>
val request: Promise<Response> =
window.fetch("<https://docs.google.com/spreadsheets/d/e/2PACX-1vTX4QuCNyDvUoA>"
+"wk6Jl6UJ4r336A87VIKQ5BVyEgowXG_raXdFBMvmUhmz1LLc07GavyC9J6pZ4"
+"YHqJ/pub?gid=650761999&single=true&output=csv")
request.then {
it.text().then {
val results = Dsv()
.parseRows(it)
.drop(1)
.map { parseWeather(it) }
.filter { it.year == year }
.toMutableList()
vc.chart(results) {
title = "Monthly average temperature in $year"
config {
cursor {
show = true
type = CursorType.Vertical
}
legend {
show = LegendVisibility.Show
}
}
series = discrete( { domain.city } )
val monthDim = discrete( { domain.month } ) {
formatter = { "${months[this - 1]} "}
}
val tempDim = quantitative( { domain.avgTemp } ) {
name = "Average temperature for the month"
formatter = { "$thisĀ°F" }
}
line(monthDim, tempDim) {
// data is loaded from an external CSV, it may take a few seconds
curve = MarkCurves.Curved
size = constant(30.0)
symbol = constant(Symbols.Circle)
showSymbols = true
strokeWidth = constant(2.0)
y {
start = .0
end = 100.0
}
}
}
}
}
}
A line chart is particularly great for these use cases:
We generally associate the X-axis of a line chart with time, so using a line chart to display the variation of a value over time allows the user to see it very quickly.
It's also working very well for any other dimension (distance, temperature...) that is expected to be continuous.
//run height=150 from=15 to=20
import io.data2viz.charts.core.*
import io.data2viz.charts.chart.*
import io.data2viz.charts.chart.mark.*
import io.data2viz.charts.viz.*
import io.data2viz.geom.*
val width = 400.0
val height = 150.0
fun main() {
val vc = newVizContainer().apply { size = Size(width, height) }
with(vc) {
val values = listOf(1.0, 1.2, 1.3, 1.2, 1.5, 1.9, 2.6, 4.2, 6.8, 7.7, 8.2, 8.4, 8.6, 8.6, 8.5, 8.7)
chart(values) {
val indexDimension = quantitative( { indexInData.toDouble() } )
val valueDimension = quantitative( { domain } )
line(indexDimension, valueDimension)
}
}
}
Line charts are good to detect patterns and identify trends, but also very good for seeing when something's wrong (pikes or holes...).
//run height=150 from=17 to=22
import io.data2viz.charts.core.*
import io.data2viz.charts.chart.*
import io.data2viz.charts.chart.mark.*
import io.data2viz.charts.viz.*
import io.data2viz.geom.*
import kotlin.random.*
import kotlin.math.*
val width = 400.0
val height = 150.0
fun main() {
val vc = newVizContainer().apply { size = Size(width, height) }
with(vc) {
val values = (0 .. 100).map{ Random.nextDouble() + if (it == 80) 16.0 else .0 }
chart(values) {
val indexDimension = quantitative( { indexInData.toDouble() } )
val valueDimension = quantitative( { domain } )
line(indexDimension, valueDimension)
}
}
}
Line charts offer a high data to ink ratio, making them a good option to identify patterns when you have multiple series.
//run height=150 from=53 to=60
import io.data2viz.charts.core.*
import io.data2viz.charts.chart.*
import io.data2viz.charts.chart.mark.*
import io.data2viz.charts.viz.*
import io.data2viz.geom.*
import kotlin.random.*
import kotlin.math.*
val width = 400.0
val height = 150.0
data class Record(val batch:Int, val value:Double)
val values = listOf(
Record(1, 1.0),
Record(1, 1.2),
Record(1, 1.3),
Record(1, 1.2),
Record(1, 1.5),
Record(1, 1.9),
Record(1, 2.6),
Record(1, 4.2),
Record(1, 6.8),
Record(1, 7.7),
Record(1, 8.2),
Record(1, 8.4),
Record(1, 8.6),
Record(1, 8.6),
Record(1, 8.5),
Record(1, 8.7),
Record(2, 8.0),
Record(2, 7.8),
Record(2, 7.3),
Record(2, 7.2),
Record(2, 7.5),
Record(2, 6.9),
Record(2, 6.6),
Record(2, 6.2),
Record(2, 5.8),
Record(2, 5.7),
Record(2, 5.2),
Record(2, 5.4),
Record(2, 5.6),
Record(2, 5.6),
Record(2, 5.5),
Record(2, 5.7)
)
fun main() {
val vc = newVizContainer().apply { size = Size(width, height) }
with(vc) {
chart(values) {
val indexDimension = quantitative( { indexInSeries.toDouble() } )
val valueDimension = quantitative( { domain.value } )
series = discrete( { domain.batch } )
line(indexDimension, valueDimension) {
strokeWidth = constant(2.0)
}
}
}
}