This is an automated email from the ASF dual-hosted git repository.
xiaozhenliu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git
The following commit(s) were added to refs/heads/main by this push:
new b3317c1fad feat(operator): Add strip chart visualization operator
(#3913)
b3317c1fad is described below
commit b3317c1fad6f3322f686679a7d0ab78381fd2167
Author: Rithwik Garapati <[email protected]>
AuthorDate: Sun Oct 19 10:28:12 2025 -0700
feat(operator): Add strip chart visualization operator (#3913)
# The Title of the proposal
Author: [email protected]
Purpose: Implementing an operator as part of the onboarding process to
understand the codebase.
# Important Implementation Details
This PR introduces a new visualization operator, Strip Chart, which
displays multiple metrics over time as a series of horizontal lines,
allowing users to track trends and patterns across different data
streams. The operator can generate one or multiple charts depending on
the input data.
X-axis: Maps the primary dimension (e.g., time or category) of the data.
Y-axis: Maps the metric values to display along the vertical axis.
Facet column: Splits the data into multiple subplots based on a
categorical column.
Color-by: Applies different colors to data points based on a categorical
or grouping attribute.
# Testing
<img width="1512" height="853" alt="StripChartPt"
src="https://github.com/user-attachments/assets/e65422a1-b636-46a6-ad44-213e7a6adc91"
/>
Co-authored-by: Chen Li <[email protected]>
Co-authored-by: Xiaozhen Liu <[email protected]>
---
.../org/apache/amber/operator/LogicalOp.scala | 2 +
.../stripChart/StripChartOpDesc.scala | 121 +++++++++++++++++++++
frontend/src/assets/operator_images/StripChart.png | Bin 0 -> 45788 bytes
3 files changed, 123 insertions(+)
diff --git
a/common/workflow-operator/src/main/scala/org/apache/amber/operator/LogicalOp.scala
b/common/workflow-operator/src/main/scala/org/apache/amber/operator/LogicalOp.scala
index cf74190f06..6fb27d92c3 100644
---
a/common/workflow-operator/src/main/scala/org/apache/amber/operator/LogicalOp.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/amber/operator/LogicalOp.scala
@@ -130,6 +130,7 @@ import
org.apache.amber.operator.visualization.volcanoPlot.VolcanoPlotOpDesc
import
org.apache.amber.operator.visualization.waterfallChart.WaterfallChartOpDesc
import org.apache.amber.operator.visualization.wordCloud.WordCloudOpDesc
import org.apache.commons.lang3.builder.{EqualsBuilder, HashCodeBuilder,
ToStringBuilder}
+import org.apache.amber.operator.visualization.stripChart.StripChartOpDesc
import java.util.UUID
import scala.util.Try
@@ -170,6 +171,7 @@ trait StateTransferFunc
new Type(value = classOf[RegexOpDesc], name = "Regex"),
new Type(value = classOf[SpecializedFilterOpDesc], name = "Filter"),
new Type(value = classOf[ProjectionOpDesc], name = "Projection"),
+ new Type(value = classOf[StripChartOpDesc], name = "StripChart"),
new Type(value = classOf[UnionOpDesc], name = "Union"),
new Type(value = classOf[KeywordSearchOpDesc], name = "KeywordSearch"),
new Type(value = classOf[SubstringSearchOpDesc], name = "SubstringSearch"),
diff --git
a/common/workflow-operator/src/main/scala/org/apache/amber/operator/visualization/stripChart/StripChartOpDesc.scala
b/common/workflow-operator/src/main/scala/org/apache/amber/operator/visualization/stripChart/StripChartOpDesc.scala
new file mode 100644
index 0000000000..1906ce469f
--- /dev/null
+++
b/common/workflow-operator/src/main/scala/org/apache/amber/operator/visualization/stripChart/StripChartOpDesc.scala
@@ -0,0 +1,121 @@
+/*
+ * 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.amber.operator.visualization.stripChart
+
+import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription}
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
+import org.apache.amber.core.tuple.{AttributeType, Schema}
+import org.apache.amber.core.workflow.OutputPort.OutputMode
+import org.apache.amber.core.workflow.{InputPort, OutputPort, PortIdentity}
+import org.apache.amber.operator.PythonOperatorDescriptor
+import org.apache.amber.operator.metadata.annotations.AutofillAttributeName
+import org.apache.amber.operator.metadata.{OperatorGroupConstants,
OperatorInfo}
+
+class StripChartOpDesc extends PythonOperatorDescriptor {
+
+ @JsonProperty(value = "x", required = true)
+ @JsonSchemaTitle("X-Axis Column")
+ @JsonPropertyDescription("Column containing numeric values for the x-axis")
+ @AutofillAttributeName
+ var x: String = ""
+
+ @JsonProperty(value = "y", required = true)
+ @JsonSchemaTitle("Y-Axis Column")
+ @JsonPropertyDescription("Column containing categorical values for the
y-axis")
+ @AutofillAttributeName
+ var y: String = ""
+
+ @JsonProperty(value = "colorBy", required = false)
+ @JsonSchemaTitle("Color By")
+ @JsonPropertyDescription("Optional - Color points by category")
+ @AutofillAttributeName
+ var colorBy: String = ""
+
+ @JsonProperty(value = "facetColumn", required = false)
+ @JsonSchemaTitle("Facet Column")
+ @JsonPropertyDescription("Optional - Create separate subplots for each
category")
+ @AutofillAttributeName
+ var facetColumn: String = ""
+
+ override def getOutputSchemas(
+ inputSchemas: Map[PortIdentity, Schema]
+ ): Map[PortIdentity, Schema] = {
+ val outputSchema = Schema()
+ .add("html-content", AttributeType.STRING)
+ Map(operatorInfo.outputPorts.head.id -> outputSchema)
+ }
+
+ override def operatorInfo: OperatorInfo =
+ OperatorInfo(
+ "Strip Chart",
+ "Visualize distribution of data points as a strip plot",
+ OperatorGroupConstants.VISUALIZATION_STATISTICAL_GROUP,
+ inputPorts = List(InputPort()),
+ outputPorts = List(OutputPort(mode = OutputMode.SINGLE_SNAPSHOT))
+ )
+
+ override def generatePythonCode(): String = {
+ val colorByParam = if (colorBy != null && colorBy.nonEmpty) s",
color='$colorBy'" else ""
+ val facetColParam =
+ if (facetColumn != null && facetColumn.nonEmpty) s",
facet_col='$facetColumn'" else ""
+
+ s"""from pytexera import *
+ |import plotly.express as px
+ |import plotly.io as pio
+ |
+ |class ProcessTableOperator(UDFTableOperator):
+ |
+ | @overrides
+ | def process_table(self, table: Table, port: int) ->
Iterator[Optional[TableLike]]:
+ | x_values = table['$x']
+ | y_values = table['$y']
+ |
+ | # Create data dictionary
+ | data = {'$x': x_values, '$y': y_values}
+ |
+ | # Add optional color column if specified
+ | if '$colorBy':
+ | data['$colorBy'] = table['$colorBy']
+ |
+ | # Add optional facet column if specified
+ | if '$facetColumn':
+ | data['$facetColumn'] = table['$facetColumn']
+ |
+ | # Create strip chart
+ | fig = px.strip(
+ | data,
+ | x='$x',
+ | y='$y'$colorByParam$facetColParam
+ | )
+ |
+ | # Update layout for better visualization
+ | fig.update_traces(marker=dict(size=8, line=dict(width=0.5,
color='DarkSlateGrey')))
+ | fig.update_layout(
+ | xaxis_title='$x',
+ | yaxis_title='$y',
+ | hovermode='closest'
+ | )
+ |
+ | # Convert to HTML
+ | html = pio.to_html(fig, include_plotlyjs='cdn',
full_html=False)
+ | yield {'html-content': html}
+ |""".stripMargin
+ }
+}
diff --git a/frontend/src/assets/operator_images/StripChart.png
b/frontend/src/assets/operator_images/StripChart.png
new file mode 100644
index 0000000000..d363bc2180
Binary files /dev/null and b/frontend/src/assets/operator_images/StripChart.png
differ