__version__ = "0.0.1"
__author__ = "ragilhadi"
app/__init__.py
ragilhadi/reverie-vis
from .src.reverievis import (
    radar,
    get_minmax
)
app/reverievis/__init__.py
ragilhadi/reverie-vis
import matplotlib.pyplot as plt
from .utils import create_data_structure, normalize_minmax\
                    ,create_angles, draw_radar\
                    ,legend_styling, put_title\
                    ,get_ymax_lim, get_yticks
from math import pi


def radar(data, category, values, 
              data_minmax = None,
              scaled=None, colors=None, colors_alpha=0.2,
              show_legend=True,
              legend_style='bottom', legend_col=2,
              title=None, title_position=1.08,
              circle=5,
              marker=None, marker_size=3, 
              show_label=False, show_circle = False,
              **kwargs):
    """
    A function to create radar visualization
    """
    if scaled is not None:
        data_norm = normalize_minmax(data=data, category=category, 
                                     data_minmax=data_minmax,
                                     vmin=scaled[0], vmax=scaled[1])
    elif data_minmax is not None:
        data_norm = normalize_minmax(data=data, category=category,
                                     data_minmax=data_minmax)
    else:
        data_norm = data
    data_radar = create_data_structure(data_norm, category, values)
    angles = create_angles(len(values))
    ylim_max = get_ymax_lim(data_radar)
    yticks, yticks_label = get_yticks(ylim_max, circle)

    if show_circle == False:
        yticks = []

    plt.figure(figsize=(16,6))
    ax = plt.subplot(111, polar=True)
    ax.set_theta_offset(pi / 2)
    ax.set_theta_direction(-1)
    ax.set_xticks(angles[:-1], values)
    ax.set_rlabel_position(0)
    if show_label == True:
        ax.set_yticks(yticks, yticks_label, color="grey", size=7)
    elif show_label == False:
        ax.set_yticks(yticks, [], color="grey", size=7)
    ax.set_ylim(0,ylim_max)

    if colors is not None:
        draw_radar(ax=ax, data_radar=data_radar, 
                   angles=angles, colors=colors,
                   marker=marker, marker_size=marker_size,
                   colors_alpha=colors_alpha)
    else:
        draw_radar(ax=ax, data_radar=data_radar, 
                   colors=colors,
                   marker=marker, marker_size=marker_size,
                   angles=angles, colors_alpha=colors_alpha)
    
    if title is not None:
        put_title(ax,title, title_position)

    if show_legend:
        legend_styling(ax, legend_style, legend_col)


def get_minmax(data, columns) -> dict:
    """
    Function to get min value and max value of the column data
    """
    data = data[columns]
    data_minmax = {}
    for column in data.columns:
        data_minmax[column] = {'max': data[column].max(),
                               'min': data[column].min()}
    return data_minmax
app/reverievis/src/reverievis.py
ragilhadi/reverie-vis
from math import pi
import pandas as pd
import numpy as np

colors_pallate = ['#FF6D60', '#6DA9E4', '#F7D060', '#BE6DB7']

def create_data_structure(data, category, values):
    data_radar = {}
    data_category = data[category].unique()
    data_values = data[values].to_numpy()
    for idx,val in enumerate(data_values):
        result = val
        result = np.append(result, val[:1])
        data_radar[data_category[idx]] = result
    
    return data_radar


def normalize_minmax(data, category, data_minmax, vmin=0, vmax=1):
    value = data.copy()
    data_scaled = data.copy()
    data_scaled = data_scaled[[category]]
    value_scaled = value.drop(category, axis=1)
    if vmin != 0 or vmax != 1:
        for column in value_scaled.columns:
            value_scaled[column] = (value_scaled[column] - vmin) / (vmax - vmin)
    elif data_minmax is not None:
        for column in value_scaled.columns:
            value_scaled[column] = (value_scaled[column] - data_minmax[column]['min']) / (data_minmax[column]['max'] - data_minmax[column]['min']) 
    else:
        for column in value_scaled.columns:
            value_scaled[column] = (value_scaled[column] - value_scaled[column].min()) / (value_scaled[column].max() - value_scaled[column].min())   

    data_full = pd.concat([data_scaled, value_scaled], axis=1)
    return data_full 


def create_angles(length):
    angles = [n / float(length) * 2 * pi for n in range(length)]
    angles += angles[:1]

    return angles


def draw_radar(ax, data_radar, angles, colors_alpha, marker, marker_size, colors):
    count = 0

    if colors is not None:
        for key in data_radar.keys():
            ax.plot(angles, data_radar[key], linewidth=1, 
                    linestyle='solid', marker=marker, 
                    markersize=marker_size,
                    color=colors[count], label=f"{key}")
            ax.fill(angles, data_radar[key], color=colors[count], alpha=colors_alpha)
            count += 1
    else:
        for key in data_radar.keys():
            ax.plot(angles, data_radar[key], linewidth=1, 
                    linestyle='solid', marker=marker, 
                    markersize=marker_size,
                    color=colors_pallate[count], label=f"{key}")
            ax.fill(angles, data_radar[key], color=colors_pallate[count], alpha=colors_alpha)
            count += 1


def legend_styling(ax, style, col):
    if style == 'bottom':
        ax.legend(loc='lower center',
            bbox_to_anchor=(0.5, -0.17),
            ncol=col,
            borderpad=1,
            frameon=False,
            fontsize=8
            )
    elif style == 'top':
        ax.legend(loc='upper center',
            bbox_to_anchor=(0.5, 1.17),
            ncol=col,
            borderpad=1,
            frameon=False,
            fontsize=8
            )


def get_ymax_lim(data):
    ymax = 0

    for key in data.keys():
        temp = data[key].max()
        if ymax < temp:
            ymax = temp
    
    if ymax < 1:
        ymax = 1
    return ymax


def get_yticks(ylim_max, circle):
    yticks_label = np.linspace(0,ylim_max, circle).astype(int)
    yticks_label = yticks_label.astype(str)
    return np.linspace(0,ylim_max, circle), yticks_label


def put_title(ax, title, y=1.08):
    ax.set_title(title, y=y, fontsize=12, fontweight='bold', color='black')
app/reverievis/src/utils.py
ragilhadi/reverie-vis
from setuptools import find_packages, setup

with open("app/README.md", "r") as f:
    long_description = f.read()

setup(
    name="reverievis",
    version="0.0.1",
    description="A package used to easily create some beutiful visualization",
    package_dir={"": "app"},
    packages=find_packages(where="app"),
    long_description=long_description,
    long_description_content_type="text/markdown",
    url="https://github.com/ragilhadi/reverie-vis",
    author="ragilhadi",
    author_email="ragilprasetyo310@gmail.com",
    license="MIT",
    classifiers=[
        "License :: OSI Approved :: MIT License",
        "Programming Language :: Python :: 3.9",
        "Operating System :: OS Independent",
    ],
    install_requires=["numpy >= 1.21.5",
                      "matplotlib >= 3.5.2",
                      "pandas >= 1.4.4"],
    extras_require={
        "dev": ["pytest>=7.0", "twine>=4.0.2"],
    },
    python_requires=">=3.9",
)
setup.py
ragilhadi/reverie-vis