import { Spec } from 'vega';

export const spec: Spec = {
  $schema: 'https://vega.github.io/schema/vega/v5.json',
  width: 500,
  padding: 5,

  signals: [
    { name: 'chartHeight', value: 100 },
    { name: 'chartPadding', value: 50 },
    { name: 'height', update: '(chartHeight + chartPadding) * 3' },
    { name: 'delayExtent', value: [-60, 180] },
    { name: 'timeExtent', value: [0, 24] },
    { name: 'distExtent', value: [0, 2400] },
    { name: 'delayStep', value: 10, bind: { input: 'range', min: 2, max: 20, step: 1 } },
    { name: 'timeStep', value: 1, bind: { input: 'range', min: 0.25, max: 2, step: 0.25 } },
    { name: 'distStep', value: 100, bind: { input: 'range', min: 50, max: 200, step: 50 } },
    {
      name: 'delayRange',
      update: 'delayExtent',
      on: [
        {
          events: { signal: 'delayZoom' },
          update:
            '[(delayRange[0]+delayRange[1])/2 - delayZoom, (delayRange[0]+delayRange[1])/2 + delayZoom]'
        },
        {
          events: '@delay:dblclick!, @delayBrush:dblclick!',
          update: '[delayExtent[0], delayExtent[1]]'
        },
        {
          events: '[@delayBrush:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[delayRange[0] + invert('delayScale', x()) - invert('delayScale', xmove), delayRange[1] + invert('delayScale', x()) - invert('delayScale', xmove)]"
        },
        {
          events: '[@delay:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[min(delayAnchor, invert('delayScale', x())), max(delayAnchor, invert('delayScale', x()))]"
        }
      ]
    },
    {
      name: 'delayZoom',
      value: 0,
      on: [
        {
          events: '@delay:wheel!, @delayBrush:wheel!',
          update:
            '0.5 * abs(span(delayRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))'
        }
      ]
    },
    {
      name: 'delayAnchor',
      value: 0,
      on: [
        {
          events: '@delay:pointerdown!',
          update: "invert('delayScale', x())"
        }
      ]
    },
    {
      name: 'timeRange',
      update: 'timeExtent',
      on: [
        {
          events: { signal: 'timeZoom' },
          update:
            '[(timeRange[0]+timeRange[1])/2 - timeZoom, (timeRange[0]+timeRange[1])/2 + timeZoom]'
        },
        {
          events: '@time:dblclick!, @timeBrush:dblclick!',
          update: '[timeExtent[0], timeExtent[1]]'
        },
        {
          events: '[@timeBrush:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[timeRange[0] + invert('timeScale', x()) - invert('timeScale', xmove), timeRange[1] + invert('timeScale', x()) - invert('timeScale', xmove)]"
        },
        {
          events: '[@time:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[min(timeAnchor, invert('timeScale', x())), max(timeAnchor, invert('timeScale', x()))]"
        }
      ]
    },
    {
      name: 'timeZoom',
      value: 0,
      on: [
        {
          events: '@time:wheel!, @timeBrush:wheel!',
          update:
            '0.5 * abs(span(timeRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))'
        }
      ]
    },
    {
      name: 'timeAnchor',
      value: 0,
      on: [
        {
          events: '@time:pointerdown!',
          update: "invert('timeScale', x())"
        }
      ]
    },
    {
      name: 'distRange',
      update: 'distExtent',
      on: [
        {
          events: { signal: 'distZoom' },
          update:
            '[(distRange[0]+distRange[1])/2 - distZoom, (distRange[0]+distRange[1])/2 + distZoom]'
        },
        {
          events: '@dist:dblclick!, @distBrush:dblclick!',
          update: '[distExtent[0], distExtent[1]]'
        },
        {
          events: '[@distBrush:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[distRange[0] + invert('distScale', x()) - invert('distScale', xmove), distRange[1] + invert('distScale', x()) - invert('distScale', xmove)]"
        },
        {
          events: '[@dist:pointerdown, window:pointerup] > window:pointermove!',
          update:
            "[min(distAnchor, invert('distScale', x())), max(distAnchor, invert('distScale', x()))]"
        }
      ]
    },
    {
      name: 'distZoom',
      value: 0,
      on: [
        {
          events: '@dist:wheel!, @distBrush:wheel!',
          update:
            '0.5 * abs(span(distRange)) * pow(1.0005, event.deltaY * pow(16, event.deltaMode))'
        }
      ]
    },
    {
      name: 'distAnchor',
      value: 0,
      on: [
        {
          events: '@dist:pointerdown!',
          update: "invert('distScale', x())"
        }
      ]
    },
    { name: 'xmove', value: 0, on: [{ events: 'window:pointermove', update: 'x()' }] }
  ],

  data: [
    {
      name: 'flights',
      url: 'data/flights-200k.json',
      transform: [
        {
          type: 'bin',
          field: 'delay',
          extent: { signal: 'delayExtent' },
          step: { signal: 'delayStep' },
          as: ['delay0', 'delay1']
        },
        {
          type: 'bin',
          field: 'time',
          extent: { signal: 'timeExtent' },
          step: { signal: 'timeStep' },
          as: ['time0', 'time1']
        },
        {
          type: 'bin',
          field: 'distance',
          extent: { signal: 'distExtent' },
          step: { signal: 'distStep' },
          as: ['dist0', 'dist1']
        },
        {
          type: 'crossfilter',
          signal: 'xfilter',
          fields: ['delay', 'time', 'distance'],
          query: [{ signal: 'delayRange' }, { signal: 'timeRange' }, { signal: 'distRange' }]
        }
      ]
    }
  ],

  scales: [
    {
      name: 'layout',
      type: 'band',
      domain: ['delay', 'time', 'distance'],
      range: 'height'
    },
    {
      name: 'delayScale',
      type: 'linear',
      round: true,
      domain: { signal: 'delayExtent' },
      range: 'width'
    },
    {
      name: 'timeScale',
      type: 'linear',
      round: true,
      domain: { signal: 'timeExtent' },
      range: 'width'
    },
    {
      name: 'distScale',
      type: 'linear',
      round: true,
      domain: { signal: 'distExtent' },
      range: 'width'
    }
  ],

  marks: [
    {
      description: 'Delay Histogram',
      name: 'delay',
      type: 'group',
      encode: {
        enter: {
          y: { scale: 'layout', value: 'delay', offset: 20 },
          width: { signal: 'width' },
          height: { signal: 'chartHeight' },
          fill: { value: 'transparent' }
        }
      },

      data: [
        {
          name: 'delay-bins',
          source: 'flights',
          transform: [
            {
              type: 'resolvefilter',
              ignore: 1,
              filter: { signal: 'xfilter' }
            },
            {
              type: 'aggregate',
              groupby: ['delay0', 'delay1'],
              key: 'delay0',
              drop: false
            }
          ]
        }
      ],

      scales: [
        {
          name: 'yscale',
          type: 'linear',
          domain: { data: 'delay-bins', field: 'count' },
          range: [{ signal: 'chartHeight' }, 0]
        }
      ],

      axes: [{ orient: 'bottom', scale: 'delayScale' }],

      marks: [
        {
          type: 'rect',
          name: 'delayBrush',
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: '#fcfcfc' }
            },
            update: {
              x: { signal: "scale('delayScale', delayRange[0])" },
              x2: { signal: "scale('delayScale', delayRange[1])" }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          from: { data: 'delay-bins' },
          encode: {
            enter: {
              fill: { value: 'steelblue' }
            },
            update: {
              x: { scale: 'delayScale', field: 'delay0' },
              x2: { scale: 'delayScale', field: 'delay1', offset: -1 },
              y: { scale: 'yscale', field: 'count' },
              y2: { scale: 'yscale', value: 0 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('delayScale', delayRange[0])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('delayScale', delayRange[1])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'text',
          interactive: false,
          encode: {
            enter: {
              y: { value: -5 },
              text: { value: 'Arrival Delay (min)' },
              baseline: { value: 'bottom' },
              fontSize: { value: 14 },
              fontWeight: { value: 500 },
              fill: { value: 'black' }
            }
          }
        }
      ]
    },

    {
      description: 'Time Histogram',
      name: 'time',
      type: 'group',
      encode: {
        enter: {
          y: { scale: 'layout', value: 'time', offset: 20 },
          width: { signal: 'width' },
          height: { signal: 'chartHeight' },
          fill: { value: 'transparent' }
        }
      },

      data: [
        {
          name: 'time-bins',
          source: 'flights',
          transform: [
            {
              type: 'resolvefilter',
              ignore: 2,
              filter: { signal: 'xfilter' }
            },
            {
              type: 'aggregate',
              groupby: ['time0', 'time1'],
              key: 'time0',
              drop: false
            }
          ]
        }
      ],

      scales: [
        {
          name: 'yscale',
          type: 'linear',
          domain: { data: 'time-bins', field: 'count' },
          range: [{ signal: 'chartHeight' }, 0]
        }
      ],

      axes: [{ orient: 'bottom', scale: 'timeScale' }],

      marks: [
        {
          type: 'rect',
          name: 'timeBrush',
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: '#fcfcfc' }
            },
            update: {
              x: { signal: "scale('timeScale', timeRange[0])" },
              x2: { signal: "scale('timeScale', timeRange[1])" }
            }
          }
        },
        {
          type: 'rect',
          from: { data: 'time-bins' },
          interactive: false,
          encode: {
            enter: {
              fill: { value: 'steelblue' }
            },
            update: {
              x: { scale: 'timeScale', field: 'time0' },
              x2: { scale: 'timeScale', field: 'time1', offset: -1 },
              y: { scale: 'yscale', field: 'count' },
              y2: { scale: 'yscale', value: 0 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('timeScale', timeRange[0])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('timeScale', timeRange[1])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'text',
          interactive: false,
          encode: {
            enter: {
              y: { value: -5 },
              text: { value: 'Local Departure Time (hour)' },
              baseline: { value: 'bottom' },
              fontSize: { value: 14 },
              fontWeight: { value: 500 },
              fill: { value: 'black' }
            }
          }
        }
      ]
    },

    {
      description: 'Distance Histogram',
      name: 'dist',
      type: 'group',
      encode: {
        enter: {
          y: { scale: 'layout', value: 'distance', offset: 20 },
          width: { signal: 'width' },
          height: { signal: 'chartHeight' },
          fill: { value: 'transparent' }
        }
      },

      data: [
        {
          name: 'dist-bins',
          source: 'flights',
          transform: [
            {
              type: 'resolvefilter',
              ignore: 4,
              filter: { signal: 'xfilter' }
            },
            {
              type: 'aggregate',
              groupby: ['dist0', 'dist1'],
              key: 'dist0',
              drop: false
            }
          ]
        }
      ],

      scales: [
        {
          name: 'yscale',
          type: 'linear',
          domain: { data: 'dist-bins', field: 'count' },
          range: [{ signal: 'chartHeight' }, 0]
        }
      ],

      axes: [{ orient: 'bottom', scale: 'distScale' }],

      marks: [
        {
          type: 'rect',
          name: 'distBrush',
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: '#fcfcfc' }
            },
            update: {
              x: { signal: "scale('distScale', distRange[0])" },
              x2: { signal: "scale('distScale', distRange[1])" }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          from: { data: 'dist-bins' },
          encode: {
            enter: {
              fill: { value: 'steelblue' }
            },
            update: {
              x: { scale: 'distScale', field: 'dist0' },
              x2: { scale: 'distScale', field: 'dist1', offset: -1 },
              y: { scale: 'yscale', field: 'count' },
              y2: { scale: 'yscale', value: 0 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('distScale', distRange[0])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'rect',
          interactive: false,
          encode: {
            enter: {
              y: { value: 0 },
              height: { signal: 'chartHeight' },
              fill: { value: 'firebrick' }
            },
            update: {
              x: { signal: "scale('distScale', distRange[1])" },
              width: { value: 1 }
            }
          }
        },
        {
          type: 'text',
          interactive: false,
          encode: {
            enter: {
              y: { value: -5 },
              text: { value: 'Travel Distance (miles)' },
              baseline: { value: 'bottom' },
              fontSize: { value: 14 },
              fontWeight: { value: 500 },
              fill: { value: 'black' }
            }
          }
        }
      ]
    }
  ]
};
