
Здравствуйте, друзья!
В прошлом году я сделал первый круглендарь.
Продолжаем, экспериментируем.
Идея проекта — создать календарь, позволяющий увидеть все дни года, вспомнить приятные моменты прошлого и ожидаемые события будущего.
Есть куча аналогов, особенностью этого проета является минималистичность и пустота, ведь на первом плане должны быть каракули обладателя.
• Скачиваем с Github (или git clone github.com/illus0r/kruglendar2014.git)
• Распечатываем на формате А1
• Вешаем на стенку в удобном месте (особенно хорошо — в туалете), рядом — фломастер на ниточке.
• Случилось что-то интересное? Лихо дорисовываем круглендарь! К концу года будет любо-дорого смотреть.

Также, как и предыдущий, этот календарь публикуется под лицензией Creative Commons «Attribution» («Атрибуция») 3.0 Непортированной.
С Новым Годом!
Под катом немного о процессе создания.
Процесс
В этом году всё началось с набросков. Думал сделать что-то навороченное и фрактальное, с лунным календарём в довесок. Но вернулся к первому минималистичному эскизу.


Как заправский лентяй, я решил написать скрипт, который выполнить львиную долю работы. Так появилась возможность поиграть размерами, толщинами и, конечно же, шрифтами.
Сперва задавал радиусы и толщины окружностей вручную, но потом решил, что будет гораздо естественнее и приятнее, если они определяться геометрической прогрессией.
Над цветами долго не думал. В фотошопе, открыл окошко выбора цвета и переключился в режим с одинаковой Lightness цветовой схемы Lab (она учитывает восприятие цвета человеком и интенсивность цветов будет схожей).

Несколько точек есть, остальные получаем путём интерполяции. Хорошо, я выбрал не самый простой путь, и можно было обойтись маской в иллюстраторе. Но ведь так веселее!
Самоё скрипт
# -*- coding: utf-8 -*-
import svgwrite, math, numpy
def frange(x, y, jump):
while x < y:
yield x
x += jump
if __name__ == '__main__':
# Calendar parameters
textAngleShift = -(0.26*math.pi*2/365)
canvasSize = (500,707)
mid = canvasSize[0]/2.0, canvasSize[1]/2.0
dayFontFamily = "PT Sans"
dayFontSize = "1.8pt"
svgFileName = 'calendar_03.svg'
# we will interpolate rainbow gradient through these points
rainbowGradient = [[ 0, 0, 0,239,255,255, 0, 0], # Red
[254,248,246,194, 0, 0,211,254], # Green
[255,193, 0, 0, 0,210,255,255]] # Blue
rainbowGradientPoints = [math.pi*2/7*0,
math.pi*2/7*1,
math.pi*2/7*2,
math.pi*2/7*3,
math.pi*2/7*4,
math.pi*2/7*5,
math.pi*2/7*6,
math.pi*2/7*7
]
#=======================================
# Calendar circle parameters
#=======================================
k = 3.0 # multiplication koef.
delta = 1.9 # first step in progression
initRad = canvasSize[0]*0.3684 # initial size
radiusProgression = [initRad]
for i in range(4):
nextValue = initRad-delta*pow(k,i)
radiusProgression += [nextValue]
R_dates = 1.2*canvasSize[0]*0.312
R_days_end, \
R_days_beg, \
R_weeks_beg, \
R_mounthes_beg, \
R_seasons_beg = radiusProgression
#R_weekend_end = R_days_beg + 20;
R_weekend_end = R_days_end;
# Stroke width
stroke_k = 1.8
stroke_thinest = 0.15
dayLineStrokeWidth, \
weekLineStrokeWidth, \
mounthLineStrokeWidth, \
seasonLineStrokeWidth = [0.1*(stroke_k**i) for i in range(4,0,-1)]
weekendStrokeWidth = 6.7
#=======================================
# Filling mounth arrays
#=======================================
dayAngle = math.pi*2/365
weekAngle = dayAngle*7
mounthes = [u'января',\
u'февраля',\
u'марта',\
u'апреля',\
u'мая',\
u'июня',\
u'июля',\
u'августа',\
u'сентября',\
u'октября',\
u'ноября',\
u'декабря'\
]
mounthDays = [31,28,31,30,31,30,31,31,30,31,30,31] # days in each mounth
yearDays = ["%d %s"%(i+1, mounthes[index]) \
for index, m in enumerate(mounthDays) \
for i in range(m)]
# array with marks for yearDays:
# 1 means first day of mounth
# 2 means first day of mounth and of season
allFirstDates = [ 0 if i!=0 else \
1 if (index+1)%3!=0 else \
2 \
for index, m in enumerate(mounthDays) \
for i in range(m)]
mounthAngles = [d*dayAngle for d in mounthDays]
#=======================================
# Making svg and groups
#=======================================
dwg = svgwrite.Drawing(svgFileName, profile='tiny')
textGroup = dwg.g(font_family=dayFontFamily, \
font_size=dayFontSize,\
text_anchor="start" )
dayLineGroup = dwg.g(stroke_width=dayLineStrokeWidth, fill='none')
weekLineGroup = dwg.g(stroke_width=weekLineStrokeWidth, fill='none')
mounthLineGroup = dwg.g(stroke_width=mounthLineStrokeWidth, fill='none')
seasonLineGroup = dwg.g(stroke_width=seasonLineStrokeWidth, fill='none')
weekendGroup = dwg.g(stroke_width=weekendStrokeWidth, fill='none')
#=======================================
# Drawing in defined groups
#=======================================
for index, angle in enumerate(frange(0,math.pi*2,dayAngle)):
# interpolation will help us to find
# all necessary segments for every segment of circles
colorTuple = (int(numpy.interp(angle, rainbowGradientPoints, \
rainbowGradient[0])), \
int(numpy.interp(angle, rainbowGradientPoints, \
rainbowGradient[1])), \
int(numpy.interp(angle, rainbowGradientPoints, \
rainbowGradient[2])))
color = "rgb%s"%(str(colorTuple))
sinus = math.sin(angle)
cosinus = math.cos(angle)
# Draw a week segment
if (index-5)%7 == 0:
weekLineGroup.add (dwg.line( (mid[0]-R_weeks_beg*sinus, \
mid[1]+R_weeks_beg*cosinus), \
(mid[0]-R_days_beg *sinus,mid[1]+R_days_beg *cosinus), \
stroke=color) )
# Draw a weekend
sinus_weekend = math.sin(angle-dayAngle)
cosinus_weekend = math.cos(angle-dayAngle)
weekendGroup.add (dwg.line( (mid[0]-R_days_beg*sinus_weekend, \
mid[1]+R_days_beg*cosinus_weekend), \
(mid[0]-R_weekend_end *sinus_weekend,mid[1]+R_weekend_end *\
cosinus_weekend), \
stroke=color) )
#if (index-)
weekLineGroup.add (dwg.line( (mid[0]-R_weeks_beg*sinus, \
mid[1]+R_weeks_beg*cosinus), \
(mid[0]-R_days_beg *sinus,mid[1]+R_days_beg *cosinus), \
stroke=color) )
# Draw a mounth+season segments
if allFirstDates[index]!=0:
mounthLineGroup.add (dwg.line( (mid[0]-R_mounthes_beg*sinus,mid[1]+\
R_mounthes_beg*cosinus), \
(mid[0]-R_weeks_beg *sinus,mid[1]+R_weeks_beg*cosinus), \
stroke=color) )
# Draw a season segments
if allFirstDates[index]==2:
seasonLineGroup.add (dwg.line( (mid[0]-R_seasons_beg*sinus,mid[1]+\
R_seasons_beg*cosinus), \
(mid[0]-R_mounthes_beg *sinus,mid[1]+R_mounthes_beg*cosinus), \
stroke=color) )
# Draw a day segments
dayLineGroup.add (dwg.line( (mid[0]-R_days_beg*sinus,mid[1]+\
R_days_beg*cosinus), \
(mid[0]-R_days_end *sinus,mid[1]+R_days_end *cosinus), \
stroke=color
) )
# Draw a day name
if (index-4)%7 == 0 or (index-3)%7 == 0:
fillColor = color #"rgb(255,255,255)"
#fontWeight = "bold"
weekendMargin = 2.0
else:
fillColor = "rgb(0,0,0)"
#fontWeight = "normal"
weekendMargin = 0
wordGroup = dwg.g(transform="rotate(%f, %f, %f) translate(%f,0)" \
%( math.degrees(angle+dayAngle+textAngleShift)+90, \
mid[0], mid[1], +R_dates+weekendMargin ) \
)
wordGroup.add( dwg.text( '%s'%(yearDays[index]), \
insert=mid, fill=fillColor \
#,style="font-weight: %s;"%(fontWeight)
))
textGroup.add(wordGroup)
#=======================================
# Dray a circle segments
#=======================================
sinus_end = math.sin(angle+dayAngle)
cosinus_end = math.cos(angle+dayAngle)
dayLineGroup.add( dwg.line( (mid[0]-R_days_beg*sinus, \
mid[1]+R_days_beg*cosinus), \
(mid[0]-R_days_beg*sinus_end,mid[1]+R_days_beg*cosinus_end), \
stroke=color ) )
weekLineGroup.add( dwg.line((mid[0]-R_weeks_beg*sinus, \
mid[1]+R_weeks_beg*cosinus), \
(mid[0]-R_weeks_beg*sinus_end,mid[1]+R_weeks_beg*cosinus_end), \
stroke=color ) )
mounthLineGroup.add( dwg.line((mid[0]-R_mounthes_beg*sinus, \
mid[1]+R_mounthes_beg*cosinus), \
(mid[0]-R_mounthes_beg*sinus_end,mid[1]+R_mounthes_beg*cosinus_end), \
stroke=color ) )
seasonLineGroup.add( dwg.line((mid[0]-R_seasons_beg*sinus, \
mid[1]+R_seasons_beg*cosinus), \
(mid[0]-R_seasons_beg*sinus_end,mid[1]+R_seasons_beg*cosinus_end), \
stroke=color ) )
#=======================================
# Simple circles for quick testing
#=======================================
#dayLineGroup.add( dwg.circle(center=mid, r=R_days_end, stroke='black' ) )
#dayLineGroup.add( dwg.circle(center=mid, r=R_days_beg, stroke='black' ) )
#weekLineGroup.add( dwg.circle(center=mid, r=R_weeks_beg, stroke='black' ) )
#mounthLineGroup.add( dwg.circle(center=mid, r=R_mounthes_beg, stroke='black' ) )
#seasonLineGroup.add( dwg.circle(center=mid, r=R_seasons_beg, stroke='black') )
dwg.add(dayLineGroup)
dwg.add(weekLineGroup)
dwg.add(mounthLineGroup)
dwg.add(seasonLineGroup)
dwg.add(weekendGroup)
dwg.add(textGroup)
# As I didn't learn how to set document width and height, just draw a rect
dwg.add(dwg.rect((0, 0), canvasSize, stroke='gray', fill="none"))
dwg.add(dwg.text("2014", insert=(mid[0],mid[1]+4), \
text_anchor="middle", font_family="PT Serif", font_size="10pt"))
#=======================================
# Finaly, draw it!
#=======================================
dwg.save()
На выходе получаем такой вот SVG файл, который остаётся слегка доработать

Оп-ля! Круглендарь готов. Делаем группу в контакте для получения обратной связи, пишем ридми для Гитхаба и статью на Хабр.
Всем спасибо за внимание, буду рад вашим комментариям.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вы представляете себе год?
65.71%
Круг, Новый Год сверху
456
14.84%
Круг, Новый Год снизу
103
3.03%
Круг, Новый Год справа
21
3.6%
Круг, Новый Год слева
25
12.54%
Спираль, Новый Год сверху
87
3.46%
Спираль, Новый Год снизу
24
1.01%
Спираль, Новый Год справа
7
1.59%
Спираль, Новый Год слева
11
6.48%
Другое (напишу в комментариях)
45
Проголосовали 694 пользователя.
Воздержались 159 пользователей.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
В какую сторону идум месяцы?
79.42%
По часовой стрелке
494
20.58%
Против часовой стрелки
128
Проголосовали 622 пользователя.
Воздержались 93 пользователя.