TypeScript

by xylab

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.11.0/d3.min.js"></script>
<div class="root">
  <div class="header">
    <h2 class="title">
 Цены. Индекс потребительских цен на товары и услуги за последний месяц отчетного периода, в % к декабрю предыдущего года
    </h2>
    <div class="preview"></div>
  </div>
  <div class="actions">
    <!--label id="form" for="show-voronoi">
      Показать разбиение Вороного
      <input type="checkbox" id="show-voronoi" disabled>
    </label-->
    <button class="reset-zoom-button">Сбросить зум</button>
  </div>
  <div class="chart"></div>
  <div class="legend"></div>
  <div class="extra-options-container"></div>
</div>

CSS

@font-face {
  font-family: 'Montserrat';
  font-style: normal;
  font-weight: 500;
  src: local('Montserrat Medium'), local('Montserrat-Medium'), url(https://fonts.gstatic.com/s/montserrat/v14/JTURjIg1_i6t8kCHKm45_ZpC3g3D_u50.woff2) format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

body {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  font-family: Montserrat, sans-serif;
  font-wight: 500;
  font-size: 12px;
}

.root {
  margin-left: 20px;
  padding-bottom: 20px;
}

.header {
  display: flex;
  width: 910px;
  padding-bottom: 10px;
  margin-bottom: 10px;
  border-bottom: 1px rgba(0,0,0,.1) solid;
}

.title {
  padding-right: 40px;
  margin: 0;
  font-weight: 500;
  font-size: 15px;
  line-height: 25px;
  margin-top: 3px;
  width: 658px;
}

.preview {
  position: relative;
}

.preview-rect {
  position: absolute;
  left: 0;
  top: 0;
}

.actions {
  display: flex;
  margin-bottom: 10px;
  width: 900px;
  justify-content: space-between;
}

.axis path {
  display: none;
}

.x-axis line {
  stroke-opacity: 0.1;
}

.y-axis line {
  stroke-opacity: 0.2;
}

.axis text {
  font-weight: bold;
  text-transform: uppercase;
  font-size: 10px;
}

.y-axis line {
  stroke-dasharray: 1, 2;
  stroke-width: 1;
}

.y-axis .tick text {
  text-anchor: end;
}

text, button {
  font-family: Montserrat, sans-serif;
}

.preview-axis.x-axis .tick:nth-child(2) text {
  text-anchor: start;
}

.preview-axis.x-axis .tick:nth-child(3) text {
  text-anchor: end;
}

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}

.region-hover {
  stroke: #000;
  stroke-width: 4px;
}

.voronoi path {
  fill: none;
  pointer-events: all;
}

.voronoi-show path {
  stroke: red;
  stroke-opacity: 0.2;
}

.dot {
  stroke-width: 1px;
  stroke: white;
}

.legend {
  font-size: 0;
  white-space: nowrap;
  margin-left: 50px;
  margin-top: 25px;
}

.legend-column {
  position: relative;
  width: 322px;
 ...

JavaScript

d3.timeFormatDefaultLocale({
  'dateTime': '%A, %e %B %Y г. %X',
  'date': '%d.%m.%Y',
  'time': '%H:%M:%S',
  'periods': ['AM', 'PM'],
  'days': ['воскресенье', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница', 'суббота'],
  'shortDays': ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
  'months': ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь'],
  'shortMonths': ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
});

const timeFormatter = d3.timeFormat('%d-%m-%Y');

function chunkHelper(data, numberOfChunks) { // eslint-disable-line
  const result = [];
  let remainingToDistribute = data.length;

  while (result.length < numberOfChunks) {
    const maxNumberOfElementsInChunk = Math.ceil(remainingToDistribute / (numberOfChunks - result.length));
    const currentlyDistributed = data.length - remainingToDistribute;
    const currentChunk = data.slice(currentlyDistributed, currentlyDistributed + maxNumberOfElementsInChunk);

    result.push(currentChunk);
    remainingToDistribute = remainingToDistribute - currentChunk.length;
  }

  return result;
}

d3.csv('https://raw.githubusercontent.com/moskau-git/vers/master/data.csv', draw);

function draw(data) {
  const margin = { top: 0, right: 20, bottom: 50, left: 50 };
  const previewMargin = { top: 10, right: 10, bottom: 15, left: 30 };
  const width = 920 - margin.left - margin.right;
  const height = 390 - margin.top - margin.bottom;

  const ratio = 4;

  const previewWidth = width / ratio;
  const previewHeight = height / ratio;

  const x = d3.scaleTime()
    .range([0, width]);

  const y = d3.scaleLinear()
    .range([height, 0]);

  let rescaledX = x;
  let rescaledY = y;

  const previewX = d3.scaleTime()
    .range([0, previewWidth]);

  const previewY = d3.scaleLinear()
    .range([previewHeight, 0]);

  const colorScale = d3.scaleOrdinal(d3.schemeCategory20);

  const zoom = d3.zoom()
   ...