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