{
  "Name": "Радиальная столбчатая диаграмма",
  "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 =\n      '<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#999;\">Нет данных</div>';\n    return;\n  }\n\n  // ===== Свойства =====\n  var showLegend = props.showLegend !== false;\n  var legendPosition = (props.legendPosition || 'top-left').toLowerCase();\n  var otherThresholdPct = props.otherThresholdPct != null ? Number(props.otherThresholdPct) : 3;\n  var clickFilter = props.clickFilter !== false;\n\n  var maxAngle = props.maxAngle != null ? Number(props.maxAngle) : 270;\n  if (maxAngle < 10) maxAngle = 10;\n  if (maxAngle > 360) maxAngle = 360;\n  var startAngle = props.startAngle != null ? Number(props.startAngle) : 90;\n  var clockwise = props.clockwise !== false;\n  var barGap = props.barGap != null ? props.barGap : '30%';\n  var innerRadiusPct = props.innerRadiusPct != null ? Number(props.innerRadiusPct) : 20;\n\n  var showCenterTotal = props.showCenterTotal !== false;\n  var centerAgg = (props.centerAgg || 'sum').toLowerCase();\n  var centerLabel = props.centerLabel != null ? props.centerLabel : 'Итого';\n  var autoRound = props.autoRound !== false;\n  var roundDecimals = props.roundDecimals != null ? Number(props.roundDecimals) : 1;\n  var centerColor = props.centerColor || '#333333';\n\n  var showGrid = props.showGrid !== false;\n  var gridColor = props.gridColor || '#dddddd';\n  var gridWidth = props.gridWidth != null ? Number(props.gridWidth) : 1;\n  var gridType = (props.gridType || 'solid').toLowerCase();\n\n  var showLabels = props.showLabels !== false;\n  var labelPosition = (props.labelPosition || 'inside').toLowerCase();\n  var labelColor = props.labelColor || '#ffffff';\n  var labelStrokeColor = props.labelStrokeColor || '#000000';\n  var labelStrokeWidth = props.labelStrokeWidth != null ? Number(props.labelStrokeWidth) : 2;\n\n  var tooltipEnabled = props.tooltipEnabled !== false;\n  var tooltipTemplate = props.tooltipTemplate || '{name}<br/>Значение: <b>{value}</b> ({percent}%)';\n\n  // ===== Утилиты =====\n  function trimZeros(s) { return s.indexOf('.') === -1 ? s : s.replace(/\\.?0+$/, ''); }\n  function formatNumber(value) {\n    if (value == null || isNaN(value)) return '0';\n    if (!autoRound) return trimZeros(Number(value).toFixed(roundDecimals));\n    var abs = Math.abs(value), out, suffix;\n    if (abs >= 1e9) { out = value / 1e9; suffix = ' млрд'; }\n    else if (abs >= 1e6) { out = value / 1e6; suffix = ' млн'; }\n    else if (abs >= 1e3) { out = value / 1e3; suffix = ' тыс'; }\n    else return trimZeros(Number(value).toFixed(roundDecimals));\n    return trimZeros(out.toFixed(roundDecimals)) + suffix;\n  }\n  var dashMap = { solid: 'solid', dashed: 'dashed', dotted: 'dotted' };\n  var gridLineType = dashMap[gridType] || 'solid';\n\n  // ===== Агрегация данных по категориям =====\n  var totalsMap = {}, order = [];\n  items.forEach(function (it) {\n    var cat = it.keys && it.keys.length ? String(it.keys[0]) : '';\n    if (cat === '') return;\n    var v = it.values && it.values.length ? Number(it.values[0]) || 0 : 0;\n    if (!(cat in totalsMap)) { totalsMap[cat] = 0; order.push(cat); }\n    totalsMap[cat] += v;\n  });\n  var baseCats = order.map(function (c) { return { name: c, value: totalsMap[c] }; });\n\n  // ===== Группировка «Прочее» =====\n  var grandTotal = baseCats.reduce(function (s, c) { return s + c.value; }, 0);\n  if (otherThresholdPct > 0 && grandTotal > 0) {\n    var keep = [], small = [];\n    baseCats.forEach(function (c) {\n      if ((c.value / grandTotal * 100) < otherThresholdPct) small.push(c); else keep.push(c);\n    });\n    if (small.length > 1) {\n      keep.push({ name: 'Прочее', value: small.reduce(function (s, c) { return s + c.value; }, 0), _other: true });\n      baseCats = keep;\n    }\n  }\n\n  var baseMax = baseCats.reduce(function (m, c) { return Math.max(m, c.value); }, 1);\n  var axisMax = 360 * baseMax / maxAngle;\n\n  var palette = (w.colors && w.colors.length) ? w.colors\n    : ['#4f9dff', '#f5a623', '#7ed321', '#bd10e0', '#50e3c2', '#e0457b', '#9013fe', '#f8cb00'];\n  function colorOf(cat, i) { return cat._other ? '#c0c0c0' : palette[i % palette.length]; }\n\n  // ===== Контейнер =====\n  var existing = echarts.getInstanceByDom(container);\n  if (existing) existing.dispose();\n  container.style.position = 'relative';\n  var chart = echarts.init(container);\n\n  var filtered = null; // имя категории-фильтра\n  var currentCats = baseCats;\n\n  function aggregate(cats) {\n    var sum = cats.reduce(function (s, c) { return s + c.value; }, 0);\n    if (centerAgg === 'count') return cats.length;\n    if (centerAgg === 'avg') return cats.length ? sum / cats.length : 0;\n    return sum;\n  }\n\n  // ===== Позиция и размеры =====\n  function legendOffsets() {\n    // отступы графика, чтобы легенда не перекрывала кольца\n    var pad = { left: 8, right: 8, top: 8, bottom: 8 };\n    if (!showLegend) return pad;\n    if (legendPosition === 'left') pad.left = 110;\n    else if (legendPosition === 'right') pad.right = 110;\n    else if (legendPosition.indexOf('top') === 0) pad.top = 40;\n    else if (legendPosition.indexOf('bottom') === 0) pad.bottom = 40;\n    return pad;\n  }\n\n  function render() {\n    var cats = filtered ? baseCats.filter(function (c) { return c.name === filtered; }) : baseCats;\n    currentCats = cats;\n    var catNames = cats.map(function (c) { return c.name; });\n    var seriesData = cats.map(function (c, i) {\n      return { value: c.value, name: c.name, itemStyle: { color: colorOf(c, baseCats.indexOf(c)) } };\n    });\n\n    var pad = legendOffsets();\n    var cx = (pad.left + (container.clientWidth - pad.right)) / 2;\n    var cy = (pad.top + (container.clientHeight - pad.bottom)) / 2;\n\n    var labelPosMap = { inside: 'insideEnd', outside: 'end', middle: 'middle' };\n    var seriesLabel = {\n      show: showLabels,\n      position: labelPosMap[labelPosition] || 'insideEnd',\n      color: labelColor,\n      fontSize: 12,\n      fontWeight: 500,\n      formatter: function (p) {\n        var pct = grandTotal > 0 ? (p.value / grandTotal * 100) : 0;\n        return p.name + ' ' + pct.toFixed(1) + '%';\n      },\n    };\n    if (labelStrokeWidth > 0) {\n      seriesLabel.textBorderColor = labelStrokeColor;\n      seriesLabel.textBorderWidth = labelStrokeWidth;\n    }\n\n    var graphic = [];\n    if (showCenterTotal) {\n      var aggVal = aggregate(cats);\n      var aggStr = centerAgg === 'count' ? String(aggVal) : formatNumber(aggVal);\n      graphic.push({\n        type: 'text',\n        left: 'center',\n        top: 'center',\n        z: 100,\n        style: {\n          text: (centerLabel ? centerLabel + '\\n' : '') + aggStr,\n          textAlign: 'center',\n          textVerticalAlign: 'middle',\n          fill: centerColor,\n          fontSize: 16,\n          fontWeight: 'bold',\n          lineHeight: 20,\n        },\n      });\n    }\n\n    var option = {\n      backgroundColor: 'transparent',\n      tooltip: tooltipEnabled ? {\n        backgroundColor: 'rgba(255,255,255,0.98)',\n        borderColor: 'rgba(0,0,0,0.1)',\n        borderWidth: 1,\n        textStyle: { color: '#333', fontSize: 12 },\n        formatter: function (p) {\n          var pct = grandTotal > 0 ? (p.value / grandTotal * 100) : 0;\n          return tooltipTemplate\n            .replace(/\\{name\\}/g, p.name)\n            .replace(/\\{value\\}/g, formatNumber(p.value))\n            .replace(/\\{percent\\}/g, pct.toFixed(1));\n        },\n      } : { show: false },\n      polar: {\n        center: [cx, cy],\n        radius: [innerRadiusPct + '%', '80%'],\n      },\n      angleAxis: {\n        type: 'value',\n        max: axisMax,\n        startAngle: startAngle,\n        clockwise: clockwise,\n        axisLine: { show: false },\n        axisTick: { show: false },\n        axisLabel: { show: false },\n        splitLine: {\n          show: showGrid,\n          lineStyle: { color: gridColor, width: gridWidth, type: gridLineType },\n        },\n      },\n      radiusAxis: {\n        type: 'category',\n        data: catNames,\n        z: 10,\n        axisLine: { show: false },\n        axisTick: { show: false },\n        axisLabel: { show: false },\n        splitLine: {\n          show: showGrid,\n          lineStyle: { color: gridColor, width: gridWidth, type: gridLineType },\n        },\n      },\n      series: [{\n        type: 'bar',\n        coordinateSystem: 'polar',\n        roundCap: true,\n        barCategoryGap: barGap,\n        data: seriesData,\n        label: seriesLabel,\n        emphasis: { focus: 'self', itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' } },\n      }],\n      graphic: graphic,\n    };\n\n    chart.setOption(option, true);\n    buildLegend(catNames);\n  }\n\n  // ===== Легенда (HTML overlay) =====\n  function buildLegend() {\n    var old = container.querySelector('.__rb_legend');\n    if (old) old.remove();\n    if (!showLegend || !baseCats.length) return;\n\n    var legend = document.createElement('div');\n    legend.className = '__rb_legend';\n    legend.style.cssText =\n      'position:absolute;z-index:20;display:flex;flex-direction:column;gap:5px;max-height:90%;overflow:auto;' +\n      'padding:6px 8px;font-size:12px;font-family:sans-serif;background:rgba(255,255,255,0.6);border-radius:6px;';\n    var posMap = {\n      'top-left': 'top:8px;left:8px;',\n      'top-right': 'top:8px;right:8px;',\n      'bottom-left': 'bottom:8px;left:8px;',\n      'bottom-right': 'bottom:8px;right:8px;',\n      'left': 'top:50%;left:8px;transform:translateY(-50%);',\n      'right': 'top:50%;right:8px;transform:translateY(-50%);',\n    };\n    legend.style.cssText += (posMap[legendPosition] || posMap['top-left']);\n\n    baseCats.forEach(function (c, i) {\n      var row = document.createElement('div');\n      var dim = filtered && filtered !== c.name;\n      row.style.cssText = 'display:flex;align-items:center;gap:6px;cursor:pointer;color:#333;opacity:' + (dim ? '0.45' : '1') + ';';\n      row.innerHTML =\n        '<span style=\"display:inline-block;width:12px;height:12px;border-radius:2px;background:' + colorOf(c, i) + ';\"></span>' +\n        '<span>' + c.name + '</span>';\n      if (clickFilter) {\n        row.addEventListener('click', function () {\n          filtered = (filtered === c.name) ? null : c.name;\n          render();\n        });\n      }\n      legend.appendChild(row);\n    });\n    container.appendChild(legend);\n  }\n\n  // ===== Фильтрация при клике на область =====\n  chart.on('click', function (params) {\n    if (!clickFilter || params.componentType !== 'series') return;\n    var name = currentCats[params.dataIndex] && currentCats[params.dataIndex].name;\n    if (!name) return;\n    filtered = (filtered === name) ? null : name;\n    render();\n  });\n\n  render();\n\n  new ResizeObserver(function () { chart.resize(); render(); }).observe(container);\n})();\n",
  "DataRoles": [
    {
      "Name": "category",
      "DisplayName": "Категория",
      "Description": "Строковое поле — категории (кольца)",
      "Kind": 0,
      "Optional": false
    },
    {
      "Name": "value",
      "DisplayName": "Значение",
      "Description": "Числовое поле — длина столбца",
      "Kind": 1,
      "Optional": false
    }
  ],
  "Properties": [
    {
      "DisplayName": "Показывать легенду",
      "PropertyName": "showLegend",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Расположение легенды",
      "PropertyName": "legendPosition",
      "Value": "top-left",
      "Type": 0
    },
    {
      "DisplayName": "Порог группировки в «Прочее» (%, 0=выкл)",
      "PropertyName": "otherThresholdPct",
      "Value": 3,
      "Type": 1
    },
    {
      "DisplayName": "Фильтрация при клике",
      "PropertyName": "clickFilter",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Макс. угол / длина столбцов (град.)",
      "PropertyName": "maxAngle",
      "Value": 270,
      "Type": 1
    },
    {
      "DisplayName": "Угол вращения (90=вверх,0=вправо,180=влево,270=вниз)",
      "PropertyName": "startAngle",
      "Value": 90,
      "Type": 1
    },
    {
      "DisplayName": "По часовой стрелке",
      "PropertyName": "clockwise",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Внутренние отступы между кольцами",
      "PropertyName": "barGap",
      "Value": "30%",
      "Type": 0
    },
    {
      "DisplayName": "Радиус отверстия (%)",
      "PropertyName": "innerRadiusPct",
      "Value": 20,
      "Type": 1
    },
    {
      "DisplayName": "Итог в центре кольца",
      "PropertyName": "showCenterTotal",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Агрегация центра (sum/count/avg)",
      "PropertyName": "centerAgg",
      "Value": "sum",
      "Type": 0
    },
    {
      "DisplayName": "Подпись в центре",
      "PropertyName": "centerLabel",
      "Value": "Итого",
      "Type": 0
    },
    {
      "DisplayName": "Авто-округление (млрд/млн/тыс)",
      "PropertyName": "autoRound",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Знаков после запятой",
      "PropertyName": "roundDecimals",
      "Value": 1,
      "Type": 1
    },
    {
      "DisplayName": "Цвет текста в центре",
      "PropertyName": "centerColor",
      "Value": "#333333",
      "Type": 3
    },
    {
      "DisplayName": "Показывать радиальную сетку",
      "PropertyName": "showGrid",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Цвет линий сетки",
      "PropertyName": "gridColor",
      "Value": "#dddddd",
      "Type": 3
    },
    {
      "DisplayName": "Толщина линий сетки",
      "PropertyName": "gridWidth",
      "Value": 1,
      "Type": 1
    },
    {
      "DisplayName": "Формат сетки (solid/dashed/dotted)",
      "PropertyName": "gridType",
      "Value": "solid",
      "Type": 0
    },
    {
      "DisplayName": "Показывать метки долей",
      "PropertyName": "showLabels",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Позиция меток (inside/outside/middle)",
      "PropertyName": "labelPosition",
      "Value": "inside",
      "Type": 0
    },
    {
      "DisplayName": "Цвет меток",
      "PropertyName": "labelColor",
      "Value": "#ffffff",
      "Type": 3
    },
    {
      "DisplayName": "Цвет контура меток",
      "PropertyName": "labelStrokeColor",
      "Value": "#000000",
      "Type": 3
    },
    {
      "DisplayName": "Толщина контура меток",
      "PropertyName": "labelStrokeWidth",
      "Value": 2,
      "Type": 1
    },
    {
      "DisplayName": "Включить подсказки",
      "PropertyName": "tooltipEnabled",
      "Value": true,
      "Type": 2
    },
    {
      "DisplayName": "Шаблон подсказки",
      "PropertyName": "tooltipTemplate",
      "Value": "{name}<br/>Значение: <b>{value}</b> ({percent}%)",
      "Type": 0
    }
  ],
  "PropertiesJson": ""
}