# pie_chart_python.py
# September - 2026
# RJM Programming
# Thanks to https://www.w3schools.com/python/default.asp

from datetime import datetime

import os
import matplotlib
import sys

# Set backend to Agg before importing pyplot
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import matplotlib.image as mpimg

from matplotlib.sankey import Sankey

import numpy as np
#import plotly.express as px
from PIL import Image

import matplotlib.dates as mdates

defarrdates = ['2019-02-26', '2019-02-26', '2018-11-10', '2018-11-10',
             '2018-09-18', '2018-08-10', '2018-03-17', '2018-03-16',
             '2018-03-06', '2018-01-18', '2017-12-10', '2017-10-07',
             '2017-05-10', '2017-05-02', '2017-01-17', '2016-09-09',
             '2016-07-03', '2016-01-10', '2015-10-29', '2015-02-16',
             '2014-10-26', '2014-10-18', '2014-08-26']
timelinens = []
defarrreleases = ['2.2.4', '3.0.3', '3.0.2', '3.0.1', '3.0.0', '2.2.3',
                '2.2.2', '2.2.1', '2.2.0', '2.1.2', '2.1.1', '2.1.0',
                '2.0.2', '2.0.1', '2.0.0', '1.5.3', '1.5.2', '1.5.1',
                '1.5.0', '1.4.3', '1.4.2', '1.4.1', '1.4.0']
timelinecs = []

defarrlens = [12.2,9.1,12.2,22.9,0.9,36.6,9.1,30.5,6.1,2.7,0.9,2.7,27.1,3.4,5.5,21.0,7.9,1.2,4.6,1.5,7.9,2.0,45.7,12.2,30.5,15.2,30.5,1.8]
dinolens = []
defarrdins = ['Acrocanthosaurus (top-spined lizard)','Albertosaurus (Alberta lizard)','Allosaurus (other lizard)','Apatosaurus (deceptive lizard)','Archaeopteryx (ancient wing)','Argentinosaurus (Argentina lizard)','Baryonyx (heavy claws)','Brachiosaurus (arm lizard)','Ceratosaurus (horned lizard)','Coelophysis (hollow form)','Compsognathus (elegant jaw)','Deinonychus (terrible claw)','Diplodocus (double beam)','Dromicelomimus (emu mimic)','Gallimimus (fowl mimic)','Mamenchisaurus (Mamenchi lizard)','Megalosaurus (big lizard)','Microvenator (small hunter)','Ornithomimus (bird mimic)','Oviraptor (egg robber)','Plateosaurus (flat lizard)','Sauronithoides (narrow-clawed lizard)','Seismosaurus (tremor lizard)','Spinosaurus (spiny lizard)','Supersaurus (super lizard)','Tyrannosaurus (tyrant lizard)','Ultrasaurus (ultra lizard)','Velociraptor (swift robber)']
dinolecs = []

defbubblens = "8.61,69.55,8.36,4.12,2.76,2.43"
bubblens = []
defbubblels = "'firefox','chrome','safari','edge','ie','opera'"
bubblels = []
defbubblecs = "'#5A69AF','#579E65','#F9C784','#FC944A','#F24C00','#00B825'"
defarrbubblecs = ['#5A69AF','#579E65','#F9C784','#FC944A','#F24C00','#00B825','#69AF5A','#9E6557','#C784F9','#944AFC','#4C00F2','#B82500','#AF5A69','#65579E','#84F9C7','#4AFC94','#00F24C','#2500B8']
bubblecs = []
kbub = len(defarrbubblecs)
ibub = 0

class BubbleChart:
    def __init__(self, area, bubble_spacing=0):
        """
        Setup for bubble collapse.

        Parameters
        ----------
        area : array-like
            Area of the bubbles.
        bubble_spacing : float, default: 0
            Minimal spacing between bubbles after collapsing.

        Notes
        -----
        If "area" is sorted, the results might look weird.
        """
        area = np.asarray(area)
        r = np.sqrt(area / np.pi)

        self.bubble_spacing = bubble_spacing
        self.bubbles = np.ones((len(area), 4))
        self.bubbles[:, 2] = r
        self.bubbles[:, 3] = area
        self.maxstep = 2 * self.bubbles[:, 2].max() + self.bubble_spacing
        self.step_dist = self.maxstep / 2

        # calculate initial grid layout for bubbles
        length = np.ceil(np.sqrt(len(self.bubbles)))
        grid = np.arange(length) * self.maxstep
        gx, gy = np.meshgrid(grid, grid)
        self.bubbles[:, 0] = gx.flatten()[:len(self.bubbles)]
        self.bubbles[:, 1] = gy.flatten()[:len(self.bubbles)]

        self.com = self.center_of_mass()

    def center_of_mass(self):
        return np.average(
            self.bubbles[:, :2], axis=0, weights=self.bubbles[:, 3]
        )

    def center_distance(self, bubble, bubbles):
        return np.hypot(bubble[0] - bubbles[:, 0],
                        bubble[1] - bubbles[:, 1])

    def outline_distance(self, bubble, bubbles):
        center_distance = self.center_distance(bubble, bubbles)
        return center_distance - bubble[2] - \
            bubbles[:, 2] - self.bubble_spacing

    def check_collisions(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        return len(distance[distance < 0])

    def collides_with(self, bubble, bubbles):
        distance = self.outline_distance(bubble, bubbles)
        return np.argmin(distance, keepdims=True)

    def collapse(self, n_iterations=50):
        """
        Move bubbles to the center of mass.

        Parameters
        ----------
        n_iterations : int, default: 50
            Number of moves to perform.
        """
        for _i in range(n_iterations):
            moves = 0
            for i in range(len(self.bubbles)):
                rest_bub = np.delete(self.bubbles, i, 0)
                # try to move directly towards the center of mass
                # direction vector from bubble to the center of mass
                dir_vec = self.com - self.bubbles[i, :2]

                # shorten direction vector to have length of 1
                dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))

                # calculate new bubble position
                new_point = self.bubbles[i, :2] + dir_vec * self.step_dist
                new_bubble = np.append(new_point, self.bubbles[i, 2:4])

                # check whether new bubble collides with other bubbles
                if not self.check_collisions(new_bubble, rest_bub):
                    self.bubbles[i, :] = new_bubble
                    self.com = self.center_of_mass()
                    moves += 1
                else:
                    # try to move around a bubble that you collide with
                    # find colliding bubble
                    for colliding in self.collides_with(new_bubble, rest_bub):
                        # calculate direction vector
                        dir_vec = rest_bub[colliding, :2] - self.bubbles[i, :2]
                        dir_vec = dir_vec / np.sqrt(dir_vec.dot(dir_vec))
                        # calculate orthogonal vector
                        orth = np.array([dir_vec[1], -dir_vec[0]])
                        # test which direction to go
                        new_point1 = (self.bubbles[i, :2] + orth *
                                      self.step_dist)
                        new_point2 = (self.bubbles[i, :2] - orth *
                                      self.step_dist)
                        dist1 = self.center_distance(
                            self.com, np.array([new_point1]))
                        dist2 = self.center_distance(
                            self.com, np.array([new_point2]))
                        new_point = new_point1 if dist1 < dist2 else new_point2
                        new_bubble = np.append(new_point, self.bubbles[i, 2:4])
                        if not self.check_collisions(new_bubble, rest_bub):
                            self.bubbles[i, :] = new_bubble
                            self.com = self.center_of_mass()

            if moves / len(self.bubbles) < 0.1:
                self.step_dist = self.step_dist / 2

    def plot(self, ax, labels, colors):
        """
        Draw the bubble plot.

        Parameters
        ----------
        ax : matplotlib.axes.Axes
        labels : list
            Labels of the bubbles.
        colors : list
            Colors of the bubbles.
        """
        for i in range(len(self.bubbles)):
            circ = plt.Circle(self.bubbles[i, :2], self.bubbles[i, 2], color=colors[i])
            ax.add_patch(circ)
            ax.text(*self.bubbles[i, :2], labels[i], horizontalalignment='center', verticalalignment='center')



oktries = [-1, 1, 0, 1, 1, 1, -1, -1, 0]
nok = len(oktries)
orients = []
pathls = []
nums = []
lbls = []
mytitle = ""
sofar = 0
izero = 0
place = ''
midsuff = ''
if len(sys.argv[0].split('/')) > 1:
  fname = sys.argv[0].split(".py")[0].split('/')[-1 + len(sys.argv[0].split(".py")[0].split('/'))];
else:
  fname = sys.argv[0].split(".py")[0];
fname = fname.split("\\")[-1 + len(fname.split("\\"))].replace('_','').replace('_','').replace('_','').replace('_','').replace('_','').replace('_','');
fname = fname.split('_0')[0].split('_1')[0].split('_2')[0].split('_3')[0].split('_4')[0].split('_5')[0].split('_6')[0].split('_7')[0].split('_8')[0].split('_9')[0]
fname = fname.split('0')[0].split('1')[0].split('2')[0].split('3')[0].split('4')[0].split('5')[0].split('6')[0].split('7')[0].split('8')[0].split('9')[0]
if len(sys.argv) > 1:
  place = sys.argv[-1 + len(sys.argv)]

if len(sys.argv) > 2:
  midsuff = sys.argv[-2 + len(sys.argv)].replace(fname,'').replace('.py','')

if len(nums) <= 0:
  while sofar < 100 and sofar >= 0:
     if fname == "piechartpython":
       print(f"Enter your percentage value regarding a pie chart where so far {sofar} has already been allocated.  Can append after a comma a pie chart sector label.")
     elif fname == "columnchartpython":
       print(f"Enter your value regarding a column chart and append after a comma a column chart label on X axis.")
     elif fname == "sankeychartpython":
       print(f"Enter your value regarding a sankey chart and append after a comma a sankey chart label.")
     elif fname == "scatterchartpython":
       print(f"Enter your X axis value regarding a scatter chart and append after a comma a scatter chart value on Y axis.")
     elif fname == "timelinechartpython":
       print(f"Enter your numerical YYYYMMDD date value regarding a timeline chart and append after a comma a timeline chart label regarding that date.")
     elif fname == "bubblechartpython":
       print(f"Enter your numerical value regarding a bubble chart and append after a comma a bubble chart label regarding that number.")
     elif fname == "histogramchartpython":
       print(f"Enter your numerical value regarding building up a histogram chart and optionally append after a comma a label that goes with that numerical.")
     elif fname == "stairschartpython":
       print(f"Enter your value regarding a stairs chart.")
     elif fname == "pseudocolourchartpython" or fname == "pseudocolorchartpython":
       print(f"Enter your value regarding a psedocolour chart.")
     elif fname == "hexbinchartpython":
       print(f"Enter your X axis value regarding a hexbin chart and append after a comma a hexbin chart value on Y axis.")
     elif fname == "stemchartpython":
       print(f"Enter your X axis value regarding a stem chart and append after a comma a stem chart value on Y axis.")
     elif fname == "barchartpython":
       print(f"Enter your value regarding a bar chart and append after a comma a bar chart label on Y axis.")
     anum = input()
     if anum == "":
       sofar = -1
     elif "," in anum:
       x = anum.split(",")
       nums.append(int(x[0]))
       bubblens.append(int(x[0]))
       timelinens.append(str(int(x[0])))
       lbls.append(anum.replace(x[0] + ',', ''))
       bubblels.append(anum.replace(x[0] + ',', ''))
       timelinecs.append(anum.replace(x[0] + ',', ''))
       bubblecs.append(defarrbubblecs[ibub % kbub])
       ibub+=1
       if fname == "piechartpython":
         sofar += int(x[0])
     else:
       nums.append(int(anum))
       bubblens.append(int(x[0]))
       timelinens.append(str(int(x[0])))
       timelinecs.append('')
       lbls.append('')
       bubblels.append('')
       bubblecs.append(defarrbubblecs[ibub % kbub])
       ibub+=1
       if fname == "piechartpython":
         sofar += int(anum)
  

y = np.array(nums)


if fname == "hexbinchartpython" or fname == "stairschartpython" or fname == "pseudocolourchartpython" or fname == "pseudocolorchartpython":
  plt.style.use('_mpl-gallery-nogrid')
  if fname == "pseudocolourchartpython" or fname == "pseudocolorchartpython":
    X, Y = np.meshgrid(nums, np.linspace(-3, 3, 128))
    Z = (1 - X/2 + X**5 + Y**3) * np.exp(-X**2 - Y**2)

fig, ax = plt.subplots()

if len(mytitle) > 0:
  plt.title(mytitle)

if fname == "scatterchartpython":
  ax.scatter(nums, lbls)
elif fname == "stemchartpython":
  ax.stem(nums, lbls)
elif fname == "hexbinchartpython":
  ax.hexbin(nums, lbls)
elif fname == "stairschartpython":
  ax.stairs(nums)
elif fname == "pseudocolourchartpython" or fname == "pseudocolorchartpython":
  ax.pcolormesh(X, Y, Z, vmin=-0.5, vmax=1.0)
elif fname == "columnchartpython":
  ax.bar(lbls, nums)
elif fname == "barchartpython":
  ax.barh(lbls, nums)
elif fname == "histogramchartpython":
  ax.hist(nums, bins=8, linewidth=0.5, edgecolor="white")
elif len(lbls) < len(nums) and fname == "piechartpython":
  ax.pie(y, autopct="%1.1f%%")
elif fname == "piechartpython":
  ax.pie(y, labels=lbls)
elif fname == "bubblechartpython":
  # Thanks to https://matplotlib.org/stable/gallery/misc/packed_bubbles.html
  browser_market_share = {
    'browsers': bubblels,
    'market_share': bubblens,
    'color': bubblecs
  }
  bubble_chart = BubbleChart(area=browser_market_share['market_share'], bubble_spacing=0.1)
  bubble_chart.collapse()
  fig, ax = plt.subplots(subplot_kw=dict(aspect="equal"))
  bubble_chart.plot(ax, browser_market_share['browsers'], browser_market_share['color'])
  ax.axis("off")
  ax.relim()
  ax.autoscale_view()
  ax.set_title(mytitle)
elif fname == "sankeychartpython":
  fig = plt.figure()
  ax = fig.add_subplot(1, 1, 1, xticks=[], yticks=[], title=mytitle)
  if len(orients) < len(nums):
    while len(orients) < len(nums):
      if izero >= len(oktries):
        oktries.append(oktries[izero % nok])
      orients.append(oktries[izero])
      pathls.append(0.25)
      izero+=1
  sankey = Sankey(ax=ax, scale=0.01, offset=0.2, head_angle=180,
                format='%.0f', unit='')
  sankey.add(flows=nums,
           labels=lbls,
           orientations=orients,
           pathlengths=pathls,
           patchlabel=mytitle)  # Arguments to matplotlib.patches.PathPatch
  diagrams = sankey.finish()
  diagrams[0].texts[-1].set_color('r')
  diagrams[0].text.set_fontweight('bold')  
elif fname == "timelinechartpython":
  # Thanks to https://matplotlib.org/stable/gallery/lines_bars_and_markers/timeline.html
  dates = [datetime.strptime(d, "%Y%m%d") for d in timelinens]  # Convert strs to dates.
  releases = [tuple(release.split('.')) for release in timelinecs]  # Split by component.
  dates, releases = zip(*sorted(zip(dates, releases)))  # Sort by increasing date.
  # Choose some nice levels: alternate meso releases between top and bottom, and
  # progressively shorten the stems for micro releases.
  levels = []
  macro_meso_releases = sorted({release[:2] for release in releases})
  for release in releases:
      macro_meso = release[:2]
      micro = int(release[2])
      h = 1 + 0.8 * (5 - micro)
      level = h if macro_meso_releases.index(macro_meso) % 2 == 0 else -h
      levels.append(level)

  def is_feature(release):
      """Return whether a version (split into components) is a feature release."""
      return release[-1] == '0'


  # The figure and the axes.
  fig, ax = plt.subplots(figsize=(8.8, 4), layout="constrained")
  ax.set(title=mytitle)

  # The vertical stems.
  ax.vlines(dates, 0, levels,
          color=[("tab:red", 1 if is_feature(release) else .5) for release in releases])
  # The baseline.
  ax.axhline(0, c="black")
  # The markers on the baseline.
  meso_dates = [date for date, release in zip(dates, releases) if is_feature(release)]
  micro_dates = [date for date, release in zip(dates, releases)
               if not is_feature(release)]
  ax.plot(micro_dates, np.zeros_like(micro_dates), "ko", mfc="white")
  ax.plot(meso_dates, np.zeros_like(meso_dates), "ko", mfc="tab:red")

  # Annotate the lines.
  for date, level, release in zip(dates, levels, releases):
      version_str = '.'.join(release)
      ax.annotate(version_str, xy=(date, level),
                xytext=(-3, np.sign(level)*3), textcoords="offset points",
                verticalalignment="bottom" if level > 0 else "top",
                weight="bold" if is_feature(release) else "normal",
                bbox=dict(boxstyle='square', pad=0, lw=0, fc=(1, 1, 1, 0.7)))

  ax.xaxis.set(major_locator=mdates.YearLocator(),
             major_formatter=mdates.DateFormatter("%Y"))

  # Remove the y-axis and some spines.
  ax.yaxis.set_visible(False)
  ax.spines[["left", "top", "right"]].set_visible(False)

  ax.margins(y=0.1)

fig.savefig(place + "plot" + midsuff + ".png", bbox_inches="tight")


# 1. Load the image
img = mpimg.imread(place + 'plot' + midsuff + '.png')

# 2. Process or manipulate the image (Optional)
# For example, let's look at the shape or apply changes
#print(f"Image shape: {img.shape}")

# 3. Create the plot without displaying it
plt.imshow(img)
plt.axis('off')  # Hide axis lines and labels

# 4. Save the result to a file instead of showing it
plt.savefig(place + 'output_image' + midsuff + '.png', bbox_inches='tight', pad_inches=0)
plt.close()


# Open the PNG image file
with Image.open(place + "plot" + midsuff + ".png") as img:
    # Display the image using your system's default viewer
    img.show()
    
    # Optional: Load the data into memory so you can safely close the file
    img.load() 


exit()


  
