SPC Control Chart & Distribution

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.layout.* import io.data2viz.charts.config.configs.* import io.data2viz.charts.config.* import io.data2viz.charts.core.CursorType import io.data2viz.math.* import io.data2viz.geom.* import io.data2viz.viz.* import io.data2viz.color.* import kotlinx.datetime.Instant import kotlin.math.* import kotlinx.browser.document import org.w3c.dom.* val width = 450.0 val height = 300.0 data class Value(val index: Int, val value: Double) val values = listOf( Value(1, 2.8), Value(2, -4.4), Value(3, -5.6), Value(4, -2.9), Value(5, -3.3), Value(6, 0.6), Value(7, 5.9), Value(8, 2.9), Value(9, 2.4), Value(10, 5.2), Value(11, 4.6), Value(12, 2.7), Value(13, 4.2), Value(14, 3.1) ) // just add (then remove) 10 to avoid rounding on "+0" and "-0" val valuesGroup = values.groupBy { ((it.value + 10) / 5.0).toInt() * 5 - 10 } val valuesCount = valuesGroup.map { Pair(it.key, it.value.size) }.sortedBy { it.first } fun main() { // set a style for placing the CANVAS val style = document.createElement("style") style.innerHTML = "canvas{display:inline !important;}" document.head!!.appendChild(style) val div = document.createElement("div") document.body!!.appendChild(div) // Creating and sizing the VizContainer val vc1 = (div as HTMLDivElement).newVizContainer() val vc2 = div.newVizContainer() vc1.apply { size = Size(width * 0.666, height) } vc2.apply { size = Size(width * 0.333, height) } div.setAttribute("style", "height: ${height}px;width: ${width}px") val chart1 = vc1.chart(values) { config { cursor { show = true type = CursorType.Vertical } events { highlightMode = HighlightMode.None } } val indexDim = discrete( { domain.index } ) { name = "Sample index" } val valueDim = quantitative( { domain.value } ) val lowDim = valueDim.child( { -5.0 } ) val meanDim = valueDim.child( { .0 } ) val hiDim = valueDim.child( { 5.0 } ) line(indexDim, valueDim) { showMarkers = true markDecorator = { datum, position, drawingZone -> defaultMarkDecorator(datum, position, drawingZone) if (abs(datum.domain.value) > 5.0) { drawingZone.circle { x = position.x y = position.y radius = 5.0 strokeWidth = 3.0 strokeColor = Colors.Web.red.withAlpha(60.pct) } } } y { start = -10.0 end = 10.0 } } line(indexDim, lowDim) { strokeLine = constant(doubleArrayOf(10.0, 10.0)) strokeColor = constant(Colors.Web.red) } line(indexDim, hiDim) { strokeLine = constant(doubleArrayOf(10.0, 10.0)) strokeColor = constant(Colors.Web.red) } line(indexDim, meanDim) { strokeColor = constant(Colors.Web.black) } } val chart2 = vc2.chart(valuesCount) { val indexDim = discrete( { domain.first } ) val countDim = quantitative( { domain.second.toDouble() } ) { name = "Distribution" } bar(countDim, indexDim) { strokeColor = discrete( { if (domain.first < -5.0 || domain.first > .0) Colors.Web.red else config.mark.strokeColors.first() } ) fill = discrete( { if (domain.first < -5.0 || domain.first > .0) Colors.Web.red.withAlpha(30.pct) else config.mark.fills.first() } ) y { bandwidthRatio = 100.pct enableAxis = false } } } sizeManager().hSynchro().addAllCharts(chart1, chart2) }
pierre avatar

Sketch created by

pierre

A very simple SPC Control chart to show how this can be made. The first chart displays some values, some limit lines, and "highlight" the violation data by drwing a red circle. The second chart displays the distribution of every values. The 2 charts are horizontally synchronized to ensure that the axes are perfectly aligned.

comments

Gaetan Zoritchak