You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by jo...@apache.org on 2022/03/27 19:28:13 UTC
[isis] 02/03: ISIS-2957 unused classes removed, PieChart prepared
This is an automated email from the ASF dual-hosted git repository.
joergrade pushed a commit to branch ISIS-2957
in repository https://gitbox.apache.org/repos/asf/isis.git
commit 5f70c5035d508a6d7b1fd3b3f7548e6c931e9551
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Sun Mar 27 14:08:14 2022 +0200
ISIS-2957 unused classes removed, PieChart prepared
---
.../kotlin/org/apache/isis/client/kroviz/App.kt | 2 +-
.../isis/client/kroviz/ui/chart/ChartModel.kt | 35 ---
.../isis/client/kroviz/ui/chart/EventChartModel.kt | 79 -------
.../client/kroviz/ui/chart/SampleChartModel.kt | 73 -------
.../apache/isis/client/kroviz/ui/core/RoMenuBar.kt | 13 --
.../isis/client/kroviz/ui/dialog/ChartDialog.kt | 53 -----
.../client/kroviz/ui/panel/DynamicMenuBuilder.kt | 22 +-
.../isis/client/kroviz/ui/panel/EventPieChart.kt | 237 +++++++++++++++++++++
.../apache/isis/client/kroviz/utils/IconManager.kt | 1 +
9 files changed, 253 insertions(+), 262 deletions(-)
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
index da3d549..caa554c 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/App.kt
@@ -47,7 +47,7 @@ class App : Application() {
return mapOf()
}
- fun initRoApp() {
+ private fun initRoApp() {
roApp = RoApp()
ViewManager.app = this
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/ChartModel.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/ChartModel.kt
deleted file mode 100644
index 50e271a..0000000
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/ChartModel.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.client.kroviz.ui.chart
-
-import io.kvision.chart.DataSets
-import io.kvision.core.Color
-
-interface ChartModel {
-
- var bgColorList: MutableList<Color>
- var bgColorList2: MutableList<Color>
- var labelList: MutableList<String>
-
- var datasetList : MutableList<DataSets>
-
- var ds1: DataSets
- var ds2: DataSets
-
-}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/EventChartModel.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/EventChartModel.kt
deleted file mode 100644
index 5efcbe6..0000000
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/EventChartModel.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.client.kroviz.ui.chart
-
-import org.apache.isis.client.kroviz.core.event.LogEntry
-import io.kvision.chart.DataSets
-import io.kvision.core.Color
-import kotlin.js.Date
-
-class EventChartModel(log: List<LogEntry>) : ChartModel {
-
- override var bgColorList = mutableListOf<Color>()
- override var bgColorList2 = mutableListOf<Color>()
- override var datasetList = mutableListOf<DataSets>()
- override var labelList = mutableListOf<String>()
- val dataList = mutableListOf<Int>()
- override var ds1 = DataSets()
- override var ds2 = DataSets()
-
- private val violett = Color.hex(0x8064A2)
- private val red = Color.hex(0xC0504D)
- private val yellow = Color.hex(0xF79646)
- private val green = Color.hex(0x9BBB59)
- private val blue = Color.hex(0x4F81BD)
-
- private var startTime = Date()
-
- init {
- var i = 0
- log.forEach { le ->
- i += 1
- if (i == 1) startTime = le.createdAt
- val q = le.responseLength
- when {
- (q >= 0) && (q <= 1024) -> bgColorList.add(blue)
- (q > 1024) && (q <= 2048) -> bgColorList.add(green)
- (q > 2048) && (q <= 4096) -> bgColorList.add(yellow)
- (q > 4096) && (q <= 8192) -> bgColorList.add(red)
- else -> bgColorList.add(violett)
- }
- labelList.add(le.toLabel(i))
- dataList.add(le.duration)
- }
- ds1 = DataSets(
- data = dataList,
- backgroundColor = bgColorList,
- label = "duration"
- )
-
- datasetList.add(ds1)
- }
-
- fun LogEntry.toLabel(index: Int): String {
- val relativeStarTime = ((this.createdAt.getTime() - startTime.getTime()) / 1000).toString()
- val sec_1 = relativeStarTime.substring(0, relativeStarTime.length -2)
- return index.toString() + "\n" +
- sec_1 + "\n" +
- this.title + "\n" +
- "start: " + this.createdAt.toISOString() + "\n" +
- "rsp.len: " + this.responseLength
- }
-
-}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/SampleChartModel.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/SampleChartModel.kt
deleted file mode 100644
index 46d1649..0000000
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/chart/SampleChartModel.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.client.kroviz.ui.chart
-
-import io.kvision.chart.DataSets
-import io.kvision.core.Color
-import io.kvision.i18n.I18n
-
-class SampleChartModel() : ChartModel {
-
- override var bgColorList = mutableListOf(
- Color.hex(0xC0504D),
- Color.hex(0xF79646),
- Color.hex(0x9BBB59),
- Color.hex(0x4BACC6),
- Color.hex(0x4F81BD),
- Color.hex(0x8064A2)
- )
-
- override var bgColorList2 = mutableListOf(
- Color.hex(0xC0504D.or(0x303030)),
- Color.hex(0xF79646.or(0x303030)),
- Color.hex(0x9BBB59.or(0x203030)),
- Color.hex(0x4BACC6.or(0x301010)),
- Color.hex(0x4F81BD.or(0x101010)),
- Color.hex(0x8064A2.or(0x303030))
- )
-
- override var labelList = mutableListOf<String>(
- I18n.tr("BU 6"),
- I18n.tr("BU 5"),
- I18n.tr("BU 4"),
- I18n.tr("BU 3"),
- I18n.tr("BU 2"),
- I18n.tr("BU 1")
- )
-
- override var datasetList = mutableListOf<DataSets>()
-
- override var ds1 = DataSets(
- data = listOf(300, 727, 589, 537, 543, 574),
- backgroundColor = bgColorList,
- label = "initial"
- )
-
- override var ds2 = DataSets(
- data = listOf(400, 238, 553, 746, 884, 903),
- backgroundColor = bgColorList2,
- label = "duplicates"
- )
-
- init {
- datasetList.add(ds1)
- datasetList.add(ds2)
- }
-
-}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
index 8e3bf2c..534f8a0 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
@@ -156,19 +156,6 @@ class RoMenuBar : SimplePanel() {
buildMenuEntry("Events", "Event", { EventDialog().open() })
)
- val chartTitle = "Sample Chart"
- mainMenu.add(
- buildMenuEntry(
- chartTitle,
- "Chart",
- {
- ViewManager.add(
- chartTitle,
- EventBubbleChart()
- )
- })
- )
-
val geoMapTitle = "Sample Geo Map"
mainMenu.add(
buildMenuEntry(geoMapTitle, "Map", { ViewManager.add(geoMapTitle, GeoMap()) })
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ChartDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ChartDialog.kt
deleted file mode 100644
index dc4ca4d..0000000
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ChartDialog.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.isis.client.kroviz.ui.dialog
-
-//mport io.data2viz.viz.bindRendererOn
-import io.kvision.html.Canvas
-import org.apache.isis.client.kroviz.core.event.LogEntry
-import org.apache.isis.client.kroviz.ui.core.RoDialog
-import org.apache.isis.client.kroviz.ui.diagram.Data2VizDiagram
-import org.w3c.dom.HTMLCanvasElement
-
-class ChartDialog(
- private val expectedEvents: List<LogEntry>,
- canvasId: String
-) : Controller() {
-
- init {
- dialog = RoDialog(
- caption = canvasId,
- items = mutableListOf(),
- controller = this,
- defaultAction = "OK",
- widthPerc = 50,
- heightPerc = 50,
- customButtons = mutableListOf()
- )
- val canvas = Canvas(200, 200)
- canvas.addAfterInsertHook {
- /* val viz = Data2VizDiagram().bubbleChartExample()
- val htmlCanvas = canvas.getElement().unsafeCast<HTMLCanvasElement>()
- viz.bindRendererOn(htmlCanvas) */
- }
- dialog.add(canvas)
- }
-
-}
-
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
index ed32438..87ce81e 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/DynamicMenuBuilder.kt
@@ -46,21 +46,27 @@ class DynamicMenuBuilder {
fun buildTableMenu(table: EventLogTable): dynamic {
val menu = mutableListOf<dynamic>()
- val a2 = buildMenuEntry("Export", "Export Events ...", {
+ val export = buildMenuEntry("Export", "Export Events ...", {
EventExportDialog().open()
})
- menu.add(a2)
+ menu.add(export)
- val a3 = buildMenuEntry("Tabulator Download", "Tabulator Download", {
+ val download = buildMenuEntry("Tabulator Download", "Tabulator Download", {
this.downLoadCsv(table)
})
- menu.add(a3)
+ menu.add(download)
- val title = "Chart"
- val a4 = buildMenuEntry(title, title, {
- ViewManager.add(title, EventBubbleChart())
+ val bubbleTitle = "Bubble Chart"
+ val bubble = buildMenuEntry(bubbleTitle, bubbleTitle, {
+ ViewManager.add(bubbleTitle, EventBubbleChart())
})
- menu.add(a4)
+ menu.add(bubble)
+
+ val pieTitle = "Pie Chart"
+ val pie = buildMenuEntry(pieTitle, pieTitle, {
+ ViewManager.add(pieTitle, EventPieChart())
+ })
+ menu.add(pie)
return menu.toTypedArray().asDynamic()
}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventPieChart.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventPieChart.kt
new file mode 100644
index 0000000..87ae40a
--- /dev/null
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventPieChart.kt
@@ -0,0 +1,237 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.isis.client.kroviz.ui.panel
+
+import io.kvision.chart.*
+import io.kvision.chart.js.LegendItem
+import io.kvision.core.Col
+import io.kvision.core.Color
+import io.kvision.core.CssSize
+import io.kvision.core.UNIT
+import io.kvision.panel.SimplePanel
+import io.kvision.utils.obj
+import org.apache.isis.client.kroviz.core.event.LogEntry
+import org.apache.isis.client.kroviz.ui.core.SessionManager
+import org.apache.isis.client.kroviz.utils.StringUtils
+import kotlin.math.pow
+
+/*
+new Chart(document.getElementById("doughnut-chart"), {
+ type: 'doughnut',
+ data: {
+ labels: ["Africa", "Asia", "Europe", "Latin America", "North America"],
+ datasets: [
+ {
+ label: "Population (millions)",
+ backgroundColor: ["#3e95cd", "#8e5ea2","#3cba9f","#e8c3b9","#c45850"],
+ data: [2478,5267,734,784,433]
+ }
+ ]
+ },
+ options: {
+ title: {
+ display: true,
+ text: 'Predicted world population (millions) in 2050'
+ }
+ }
+});
+ */
+
+class EventPieChart : SimplePanel() {
+ private val model = SessionManager.getEventStore()
+ private var chart: Chart
+
+ init {
+ width = CssSize(90, UNIT.vw)
+ chart = chart(
+ configuration = buildConfiguration()
+ )
+ }
+
+ private fun buildConfiguration(): Configuration {
+ fun buildToolTipList(): List<String> {
+ val labelList = mutableListOf<String>()
+ model.log.forEachIndexed { i, it ->
+ val l = it.buildToolTip(i)
+ labelList.add(l)
+ }
+ return labelList
+ }
+
+ val dataSetsList = listOf(buildDataSets())
+ return Configuration(
+ type = ChartType.DOUGHNUT,
+ dataSets = dataSetsList,
+ labels = buildToolTipList(),
+ options = buildChartOptions(),
+ )
+ }
+
+ private fun buildChartOptions(): ChartOptions {
+ fun buildLegend(): LegendOptions {
+ fun buildLegendLabelList(): Array<LegendItem> {
+ val legendLabelList = mutableListOf<LegendItem>()
+ label2color.forEach {
+ val li = obj {
+ text = it.key
+ fillStyle = it.value
+ }
+ legendLabelList.add(li as LegendItem)
+ }
+ val error = obj {
+ text = "error"
+ fillStyle = TRANSPARENT
+ strokeStyle = ERROR_COLOR
+ }
+ legendLabelList.add(error as LegendItem)
+ val size = obj {
+ text = "bubble size ^= response size"
+ fillStyle = TRANSPARENT
+ strokeStyle = TRANSPARENT
+ }
+ legendLabelList.add(size as LegendItem)
+ return legendLabelList.toTypedArray()
+ }
+
+ return LegendOptions(
+ display = true,
+ position = Position.RIGHT,
+ labels = LegendLabelOptions(generateLabels = {
+ buildLegendLabelList()
+ }),
+ title = LegendTitleOptions(text = "Parallel Requests", display = true),
+ )
+ }
+
+ return ChartOptions(
+ plugins = PluginsOptions(
+ title = TitleOptions(
+ text = listOf("Request Duration over Time by Request Parallelism and Response Size"),
+ display = true
+ ),
+ legend = buildLegend(),
+ ),
+ showLine = true,
+ scales = mapOf(
+ "x" to ChartScales(
+ title = ScaleTitleOptions(
+ text = "Time since Connect(ms)", display = true
+ )
+ ),
+ "y" to ChartScales(
+ title = ScaleTitleOptions(text = "duration in ms (log)", display = true),
+ type = ScalesType.LOGARITHMIC
+ )
+ )
+ )
+ }
+
+ private fun buildDataSets(): DataSets {
+ fun buildBgColorList(): List<Color> {
+ val bgColorList = mutableListOf<Color>()
+ model.log.forEach {
+ val c = it.determineBubbleColor()
+ bgColorList.add(c)
+ }
+ return bgColorList
+ }
+
+ fun buildBorderColorList(): List<Color> {
+ val borderColorList = mutableListOf<Color>()
+ model.log.forEach {
+ when {
+ it.isError() -> borderColorList.add(ERROR_COLOR)
+ else -> borderColorList.add(Color.name(Col.LIGHTGRAY))
+ }
+ }
+ return borderColorList
+ }
+
+ /**
+ * The term DataSets is severely miss leading:
+ * 1. a plural form is used (where actually a singular would be more appropriate) -> "a DataSets"
+ * 2. datasets are used inside datasets, data inside data
+ */
+ fun buildDataSetsList(): List<DataSets> {
+ val dataSetsList = mutableListOf<DataSets>()
+ model.log.forEach {
+ // val d = it //.buildData()
+ // dataSetsList.add(d)
+ }
+ return dataSetsList
+ }
+
+ return DataSets(
+ backgroundColor = buildBgColorList(),
+ borderColor = buildBorderColorList(),
+ data = buildDataSetsList(),
+ )
+ }
+
+ private fun LogEntry.buildToolTip(index: Int): String {
+ val size = StringUtils.format(this.responseLength)
+ val title = StringUtils.shortTitle(this.title)
+ return title +
+ "\nseq.no.: $index" +
+ "\nparallel runs: ${this.runningAtStart}" +
+ "\nrsp.len.: $size" +
+ "\ntype: ${this.type}"
+ }
+
+ private fun LogEntry.calculateBubbleSize(): Int {
+ val i = responseLength
+ return i.toDouble().pow(1 / 3.toDouble()).toInt()
+ }
+
+ private fun LogEntry.determineBubbleColor(): Color {
+ val i = runningAtStart
+ return when {
+ (i >= 0) && (i <= 4) -> LIGHT_BLUE
+ (i > 4) && (i <= 8) -> DARK_BLUE
+ (i > 8) && (i <= 16) -> GREEN
+ (i > 16) && (i <= 32) -> YELLOW
+ (i > 32) && (i <= 64) -> RED
+ (i > 64) && (i <= 128) -> RED_VIOLET
+ else -> VIOLET
+ }
+ }
+
+ companion object {
+ val TRANSPARENT = Color.rgba(0xFF, 0xFF, 0xFF, 0x00)
+ val ERROR_COLOR = Color.rgba(0xFF, 0x00, 0x00, 0xFF)
+ val LIGHT_BLUE = Color.rgba(0x4B, 0xAC, 0xC6, 0x80)
+ val DARK_BLUE = Color.rgba(0x4F, 0x81, 0xBD, 0x80)
+ val GREEN = Color.rgba(0x9B, 0xBB, 0x59, 0x80)
+ val YELLOW = Color.rgba(0xF7, 0x96, 0x46, 0x80)
+ val RED = Color.rgba(0xC0, 0x50, 0x4D, 0x80)
+ val RED_VIOLET = Color.rgba(0xA0, 0x5A, 0x78, 0x80)
+ val VIOLET = Color.rgba(0x80, 0x64, 0xA2, 0x80)
+
+ val label2color = mapOf(
+ "0 .. 4" to LIGHT_BLUE,
+ "5 .. 8" to DARK_BLUE,
+ "9 .. 16" to GREEN,
+ "17 .. 32" to YELLOW,
+ "33 .. 64" to RED,
+ "65 .. 128" to RED_VIOLET,
+ ">= 129" to VIOLET
+ )
+ }
+
+}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/IconManager.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/IconManager.kt
index b48b67e..3e56d72 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/IconManager.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/IconManager.kt
@@ -88,6 +88,7 @@ object IconManager {
"OK" to "check",
"Open" to "book",
"Other" to "asterisk",
+ "Pie" to "pie-chart",
"Pin" to "map-pin",
"Primitives" to "hashtag",
"Properties" to "indent",