{
  "Name": "Small Multiples — Standard",
  "Code": "(function() {\n  var container = document.getElementById(w.general.renderTo);\n  var props = w.props || {};\n  var items = w.data?.primaryData?.items || [];\n\n  if (!items.length) {\n    container.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#999;\">Нет данных</div>';\n    return;\n  }\n\n  // === Свойства с дефолтными значениями ===\n  var primaryCategory = props.primaryCategory || '';\n  var topN = parseInt(props.topN) || 11;\n  var showOther = props.showOther !== false;\n  var showSummary = props.showSummary !== false;\n  var syncValueAxis = props.syncValueAxis !== false;\n  var positiveColor = props.positiveColor || '#8BC34A';\n  var negativeColor = props.negativeColor || '#F44336';\n  var summaryColor = props.summaryColor || '#616161';\n  var showValueLabels = props.showValueLabels !== false;\n  var columns = parseInt(props.columns) || 5;\n\n  // === Парсинг данных ===\n  // Структура: keys[0] = Category, keys[1] = Date, values[0] = Value\n  var dataByCategory = {};\n  var allDates = [];\n  var summaryByDate = {};\n\n  items.forEach(function(item) {\n    var category = item.keys[0];\n    var date = item.keys[1];\n    var value = item.values[0];\n\n    if (!dataByCategory[category]) {\n      dataByCategory[category] = { \n        name: category, \n        data: {}, \n        total: 0 \n      };\n    }\n    dataByCategory[category].data[date] = value;\n    dataByCategory[category].total += Math.abs(value);\n\n    if (allDates.indexOf(date) === -1) {\n      allDates.push(date);\n    }\n\n    // Суммирование для Summary\n    if (!summaryByDate[date]) {\n      summaryByDate[date] = { sum: 0, count: 0 };\n    }\n    summaryByDate[date].sum += value;\n    summaryByDate[date].count += 1;\n  });\n\n  // Сортировка дат\n  allDates.sort(function(a, b) {\n    return String(a).localeCompare(String(b));\n  });\n\n  // === Отбор категорий ===\n  var allCategories = Object.keys(dataByCategory);\n  \n  // Сортировка по сумме значений (по убыванию)\n  allCategories.sort(function(a, b) {\n    return dataByCategory[b].total - dataByCategory[a].total;\n  });\n\n  // Выделяем главную категорию\n  var selectedCategories = [];\n  var otherCategories = [];\n\n  // Если указана главная категория, ставим её первой\n  if (primaryCategory && dataByCategory[primaryCategory]) {\n    selectedCategories.push(primaryCategory);\n    // Убираем из списка для дальнейшей сортировки\n    allCategories = allCategories.filter(function(c) { return c !== primaryCategory; });\n  }\n\n  // Добавляем Top N категорий\n  var remaining = topN - selectedCategories.length;\n  for (var i = 0; i < allCategories.length && selectedCategories.length < topN; i++) {\n    selectedCategories.push(allCategories[i]);\n  }\n\n  // Остальные категории для \"Other\"\n  for (var i = selectedCategories.length - (primaryCategory ? 1 : 0); i < allCategories.length; i++) {\n    if (selectedCategories.indexOf(allCategories[i]) === -1) {\n      otherCategories.push(allCategories[i]);\n    }\n  }\n\n  // === Подготовка данных для мини-графиков ===\n  var chartsData = [];\n\n  // Добавляем выбранные категории\n  selectedCategories.forEach(function(cat) {\n    var catData = dataByCategory[cat];\n    var values = allDates.map(function(d) { return catData.data[d] || 0; });\n    chartsData.push({\n      name: cat,\n      values: values,\n      type: 'normal',\n      isPrimary: cat === primaryCategory\n    });\n  });\n\n  // Добавляем \"Other\" (агрегированные остальные)\n  if (showOther && otherCategories.length > 0) {\n    var otherValues = allDates.map(function(d) {\n      var sum = 0;\n      otherCategories.forEach(function(cat) {\n        sum += dataByCategory[cat].data[d] || 0;\n      });\n      return sum;\n    });\n    chartsData.push({\n      name: otherCategories.length + ' more small locations',\n      values: otherValues,\n      type: 'other'\n    });\n  }\n\n  // Добавляем \"Summary\" (среднее по всем категориям)\n  if (showSummary) {\n    var summaryValues = allDates.map(function(d) {\n      var s = summaryByDate[d];\n      return s ? Math.round(s.sum / s.count) : 0;\n    });\n    chartsData.push({\n      name: 'Ø ' + allCategories.length + ' Cities',\n      values: summaryValues,\n      type: 'summary'\n    });\n  }\n\n  // === Расчёт общего диапазона для синхронизации осей ===\n  var globalMin = 0;\n  var globalMax = 0;\n\n  if (syncValueAxis) {\n    chartsData.forEach(function(cd) {\n      cd.values.forEach(function(v) {\n        if (v < globalMin) globalMin = v;\n        if (v > globalMax) globalMax = v;\n      });\n    });\n    // Добавляем отступ\n    var range = globalMax - globalMin;\n    globalMin = globalMin - range * 0.15;\n    globalMax = globalMax + range * 0.25;\n  }\n\n  // === Создание контейнера для сетки ===\n  container.innerHTML = '';\n  container.style.cssText = 'display:grid;grid-template-columns:repeat(' + columns + ',1fr);grid-auto-rows:1fr;gap:8px;padding:8px;box-sizing:border-box;height:100%;overflow-y:auto;';\n\n  var charts = [];\n\n  // === Создание мини-графиков ===\n  chartsData.forEach(function(chartItem, idx) {\n    var chartDiv = document.createElement('div');\n    chartDiv.style.cssText = 'height:100%;min-height:120px;background:#fafafa;border-radius:4px;position:relative;';\n    \n    // Highlight для Summary и Primary\n    if (chartItem.type === 'summary') {\n      chartDiv.style.background = '#f0f0f0';\n    } else if (chartItem.isPrimary) {\n      chartDiv.style.background = '#fff9e6';\n      chartDiv.style.border = '2px solid #ffc107';\n    }\n    \n    container.appendChild(chartDiv);\n\n    var chart = echarts.init(chartDiv);\n    charts.push(chart);\n\n    // Определение цветов столбцов\n    var barColors = chartItem.values.map(function(v) {\n      if (chartItem.type === 'summary') {\n        return summaryColor;\n      }\n      return v >= 0 ? positiveColor : negativeColor;\n    });\n\n    // Подготовка данных с метками\n    var seriesData = chartItem.values.map(function(v, i) {\n  return {\n    value: v,\n    itemStyle: { color: barColors[i] },\n    label: {\n      show: showValueLabels,\n      position: v >= 0 ? 'top' : 'bottom',\n      formatter: function(params) {\n        var val = params.value;\n        var idx = params.dataIndex;\n        if (idx !== 0 && idx !== chartItem.values.length - 1) return '';\n        return (val > 0 ? '+' : '') + Math.round(val);\n      },\n      fontSize: 8,\n      color: '#333',\n      fontWeight: 'bold'\n    }\n  };\n});\n\n    // Расчёт локального диапазона\n    var localMin = Math.min.apply(null, chartItem.values);\n    var localMax = Math.max.apply(null, chartItem.values);\n    var localRange = localMax - localMin;\n\n    var yMin = syncValueAxis ? globalMin : (localMin - localRange * 0.15);\n    var yMax = syncValueAxis ? globalMax : (localMax + localRange * 0.25);\n\n    // Подписи для оси X (только первый и последний год)\n    var xAxisLabels = allDates.map(function(d, i) {\n      if (i === 0 || i === allDates.length - 1) {\n        return String(d);\n      }\n      return '';\n    });\n\n    var option = {\n      backgroundColor: 'transparent',\n      title: {\n        text: chartItem.name,\n        left: 'center',\n        top: 5,\n        textStyle: {\n          fontSize: 11,\n          fontWeight: chartItem.isPrimary ? 'bold' : 'normal',\n          color: '#333'\n        }\n      },\n      tooltip: {\n        trigger: 'axis',\n        backgroundColor: 'rgba(255,255,255,0.95)',\n        borderColor: '#ddd',\n        borderWidth: 1,\n        textStyle: { color: '#333', fontSize: 11 },\n        formatter: function(params) {\n          var p = params[0];\n          var sign = p.value >= 0 ? '+' : '';\n          return '<b>' + chartItem.name + '</b><br/>' +\n                 allDates[p.dataIndex] + ': ' + sign + p.value;\n        }\n      },\n      grid: {\n        left: 8,\n        right: 8,\n        top: 35,\n        bottom: 20,\n        containLabel: false\n      },\n      xAxis: {\n        type: 'category',\n        data: allDates,\n        axisLine: { lineStyle: { color: '#ccc' } },\n        axisTick: { show: false },\n        axisLabel: {\n          show: true,\n          fontSize: 8,\n          color: '#999',\n          interval: function(index) {\n            return index === 0 || index === allDates.length - 1;\n          },\n          formatter: function(value, index) {\n            if (index === 0 || index === allDates.length - 1) {\n              return String(value);\n            }\n            return '';\n          }\n        },\n        splitLine: { show: false }\n      },\n      yAxis: {\n        type: 'value',\n        min: yMin,\n        max: yMax,\n        show: false,\n        splitLine: { show: false }\n      },\n      series: [{\n        type: 'bar',\n        data: seriesData,\n        barWidth: '60%',\n        itemStyle: {\n          borderRadius: [2, 2, 0, 0]\n        }\n      }]\n    };\n\n    chart.setOption(option);\n  });\n\n  // === ResizeObserver для адаптивности ===\n  var resizeObserver = new ResizeObserver(function() {\n    charts.forEach(function(c) {\n      c.resize();\n    });\n  });\n  resizeObserver.observe(container);\n})();\n",
  "DataRoles": [
    {
      "Name": "category",
      "DisplayName": "Категория и Дата",
      "Description": "S-01 Category (String[0]) — основная категория (например: City). S-02 Date (String[1]) — дата или период.",
      "Kind": 0,
      "Type": 0,
      "Optional": false
    },
    {
      "Name": "value",
      "DisplayName": "Значение",
      "Description": "N-01 Value (Number[0]) — значение показателя для категории и периода.",
      "Kind": 1,
      "Type": 0,
      "Optional": false
    }
  ],
  "Properties": [
    {
      "Value": "",
      "Type": 0,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Главная категория",
      "PropertyName": "primaryCategory"
    },
    {
      "Value": 11,
      "Type": 1,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Количество категорий (Top N)",
      "PropertyName": "topN"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показывать Other",
      "PropertyName": "showOther"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показывать Summary",
      "PropertyName": "showSummary"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Синхронизировать оси",
      "PropertyName": "syncValueAxis"
    },
    {
      "Value": "#8BC34A",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет положительных значений",
      "PropertyName": "positiveColor"
    },
    {
      "Value": "#F44336",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет отрицательных значений",
      "PropertyName": "negativeColor"
    },
    {
      "Value": "#616161",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет Summary",
      "PropertyName": "summaryColor"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показывать подписи значений",
      "PropertyName": "showValueLabels"
    },
    {
      "Value": 5,
      "Type": 1,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Количество колонок",
      "PropertyName": "columns"
    }
  ],
  "PropertiesJson": ""
}