Skip to content

Commit 8cd1570

Browse files
anishshiva7Anish ShivamurthyAnish Shivamurthyaglinxinyuan
authored
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 <xinyual3@uci.edu> Co-authored-by: Anish Shivamurthy <anish@dhcp-10-8-033-008.mobile.reshsg.uci.edu> Co-authored-by: Anish Shivamurthy <anish@dhcp-172-31-198-097.mobile.uci.edu> Co-authored-by: Xinyuan Lin <xinyual3@uci.edu>
1 parent d868b85 commit 8cd1570

3 files changed

Lines changed: 115 additions & 0 deletions

File tree

common/workflow-operator/src/main/scala/org/apache/texera/amber/operator/LogicalOp.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ import org.apache.texera.amber.operator.visualization.scatter3DChart.Scatter3dCh
132132
import org.apache.texera.amber.operator.visualization.scatterplot.ScatterplotOpDesc
133133
import org.apache.texera.amber.operator.visualization.tablesChart.TablesPlotOpDesc
134134
import org.apache.texera.amber.operator.visualization.ternaryPlot.TernaryPlotOpDesc
135+
import org.apache.texera.amber.operator.visualization.polarChart.PolarChartOpDesc
135136
import org.apache.texera.amber.operator.visualization.timeSeriesplot.TimeSeriesOpDesc
136137
import org.apache.texera.amber.operator.visualization.treeplot.TreePlotOpDesc
137138
import org.apache.texera.amber.operator.visualization.urlviz.UrlVizOpDesc
@@ -194,6 +195,7 @@ trait StateTransferFunc
194195
new Type(value = classOf[WaterfallChartOpDesc], name = "WaterfallChart"),
195196
new Type(value = classOf[WindRoseChartOpDesc], name = "WindRoseChart"),
196197
new Type(value = classOf[BarChartOpDesc], name = "BarChart"),
198+
new Type(value = classOf[PolarChartOpDesc], name = "PolarChart"),
197199
new Type(value = classOf[RangeSliderOpDesc], name = "RangeSlider"),
198200
new Type(value = classOf[PieChartOpDesc], name = "PieChart"),
199201
new Type(value = classOf[QuiverPlotOpDesc], name = "QuiverPlot"),
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.texera.amber.operator.visualization.polarChart
21+
22+
import com.fasterxml.jackson.annotation.{JsonProperty, JsonPropertyDescription}
23+
import com.kjetland.jackson.jsonSchema.annotations.JsonSchemaTitle
24+
import org.apache.texera.amber.core.tuple.{AttributeType, Schema}
25+
import org.apache.texera.amber.core.workflow.OutputPort.OutputMode
26+
import org.apache.texera.amber.core.workflow.{InputPort, OutputPort, PortIdentity}
27+
import org.apache.texera.amber.operator.PythonOperatorDescriptor
28+
import org.apache.texera.amber.operator.metadata.annotations.AutofillAttributeName
29+
import org.apache.texera.amber.operator.metadata.{OperatorGroupConstants, OperatorInfo}
30+
import org.apache.texera.amber.pybuilder.PyStringTypes.EncodableString
31+
import org.apache.texera.amber.pybuilder.PythonTemplateBuilder.PythonTemplateBuilderStringContext
32+
33+
class PolarChartOpDesc extends PythonOperatorDescriptor {
34+
35+
@JsonProperty(value = "r", required = true)
36+
@JsonSchemaTitle("r")
37+
@JsonPropertyDescription("The column name for radial values (must be numeric)")
38+
@AutofillAttributeName
39+
var r: EncodableString = ""
40+
41+
@JsonProperty(value = "theta", required = true)
42+
@JsonSchemaTitle("theta")
43+
@JsonPropertyDescription("The column name for angular values (must be numeric)")
44+
@AutofillAttributeName
45+
var theta: EncodableString = ""
46+
47+
override def getOutputSchemas(
48+
inputSchemas: Map[PortIdentity, Schema]
49+
): Map[PortIdentity, Schema] = {
50+
val outputSchema = Schema()
51+
.add("html-content", AttributeType.STRING)
52+
53+
Map(operatorInfo.outputPorts.head.id -> outputSchema)
54+
}
55+
56+
override def operatorInfo: OperatorInfo =
57+
OperatorInfo(
58+
"Polar Chart",
59+
"Displays data points in a polar scatter plot",
60+
OperatorGroupConstants.VISUALIZATION_SCIENTIFIC_GROUP,
61+
inputPorts = List(InputPort()),
62+
outputPorts = List(OutputPort(mode = OutputMode.SINGLE_SNAPSHOT))
63+
)
64+
65+
override def generatePythonCode(): String = {
66+
val finalCode =
67+
pyb"""from pytexera import *
68+
|import plotly.graph_objects as go
69+
|import plotly.io as pio
70+
|import numpy as np
71+
|
72+
|class ProcessTableOperator(UDFTableOperator):
73+
|
74+
| @overrides
75+
| def process_table(self, table: Table, port: int) -> Iterator[Optional[TableLike]]:
76+
|
77+
| if table is None or table.empty:
78+
| yield {'html-content': '<h3>No data available for Polar Chart</h3>'}
79+
| return
80+
|
81+
| if $r not in table.columns or $theta not in table.columns:
82+
| yield {'html-content': '<h3>Selected columns not found in input table</h3>'}
83+
| return
84+
|
85+
| if not np.issubdtype(table[$r].dtype, np.number) or not np.issubdtype(table[$theta].dtype, np.number):
86+
| yield {'html-content': '<h3>Selected columns must be numeric</h3>'}
87+
| return
88+
|
89+
| r_vals = table[$r].values
90+
| theta_vals = table[$theta].values
91+
|
92+
| fig = go.Figure(data=go.Scatterpolargl(
93+
| r=r_vals,
94+
| theta=theta_vals,
95+
| mode='markers',
96+
| marker=dict(
97+
| size=10,
98+
| opacity=0.7,
99+
| line=dict(color='white')
100+
| )
101+
| ))
102+
|
103+
| fig.update_layout(
104+
| title='Polar Chart',
105+
| showlegend=False
106+
| )
107+
|
108+
| html = pio.to_html(fig, include_plotlyjs='cdn', full_html=False)
109+
| yield {'html-content': html}
110+
|"""
111+
finalCode.encode
112+
}
113+
}
131 KB
Loading

0 commit comments

Comments
 (0)