diff --git a/plots/chord-basic/implementations/plotly.py b/plots/chord-basic/implementations/plotly.py index 66dc4e5179..5a748e593d 100644 --- a/plots/chord-basic/implementations/plotly.py +++ b/plots/chord-basic/implementations/plotly.py @@ -1,19 +1,18 @@ """ pyplots.ai chord-basic: Basic Chord Diagram -Library: plotly 6.5.0 | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-23 +Library: plotly 6.5.2 | Python 3.14 +Quality: 88/100 | Updated: 2026-04-06 """ import numpy as np import plotly.graph_objects as go -# Data: Migration flows between 6 continents (bidirectional) +# Data: Migration flows between 6 continents (bidirectional, millions of people) continents = ["Africa", "Asia", "Europe", "N. America", "S. America", "Oceania"] n = len(continents) # Flow matrix (row = source, col = target) - realistic migration patterns -np.random.seed(42) flow_matrix = np.array( [ [0, 15, 25, 10, 5, 3], # Africa to others @@ -25,19 +24,33 @@ ] ) -# Colors for each continent (Python Blue first, then colorblind-safe palette) -colors = ["#306998", "#FFD43B", "#2E8B57", "#DC143C", "#9370DB", "#FF8C00"] +# Colors: Python Blue first, then colorblind-safe palette +# Replaced green (#2E8B57) with teal (#00B4D8) for deuteranopia accessibility +colors = ["#306998", "#FFD43B", "#00B4D8", "#DC143C", "#9370DB", "#FF8C00"] +colors_dim = [ + "rgba(48,105,152,0.4)", + "rgba(255,212,59,0.4)", + "rgba(0,180,216,0.4)", + "rgba(220,20,60,0.4)", + "rgba(147,112,219,0.4)", + "rgba(255,140,0,0.4)", +] # Calculate totals for each continent totals = flow_matrix.sum(axis=0) + flow_matrix.sum(axis=1) total_flow = flow_matrix.sum() +# Identify the dominant corridor for storytelling emphasis +max_flow_idx = np.unravel_index(np.argmax(flow_matrix + flow_matrix.T), flow_matrix.shape) +dominant_src, dominant_tgt = max_flow_idx +dominant_flow = flow_matrix[dominant_src, dominant_tgt] + flow_matrix[dominant_tgt, dominant_src] + # Calculate arc positions around the circle gap = 0.02 arc_starts = [] arc_ends = [] current_pos = 0 -for _, total in enumerate(totals): +for total in totals: arc_starts.append(current_pos) arc_ends.append(current_pos + (total / total_flow) * (1 - n * gap)) current_pos = arc_ends[-1] + gap @@ -45,17 +58,33 @@ # Create figure fig = go.Figure() -# Draw outer arcs for each continent +# Draw outer arcs with gradient-like layered effect for i in range(n): - # Generate arc points (outer) angles_outer = np.linspace(2 * np.pi * arc_starts[i] - np.pi / 2, 2 * np.pi * arc_ends[i] - np.pi / 2, 100) + # Outer ring (thicker, slightly transparent for depth) + x_o = 1.02 * np.cos(angles_outer) + y_o = 1.02 * np.sin(angles_outer) + angles_rev = angles_outer[::-1] + x_i = 0.98 * np.cos(angles_rev) + y_i = 0.98 * np.sin(angles_rev) + + fig.add_trace( + go.Scatter( + x=np.concatenate([x_o, x_i]), + y=np.concatenate([y_o, y_i]), + fill="toself", + fillcolor=colors_dim[i], + line={"color": "rgba(255,255,255,0)", "width": 0}, + hoverinfo="skip", + showlegend=False, + ) + ) + + # Inner ring (solid color, main arc) x_outer = 1.0 * np.cos(angles_outer) y_outer = 1.0 * np.sin(angles_outer) - - # Generate arc points (inner, reversed) - angles_inner = np.linspace(2 * np.pi * arc_ends[i] - np.pi / 2, 2 * np.pi * arc_starts[i] - np.pi / 2, 100) - x_inner = 0.95 * np.cos(angles_inner) - y_inner = 0.95 * np.sin(angles_inner) + x_inner = 0.94 * np.cos(angles_rev) + y_inner = 0.94 * np.sin(angles_rev) fig.add_trace( go.Scatter( @@ -63,98 +92,187 @@ y=np.concatenate([y_outer, y_inner]), fill="toself", fillcolor=colors[i], - line=dict(color="white", width=1), - hoverinfo="text", - text=f"{continents[i]}
Total flow: {totals[i]}", + line={"color": "white", "width": 0.5}, + hovertemplate=( + f"{continents[i]}
" + f"Outgoing: {int(flow_matrix[i].sum())}M
" + f"Incoming: {int(flow_matrix[:, i].sum())}M
" + f"Total: {int(totals[i])}M people" + "" + ), name=continents[i], showlegend=True, + legendgroup=continents[i], ) ) -# Draw chords between continents -shapes = [] +# Draw chords with enhanced visibility and storytelling +min_chord_width = 0.008 # Minimum visual width for thin chords for i in range(n): src_pos = arc_starts[i] for j in range(n): - if i != j and flow_matrix[i, j] > 0: - flow = flow_matrix[i, j] - chord_width = (flow / total_flow) * (1 - n * gap) - - # Calculate target position offset - tgt_base = arc_starts[j] - tgt_offset = sum( - (flow_matrix[k, j] / total_flow) * (1 - n * gap) for k in range(i) if flow_matrix[k, j] > 0 - ) + if i == j or flow_matrix[i, j] == 0: + continue - # Calculate chord endpoints (source) - src_angle1 = 2 * np.pi * src_pos - np.pi / 2 - src_angle2 = 2 * np.pi * (src_pos + chord_width) - np.pi / 2 - x1 = 0.95 * np.cos(src_angle1) - y1 = 0.95 * np.sin(src_angle1) - x2 = 0.95 * np.cos(src_angle2) - y2 = 0.95 * np.sin(src_angle2) - - # Calculate chord endpoints (target) - tgt_start = tgt_base + tgt_offset - tgt_end = tgt_start + chord_width - tgt_angle1 = 2 * np.pi * tgt_start - np.pi / 2 - tgt_angle2 = 2 * np.pi * tgt_end - np.pi / 2 - x3 = 0.95 * np.cos(tgt_angle1) - y3 = 0.95 * np.sin(tgt_angle1) - x4 = 0.95 * np.cos(tgt_angle2) - y4 = 0.95 * np.sin(tgt_angle2) - - # SVG path with quadratic bezier curves through center - path = ( - f"M {x1},{y1} Q 0,0 {x3},{y3} A 0.95,0.95 0 0,1 {x4},{y4} Q 0,0 {x2},{y2} A 0.95,0.95 0 0,1 {x1},{y1} Z" - ) + flow = flow_matrix[i, j] + chord_width = max((flow / total_flow) * (1 - n * gap), min_chord_width) + + # Highlight dominant corridor with higher opacity + is_dominant = (i == dominant_src and j == dominant_tgt) or (i == dominant_tgt and j == dominant_src) + opacity = 0.72 if is_dominant else 0.45 + line_width = 1.0 if is_dominant else 0.3 + + # Target position offset based on prior incoming flows + tgt_base = arc_starts[j] + tgt_offset = sum( + max((flow_matrix[k, j] / total_flow) * (1 - n * gap), min_chord_width) + for k in range(i) + if flow_matrix[k, j] > 0 + ) - shapes.append( - dict(type="path", path=path, fillcolor=colors[i], opacity=0.6, line=dict(color=colors[i], width=0.5)) + # Source arc endpoints + src_angle1 = 2 * np.pi * src_pos - np.pi / 2 + src_angle2 = 2 * np.pi * (src_pos + chord_width) - np.pi / 2 + sx1, sy1 = 0.94 * np.cos(src_angle1), 0.94 * np.sin(src_angle1) + sx2, sy2 = 0.94 * np.cos(src_angle2), 0.94 * np.sin(src_angle2) + + # Target arc endpoints + tgt_start = tgt_base + tgt_offset + tgt_end = tgt_start + chord_width + tgt_angle1 = 2 * np.pi * tgt_start - np.pi / 2 + tgt_angle2 = 2 * np.pi * tgt_end - np.pi / 2 + tx1, ty1 = 0.94 * np.cos(tgt_angle1), 0.94 * np.sin(tgt_angle1) + tx2, ty2 = 0.94 * np.cos(tgt_angle2), 0.94 * np.sin(tgt_angle2) + + # Build chord path with smoother bezier curves + src_angles = np.linspace(src_angle1, src_angle2, 20) + src_x = 0.94 * np.cos(src_angles) + src_y = 0.94 * np.sin(src_angles) + + t = np.linspace(0, 1, 40) + bez1_x = (1 - t) ** 2 * sx2 + 2 * (1 - t) * t * 0 + t**2 * tx1 + bez1_y = (1 - t) ** 2 * sy2 + 2 * (1 - t) * t * 0 + t**2 * ty1 + + tgt_angles = np.linspace(tgt_angle1, tgt_angle2, 20) + tgt_x = 0.94 * np.cos(tgt_angles) + tgt_y = 0.94 * np.sin(tgt_angles) + + bez2_x = (1 - t) ** 2 * tx2 + 2 * (1 - t) * t * 0 + t**2 * sx1 + bez2_y = (1 - t) ** 2 * ty2 + 2 * (1 - t) * t * 0 + t**2 * sy1 + + chord_x = np.concatenate([src_x, bez1_x, tgt_x, bez2_x]) + chord_y = np.concatenate([src_y, bez1_y, tgt_y, bez2_y]) + + fig.add_trace( + go.Scatter( + x=chord_x, + y=chord_y, + fill="toself", + fillcolor=colors[i], + opacity=opacity, + line={"color": colors[i], "width": line_width}, + hovertemplate=( + f"{continents[i]} → {continents[j]}
" + f"Flow: {flow}M people
" + f"Share: {flow / total_flow * 100:.1f}% of total" + "" + ), + showlegend=False, + hoveron="fills", ) + ) - src_pos += chord_width + src_pos += chord_width -# Add continent labels around the perimeter +# Add continent labels around the perimeter (horizontal for clarity) for i in range(n): mid_pos = (arc_starts[i] + arc_ends[i]) / 2 angle = 2 * np.pi * mid_pos - np.pi / 2 - label_radius = 1.12 + label_radius = 1.16 + + lx = label_radius * np.cos(angle) + ly = label_radius * np.sin(angle) + angle_deg = np.degrees(angle) % 360 - # Rotate text for readability - text_angle_deg = np.degrees(angle) - if 90 < text_angle_deg < 270 or -270 < text_angle_deg < -90: - text_angle_deg += 180 - rotation = -text_angle_deg + 90 if -90 < np.degrees(angle) < 90 else -text_angle_deg - 90 + # Anchor text toward the circle center for clean alignment + if 45 < angle_deg < 135: + xanchor, yanchor = "center", "bottom" + elif 135 <= angle_deg < 225: + xanchor, yanchor = "right", "middle" + elif 225 <= angle_deg < 315: + xanchor, yanchor = "center", "top" + else: + xanchor, yanchor = "left", "middle" fig.add_annotation( - x=label_radius * np.cos(angle), - y=label_radius * np.sin(angle), - text=f"{continents[i]}", - font=dict(size=18, color=colors[i]), + x=lx, + y=ly, + text=f"{continents[i]} {int(totals[i])}M", + font={"size": 20, "color": colors[i], "family": "Arial, Helvetica, sans-serif"}, showarrow=False, - textangle=rotation, + xanchor=xanchor, + yanchor=yanchor, ) -# Layout -fig.update_layout( - title=dict( - text="Migration Flows Between Continents · chord-basic · plotly · pyplots.ai", - font=dict(size=28), - x=0.5, - xanchor="center", +# Subtitle annotation for storytelling +fig.add_annotation( + text=( + f"Europe–Asia corridor dominates at {dominant_flow}M combined flow" + " · Chord width proportional to flow magnitude" ), - shapes=shapes, - xaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-1.4, 1.4]), - yaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-1.4, 1.4], scaleanchor="x"), + xref="paper", + yref="paper", + x=0.5, + y=0.955, + showarrow=False, + font={"size": 17, "color": "#666666", "family": "Arial, Helvetica, sans-serif"}, + xanchor="center", +) + +# Layout with refined styling +fig.update_layout( + title={ + "text": "Migration Flows Between Continents · chord-basic · plotly · pyplots.ai", + "font": {"size": 28, "color": "#222222", "family": "Arial Black, Arial, sans-serif"}, + "x": 0.5, + "xanchor": "center", + "y": 0.98, + }, + xaxis={"showgrid": False, "zeroline": False, "showticklabels": False, "showline": False, "range": [-1.5, 1.5]}, + yaxis={ + "showgrid": False, + "zeroline": False, + "showticklabels": False, + "showline": False, + "range": [-1.6, 1.4], + "scaleanchor": "x", + }, template="plotly_white", showlegend=True, - legend=dict(font=dict(size=16), x=1.02, y=0.5, yanchor="middle"), - margin=dict(l=50, r=150, t=100, b=50), + legend={ + "font": {"size": 18, "family": "Arial, Helvetica, sans-serif"}, + "title": {"text": "Continents", "font": {"size": 18, "color": "#444"}}, + "x": 0.98, + "y": 0.02, + "xanchor": "right", + "yanchor": "bottom", + "bgcolor": "rgba(255,255,255,0.9)", + "bordercolor": "#ddd", + "borderwidth": 1, + "tracegroupgap": 6, + "itemsizing": "constant", + }, + margin={"l": 20, "r": 20, "t": 80, "b": 15}, plot_bgcolor="white", - paper_bgcolor="white", + paper_bgcolor="#FAFAFA", + hovermode="closest", + hoverlabel={ + "bgcolor": "white", + "bordercolor": "#ccc", + "font": {"size": 16, "family": "Arial, Helvetica, sans-serif", "color": "#333"}, + }, ) # Save outputs -fig.write_image("plot.png", width=1600, height=900, scale=3) +fig.write_image("plot.png", width=1200, height=1200, scale=3) fig.write_html("plot.html", include_plotlyjs="cdn") diff --git a/plots/chord-basic/metadata/plotly.yaml b/plots/chord-basic/metadata/plotly.yaml index 8fcb4f99bb..915b9e5e12 100644 --- a/plots/chord-basic/metadata/plotly.yaml +++ b/plots/chord-basic/metadata/plotly.yaml @@ -1,162 +1,190 @@ library: plotly specification_id: chord-basic created: '2025-12-23T10:01:12Z' -updated: '2025-12-23T10:06:59Z' -generated_by: claude-opus-4-5-20251101 +updated: '2026-04-06T21:00:11Z' +generated_by: claude-opus-4-6 workflow_run: 20457530538 issue: 0 -python_version: 3.13.11 -library_version: 6.5.0 +python_version: '3.14' +library_version: 6.5.2 preview_url: https://storage.googleapis.com/pyplots-images/plots/chord-basic/plotly/plot.png preview_html: https://storage.googleapis.com/pyplots-images/plots/chord-basic/plotly/plot.html -quality_score: 91 +quality_score: 88 impl_tags: dependencies: [] techniques: - bezier-curves - - html-export + - annotations - hover-tooltips + - html-export patterns: - data-generation - matrix-construction - iteration-over-groups dataprep: [] styling: + - minimal-chrome - alpha-blending + - edge-highlighting review: strengths: - - Excellent chord diagram implementation from scratch using Plotly low-level primitives - - Clean visual design with well-chosen colorblind-safe color palette - - Proper bidirectional flow handling with chord widths proportional to flow values - - Good use of SVG path shapes for chord ribbons with bezier curves through center - - Informative hover tooltips on outer arcs showing total flow per continent - - Well-positioned rotated labels around the perimeter - - Dual output format (PNG + interactive HTML) leverages Plotly strength + - Excellent data storytelling with dominant corridor highlighting and informative + subtitle + - Professional layered arc effect with depth perception + - Full spec compliance including all 4 required features (colors, proportional widths, + tooltips, bidirectional flows) + - Colorblind-safe palette with intentional green-to-teal replacement + - Clean, well-structured code with appropriate complexity + - Dual output (PNG + interactive HTML) leverages Plotly strengths weaknesses: - - Some smaller flow chords are difficult to distinguish due to overlapping - - Interactive hover on individual chords would enhance the HTML version - image_description: 'The chord diagram displays migration flows between 6 continents - arranged in a circular layout. The outer ring shows colored arcs for each continent: - Africa (blue, #306998), Asia (yellow, #FFD43B), Europe (green, #2E8B57), N. America - (red, #DC143C), S. America (purple, #9370DB), and Oceania (orange, #FF8C00). Continent - labels are positioned around the perimeter with rotated text for readability. - Chords connect the continents with widths proportional to migration flow values, - using semi-transparent fills (opacity 0.6) to show overlapping flows. The title - "Migration Flows Between Continents · chord-basic · plotly · pyplots.ai" appears - at the top. A legend on the right identifies each continent. The layout is clean - with a white background.' + - Some thin chords between minor flows are hard to visually distinguish from each + other + - Vertical layout balance could be improved (more whitespace at top than bottom) + - Low-level construction is necessary but limits idiomatic library usage score + image_description: 'The plot displays a chord diagram with 6 continents arranged + around a circle. Each continent has a layered arc segment (outer translucent ring + + inner solid ring) with distinct colors: Africa (dark blue #306998), Asia (yellow + #FFD43B), Europe (teal #00B4D8), N. America (crimson #DC143C), S. America (purple + #9370DB), Oceania (orange #FF8C00). Continent labels with total flow values (e.g., + "Europe 205M", "Asia 175M") are positioned around the perimeter in matching colors. + Chords of varying widths connect continents, with the Europe-Asia corridor visually + emphasized through higher opacity. The title reads "Migration Flows Between Continents + · chord-basic · plotly · pyplots.ai" with a subtitle noting "Europe–Asia corridor + dominates at 65M combined flow · Chord width proportional to flow magnitude". + A legend in the bottom-right lists all continents. Background is clean white (#FAFAFA + paper).' criteria_checklist: visual_quality: - score: 36 - max: 40 + score: 27 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 7 + max: 8 passed: true - comment: Title and legend are clear; perimeter labels are readable but some - rotation angles could be improved + comment: All font sizes explicitly set (title 28pt, labels 20pt, subtitle + 17pt, legend 18pt). All text clearly readable. Subtitle slightly small but + acceptable. - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: No overlapping text elements + comment: No overlapping text elements. Labels well-positioned around perimeter + with proper anchoring. - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: Chords are well-sized with good opacity; some thin flows harder to - see + comment: Chords clearly visible with minimum width enforcement. Dominant corridor + stands out via opacity. Some thinner chords hard to distinguish. - id: VQ-04 name: Color Accessibility - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: Distinct colors for all 6 continents, colorblind-friendly palette + comment: Palette explicitly avoids green, replacing with teal for deuteranopia + safety. Six distinct hues well-separated. - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 + name: Layout & Canvas + score: 3 + max: 4 passed: true - comment: Well-centered circle with good margins and legend placement - - id: VQ-07 - name: Grid & Legend + comment: 1:1 aspect ratio ideal for circular diagram. Slight vertical imbalance + with more whitespace at top. + - id: VQ-06 + name: Axis Labels & Title score: 2 max: 2 passed: true - comment: No grid (appropriate), legend well-positioned on right + comment: Axes appropriately hidden for chord diagram. Labels include units + (M for millions). + design_excellence: + score: 16 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Custom palette, layered arc effect, styled hover labels, intentional + typography hierarchy. Clearly above defaults. + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: All grid/axes hidden. Clean white background. White line borders + on arcs. Outer ring glow effect adds polish. + - id: DE-03 + name: Data Storytelling + score: 5 + max: 6 + passed: true + comment: Dominant corridor highlighted with higher opacity. Subtitle calls + out insight. Continent totals displayed. Strong visual hierarchy. spec_compliance: - score: 25 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct chord diagram implementation - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: Source/target/value correctly mapped to arcs and chords - - id: SC-03 + comment: Correct chord diagram with entities on circle perimeter connected + by proportional-width chords. + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: 'All spec features present: distinct colors, proportional widths, - bidirectional flows' - - id: SC-04 - name: Data Range + comment: Distinct colors, proportional chord widths, hover tooltips, bidirectional + flows all present. + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: All data visible within the circular layout - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Legend correctly identifies all 6 continents - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Flow matrix correctly maps sources to targets. Arc sizes proportional + to totals. + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: 'Correct format: "Migration Flows Between Continents · chord-basic - · plotly · pyplots.ai"' + comment: Title follows exact format. Legend shows all 6 continents with correct + colors. data_quality: - score: 18 - max: 20 + score: 13 + max: 15 items: - id: DQ-01 name: Feature Coverage - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: Shows bidirectional flows with varying magnitudes; good variety in - flow sizes + comment: 6 entities with varying connection strengths. Both strong and weak + flows present. Some weaker flows hard to visually distinguish. - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: Migration flows between continents is a realistic and comprehensible - scenario + comment: Migration flows between continents is real-world, comprehensible, + neutral scenario. - id: DQ-03 name: Appropriate Scale - score: 4 - max: 5 + score: 3 + max: 4 passed: true - comment: Values are plausible migration units; largest flows from Europe/Asia - align with reality + comment: Values in millions, individual flows range 2-35M. Total ~704M is + slightly high but acceptable for demonstration. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -164,40 +192,49 @@ review: score: 3 max: 3 passed: true - comment: 'Clean linear structure: imports → data → plot → save' + comment: 'Linear flow: imports, data, calculations, figure creation, save. + No functions or classes.' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: Uses np.random.seed(42) (though data is actually hardcoded matrix) + comment: Fully deterministic with hardcoded flow matrix. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only numpy and plotly.graph_objects imported + comment: Only numpy and plotly.graph_objects imported, both used. - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: Uses current Plotly API + comment: Well-structured with clear sections. Appropriate complexity. No fake + UI. - id: CQ-05 - name: Output Correct - score: 0 + name: Output & API + score: 1 max: 1 passed: true - comment: Saves as plot.png but also plot.html (minor) - library_features: - score: 3 - max: 5 + comment: Saves plot.png and plot.html. Uses current Plotly API. + library_mastery: + score: 7 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features + - id: LM-01 + name: Idiomatic Usage score: 3 max: 5 passed: true - comment: Uses go.Scatter with SVG paths for chords; could leverage hover interactivity - more in the HTML output - verdict: APPROVED + comment: Plotly lacks native chord diagram. Manual go.Scatter construction + necessary. Good use of available features. + - id: LM-02 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: Leverages hovertemplate, hoveron=fills, HTML export, hoverlabel customization. + Genuinely distinctive Plotly capabilities. + verdict: REJECTED