import matplotlib.pyplot as plt
import numpy as np
from io import StringIO

# The three losses as a function of the signed margin m = y * f(x)
m = np.linspace(-2.5, 3.5, 601)
zero_one = (m <= 0).astype(float)
hinge = np.maximum(0, 1 - m)
logloss = np.log(1 + np.exp(-m))                 # logistic regression, natural log

fig, ax = plt.subplots(figsize=(7.2, 3.8))
ax.axvspan(-2.5, 0, color='#ff7b72', alpha=0.07, lw=0)
ax.axvspan(0, 1, color='gray', alpha=0.10, lw=0)
ax.plot(m, zero_one, color='gray', lw=1.6, ls='--', label='0-1 loss (counts errors)')
ax.plot(m, logloss, color='#58a6ff', lw=2, label='log loss  ln(1 + e$^{-m}$)  (logistic regression)')
ax.plot(m, hinge, color='#f0883e', lw=2.2, label='hinge  max(0, 1 − m)  (SVM)')

# the worked points of the lesson
for mv in [2.33, 1.0, 0.33, -0.67]:
    ax.plot([mv], [max(0, 1 - mv)], 'o', color='#f0883e', ms=5, zorder=5)
    ax.plot([mv], [np.log(1 + np.exp(-mv))], 'o', color='#58a6ff', ms=5, zorder=5)

ax.text(-2.4, 0.75, 'wrong side\n(m < 0)', fontsize=8, color='#ff7b72', va='top')
ax.text(0.5, 3.25, 'inside\nthe margin', fontsize=8, color='gray', va='top', ha='center')
ax.text(2.25, 1.35, 'beyond the margin (m ≥ 1):\nhinge is exactly 0,\nlog loss is small but never 0',
        fontsize=8, color='gray', ha='center')
ax.set_xlim(-2.5, 3.5)
ax.set_ylim(-0.1, 3.4)
ax.set_xlabel('m = y · f(x)   (signed margin: > 0 correct, ≥ 1 comfortably correct)', fontsize=9)
ax.set_ylabel('loss of one point', fontsize=9)
ax.tick_params(labelsize=8)
ax.legend(fontsize=7.5, loc='upper right', framealpha=0.3)
ax.set_title('Hinge vs log loss: the SVM stops caring once a point is beyond the margin', fontsize=10)

plt.tight_layout()

buffer = StringIO()
plt.savefig(buffer, format="svg", transparent=True)
print(buffer.getvalue())

plt.close()
