{
  "Name": "Multi-tier Column Chart — Standard",
  "Code": "(function() {\r\n  var container = document.getElementById(w.general.renderTo);\r\n  var props = w.props || {};\r\n  var items = w.data?.primaryData?.items || [];\r\n\r\n  if (!items.length) {\r\n    container.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#999;\">Нет данных</div>';\r\n    return;\r\n  }\r\n\r\n  // Очистка предыдущего графика\r\n  var existingChart = echarts.getInstanceByDom(container);\r\n  if (existingChart) existingChart.dispose();\r\n\r\n  // Свойства с дефолтными значениями (IBCS)\r\n  var showDeltaAbsolute = props.showDeltaAbsolute !== false;\r\n  var showDeltaPercent = props.showDeltaPercent !== false;\r\n  var showValueLabels = props.showValueLabels !== false;\r\n  var actualColor = props.actualColor || '#1a1a1a';\r\n  var previousYearColor = props.previousYearColor || '#a0a0a0';\r\n  var positiveDeltaColor = props.positiveDeltaColor || '#7cb342';\r\n  var negativeDeltaColor = props.negativeDeltaColor || '#e53935';\r\n  var axisStartAtZero = props.axisStartAtZero !== false;\r\n  var valueFontSize = props.valueFontSize || 11;\r\n  var deltaFontSize = props.deltaFontSize || 10;\r\n\r\n  // Группировка данных по годам\r\n  // Структура: keys[0] = Date (период), keys[1] = Year, values[0] = Value\r\n  var dataByYear = {};\r\n  var allPeriods = [];\r\n  var years = [];\r\n\r\n  items.forEach(function(item) {\r\n    var period = item.keys[0];\r\n    var year = item.keys[1];\r\n    var value = item.values[0];\r\n\r\n    if (!dataByYear[year]) {\r\n      dataByYear[year] = {};\r\n      years.push(year);\r\n    }\r\n    dataByYear[year][period] = value;\r\n\r\n    if (allPeriods.indexOf(period) === -1) {\r\n      allPeriods.push(period);\r\n    }\r\n  });\r\n\r\n  // Сортировка годов (последний = текущий = AC)\r\n  years.sort(function(a, b) { return parseInt(a) - parseInt(b); });\r\n\r\n  var currentYear = years[years.length - 1];\r\n  var previousYear = years.length > 1 ? years[years.length - 2] : null;\r\n\r\n  var currentYearData = dataByYear[currentYear] || {};\r\n  var previousYearData = previousYear ? (dataByYear[previousYear] || {}) : {};\r\n\r\n  // Данные для серий\r\n  var acValues = [];\r\n  var pyValues = [];\r\n  var deltaPY = [];\r\n  var deltaPYPercent = [];\r\n\r\n  allPeriods.forEach(function(period) {\r\n    var ac = currentYearData[period] || null;\r\n    var py = previousYearData[period] || null;\r\n\r\n    acValues.push(ac);\r\n    pyValues.push(py);\r\n\r\n    // Расчёт отклонений\r\n    if (ac !== null && py !== null && py !== 0) {\r\n      deltaPY.push(ac - py);\r\n      deltaPYPercent.push(((ac - py) / py) * 100);\r\n    } else {\r\n      deltaPY.push(null);\r\n      deltaPYPercent.push(null);\r\n    }\r\n  });\r\n\r\n  // Вычисление границ осей\r\n  var maxValue = Math.max.apply(null, acValues.concat(pyValues).filter(function(v) { return v !== null; }));\r\n  var minValue = axisStartAtZero ? 0 : Math.min.apply(null, acValues.concat(pyValues).filter(function(v) { return v !== null; }));\r\n  var maxDelta = Math.max.apply(null, deltaPY.map(function(v) { return Math.abs(v || 0); }));\r\n  var maxDeltaPercent = Math.max.apply(null, deltaPYPercent.map(function(v) { return Math.abs(v || 0); }));\r\n\r\n  // Padding\r\n  maxValue = maxValue * 1.15;\r\n  maxDelta = maxDelta * 1.3;\r\n  maxDeltaPercent = Math.max(maxDeltaPercent * 1.3, 15);\r\n\r\n  // Высоты секций (проценты)\r\n  var deltaPercentHeight = showDeltaPercent ? 12 : 0;\r\n  var deltaAbsoluteHeight = showDeltaAbsolute ? 20 : 0;\r\n  var valuesHeight = 100 - deltaPercentHeight - deltaAbsoluteHeight - 15;\r\n\r\n  // Расчёт позиций grid\r\n  var gridGap = 5;\r\n  var topMargin = 10;\r\n\r\n  var grids = [];\r\n  var xAxes = [];\r\n  var yAxes = [];\r\n  var series = [];\r\n\r\n  var currentTop = topMargin;\r\n\r\n  // === ВЕРХНИЙ УРОВЕНЬ: ΔPY% ===\r\n  if (showDeltaPercent) {\r\n    var gridIdx = grids.length;\r\n    grids.push({\r\n      left: 50,\r\n      right: 30,\r\n      top: currentTop,\r\n      height: deltaPercentHeight + '%'\r\n    });\r\n\r\n    xAxes.push({\r\n      type: 'category',\r\n      gridIndex: gridIdx,\r\n      data: allPeriods,\r\n      show: false\r\n    });\r\n\r\n    yAxes.push({\r\n      type: 'value',\r\n      gridIndex: gridIdx,\r\n      min: -maxDeltaPercent,\r\n      max: maxDeltaPercent,\r\n      show: false,\r\n      splitLine: { show: false }\r\n    });\r\n\r\n    // Маркеры ΔPY%\r\n    series.push({\r\n      name: 'ΔPY%',\r\n      type: 'bar',\r\n      xAxisIndex: gridIdx,\r\n      yAxisIndex: gridIdx,\r\n      barWidth: 8,\r\n      data: deltaPYPercent.map(function(v, i) {\r\n        if (v === null) return null;\r\n        return {\r\n          value: v,\r\n          itemStyle: {\r\n            color: v >= 0 ? positiveDeltaColor : negativeDeltaColor\r\n          }\r\n        };\r\n      }),\r\n      label: {\r\n        show: true,\r\n        position: function(params) {\r\n          return params.value >= 0 ? 'top' : 'bottom';\r\n        },\r\n        distance: 4,\r\n        formatter: function(params) {\r\n          if (params.value === null) return '';\r\n          var sign = params.value >= 0 ? '+' : '';\r\n          return sign + Math.round(params.value);\r\n        },\r\n        fontSize: deltaFontSize,\r\n        fontWeight: 'bold',\r\n        color: '#333'\r\n      },\r\n      z: 10\r\n    });\r\n\r\n    // Нулевая линия\r\n    series.push({\r\n      type: 'line',\r\n      xAxisIndex: gridIdx,\r\n      yAxisIndex: gridIdx,\r\n      data: [],\r\n      markLine: {\r\n        silent: true,\r\n        symbol: 'none',\r\n        lineStyle: {\r\n          color: '#888',\r\n          width: 1\r\n        },\r\n        label: { show: false },\r\n        data: [{ yAxis: 0 }]\r\n      }\r\n    });\r\n\r\n    currentTop += (container.offsetHeight * deltaPercentHeight / 100) + gridGap;\r\n  }\r\n\r\n  // === СРЕДНИЙ УРОВЕНЬ: ΔPY ===\r\n  if (showDeltaAbsolute) {\r\n    var gridIdx = grids.length;\r\n    var topPct = showDeltaPercent ? deltaPercentHeight + 8 : 5;\r\n    \r\n    grids.push({\r\n      left: 50,\r\n      right: 30,\r\n      top: topPct + '%',\r\n      height: deltaAbsoluteHeight + '%'\r\n    });\r\n\r\n    xAxes.push({\r\n      type: 'category',\r\n      gridIndex: gridIdx,\r\n      data: allPeriods,\r\n      show: false\r\n    });\r\n\r\n    yAxes.push({\r\n      type: 'value',\r\n      gridIndex: gridIdx,\r\n      min: -maxDelta,\r\n      max: maxDelta,\r\n      show: false,\r\n      splitLine: { show: false }\r\n    });\r\n\r\n    // Столбцы ΔPY\r\n    series.push({\r\n      name: 'ΔPY',\r\n      type: 'bar',\r\n      xAxisIndex: gridIdx,\r\n      yAxisIndex: gridIdx,\r\n      barWidth: '50%',\r\n      data: deltaPY.map(function(v, i) {\r\n        if (v === null) return null;\r\n        return {\r\n          value: v,\r\n          itemStyle: {\r\n            color: v >= 0 ? positiveDeltaColor : negativeDeltaColor\r\n          }\r\n        };\r\n      }),\r\n      label: {\r\n        show: true,\r\n        position: function(params) {\r\n          return params.value >= 0 ? 'top' : 'bottom';\r\n        },\r\n        distance: 4,\r\n        formatter: function(params) {\r\n          if (params.value === null) return '';\r\n          var sign = params.value >= 0 ? '+' : '';\r\n          return sign + Math.round(params.value);\r\n        },\r\n        fontSize: deltaFontSize,\r\n        fontWeight: 'bold',\r\n        color: '#333'\r\n      },\r\n      z: 10\r\n    });\r\n\r\n    // Нулевая линия\r\n    series.push({\r\n      type: 'line',\r\n      xAxisIndex: gridIdx,\r\n      yAxisIndex: gridIdx,\r\n      data: [],\r\n      markLine: {\r\n        silent: true,\r\n        symbol: 'none',\r\n        lineStyle: {\r\n          color: '#888',\r\n          width: 1\r\n        },\r\n        label: { show: false },\r\n        data: [{ yAxis: 0 }]\r\n      }\r\n    });\r\n  }\r\n\r\n  // === НИЖНИЙ УРОВЕНЬ: PY и AC столбцы ===\r\n  var valuesGridIdx = grids.length;\r\n  var valuesTop = (showDeltaPercent ? deltaPercentHeight + 8 : 5) + (showDeltaAbsolute ? deltaAbsoluteHeight + 5 : 0);\r\n\r\n  grids.push({\r\n    left: 50,\r\n    right: 30,\r\n    top: valuesTop + '%',\r\n    bottom: 70\r\n  });\r\n\r\n  xAxes.push({\r\n    type: 'category',\r\n    gridIndex: valuesGridIdx,\r\n    data: allPeriods,\r\n    axisLine: { lineStyle: { color: '#ccc' } },\r\n    axisTick: { show: false },\r\n    axisLabel: {\r\n      fontSize: 11,\r\n      color: '#333',\r\n      fontWeight: 500\r\n    }\r\n  });\r\n\r\n  yAxes.push({\r\n    type: 'value',\r\n    gridIndex: valuesGridIdx,\r\n    min: minValue,\r\n    max: maxValue,\r\n    axisLine: { show: false },\r\n    axisTick: { show: false },\r\n    axisLabel: { show: false },\r\n    splitLine: { show: false }\r\n  });\r\n\r\n  // PY столбцы (задний план, серые, широкие)\r\n  if (previousYear) {\r\n    series.push({\r\n      name: 'PY',\r\n      type: 'bar',\r\n      xAxisIndex: valuesGridIdx,\r\n      yAxisIndex: valuesGridIdx,\r\n      barWidth: '65%',\r\n      barGap: '-100%',\r\n      itemStyle: {\r\n        color: previousYearColor,\r\n        borderColor: '#999',\r\n        borderWidth: 0\r\n      },\r\n      data: pyValues,\r\n      z: 1\r\n    });\r\n  }\r\n\r\n  // AC столбцы (передний план, чёрные, узкие)\r\n  series.push({\r\n    name: 'AC',\r\n    type: 'bar',\r\n    xAxisIndex: valuesGridIdx,\r\n    yAxisIndex: valuesGridIdx,\r\n    barWidth: '40%',\r\n    barGap: '-100%',\r\n    itemStyle: {\r\n      color: actualColor\r\n    },\r\n    data: acValues,\r\n    label: {\r\n      show: showValueLabels,\r\n      position: 'top',\r\n      align: 'center',\r\n      verticalAlign: 'bottom',\r\n      formatter: function(params) {\r\n        if (params.value === null) return '';\r\n        return Math.round(params.value);\r\n      },\r\n      fontSize: valueFontSize,\r\n      fontWeight: 'bold',\r\n      color: '#333',\r\n      distance: 5\r\n    },\r\n    z: 5\r\n  });\r\n\r\n  // Легенда внизу для PY и AC\r\n  var legendData = [];\r\n  if (previousYear) {\r\n    legendData.push({ name: 'PY', icon: 'rect', itemStyle: { color: previousYearColor } });\r\n  }\r\n  legendData.push({ name: 'AC', icon: 'rect', itemStyle: { color: actualColor } });\r\n\r\n  // === Опции графика ===\r\n  var option = {\r\n    backgroundColor: 'transparent',\r\n    title: {\r\n      show: false\r\n    },\r\n    tooltip: {\r\n      trigger: 'axis',\r\n      axisPointer: { type: 'shadow' },\r\n      backgroundColor: 'rgba(255,255,255,0.95)',\r\n      borderColor: '#ddd',\r\n      borderWidth: 1,\r\n      textStyle: { color: '#333', fontSize: 11 },\r\n      formatter: function(params) {\r\n        if (!params || !params.length) return '';\r\n        var period = params[0].axisValue;\r\n        var idx = allPeriods.indexOf(period);\r\n        var ac = acValues[idx];\r\n        var py = pyValues[idx];\r\n        var dPY = deltaPY[idx];\r\n        var dPYPct = deltaPYPercent[idx];\r\n\r\n        var html = '<b>' + period + '</b><br/>';\r\n        if (ac !== null) {\r\n          html += '<span style=\"color:' + actualColor + ';\">●</span> AC: <b>' + Math.round(ac) + '</b><br/>';\r\n        }\r\n        if (py !== null) {\r\n          html += '<span style=\"color:' + previousYearColor + ';\">●</span> PY: <b>' + Math.round(py) + '</b><br/>';\r\n        }\r\n        if (dPY !== null) {\r\n          var col = dPY >= 0 ? positiveDeltaColor : negativeDeltaColor;\r\n          var sign = dPY >= 0 ? '+' : '';\r\n          html += '<hr style=\"margin:5px 0;border:none;border-top:1px solid #eee;\"/>';\r\n          html += '<span style=\"color:' + col + ';\">ΔPY: ' + sign + Math.round(dPY) + '</span><br/>';\r\n          html += '<span style=\"color:' + col + ';\">ΔPY%: ' + sign + dPYPct.toFixed(1) + '%</span>';\r\n        }\r\n        return html;\r\n      }\r\n    },\r\n    legend: {\r\n      show: true,\r\n      data: legendData,\r\n      bottom: 8,\n      left: 'center',\n      itemWidth: 12,\n      itemHeight: 12,\n      itemGap: 20,\n      padding: [4, 10],\n      textStyle: { fontSize: 11, color: '#666' }\r\n    },\r\n    grid: grids,\r\n    xAxis: xAxes,\r\n    yAxis: yAxes,\r\n    series: series\r\n  };\r\n\r\n  // Создание графика\r\n  var chart = echarts.init(container);\r\n  chart.setOption(option);\r\n\r\n  // Адаптивность\r\n  new ResizeObserver(function() { chart.resize(); }).observe(container);\r\n})();\r\n",
  "DataRoles": [
    {
      "Name": "category",
      "DisplayName": "Категория",
      "Description": "Дата/период (Date)",
      "Kind": 0,
      "Type": 0,
      "Optional": false
    },
    {
      "Name": "value",
      "DisplayName": "Значение",
      "Description": "Числовые значения (Value)",
      "Kind": 1,
      "Type": 0,
      "Optional": false
    }
  ],
  "Properties": [
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показать ΔPY",
      "PropertyName": "showDeltaAbsolute"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показать ΔPY%",
      "PropertyName": "showDeltaPercent"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Показать значения",
      "PropertyName": "showValueLabels"
    },
    {
      "Value": "#1a1a1a",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет AC",
      "PropertyName": "actualColor"
    },
    {
      "Value": "#a0a0a0",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет PY",
      "PropertyName": "previousYearColor"
    },
    {
      "Value": "#7cb342",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет положит. отклонений",
      "PropertyName": "positiveDeltaColor"
    },
    {
      "Value": "#e53935",
      "Type": 3,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Цвет отриц. отклонений",
      "PropertyName": "negativeDeltaColor"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Ось от нуля",
      "PropertyName": "axisStartAtZero"
    },
    {
      "Value": true,
      "Type": 2,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Синхронизация осей",
      "PropertyName": "syncAxes"
    },
    {
      "Value": 11,
      "Type": 1,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Размер шрифта значений",
      "PropertyName": "valueFontSize"
    },
    {
      "Value": 10,
      "Type": 1,
      "ElementType": "UserWidgetProperty",
      "DisplayName": "Размер шрифта отклонений",
      "PropertyName": "deltaFontSize"
    }
  ],
  "PropertiesJson": ""
}