This is an automated email from the ASF dual-hosted git repository.
mengw15 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 0485b4d6d8 feat(operator): add carpet plot operator (#4398)
0485b4d6d8 is described below
commit 0485b4d6d8d84915843626bbfc1fe51e1bc7da25
Author: Nicole Ying <[email protected]>
AuthorDate: Sun Apr 19 14:21:11 2026 -0700
feat(operator): add carpet plot operator (#4398)
### What changes were proposed in this PR?
This PR introduces a new scientific visualization operator, Carpet Plot,
which visualizes data over two axes with a corresponding value to
generate a carpet plot representation.
The operator takes three required attributes:
- Axis A: first dimension of the plot
- Axis B: second dimension of the plot
- Value (Y): numeric value used to generate the visualization
Based on these inputs, the operator generates a carpet plot
visualization.
File Changes:
- Added CarpetPlot.png to
frontend/src/assets/operator_images/CarpetPlot.png
- Added CarpetPlotOpDesc to
common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/carpetPlot/CarpetPlotOpDesc.scala
- Added lines to import CarpetPlot operator to
common/workflow-operator/src/main/scala/edu/uci/ics/amber/operator/LogicalOp.scala
Testing CSV file:
[carpet_test.csv](https://github.com/user-attachments/files/26803094/texera_carpet_test.csv)
Sample Output:
<img width="1512" height="862" alt="CarpetSample"
src="https://github.com/user-attachments/assets/f6230d71-e1ac-4559-ac30-48c0ec87ed7f"
/>
### Any related issues, documentation, discussions?
No
### How was this PR tested?
This PR was tested both manually and using existing backend test suites.
Backend Tests:
- Ran `sbt test` from the root directory.
- All operator validation and compilation checks passed successfully,
including the newly added CarpetPlot operator.
Manual UI Testing:
- Started Texera frontend and backend locally.
- Verified that the Carpet Plot operator appears in the operator panel.
- Verified the generated result.
- Confirmed that the custom icon is displayed correctly.
### Was this PR authored or co-authored using generative AI tooling?
No
---
.../apache/texera/amber/operator/LogicalOp.scala | 2 +
.../carpetPlot/CarpetPlotOpDesc.scala | 124 +++++++++++++++++++++
frontend/src/assets/operator_images/CarpetPlot.png | Bin 0 -> 17941 bytes
3 files changed, 126 insertions(+)
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/LogicalOp.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/LogicalOp.scala
index 391490a67c..d9b9cd9f10 100644
---
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/LogicalOp.scala
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/LogicalOp.scala
@@ -144,6 +144,7 @@ import org.apache.commons.lang3.builder.{EqualsBuilder,
HashCodeBuilder, ToStrin
import org.apache.texera.amber.operator.sklearn.testing.SklearnTestingOpDesc
import org.apache.texera.amber.operator.source.scan.file.{FileScanOpDesc,
FileScanSourceOpDesc}
import
org.apache.texera.amber.operator.visualization.stripChart.StripChartOpDesc
+import
org.apache.texera.amber.operator.visualization.carpetPlot.CarpetPlotOpDesc
import java.util.UUID
import scala.util.Try
@@ -233,6 +234,7 @@ trait StateTransferFunc
new Type(value = classOf[VolcanoPlotOpDesc], name = "VolcanoPlot"),
new Type(value = classOf[CartesianProductOpDesc], name =
"CartesianProduct"),
new Type(value = classOf[FilledAreaPlotOpDesc], name = "FilledAreaPlot"),
+ new Type(value = classOf[CarpetPlotOpDesc], name = "CarpetPlot"),
new Type(value = classOf[DotPlotOpDesc], name = "DotPlot"),
new Type(value = classOf[TreePlotOpDesc], name = "TreePlot"),
new Type(value = classOf[BubbleChartOpDesc], name = "BubbleChart"),
diff --git
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/carpetPlot/CarpetPlotOpDesc.scala
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/carpetPlot/CarpetPlotOpDesc.scala
new file mode 100644
index 0000000000..1429f2242c
--- /dev/null
+++
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/carpetPlot/CarpetPlotOpDesc.scala
@@ -0,0 +1,124 @@
+/*
+ * 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.texera.amber.operator.visualization.carpetPlot
+
+import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription}
+import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
+import org.apache.texera.amber.core.tuple.{AttributeType, Schema}
+import
org.apache.texera.amber.pybuilder.PythonTemplateBuilder.PythonTemplateBuilderStringContext
+import org.apache.texera.amber.pybuilder.PyStringTypes.EncodableString
+import org.apache.texera.amber.core.workflow.PortIdentity
+import org.apache.texera.amber.operator.PythonOperatorDescriptor
+import
org.apache.texera.amber.operator.metadata.annotations.AutofillAttributeName
+import org.apache.texera.amber.operator.metadata.{OperatorGroupConstants,
OperatorInfo}
+import javax.validation.constraints.NotNull
+
+class CarpetPlotOpDesc extends PythonOperatorDescriptor {
+
+ @JsonProperty(value = "a", required = true)
+ @NotNull(message = "A-axis Attribute cannot be empty")
+ @JsonSchemaTitle("First Parameter Axis Column")
+ @JsonPropertyDescription("Column representing the first parameter axis (a)")
+ @AutofillAttributeName
+ var a: EncodableString = ""
+
+ @JsonProperty(value = "b", required = true)
+ @NotNull(message = "B-axis Attribute cannot be empty")
+ @JsonSchemaTitle("Second Parameter Axis Column")
+ @JsonPropertyDescription("Column representing the second parameter axis (b)")
+ @AutofillAttributeName
+ var b: EncodableString = ""
+
+ @JsonProperty(value = "y", required = true)
+ @NotNull(message = "Y Value cannot be empty")
+ @JsonSchemaTitle("Value Column")
+ @JsonPropertyDescription("Column representing the value at each (a, b)
coordinate")
+ @AutofillAttributeName
+ var y: EncodableString = ""
+
+ 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.forVisualization(
+ "Carpet Plot",
+ "Visualize data in a Carpet Plot",
+ OperatorGroupConstants.VISUALIZATION_SCIENTIFIC_GROUP
+ )
+
+ override def generatePythonCode(): String = {
+ val finalCode =
+ pyb"""
+ |from pytexera import *
+ |import plotly.graph_objects as go
+ |import plotly.io as pio
+ |
+ |class ProcessTableOperator(UDFTableOperator):
+ |
+ | @overrides
+ | def process_table(self, table: Table, port: int) ->
Iterator[Optional[TableLike]]:
+ |
+ | if table.empty:
+ | yield {"html-content": "<h3>Input table is empty</h3>"}
+ | return
+ |
+ | a_col = $a
+ | b_col = $b
+ | y_col = $y
+ |
+ | for col in [a_col, b_col, y_col]:
+ | if col not in table.columns:
+ | yield {"html-content": f"<h3>Column '{col}' not
found</h3>"}
+ | return
+ |
+ | table = table.dropna(subset=[a_col, b_col, y_col])
+ |
+ | if table.empty:
+ | yield {"html-content": "<h3>No valid rows after
removing nulls</h3>"}
+ | return
+ |
+ | try:
+ | table[a_col] = table[a_col].astype(float)
+ | table[b_col] = table[b_col].astype(float)
+ | table[y_col] = table[y_col].astype(float)
+ | except Exception as e:
+ | yield {"html-content": f"<h3>Error converting input
columns to numeric values: {str(e)}</h3>"}
+ | return
+ |
+ | try:
+ | fig = go.Figure(go.Carpet(
+ | a=table[a_col],
+ | b=table[b_col],
+ | y=table[y_col]
+ | ))
+ | html = pio.to_html(fig, include_plotlyjs='cdn',
auto_play=False)
+ | yield {"html-content": html}
+ | except Exception as e:
+ | yield {"html-content": f"<h3>Error generating carpet
plot: {str(e)}</h3>"}
+ |"""
+ finalCode.encode
+ }
+
+}
diff --git a/frontend/src/assets/operator_images/CarpetPlot.png
b/frontend/src/assets/operator_images/CarpetPlot.png
new file mode 100644
index 0000000000..2ef1301a2f
Binary files /dev/null and b/frontend/src/assets/operator_images/CarpetPlot.png
differ