This is an automated email from the ASF dual-hosted git repository.

linxinyuan 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 8cd1570dd2 feat: add a new webgl polar chart operator (#4221)
8cd1570dd2 is described below

commit 8cd1570dd2759a515d8168db80e44d55bab84314
Author: Anish Shivamurthy <[email protected]>
AuthorDate: Fri Apr 17 00:16:43 2026 -0700

    feat: add a new webgl polar chart operator (#4221)
    
    ### What changes were proposed in this PR?
    <img width="1502" height="855" alt="Screenshot 2026-02-16 at 1 51 11 PM"
    
src="https://github.com/user-attachments/assets/0833347c-b6f0-4f7c-8468-f7a773bffab3";
    />
    
    This change introduces a WebGL Polar Chart operator, which visualizes
    data using polar coordinates rendered with GPU-accelerated WebGL. The
    WebGL Polar Chart operator enables high-performance and interactive
    visualization of datasets that are naturally expressed in angular and
    radial dimensions.
    In a WebGL polar chart:
    - The angular axis represents categories or continuous angular values.
    - The radial axis represents magnitude or distance from the center.
    - Data points are rendered using WebGL for efficient GPU-based
    visualization.
    - The visualization supports smooth rendering of larger datasets
    compared to traditional DOM/SVG approaches.
    
    This visualization is useful for:
    - Displaying cyclic or directional data.
    - Comparing magnitudes across angular segments.
    - Identifying patterns in periodic datasets.
    - Enabling efficient, interactive visual analytics workflows.
    
    The operator has been integrated into the Texera workflow system and
    appears under the visualization category.
    
    ### Any related issues, documentation, discussions?
    Needs python library scikit-image
    Can be installed using: pip install scikit-image
    
    ### How was this PR tested?
    Tested with existing test cases
    
    ### Was this PR authored or co-authored using generative AI tooling?
    No
    
    ---------
    
    Signed-off-by: Xinyuan Lin <[email protected]>
    Co-authored-by: Anish Shivamurthy 
<[email protected]>
    Co-authored-by: Anish Shivamurthy <[email protected]>
    Co-authored-by: Xinyuan Lin <[email protected]>
---
 .../apache/texera/amber/operator/LogicalOp.scala   |   2 +
 .../polarChart/PolarChartOpDesc.scala              | 113 +++++++++++++++++++++
 frontend/src/assets/operator_images/PolarChart.png | Bin 0 -> 133686 bytes
 3 files changed, 115 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 7671555d0e..391490a67c 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
@@ -132,6 +132,7 @@ import 
org.apache.texera.amber.operator.visualization.scatter3DChart.Scatter3dCh
 import 
org.apache.texera.amber.operator.visualization.scatterplot.ScatterplotOpDesc
 import 
org.apache.texera.amber.operator.visualization.tablesChart.TablesPlotOpDesc
 import 
org.apache.texera.amber.operator.visualization.ternaryPlot.TernaryPlotOpDesc
+import 
org.apache.texera.amber.operator.visualization.polarChart.PolarChartOpDesc
 import 
org.apache.texera.amber.operator.visualization.timeSeriesplot.TimeSeriesOpDesc
 import org.apache.texera.amber.operator.visualization.treeplot.TreePlotOpDesc
 import org.apache.texera.amber.operator.visualization.urlviz.UrlVizOpDesc
@@ -194,6 +195,7 @@ trait StateTransferFunc
     new Type(value = classOf[WaterfallChartOpDesc], name = "WaterfallChart"),
     new Type(value = classOf[WindRoseChartOpDesc], name = "WindRoseChart"),
     new Type(value = classOf[BarChartOpDesc], name = "BarChart"),
+    new Type(value = classOf[PolarChartOpDesc], name = "PolarChart"),
     new Type(value = classOf[RangeSliderOpDesc], name = "RangeSlider"),
     new Type(value = classOf[PieChartOpDesc], name = "PieChart"),
     new Type(value = classOf[QuiverPlotOpDesc], name = "QuiverPlot"),
diff --git 
a/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/polarChart/PolarChartOpDesc.scala
 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/polarChart/PolarChartOpDesc.scala
new file mode 100644
index 0000000000..0ff575063b
--- /dev/null
+++ 
b/common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/visualization/polarChart/PolarChartOpDesc.scala
@@ -0,0 +1,113 @@
+/*
+ * 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.polarChart
+
+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.core.workflow.OutputPort.OutputMode
+import org.apache.texera.amber.core.workflow.{InputPort, OutputPort, 
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 org.apache.texera.amber.pybuilder.PyStringTypes.EncodableString
+import 
org.apache.texera.amber.pybuilder.PythonTemplateBuilder.PythonTemplateBuilderStringContext
+
+class PolarChartOpDesc extends PythonOperatorDescriptor {
+
+  @JsonProperty(value = "r", required = true)
+  @JsonSchemaTitle("r")
+  @JsonPropertyDescription("The column name for radial values (must be 
numeric)")
+  @AutofillAttributeName
+  var r: EncodableString = ""
+
+  @JsonProperty(value = "theta", required = true)
+  @JsonSchemaTitle("theta")
+  @JsonPropertyDescription("The column name for angular values (must be 
numeric)")
+  @AutofillAttributeName
+  var theta: 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(
+      "Polar Chart",
+      "Displays data points in a polar scatter plot",
+      OperatorGroupConstants.VISUALIZATION_SCIENTIFIC_GROUP,
+      inputPorts = List(InputPort()),
+      outputPorts = List(OutputPort(mode = OutputMode.SINGLE_SNAPSHOT))
+    )
+
+  override def generatePythonCode(): String = {
+    val finalCode =
+      pyb"""from pytexera import *
+       |import plotly.graph_objects as go
+       |import plotly.io as pio
+       |import numpy as np
+       |
+       |class ProcessTableOperator(UDFTableOperator):
+       |
+       |    @overrides
+       |    def process_table(self, table: Table, port: int) -> 
Iterator[Optional[TableLike]]:
+       |
+       |        if table is None or table.empty:
+       |            yield {'html-content': '<h3>No data available for Polar 
Chart</h3>'}
+       |            return
+       |
+       |        if $r not in table.columns or $theta not in table.columns:
+       |            yield {'html-content': '<h3>Selected columns not found in 
input table</h3>'}
+       |            return
+       |
+       |        if not np.issubdtype(table[$r].dtype, np.number) or not 
np.issubdtype(table[$theta].dtype, np.number):
+       |            yield {'html-content': '<h3>Selected columns must be 
numeric</h3>'}
+       |            return
+       |
+       |        r_vals = table[$r].values
+       |        theta_vals = table[$theta].values
+       |
+       |        fig = go.Figure(data=go.Scatterpolargl(
+       |            r=r_vals,
+       |            theta=theta_vals,
+       |            mode='markers',
+       |            marker=dict(
+       |                size=10,
+       |                opacity=0.7,
+       |                line=dict(color='white')
+       |            )
+       |        ))
+       |
+       |        fig.update_layout(
+       |            title='Polar Chart',
+       |            showlegend=False
+       |        )
+       |
+       |        html = pio.to_html(fig, include_plotlyjs='cdn', 
full_html=False)
+       |        yield {'html-content': html}
+       |"""
+    finalCode.encode
+  }
+}
diff --git a/frontend/src/assets/operator_images/PolarChart.png 
b/frontend/src/assets/operator_images/PolarChart.png
new file mode 100644
index 0000000000..3db7c154b5
Binary files /dev/null and b/frontend/src/assets/operator_images/PolarChart.png 
differ

Reply via email to