{
  "$schema": "https://vega.github.io/schema/vega/v6.json",
  "description": "Упаковка кругов для иерархических данных с масштабированием.",
  "width": 600,
  "height": 600,
  "padding": 5,
  "signals": [
    {
      "name": "duration",
      "init": "750",
      "description": "Длительность перехода при масштабировании. Появление длится столько же, но начинается с заданной здесь задержкой.",
      "on": [
        {
          "events": {"type": "click", "marknames": ["circles", "background"]},
          "update": "(event.metaKey || event.ctrlKey ? 4 : 1) *750"
        }
      ]
    },
    {
      "name": "k",
      "value": 1,
      "description": "Масштаб увеличения относительно выбранного узла.",
      "on": [
        {
          "events": [{"signal": "focus"}],
          "update": "focus ? width/(focus.r*2) : 1"
        }
      ]
    },
    {
      "name": "root",
      "update": "{'id': data('tree')[0]['id'], 'x': data('tree')[0]['x'], 'y': data('tree')[0]['y'], 'r': data('tree')[0]['r'], 'k': 1, 'children': data('tree')[0]['children']}",
      "description": "Корневой узел иерархии."
    },
    {
      "name": "focus",
      "init": "root",
      "description": "Увеличенный узел иерархии.",
      "on": [
        {
          "events": {"type": "click", "markname": "background"},
          "update": "{id: root['id'], 'x': root['x'], 'y': root['y'], 'r': root['r'], 'k': 1,'children': root['children']}"
        },
        {
          "events": {"type": "click", "markname": "circles"},
          "update": "(focus['x'] === datum['x'] && focus['y'] === datum['y'] && focus['r'] === datum['r'] && focus['r'] !== root['r']) ? {'id': root['id'], 'x': root['x'], 'y': root['y'], 'r': root['r'], 'k': 1, 'children': root['children']} : {'id': datum['id'], 'x': datum['x'], 'y': datum['y'], 'r': datum['r'], 'k': k, 'children': datum['children']}"
        }
      ]
    },
    {
      "name": "focus0",
      "update": "data('focus0') && length(data('focus0'))>0 ? data('focus0')[0] : focus",
      "description": "Ранее увеличенный узел иерархии."
    },
    {
      "name": "timer",
      "description": "Таймер переходов, включая масштабирование и плавное появление.",
      "on": [{"events": "timer", "update": "now()"}]
    },
    {
      "name": "interpolateTime",
      "description": "Время начала и конца интерполяции анимации в миллисекундах.",
      "on": [
        {
          "events": {
            "type": "click",
            "marknames": ["circles", "background"]
          },
          "update": "{'start': timer, 'end': timer+duration}"
        }
      ]
    },
    {
      "name": "t",
      "description": "Нормализованное время для функции плавности.",
      "update": "interpolateTime ? clamp((timer-interpolateTime.start)/(interpolateTime.end-interpolateTime.start), 0, 1): null"
    },
    {
      "name": "tEase",
      "description": "Расчёт плавности; сейчас используется easeInOutCubic.",
      "update": "t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1"
    },
    {
      "name": "interpolateTimeDelayed",
      "description": "Время плавного перехода с задержкой.",
      "on": [
        {
          "events": {"signal": "interpolateTime"},
          "update": "{'start': interpolateTime['end'], 'end': interpolateTime['end']+duration}"
        }
      ]
    },
    {
      "name": "tDelayed",
      "description": "Нормализованное время плавного перехода с задержкой.",
      "update": "interpolateTimeDelayed ? clamp((timer-interpolateTimeDelayed.start)/(interpolateTimeDelayed.end-interpolateTimeDelayed.start), 0, 1): null"
    },
    {
      "name": "tEaseDelayed",
      "description": "Расчёт плавности с задержкой; сейчас используется easeInOutCubic.",
      "update": "tDelayed < 0.5 ? 4 * tDelayed * tDelayed * tDelayed : (tDelayed - 1) * (2 * tDelayed - 2) * (2 * tDelayed - 2) + 1"
    },
    {
      "name": "showDetails",
      "description": "Логический признак показа сведений об узле.",
      "value": false,
      "on": [
        {
          "events": {
            "type": "click",
            "marknames": ["circles", "background"],
            "filter": [
              "!event.altKey && !event.shiftKey",
              "event.button === 0"
            ],
            "markname": "circles"
          },
          "update": "focus['children'] > 0 ? false : datum['id'] === root['id'] || focus0['id'] !== root['id'] && focus['id'] === root['id'] ? false : true"
        },
        {
          "events": {
            "type": "click",
            "marknames": ["circles", "background"],
            "filter": ["event.altKey || event.shiftKey", "event.button === 0"]
          },
          "update": "focus0['id'] === focus['id'] ? !showDetails : true"
        }
      ]
    }
  ],
  "data": [
    {
      "name": "source",
      "url": "data/flare.json",
      "transform": [
        {
          "type": "formula",
          "expr": "isValid(datum['parent']) ? datum['parent'] : null",
          "as": "parent"
        },
        {
          "type": "formula",
          "expr": "isValid(datum['size']) ? datum['size'] : null",
          "as": "size"
        }
      ]
    },
    {
      "name": "tree",
      "source": "source",
      "transform": [
        {"type": "stratify", "key": "id", "parentKey": "parent"},
        {
          "type": "pack",
          "field": "size",
          "sort": {"field": "value"},
          "size": [{"signal": "width"}, {"signal": "height"}]
        }
      ]
    },
    {
      "name": "focus0",
      "on": [{"trigger": "focus", "insert": "focus"}],
      "transform": [
        {"type": "formula", "expr": "now()", "as": "now"},
        {
          "type": "window",
          "ops": ["row_number"],
          "as": ["row"],
          "sort": {"field": "now", "order": "descending"}
        },
        {"type": "filter", "expr": "datum['row'] ? datum['row'] == 2 : true "},
        {"type": "project", "fields": ["id", "x", "y", "r", "children"]},
        {"type": "formula", "expr": "width/(datum['r']*2)", "as": "k"}
      ]
    },
    {
      "name": "details_data",
      "source": "tree",
      "transform": [
        {
          "type": "filter",
          "expr": "datum['id'] === focus['id'] && showDetails"
        },
        {
          "type": "formula",
          "expr": "['глубина иерархии: ' + datum['depth'], 'число потомков: ' + datum['children'],isValid( datum['size']) ? 'размер: ' + datum['size'] + ' байт' : '']",
          "as": "details"
        }
      ]
    }
  ],
  "scales": [
    {
      "name": "color",
      "type": "ordinal",
      "domain": {"data": "tree", "field": "depth"},
      "range": {"scheme": "magma"}
    }
  ],
  "marks": [
    {
      "name": "background",
      "description": "Невидимый прямоугольник позади всех элементов, покрывающий весь холст.",
      "type": "rect",
      "encode": {
        "enter": {
          "x": {"signal": "-padding['left']"},
          "y": {"signal": "-padding['top']"},
          "width": {"signal": "width+padding['left']+padding['right']"},
          "height": {"signal": "height+padding['top']+padding['bottom']"},
          "fillOpacity": {"value": 0}
        }
      }
    },
    {
      "name": "circles",
      "description": "Упакованные круги с масштабированием.",
      "type": "symbol",
      "from": {"data": "tree"},
      "encode": {
        "enter": {
          "shape": {"value": "circle"},
          "fill": {"scale": "color", "field": "depth"},
          "cursor": {"value": "pointer"},
          "tooltip": {"field": "name"}
        },
        "update": {
          "x": {
            "signal": "lerp([root['x']+ (datum['x'] - focus0['x']) * focus0['k'], root['x'] + (datum['x'] - focus['x']) * k], tEase)"
          },
          "y": {
            "signal": "lerp([ root['y'] + (datum['y'] - focus0['y']) * focus0['k'],  root['y'] + (datum['y'] - focus['y']) * k], tEase)"
          },
          "size": {
            "signal": "pow(2*(datum['r'] * lerp([focus0['k'], k],tEase)),2)"
          },
          "fill": {
            "signal": "showDetails && focus['id'] === datum['id'] ? '#fff' : scale('color',datum['depth'])"
          },
          "zindex": {
            "signal": "!showDetails ? 1 : (focus['id'] === root['id'] && isValid(datum['parent'])) ? -99 : indexof(pluck(treeAncestors('tree', datum['id']), 'id'), focus['id']) > 0 ? -99 : 1"
          },
          "stroke": {
            "signal": "showDetails ? scale('color', datum['depth']) : luminance(scale('color', datum['depth'])) > 0.5 ?  'black' : 'white'"
          },
          "strokeWidth": {
            "signal": "focus['id'] === datum['id'] && showDetails ? 20 : 0.5"
          },
          "strokeOpacity": {
            "signal": "!showDetails ? 0.5 : focus['id'] === root['id'] ? min(tEase, 0.35) : min(tEaseDelayed, 0.35)"
          }
        },
        "hover": {
          "color": {
            "signal": "showDetails ? scale('color', datum['depth']) : luminance(scale('color', datum['depth'])) > 0.5 ?  'black' : 'white'"
          },
          "strokeWidth": {"value": 2}
        }
      }
    },
    {
      "name": "details_title",
      "details": "the name of the node (appears on zoom)",
      "type": "text",
      "from": {"data": "details_data"},
      "interactive": false,
      "encode": {
        "enter": {
          "text": {"signal": "datum['name']"},
          "fill": {"scale": "color", "field": "depth"},
          "fontSize": {"signal": "0.055*width"},
          "align": {"value": "center"},
          "x": {"signal": "width/2"},
          "y": {"signal": "height/4"},
          "opacity": {"value": 0}
        },
        "update": {
          "opacity": {
            "signal": "!showDetails ? 0 : focus['id'] === root['id'] ? tEase : tEaseDelayed"
          }
        }
      }
    },
    {
      "name": "details",
      "description": "Дополнительные сведения об узле, появляющиеся при увеличении.",
      "type": "text",
      "from": {"data": "details_data"},
      "interactive": false,
      "encode": {
        "enter": {
          "text": {"signal": "datum['details']"},
          "fontSize": {"signal": "0.045*width"},
          "align": {"value": "center"},
          "x": {"signal": "width/2"},
          "y": {"signal": "height/3"},
          "fill": {"value": "gray"},
          "opacity": {"value": 0}
        },
        "update": {
          "opacity": {
            "signal": "!showDetails ? 0 : focus['id'] === root['id'] ? tEase : tEaseDelayed"
          }
        }
      }
    },
    {
      "name": "helper_text",
      "interactive": false,
      "description": "Подсказка по управлению в левом нижнем углу.",
      "type": "text",
      "encode": {
        "enter": {
          "fontSize": {"value": 14},
          "text": {
            "signal": "['Управление:', '• нажмите на узел, чтобы приблизить его','• для узла с потомками нажмите Shift и щёлкните, чтобы увидеть подробности', '• чтобы замедлить анимацию, нажмите ⌘ и щёлкните (Mac) или Ctrl и щёлкните (Windows)']"
          },
          "y": {"signal": "height+5"}
        },
        "update": {
          "opacity": {
            "signal": "ceil(k) === 1 ? isValid(t) ? tEaseDelayed : 1 : 0"
          }
        }
      }
    }
  ]
}
