diff --git a/plots/point-and-figure-basic/implementations/highcharts.py b/plots/point-and-figure-basic/implementations/highcharts.py new file mode 100644 index 0000000000..98366f8988 --- /dev/null +++ b/plots/point-and-figure-basic/implementations/highcharts.py @@ -0,0 +1,271 @@ +""" pyplots.ai +point-and-figure-basic: Point and Figure Chart +Library: highcharts unknown | Python 3.13.11 +Quality: 91/100 | Created: 2026-01-15 +""" + +import tempfile +import time +import urllib.request +from pathlib import Path + +import numpy as np +from highcharts_core.chart import Chart +from highcharts_core.options import HighchartsOptions +from highcharts_core.options.series.scatter import ScatterSeries +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + + +# Data - Generate realistic stock price data with trends and reversals +np.random.seed(42) +n_days = 300 +base_price = 100 + +# Create price series with multiple trends for interesting P&F patterns +returns = np.zeros(n_days) +# Uptrend phase +returns[:80] = np.random.normal(0.003, 0.015, 80) +# Downtrend phase +returns[80:140] = np.random.normal(-0.004, 0.018, 60) +# Consolidation +returns[140:200] = np.random.normal(0.0, 0.012, 60) +# Strong uptrend +returns[200:260] = np.random.normal(0.005, 0.015, 60) +# Correction +returns[260:] = np.random.normal(-0.003, 0.016, 40) + +prices = base_price * np.cumprod(1 + returns) + +# P&F parameters +box_size = 2.0 # Each box represents $2 +reversal = 3 # 3-box reversal + + +# Round to box size +def to_box(price): + return int(np.floor(price / box_size)) + + +# Build P&F chart - track boxes in each column +columns = [] # List of dicts: {'direction': 'X'/'O', 'boxes': set of box indices} +current_col = None +last_box = to_box(prices[0]) + +for price in prices: + current_box = to_box(price) + + if current_col is None: + # Start first column + if current_box > last_box: + current_col = {"direction": "X", "boxes": set(range(last_box, current_box + 1))} + columns.append(current_col) + elif current_box < last_box: + current_col = {"direction": "O", "boxes": set(range(current_box, last_box + 1))} + columns.append(current_col) + last_box = current_box + continue + + if current_col["direction"] == "X": + if current_box > last_box: + # Continue upward - add all boxes from last to current + for b in range(last_box + 1, current_box + 1): + current_col["boxes"].add(b) + last_box = current_box + elif current_box <= last_box - reversal: + # Reversal down - start new O column + new_col = {"direction": "O", "boxes": set(range(current_box, last_box))} + columns.append(new_col) + current_col = new_col + last_box = current_box + else: # direction == 'O' + if current_box < last_box: + # Continue downward - add all boxes from current to last + for b in range(current_box, last_box): + current_col["boxes"].add(b) + last_box = current_box + elif current_box >= last_box + reversal: + # Reversal up - start new X column + new_col = {"direction": "X", "boxes": set(range(last_box + 1, current_box + 1))} + columns.append(new_col) + current_col = new_col + last_box = current_box + +# Convert to plot points +x_points = [] +o_points = [] + +for col_idx, col in enumerate(columns): + for box in col["boxes"]: + price_val = box * box_size + point = {"x": col_idx, "y": price_val} + if col["direction"] == "X": + x_points.append(point) + else: + o_points.append(point) + +# Create chart +chart = Chart(container="container") +chart.options = HighchartsOptions() + +# Chart configuration +chart.options.chart = { + "type": "scatter", + "width": 4800, + "height": 2700, + "backgroundColor": "#ffffff", + "marginBottom": 200, + "marginLeft": 200, + "marginRight": 200, + "marginTop": 180, +} + +# Title +chart.options.title = { + "text": "point-and-figure-basic · highcharts · pyplots.ai", + "style": {"fontSize": "56px", "fontWeight": "bold"}, +} + +chart.options.subtitle = { + "text": f"Box Size: ${box_size:.0f} | {reversal}-Box Reversal | Simulated Stock Data", + "style": {"fontSize": "36px", "color": "#666666"}, +} + +# Get ranges +max_col = len(columns) - 1 if columns else 0 +all_boxes = set() +for col in columns: + all_boxes.update(col["boxes"]) +min_box = min(all_boxes) if all_boxes else 40 +max_box = max(all_boxes) if all_boxes else 70 + +# X-axis (columns) +chart.options.x_axis = { + "title": {"text": "Column (Reversal Number)", "style": {"fontSize": "40px", "fontWeight": "bold"}}, + "labels": {"style": {"fontSize": "32px"}}, + "tickInterval": 1, + "min": -0.5, + "max": max_col + 0.5, + "gridLineWidth": 1, + "gridLineColor": "#e0e0e0", +} + +# Y-axis (price) +chart.options.y_axis = { + "title": {"text": "Price ($)", "style": {"fontSize": "40px", "fontWeight": "bold"}}, + "labels": {"style": {"fontSize": "32px"}, "format": "${value}"}, + "tickInterval": box_size * 2, + "min": (min_box - 2) * box_size, + "max": (max_box + 2) * box_size, + "gridLineWidth": 1, + "gridLineColor": "#e0e0e0", +} + +# Legend +chart.options.legend = { + "enabled": True, + "align": "right", + "verticalAlign": "top", + "layout": "vertical", + "x": -30, + "y": 80, + "itemStyle": {"fontSize": "36px", "fontWeight": "normal"}, + "symbolRadius": 0, + "symbolHeight": 32, + "symbolWidth": 32, +} + +# X series (bullish/rising) - display X character +x_series = ScatterSeries() +x_series.name = "X (Rising)" +x_series.data = x_points +x_series.color = "#306998" # Python Blue for bullish +x_series.marker = {"enabled": False} +x_series.data_labels = { + "enabled": True, + "format": "X", + "style": {"fontSize": "36px", "fontWeight": "bold", "color": "#306998", "textOutline": "none"}, + "align": "center", + "verticalAlign": "middle", + "y": 0, +} +chart.add_series(x_series) + +# O series (bearish/falling) - display O character +o_series = ScatterSeries() +o_series.name = "O (Falling)" +o_series.data = o_points +o_series.color = "#E74C3C" # Red for bearish +o_series.marker = {"enabled": False} +o_series.data_labels = { + "enabled": True, + "format": "O", + "style": {"fontSize": "36px", "fontWeight": "bold", "color": "#E74C3C", "textOutline": "none"}, + "align": "center", + "verticalAlign": "middle", + "y": 0, +} +chart.add_series(o_series) + +# Tooltip +chart.options.tooltip = { + "headerFormat": "Column {point.x}
", + "pointFormat": "Price: ${point.y}", + "style": {"fontSize": "28px"}, +} + +# Download Highcharts JS +highcharts_url = "https://code.highcharts.com/highcharts.js" +with urllib.request.urlopen(highcharts_url, timeout=30) as response: + highcharts_js = response.read().decode("utf-8") + +# Generate HTML +html_str = chart.to_js_literal() +html_content = f""" + + + + + + +
+ + +""" + +# Write HTML file +with tempfile.NamedTemporaryFile(mode="w", suffix=".html", delete=False, encoding="utf-8") as f: + f.write(html_content) + temp_path = f.name + +# Also save as plot.html for interactive version +with open("plot.html", "w", encoding="utf-8") as f: + # Use CDN for the standalone HTML file + standalone_html = f""" + + + + + + +
+ + +""" + f.write(standalone_html) + +# Screenshot with Selenium +chrome_options = Options() +chrome_options.add_argument("--headless") +chrome_options.add_argument("--no-sandbox") +chrome_options.add_argument("--disable-dev-shm-usage") +chrome_options.add_argument("--disable-gpu") +chrome_options.add_argument("--window-size=4800,2700") + +driver = webdriver.Chrome(options=chrome_options) +driver.get(f"file://{temp_path}") +time.sleep(5) +driver.save_screenshot("plot.png") +driver.quit() + +Path(temp_path).unlink() diff --git a/plots/point-and-figure-basic/metadata/highcharts.yaml b/plots/point-and-figure-basic/metadata/highcharts.yaml new file mode 100644 index 0000000000..77046bdd67 --- /dev/null +++ b/plots/point-and-figure-basic/metadata/highcharts.yaml @@ -0,0 +1,217 @@ +library: highcharts +specification_id: point-and-figure-basic +created: '2026-01-15T21:42:44Z' +updated: '2026-01-15T21:46:04Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 21047207473 +issue: 3755 +python_version: 3.13.11 +library_version: unknown +preview_url: https://storage.googleapis.com/pyplots-images/plots/point-and-figure-basic/highcharts/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/point-and-figure-basic/highcharts/plot_thumb.png +preview_html: https://storage.googleapis.com/pyplots-images/plots/point-and-figure-basic/highcharts/plot.html +quality_score: 91 +review: + strengths: + - Excellent implementation of P&F chart logic with proper column-based structure + - Creative use of data labels to render X and O characters instead of traditional + markers + - Good separation of X and O series for proper coloring and legend + - Proper box size and 3-box reversal implementation + - Clean subtitle with chart parameters + - Generates both PNG and interactive HTML versions + weaknesses: + - Missing support/resistance trend lines mentioned in the specification notes + - Legend shows square symbols instead of X/O characters (Highcharts limitation) + - Uses a helper function to_box() instead of inline code (minor KISS violation) + - Red color for Os could be changed to a more colorblind-friendly option like orange + image_description: 'The plot displays a Point and Figure (P&F) chart with 8 columns + (0-7) on the X-axis representing reversals, and price values ($76-$136) on the + Y-axis. Blue "X" characters mark rising price columns, while red "O" characters + mark falling price columns. The chart shows a clear pattern: column 0 starts with + X''s rising from ~$100 to ~$110, column 1 shows O''s falling from ~$108 to ~$96, + column 2 has X''s rising from ~$100 to ~$112, column 3 shows a deep O decline + from ~$110 to ~$80, column 4 has X''s rising from ~$82 to ~$90, column 5 shows + O''s at ~$84-88, column 6 has a strong X rally from ~$88 to ~$132, and column + 7 shows O''s falling from ~$130 to ~$112. The title correctly shows "point-and-figure-basic + · highcharts · pyplots.ai" with a subtitle indicating "$2 Box Size | 3-Box Reversal + | Simulated Stock Data". A legend in the top-right shows "X (Rising)" in blue + and "O (Falling)" in red. Grid lines are subtle and the layout is clean with good + proportions.' + criteria_checklist: + visual_quality: + score: 37 + max: 40 + items: + - id: VQ-01 + name: Text Legibility + score: 10 + max: 10 + passed: true + comment: Title, axis labels, tick marks, and X/O characters all clearly readable + at full resolution + - id: VQ-02 + name: No Overlap + score: 8 + max: 8 + passed: true + comment: No overlapping text or elements; X and O characters properly spaced + in their columns + - id: VQ-03 + name: Element Visibility + score: 7 + max: 8 + passed: true + comment: X and O symbols well-sized and clearly visible; minor deduction as + symbols could be slightly larger + - id: VQ-04 + name: Color Accessibility + score: 4 + max: 5 + passed: true + comment: Blue and red have good contrast; symbols X vs O provide redundant + encoding + - id: VQ-05 + name: Layout Balance + score: 5 + max: 5 + passed: true + comment: Good use of canvas space, chart properly centered with balanced margins + - id: VQ-06 + name: Axis Labels + score: 2 + max: 2 + passed: true + comment: Y-axis has Price ($) with unit formatting, X-axis has Column (Reversal + Number) + - id: VQ-07 + name: Grid & Legend + score: 1 + max: 2 + passed: true + comment: Grid is subtle, legend well-placed; legend shows squares instead + of X/O markers + spec_compliance: + score: 24 + max: 25 + items: + - id: SC-01 + name: Plot Type + score: 8 + max: 8 + passed: true + comment: Correct Point and Figure chart implementation using X's and O's + - id: SC-02 + name: Data Mapping + score: 5 + max: 5 + passed: true + comment: Columns on X-axis, price on Y-axis as specified + - id: SC-03 + name: Required Features + score: 4 + max: 5 + passed: true + comment: Has X/O columns, box size, reversal logic; missing trend lines from + spec notes + - id: SC-04 + name: Data Range + score: 3 + max: 3 + passed: true + comment: Full price range visible from $76 to $136 + - id: SC-05 + name: Legend Accuracy + score: 2 + max: 2 + passed: true + comment: Legend correctly identifies X (Rising) and O (Falling) + - id: SC-06 + name: Title Format + score: 2 + max: 2 + passed: true + comment: Correctly uses {spec-id} · {library} · pyplots.ai format + data_quality: + score: 18 + max: 20 + items: + - id: DQ-01 + name: Feature Coverage + score: 7 + max: 8 + passed: true + comment: Shows uptrends, downtrends, reversals, and consolidation; demonstrates + multiple phases + - id: DQ-02 + name: Realistic Context + score: 6 + max: 7 + passed: true + comment: Simulated stock data with plausible price movements; good use of + different market phases + - id: DQ-03 + name: Appropriate Scale + score: 5 + max: 5 + passed: true + comment: Stock prices around $80-130 are realistic; box size of $2 is sensible + code_quality: + score: 8 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 1 + max: 3 + passed: false + comment: Uses helper function to_box() instead of inline code; has some structural + complexity + - id: CQ-02 + name: Reproducibility + score: 3 + max: 3 + passed: true + comment: Uses np.random.seed(42) for reproducible data + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports are used + - id: CQ-04 + name: No Deprecated API + score: 1 + max: 1 + passed: true + comment: Uses current APIs + - id: CQ-05 + name: Output Correct + score: 1 + max: 1 + passed: true + comment: Saves as plot.png + library_features: + score: 4 + max: 5 + items: + - id: LF-01 + name: Distinctive Features + score: 4 + max: 5 + passed: true + comment: Good use of Highcharts data labels for X/O rendering, scatter series, + interactive HTML export + verdict: APPROVED +impl_tags: + dependencies: + - selenium + techniques: + - html-export + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - grid-styling