Универсальная аналитическая машина
medium

Универсальная аналитическая машина

Она может предсказать что угодно, если правильно её настроить.

Несколько лет назад была настоящая криптолихорадка: люди покупали и продавали биткоины, майнили эфир, скупали видеокарты и царил дух авантюризма. В те времена все искали проверенные источники, инсайды и аналитику, которые помогут принять верное решение о покупке.

Как и всегда, прогнозы — дело неблагодарное, и прогноз из серии «Пальцем в небо» будет всяко точнее. Поэтому мы решили автоматизировать этот процесс: сделать универсальную аналитическую машину, которая способна прогнозировать что угодно, от курса доллара до маршрута лосося на нерест. Метод прогнозирования хорошо изучен — наугад.

Сейчас наша машина умеет предсказывать поведение курса криптовалюты в ближайшей перспективе:

Собрав такую же машину у себя, вы сможете предсказывать что угодно. Вот как это сделать.

Что происходит на самом деле

Вся эта история про аналитическую машину — полный фейк. Мы придумали этот проект от начала и до конца, и логика была такая:

  1. Показать красивый прогресс-бар.
  2. Запустить его по кнопке, чтобы он начал работать.
  3. Пока он движется, выводим всякие сообщения, чтобы создать видимость работы. Очень важно, чтобы человек на том конце думал, что машина реально что-то прогнозирует.
  4. В конце показываем какое-то сообщение, мол, вот ваш прогноз. Сообщение может быть совершенно рандомным, на качество прогноза это не влияет.

В итоге получилась машина, которая просто показывает вам красивый прогресс-бар и выдаёт в итоге рандомную фразу. В принципе, ничем не хуже большинства прогнозных систем, только мы честно об этом говорим.

Зачем этот проект

Этот проект — упражнение в главной составляющей программистского дзена: бери чужое, вкручивай в своё. Мы будем пользоваться готовыми чужими решениями и получим в итоге работающий и довольно полезный продукт.

Прогресс-бар

Сделать красивый прогресс-бар — дело не пяти минут, поэтому мы поискали уже готовые решения в Сети и нашли вот такое:

Там было почти всё, что нам нужно, кроме двух вещей:

  • прогресс-бар заполнялся слишком быстро,
  • не менялись надписи в процессе работы.

Мы не будем показывать весь исходный код, потому что можно зайти в начальный проект и посмотреть — вместо этого покажем, что поменяли.

Замедляем прогресс-бар. Смотрим в function preload(imgArray):

Было:

$(imgArray).each(function() {
$('<img>').attr("src", this).load(function() {
$progress.animate({
width: "+=" + increment + "%"
}, 100);
});

Стало:

var i = 0;
    while (i < 300) {
$progress.animate({
width: "+=" + 0.25 + "%"
}, 100);
         i = i+1;
       
     }

Вместо того, чтобы увеличивать прогресс на какую-то непонятную величину, мы прибавляем к нему на каждом шаге всего по 0.25 пункта. Это мало, поэтому двигаться будет медленно. Число 300 в цикле мы подобрали опытным путём: почему-то если прибавлять по 0.25, то именно за 300 шагов мы наберём 100%.

Выводим сообщения по ходу работы. Смотрим в ту же функцию.

Было: ничего, потому что в исходном проекте этого не требовалось.

Стало:

if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
  $('span').text(s1_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
  $('span').text(s20_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
  $('span').text(s40_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
  $('span').text(s60_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
  $('span').text(s80_final);
}
if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
  yes = false;
  $('span').text('Примерная точность прогноза: ' + percent_final + '%');
  $('p').text(result_final);
}

Первые 5 раз мы сравниваем показатели процентов на шкале с каким-то числом и если совпадаем — выводим то, что лежит в переменной. В последнем сравнении выдаём проценты и финальный текст. Про переменные расскажем ниже.

Готовим текст

У нас уже был проект, где мы генерировали текст случайным образом. Возьмём оттуда немного кода и перепишем для себя:

// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];

Логика простая:

  • готовим шаблоны текста для каждого этапа;
  • вставляем функцию, которая вернёт нам случайное число в нужном диапазоне;
  • заполняем финальные значения случайным образом.

Вернитесь немного назад и посмотрите: именно эти переменные мы использовали в разделе, когда сравнивали значения с процентами на шкале и выводили результат.

Собираем страницу

Основное мы сделали, осталось всё это красиво разместить в браузере. Берём код страницы исходного проекта, немного правим и получаем такое:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Image Preloader Progress Bar</title>
  <link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
  button {
    font-size: 100%;
    padding: 5px 10px 10px 10px;
    margin-top: 10%;
  }
</style>

<body>
  <!-- кнопка для запуска -->
  <button value="Получить предсказание по крипте" onclick="tested();">Получить прогноз по криптовалюте</button>
  <!-- наш прогресс-бар -->
  <div class="loader">
    <div class="progress-bar">
      <div class="progress-stripes"></div>
      <div class="percentage">0%</div>
    </div>
  </div>
  <span>Система готова</span>
  <p>
    <!-- тут будет финальный текст -->
  </p>
  <!-- подключаем jQuery и сам скрипт -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
  <script src="./script.js"></script>
</body>

</html>

Мы добавили кнопку и раздел с тегом <p> для финального текста. Всё остальное донастроим в скрипте.

Запуск по кнопке

В исходном проекте прогресс-бар начинает работать автоматически сразу после загрузки страницы. Нам такое не нужно, а нужно, чтобы всё запускалось после нажатия на кнопку. Поэтому снова правим скрипт.

Убираем автозагрузку. За это отвечает свойство $(window).load(function(). Находим его и полностью удаляем эту функцию.

Привязываем запуск к кнопке. Помните, мы правили функцию function preload(imgArray)? Именно она отвечает за работу всего прогресс-бара. Чтобы привязать её к кнопке, оборачиваем вызов функции в такой код:

function launch() {
preload(demoImgArray);
}

Смотрим, чтобы в описании кнопки на странице тоже всё было правильно:

<button value="Получить предсказание по крипте" <strong>onclick="launch()</strong>;">Получить прогноз по криптовалюте</button>

Строка onclick="launch()" означает, что при нажатии на кнопку сработает функция launch(), а именно она у нас в скрипте отвечает за запуск прогресс-бара.

Делаем кнопку одноразовой. Чтобы всё работало без сбоев и предсказуемо, давайте запретим нажимать на кнопку больше одного раза. Для этого используем jQuery в функции preload(imgArray):

$('button').prop('disabled', true);

Этот код делает следующее:

  • Находит на странице элемент <button>.
  • Присваивает свойству кнопки disabled значение true. Это значит, что кнопка становится неактивной — она видна, но нажать на неё нельзя. То что нужно.

Что в результате

Мы разместили свою версию проекта на сайте — можно пользоваться самому и делиться с друзьями. Если вы хотите сделать себе такое же и даже круче — держите исходный код:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Image Preloader Progress Bar</title>
  <link rel="stylesheet" href="./style.css">
</head>
<style type="text/css">
  button {
    font-size: 100%;
    padding: 5px 10px 10px 10px;
    margin-top: 10%;
  }
</style>

<body>
  <!-- кнопка для запуска -->
  <button value="Получить предсказание по крипте" onclick="launch();">Получить прогноз по криптовалюте</button>
  <!-- наш прогресс-бар -->
  <div class="loader">
    <div class="progress-bar">
      <div class="progress-stripes"></div>
      <div class="percentage">0%</div>
    </div>
  </div>
  <span>Система готова</span>
  <p>
    <!-- тут будет финальный текст -->
  </p>
  <!-- подключаем jQuery и сам скрипт -->
  <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
  <script src="./script.js"></script>
</body>

</html>

body {
  background: #e7e7e7;
  font-family: Arial, Helvetica, Sans-serif;
  text-align: center;
  padding: 5px;
  margin: 0;
}
.loader {
  margin: 50px auto 10px;
  width: 300px;
  height: 25px;
  border-radius: 14px;
  border-bottom: 1px solid #fff;
  border-top: 1px solid #999;
  background: #ccc;
  overflow: hidden;
  position: relative;
}
.loader.gray {
  background: #999;
}
.progress-bar {
  height: inherit;
  width: 0%;
  border-radius: inherit;
  position: relative;
  overflow: hidden;
}
.progress-stripes {
  width: inherit;
  height: inherit;
  font-size: 180px;
  font-weight: bold;
  margin-top: -50px;
  letter-spacing: -14px;
}
.percentage {
  position: absolute;
  top: 4px;
  right: 5px;
  font-weight: bold;
  font-size: 16px;
}
/***************************************/
/* BELOW HERE IS SOLELY FOR AESTHETICS */
/*_____________________________________*/
/*** COLOR SCHEMES ***/
/* RED */
.red .progress-bar {
  background: #e74c3c;
}
.red .progress-stripes {
  color: #c0392b;
}
.red .percentage {
  color: #eee;
}
/* BLUE */
.blue .progress-bar {
  background: #3498db;
}
.blue .progress-stripes {
  color: #2980b9;
}
.blue .percentage {
  color: #eee;
}
/* GREEN */
.green .progress-bar {
  background: #2ecc71;
}
.green .progress-stripes {
  color: #27ae60;
}
.green .percentage {
  color: #fff;
}
/* YELLOW */
.yellow .progress-bar {
  background: #f1c40f;
}
.yellow .progress-stripes {
  color: #f39c12;
}
.yellow .percentage {
  color: #fff;
}
/* PURPLE */
.purple .progress-bar {
  background: #9b59b6;
}
.purple .progress-stripes {
  color: #8e44ad;
}
.purple .percentage {
  color: #eee;
}
/* GRAY */
.gray .progress-bar {
  background: #ecf0f1;
}
.gray .progress-stripes {
  color: #bdc3c7;
}
.gray .percentage {
  color: #333;
}
/*** LOADED ***/
span {
  color: #888;
  text-shadow: 0 1px 0 #fff;
}
span.loaded.red {
  color: #c0392b;
}
span.loaded.blue {
  color: #2980b9;
}
span.loaded.green {
  color: #27ae60;
}
span.loaded.yellow {
  color: #d35400;
}
span.loaded.purple {
  color: #8e44ad;
}
span.loaded.gray {
  color: #444;
}
/*** SKINS ***/
.skins {
  padding: 4px 0 8px;
  cursor: default;
  font-size: 14px;
  color: #666;
  background: #fff;
  opacity: 0.5;
  -moz-transition: opacity 0.25s linear;
  -webkit-transition: opacity 0.25s linear;
  transition: opacity 0.25s linear;
}
.skins:hover {
  opacity: 1;
}
.skin {
  width: 20px;
  height: 20px;
  cursor: pointer;
  margin-bottom: -7px;
  border: 1px solid #fff;
  display: inline-block;
  *display: inline;
  zoom: 1;
}
#red {
  background: #c0392b;
}
#red:hover {
  background: #e74c3c;
}
#blue {
  background: #2980b9;
}
#blue:hover {
  background: #3498db;
}
#green {
  background: #27ae60;
}
#green:hover {
  background: #2ecc71;
}
#yellow {
  background: #f39c12;
}
#yellow:hover {
  background: #f1c40f;
}
#purple {
  background: #8e44ad;
}
#purple:hover {
  background: #9b59b6;
}
#gray {
  background: #7f8c8d;
}
#gray:hover {
  background: #95a5a6;
}

// этапы в прогресс-баре
var s1 = ['Собираю данные из интернета', 'Читаю Википедию', 'Смотрю паблики во ВКонтакте'];
var s20 = ['Обучаю нейросеть', 'Анализирую увиденное', 'Выделяю важное'];
var s40 = ['Готовлю технический анализ', 'Думаю о будущем', 'Осознаю важность момента'];
var s60 = ['Постулирую неведомое', 'Инкапсулирую полиморфизм', 'Кристаллизую суть'];
var s80 = ['Тыкаю пальцем в небо', 'Формализую данность как класс', 'Удаляю противоречия и вношу ясность'];
var result = ['Сначала крипта подрастет, потом обвалится. Потом снова подрастет, потом снова обвалится.', 'Можно закупать, хотя лучше сначала продать, но если важнее купить, то можно и не продавать', 'По всем показателям рецессия будет следовать за отскоком, но это вряд ли отразится на текущей ситуации, поэтому необходимо обратить внимание на волатильность.'];
// генератор случайных чисел в диапазоне от минимального до максимального
function randz(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}
// готовим итоговые значения
var s1_final = s1[randz(0, 2)];
var s20_final = s20[randz(0, 2)];
var s40_final = s40[randz(0, 2)];
var s60_final = s60[randz(0, 2)];
var s80_final = s80[randz(0, 2)];
var percent_final = randz(60, 99);
var result_final = result[randz(0, 2)];
/* SET RANDOM LOADER COLORS FOR DEMO PURPOSES */
var demoColorArray = ['red', 'blue', 'green', 'yellow', 'purple', 'gray'];
var colorIndex = Math.floor(Math.random() * demoColorArray.length);
setSkin(demoColorArray[colorIndex]);
var yes = true;
/* RANDOM LARGE IMAGES FOR DEMO PURPOSES */
var demoImgArray = ['http://www.hdwallpapers.in/walls/halloween_2013-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_print_tech_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/ama_dablam_himalaya_mountains-wide.jpg', 'http://www.hdwallpapers.in/walls/arrow_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/anna_in_frozen-wide.jpg', 'http://www.hdwallpapers.in/walls/frozen_elsa-wide.jpg', 'http://www.hdwallpapers.in/walls/shraddha_kapoor-wide.jpg', 'http://www.hdwallpapers.in/walls/sahara_force_india_f1_team-HD.jpg', 'http://www.hdwallpapers.in/walls/lake_sunset-wide.jpg', 'http://www.hdwallpapers.in/walls/2013_movie_cloudy_with_a_chance_of_meatballs_2-wide.jpg', 'http://www.hdwallpapers.in/walls/bates_motel_2013_tv_series-wide.jpg', 'http://www.hdwallpapers.in/walls/krrish_3_movie-wide.jpg', 'http://www.hdwallpapers.in/walls/universe_door-wide.jpg', 'http://www.hdwallpapers.in/walls/night_rider-HD.jpg', 'http://www.hdwallpapers.in/walls/tide_and_waves-wide.jpg', 'http://www.hdwallpapers.in/walls/2014_lamborghini_veneno_roadster-wide.jpg', 'http://www.hdwallpapers.in/walls/peeta_katniss_the_hunger_games_catching_fire-wide.jpg', 'http://www.hdwallpapers.in/walls/captain_america_the_winter_soldier-wide.jpg', 'http://www.hdwallpapers.in/walls/puppeteer_ps3_game-wide.jpg', 'http://www.hdwallpapers.in/walls/lunar_space_galaxy-HD.jpg', 'http://www.hdwallpapers.in/walls/2013_wheelsandmore_lamborghini_aventador-wide.jpg', 'http://www.hdwallpapers.in/walls/destiny_2014_game-wide.jpg', 'http://www.hdwallpapers.in/colors_of_nature-wallpapers.html', 'http://www.hdwallpapers.in/walls/sunset_at_laguna_beach-wide.jpg'];
// Stripes interval
var stripesAnim;
var calcPercent;
$progress = $('.progress-bar');
$percent = $('.percentage');
$stripes = $('.progress-stripes');
$stripes.text('////////////////////////');
/* CHANGE LOADER SKIN */
$('.skin').click(function () {
  var whichColor = $(this).attr('id');
  setSkin(whichColor);
});
// Call function to load array of images
function launch() {
  preload(demoImgArray);
}
// Call function to animate stripes
stripesAnimate();
/*** FUNCTIONS ***/
/* LOADING */
function preload(imgArray) {
  $('button').prop('disabled', true);
  var increment = Math.floor(100 / imgArray.length);
  var i = 0;
  while (i < 300) {
    $progress.animate({
      width: "+=" + 0.25 + "%"
    }, 100);
    i = i + 1;
  }
  calcPercent = setInterval(function () {
    //loop through the items
    $percent.text(Math.floor(($progress.width() / $('.loader').width()) * 100) + '%');
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 1) {
      $('span').text(s1_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 20) {
      $('span').text(s20_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 40) {
      $('span').text(s40_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 60) {
      $('span').text(s60_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 80) {
      $('span').text(s80_final);
    }
    if (Math.floor(($progress.width() / $('.loader').width()) * 100) == 100) {
      yes = false;
      $('span').text('Примерная точность прогноза: ' + percent_final + '%');
      $('p').text(result_final);
    }
  });
}
/* STRIPES ANIMATION */
function stripesAnimate() {
  if (yes) {
    animating();
    stripesAnim = setInterval(animating, 2500);
  }
}
function animating() {
  if (yes) {
    $stripes.animate({
      marginLeft: "-=30px"
    }, 2500, "linear").append('/');
  }
}
function setSkin(skin) {
  $('.loader').attr('class', 'loader ' + skin);
  $('span').hasClass('loaded') ? $('span').attr('class', 'loaded ' + skin) : $('span').attr('class', skin);
}

В этом коде много лишних строк, он неоптимальный и его можно существенно улучшить. Но главное — он уже работает, и его можно быстро допилить под свои задачи. Поэтому вот домашнее задание для тех, кто хочет идеал:

  • найти и убрать лишние функции;
  • заменить настоящую загрузку картинок (посмотрите внимательно код) на что-то не настолько нагрузочное;
  • сделать больше вариантов текста на каждом этапе.

Обложка:

Даня Берковский

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
medium