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

Reply via email to