{"spec_id":"point-and-figure-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\npoint-and-figure-basic: Point and Figure Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 83/100 | Updated: 2026-05-20\n\"\"\"\n\nimport os\nimport sys\n\n\n# Work around filename shadowing the plotnine library\nsys.path.pop(0)\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_point,\n    geom_segment,\n    geom_text,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nX_COLOR = \"#009E73\"  # imprint green - rising columns\nO_COLOR = \"#AE3030\"  # imprint red - falling columns\n\n# Data\nnp.random.seed(42)\nn_days = 300\n\nreturns = np.random.normal(0.001, 0.02, n_days)\nreturns[50:100] += 0.005\nreturns[120:160] -= 0.008\nreturns[180:250] += 0.004\n\nprice = 100 * np.cumprod(1 + returns)\nclose = price\n\nbox_size = 2.0\nreversal = 3\n\n# Build Point and Figure data\npf_data = []\ncurrent_direction = None\ncurrent_column = 0\n\nstart_box = int(np.floor(close[0] / box_size))\ncurrent_high_box = start_box\ncurrent_low_box = start_box\n\nfor i in range(1, len(close)):\n    current_box = int(np.floor(close[i] / box_size))\n\n    if current_direction is None:\n        if current_box > current_high_box:\n            current_direction = \"X\"\n            for b in range(current_low_box, current_box + 1):\n                pf_data.append((current_column, b * box_size, \"X\"))\n            current_high_box = current_box\n        elif current_box < current_low_box:\n            current_direction = \"O\"\n            for b in range(current_box, current_high_box + 1):\n                pf_data.append((current_column, b * box_size, \"O\"))\n            current_low_box = current_box\n\n    elif current_direction == \"X\":\n        if current_box > current_high_box:\n            for b in range(current_high_box + 1, current_box + 1):\n                pf_data.append((current_column, b * box_size, \"X\"))\n            current_high_box = current_box\n        elif current_box <= current_high_box - reversal:\n            current_column += 1\n            current_direction = \"O\"\n            current_low_box = current_box\n            for b in range(current_box, current_high_box):\n                pf_data.append((current_column, b * box_size, \"O\"))\n\n    elif current_direction == \"O\":\n        if current_box < current_low_box:\n            for b in range(current_box, current_low_box):\n                pf_data.append((current_column, b * box_size, \"O\"))\n            current_low_box = current_box\n        elif current_box >= current_low_box + reversal:\n            current_column += 1\n            current_direction = \"X\"\n            current_high_box = current_box\n            for b in range(current_low_box + 1, current_box + 1):\n                pf_data.append((current_column, b * box_size, \"X\"))\n\ndf = pd.DataFrame(pf_data, columns=[\"column\", \"price\", \"symbol\"])\n# Use full label text as the color aesthetic value to avoid plotnine legend prefix bug\ndf[\"direction\"] = pd.Categorical(\n    df[\"symbol\"].map({\"X\": \"Rising (X)\", \"O\": \"Falling (O)\"}), categories=[\"Rising (X)\", \"Falling (O)\"]\n)\n\n# 45-degree support and resistance trend lines (one box per column)\nmin_idx = df[\"price\"].idxmin()\nmax_idx = df[\"price\"].idxmax()\nmax_col = float(df[\"column\"].max())\nsupport_col = float(df.loc[min_idx, \"column\"])\nsupport_price = float(df.loc[min_idx, \"price\"])\nresist_col = float(df.loc[max_idx, \"column\"])\nresist_price = float(df.loc[max_idx, \"price\"])\n\ntrend_lines = pd.DataFrame(\n    {\n        \"x\": [support_col, resist_col],\n        \"y\": [support_price, resist_price],\n        \"xend\": [max_col, max_col],\n        \"yend\": [support_price + (max_col - support_col) * box_size, resist_price - (max_col - resist_col) * box_size],\n    }\n)\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"column\", y=\"price\"))\n    + geom_segment(\n        data=trend_lines,\n        mapping=aes(x=\"x\", y=\"y\", xend=\"xend\", yend=\"yend\"),\n        color=INK_SOFT,\n        size=0.6,\n        linetype=\"dashed\",\n        alpha=0.7,\n        inherit_aes=False,\n    )\n    + geom_text(mapping=aes(color=\"direction\", label=\"symbol\"), size=8, fontweight=\"bold\", show_legend=False)\n    + geom_point(mapping=aes(color=\"direction\"), size=0.01, alpha=0.01)\n    + coord_fixed(ratio=box_size)\n    + scale_color_manual(values={\"Rising (X)\": X_COLOR, \"Falling (O)\": O_COLOR}, name=\"Direction\")\n    + guides(color=guide_legend(override_aes={\"size\": 3, \"alpha\": 1}))\n    + scale_y_continuous(\n        breaks=np.arange(\n            int(df[\"price\"].min() / box_size) * box_size, int(df[\"price\"].max() / box_size + 2) * box_size, box_size * 2\n        )\n    )\n    + labs(x=\"Column (Reversals)\", y=\"Price Level ($)\", title=\"point-and-figure-basic · python · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        figure_size=(6, 6),\n        text=element_text(size=7, color=INK),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=12, color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_title=element_text(size=9, color=INK),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        legend_background=element_rect(fill=ELEVATED_BG, size=0),\n        panel_border=element_blank(),\n        axis_line=element_line(color=INK_SOFT, size=0.5),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.10),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=6, height=6, units=\"in\")\n"}