Last active
January 26, 2025 21:06
-
-
Save Giammaria/7d8cb27a0ecc9f9a6afb3625cf4f1fcc to your computer and use it in GitHub Desktop.
20250120_dynamic_date_granularity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$schema": "https://vega.github.io/schema/vega/v5.json", | |
| "width": 800, | |
| "height": 300, | |
| "autosize": "fit-y", | |
| "padding": 5, | |
| "signals": [ | |
| {"name": "ganttWidth", "update": "width"}, | |
| { | |
| "name": "dateGranularityPre", | |
| "init": "0.5", | |
| "bind": { | |
| "name": "Date Granularity", | |
| "input": "range", | |
| "min": 0, | |
| "max": 1, | |
| "step": 0.005 | |
| } | |
| }, | |
| { | |
| "name": "horizontalScrollPercentage", | |
| "init": "0.5", | |
| "bind": { | |
| "name": "Horizontal Scroll", | |
| "input": "range", | |
| "min": 0, | |
| "max": 1, | |
| "step": 0.01 | |
| } | |
| }, | |
| { | |
| "name": "minDateBandwidths", | |
| "init": "{hour: 20, day: 20, month: 20, year: 20}" | |
| }, | |
| { | |
| "name": "minDateUnit", | |
| "init": "'hours'", | |
| "bind": { | |
| "name": "Min Date Unit", | |
| "input": "select", | |
| "options": ["hours", "days", "months", "years"] | |
| } | |
| }, | |
| {"name": "today", "update": "utc(year(now()),month(now()),date(now()))"}, | |
| { | |
| "name": "hourBandwidth", | |
| "update": "scale('x', timeOffset('hours', utchours(0,0,0,0),1)) - scale('x', utchours(0,0,0,0))" | |
| }, | |
| {"name": "currentXBandwidth", "update": "(round(hourBandwidth *100)/100)"}, | |
| { | |
| "name": "thresholdMinuteBandwidth", | |
| "update": "17.78*(span(currentXDateProperties.domain)/63162000000)" | |
| }, | |
| { | |
| "name": "thresholdHourBandwidth", | |
| "update": "0.6*(span(currentXDateProperties.domain)/63162000000)" | |
| }, | |
| { | |
| "name": "thresholdDayBandwidth", | |
| "update": "0.26*(span(currentXDateProperties.domain)/63162000000)" | |
| }, | |
| { | |
| "name": "thresholdMonthBandwidth", | |
| "update": "0.04*(span(currentXDateProperties.domain)/63162000000)" | |
| }, | |
| { | |
| "name": "thresholdYearBandwidth", | |
| "update": "0.03*(span(currentXDateProperties.domain)/63162000000)" | |
| }, | |
| { | |
| "name": "xCurrentUnit", | |
| "update": "(currentXBandwidth>=thresholdMinuteBandwidth ? 'hour': currentXBandwidth>=thresholdHourBandwidth ? 'day' : currentXBandwidth >= thresholdDayBandwidth ? 'month' : currentXBandwidth >= thresholdMonthBandwidth ? 'month' : 'year')" | |
| }, | |
| { | |
| "name": "xCurrentDateFormat", | |
| "update": "['%H', '%d', currentXBandwidth >= 0.12 ? '%B %Y' : '%b', '%Y'][indexof(['hour', 'day', 'month', 'year'],xCurrentUnit)]" | |
| }, | |
| { | |
| "name": "xCurrentDateLabelDX", | |
| "update": "xCurrentUnit === 'hour' ? ((scale('x', utchours(0,0,0,0))-scale('x', timeOffset('hours', utchours(0,0,0,0), -1)))/2) : (scale('x', now())-scale('x', timeOffset(xCurrentUnit, now(), -1)))/2" | |
| }, | |
| { | |
| "name": "xCurrentUnitOffset", | |
| "update": "xCurrentUnit === 'year' ? null : ['hour', 'day', 'month', 'year'][indexof(['hour','day', 'month'], xCurrentUnit)+1]" | |
| }, | |
| { | |
| "name": "xCurrentDateFormatOffset", | |
| "update": "['%H', '%d %B %Y', '%B %Y', '%Y'][indexof(['hour', 'day', 'month', 'year'],xCurrentUnitOffset)]" | |
| }, | |
| { | |
| "name": "xCurrentDateOffsetLabelDX", | |
| "update": "xCurrentUnitOffset === 'hour' ? ((scale('x', utchours(0,0,0,0))-scale('x', timeOffset('hours', utchours(0,0,0,0), -1)))/2) : (scale('x', now())-scale('x', timeOffset(xCurrentUnitOffset, now(), -1)))/2" | |
| }, | |
| { | |
| "name": "minDatebandwidth", | |
| "update": "[3.6e+6, 8.64e+7, 2.628e+9, 3.154e+10][indexof(['hours', 'days', 'months', 'years'], minDateUnit)]" | |
| }, | |
| { | |
| "name": "hourDomain", | |
| "update": "[data('xDomain')[0]['minDate']-scale('dateUnitIncrementMS', 'hours'), data('xDomain')[0]['minDate'] + ((ganttWidth-minDateBandwidths.hour)/minDateBandwidths.hour)*scale('dateUnitIncrementMS', 'hours')]" | |
| }, | |
| { | |
| "name": "currentXDateProperties", | |
| "update": "{domain: data('xDomain')[0].domain, span: span(data('xDomain')[0].domain)}" | |
| }, | |
| {"name": "columnsWidth", "value": 0}, | |
| { | |
| "name": "dateGranularity", | |
| "init": "{value: 0.5, zoomDelta: 0}", | |
| "on": [ | |
| { | |
| "events": {"signal": "dateGranularityPre"}, | |
| "update": "{value: dateGranularityPre, zoomDelta: dateGranularityPre > dateGranularity.value ? pow(1.01, abs(dateGranularityPre - dateGranularity.value) * 1000) : 1 / pow(1.01, abs(dateGranularity.value - dateGranularityPre) * 1000)}" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "zoom", | |
| "value": 1, | |
| "on": [ | |
| { | |
| "events": "wheel!", | |
| "force": true, | |
| "update": "x()>columnsWidth?pow(1.001, (event.deltaY) * pow(16, event.deltaMode)):1" | |
| }, | |
| { | |
| "events": {"signal": "dateGranularity"}, | |
| "update": "dateGranularity.zoomDelta" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "xDomPre", | |
| "value": [0, 0], | |
| "on": [ | |
| { | |
| "events": {"signal": "zoom"}, | |
| "update": "[initialDomainCenter + (xDom[0] - initialDomainCenter) * zoom, initialDomainCenter + (xDom[1] - initialDomainCenter) * zoom]" | |
| }, | |
| { | |
| "events": {"signal": "horizontalScrollPercentage"}, | |
| "update": "[initialDomainCenter - span(xDom) / 2 + span(currentXDateProperties.domain) * horizontalScrollPercentage, initialDomainCenter + span(xDom) / 2 + span(currentXDateProperties.domain) * horizontalScrollPercentage]" | |
| } | |
| ] | |
| }, | |
| { | |
| "name": "initialDomainCenter", | |
| "init": "(data('xDomain')[0]['minDate'] + data('xDomain')[0]['maxDate']) / 2" | |
| }, | |
| {"name": "anchorDate", "init": "initialDomainCenter"}, | |
| {"name": "xDomMinSpan", "update": "span(data('xDomain')[0]['hourDomain'])"}, | |
| { | |
| "name": "xDomMaxSpan", | |
| "update": "round((ganttWidth/0.15)* scale('dateUnitIncrementMS', 'years'))" | |
| }, | |
| { | |
| "name": "xDom", | |
| "update": "currentXDateProperties.domain", | |
| "on": [ | |
| { | |
| "events": {"signal": "xDomPre"}, | |
| "update": "span(xDomPre)<xDomMinSpan ? [initialDomainCenter - xDomMinSpan/2, initialDomainCenter + xDomMinSpan/2] : span(xDomPre)>xDomMaxSpan ? [initialDomainCenter - xDomMaxSpan/2, initialDomainCenter + xDomMaxSpan/2] : xDomPre" | |
| } | |
| ] | |
| } | |
| ], | |
| "marks": [ | |
| { | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 20}, | |
| "y": {"value": 20}, | |
| "text": { | |
| "signal": "[utcFormat(xDom[0], '%d-%b-%Y %H:%M:%S.%LZ'), utcFormat(xDom[1], '%d-%b-%Y %H:%M:%S.%LZ')]" | |
| }, | |
| "fill": {"value": "black"} | |
| } | |
| } | |
| }, | |
| { | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 20}, | |
| "y": {"value": 60}, | |
| "text": { | |
| "signal": "['currentXBandwidth: ' + currentXBandwidth, '', 'thresholdMinuteBandwidth: ' + thresholdMinuteBandwidth, 'thresholdHourBandwidth: ' + thresholdHourBandwidth, 'thresholdDayBandwidth: ' + thresholdDayBandwidth, 'thresholdMonthBandwidth: '+thresholdMonthBandwidth, 'thresholdYearBandwidth: ' + thresholdYearBandwidth, '']" | |
| }, | |
| "fill": {"value": "black"} | |
| } | |
| } | |
| }, | |
| { | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 20}, | |
| "y": {"value": 160}, | |
| "text": { | |
| "signal": "['xCurrentUnit: ' + xCurrentUnit, 'xCurrentDateFormat: ' + xCurrentDateFormat, 'xCurrentDateLabelDX: '+xCurrentDateLabelDX]" | |
| }, | |
| "fill": {"value": "black"} | |
| } | |
| } | |
| }, | |
| { | |
| "type": "text", | |
| "encode": { | |
| "update": { | |
| "x": {"value": 20}, | |
| "y": {"value": 210}, | |
| "text": { | |
| "signal": "['xCurrentUnitOffset: ' + xCurrentUnitOffset, 'xCurrentDateFormatOffset: ' + xCurrentDateFormatOffset, 'xCurrentDateOffsetLabelDX: '+xCurrentDateOffsetLabelDX]" | |
| }, | |
| "fill": {"value": "black"} | |
| } | |
| } | |
| } | |
| ], | |
| "scales": [ | |
| { | |
| "name": "dateUnitIncrementMS", | |
| "type": "band", | |
| "domain": ["hours", "days", "months", "years"], | |
| "range": [3600000, 86400000, 2628000000, 31540000000] | |
| }, | |
| { | |
| "name": "x", | |
| "type": "time", | |
| "domain": {"signal": "xDom"}, | |
| "range": {"signal": "[0,ganttWidth]"} | |
| } | |
| ], | |
| "axes": [ | |
| { | |
| "description": "Bottom date axis", | |
| "ticks": true, | |
| "labelPadding": {"signal": "xCurrentDateFormat === '%B %Y' ? 2 :-12"}, | |
| "scale": "x", | |
| "position": {"signal": "columnsWidth"}, | |
| "orient": "top", | |
| "domainColor": "#CCC", | |
| "domainWidth": 1, | |
| "tickSize": 15, | |
| "tickOpacity": 1, | |
| "grid": true, | |
| "zindex": -1, | |
| "labelOverlap": true, | |
| "formatType": "time", | |
| "labelBound": true, | |
| "tickCount": { | |
| "signal": "xCurrentUnit === 'hour' ? 24*(span(xDom)/86400000) : xCurrentUnit" | |
| }, | |
| "format": {"signal": "xCurrentDateFormat"}, | |
| "labelSeparation": 3, | |
| "tickExtra": true, | |
| "encode": { | |
| "labels": { | |
| "update": { | |
| "dx": {"signal": "xCurrentDateLabelDX"}, | |
| "text": {"signal": "xCurrentDateFormat === '%B %Y' ? split(datum.label, ' ') : datum.label"} | |
| } | |
| }, | |
| "ticks": { | |
| "update": { | |
| "stroke": {"value": "#CCC"}, | |
| "strokeWidth": { | |
| "signal": "xCurrentUnit === 'day' && datum.label === '01' ? 0 : indexof(datum.label, 'Jan') >= 0 ? 0 : 1" | |
| } | |
| } | |
| }, | |
| "grid": { | |
| "update": { | |
| "stroke": {"value": "#CCC"}, | |
| "strokeWidth": { | |
| "signal": "xCurrentUnit === 'day' && datum.label === '01' ? 0 : indexof(datum.label, 'Jan') >= 0 ? 0 : 1" | |
| } | |
| } | |
| } | |
| } | |
| }, | |
| { | |
| "description": "Top date axis", | |
| "title": { | |
| "signal": "xCurrentDateFormatOffset === '%B %Y' && (toDate(utcFormat((xDom[0] + (xDom[1]-xDom[0])/2), '01-%B-%Y')) < domain('x')[0] && toDate(utcFormat((xDom[0] + (xDom[1]-xDom[0])/2), '15-%B-%Y')) > domain('x')[1]) ? utcFormat((xDom[0] + (xDom[1]-xDom[0])/2), '%B %Y') : null" | |
| }, | |
| "titleX": {"signal": "columnsWidth+ganttWidth/2"}, | |
| "titleY": -25, | |
| "titleBaseline": {"value": "middle"}, | |
| "titleFontSize": {"value": 10}, | |
| "titleFontWeight": "400", | |
| "scale": "x", | |
| "position": {"signal": "columnsWidth"}, | |
| "domain": false, | |
| "orient": "top", | |
| "offset": 0, | |
| "tickSize": 20, | |
| "tickOpacity": 1, | |
| "tickWidth": 1, | |
| "tickColor": "#666", | |
| "labelBaseline": "middle", | |
| "grid": true, | |
| "gridWidth": 1, | |
| "gridColor": {"value": "#666"}, | |
| "zindex": 1, | |
| "labelOverlap": true, | |
| "formatType": "time", | |
| "labelBound": true, | |
| "tickCount": {"signal": "(xCurrentUnitOffset || 0)"}, | |
| "format": {"signal": "xCurrentDateFormatOffset"}, | |
| "labelSeparation": 5, | |
| "encode": { | |
| "labels": { | |
| "update": { | |
| "text": { | |
| "signal": "xCurrentDateFormat === '%B %Y' ? null : datum.label" | |
| }, | |
| "x": { | |
| "signal": "xCurrentDateFormatOffset === '%Y' ? toDate(utcFormat(datum.value, '01-Jun-%Y')) > xDom[1] ? scale('x', datum.value)+(scale('x', xDom[1])-scale('x', datum.value))/2 : scale('x', toDate(utcFormat(datum.value, '01-Jun-%Y'))) : xCurrentDateFormatOffset === '%B %Y' ? toDate(utcFormat(datum.value, '17-%b-%Y')) > xDom[1] ? scale('x', datum.value)+(scale('x', xDom[1])-scale('x', datum.value))/2 : scale('x', toDate(utcFormat(datum.value, '15-%b-%Y'))) : scale('x', datum.value)" | |
| }, | |
| "dx": { | |
| "signal": "xCurrentDateFormatOffset === '%Y' || xCurrentDateFormatOffset === '%B %Y' ? 0 : xCurrentDateOffsetLabelDX" | |
| }, | |
| "align": {"value": "center"} | |
| } | |
| } | |
| } | |
| } | |
| ], | |
| "data": [ | |
| { | |
| "name": "dataset", | |
| "values": [{"startDate": "2020-01-01"}], | |
| "transform": [ | |
| { | |
| "type": "formula", | |
| "expr": "minDatebandwidth", | |
| "as": "minDatebandwidth" | |
| }, | |
| {"type": "aggregate", "groupby": ["startDate", "minDatebandwidth"]}, | |
| { | |
| "type": "formula", | |
| "expr": "sequence(toDate(datum.startDate), timeOffset('year', toDate(datum.startDate), 2), datum.minDatebandwidth)", | |
| "as": "startDate" | |
| }, | |
| {"type": "flatten", "fields": ["startDate"]}, | |
| {"type": "formula", "expr": "datum.startDate", "as": "endDate"}, | |
| { | |
| "type": "formula", | |
| "expr": "utcFormat(datum.startDate, '%Y-%m-%dT%H:%M:%S.%LZ')", | |
| "as": "startDateFormatted" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "utcFormat(datum.endDate, '%Y-%m-%dT%H:%M:%S.%LZ')", | |
| "as": "endDateFormatted" | |
| }, | |
| { | |
| "type": "project", | |
| "fields": [ | |
| "startDate", | |
| "endDate", | |
| "startDateFormatted", | |
| "endDateFormatted", | |
| "minDatebandwidth" | |
| ] | |
| } | |
| ] | |
| }, | |
| {"name": "dates", "source": "dataset", "transform": []}, | |
| { | |
| "name": "xDomain", | |
| "source": "dates", | |
| "transform": [ | |
| { | |
| "type": "aggregate", | |
| "fields": ["startDate", "endDate"], | |
| "ops": ["min", "max"], | |
| "as": ["minDate", "maxDate"] | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "(datum.maxDate-datum.minDate)/scale('dateUnitIncrementMS', 'hours')", | |
| "as": "hours" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "[datum.minDate-scale('dateUnitIncrementMS', 'hours'), datum.minDate + ((ganttWidth-minDateBandwidths.hour)/minDateBandwidths.hour)*scale('dateUnitIncrementMS', 'hours')]", | |
| "as": "hourDomain" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "[datum.minDate - scale('dateUnitIncrementMS', 'hours'), datum.minDate + ganttWidth*0.7*scale('dateUnitIncrementMS', 'hours')]", | |
| "as": "dayDomain" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "[datum.minDate - scale('dateUnitIncrementMS', 'hours'), datum.minDate + ganttWidth*0.5*scale('dateUnitIncrementMS', 'hours')]", | |
| "as": "monthDomain" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "[datum.minDate - scale('dateUnitIncrementMS', 'hours'), datum.minDate + ganttWidth*0.3*scale('dateUnitIncrementMS', 'hours')]", | |
| "as": "yearDomain" | |
| }, | |
| { | |
| "type": "formula", | |
| "expr": "[datum.minDate-scale('dateUnitIncrementMS', 'hours'),datum.maxDate+scale('dateUnitIncrementMS', 'hours')]", | |
| "as": "allDomain" | |
| }, | |
| {"type": "formula", "expr": "datum.allDomain", "as": "domain"}, | |
| { | |
| "type": "formula", | |
| "expr": "[utcFormat(datum.domain[0], '%d-%b-%Y %H:%M:%S.%LZ'), utcFormat(datum.domain[1], '%d-%b-%Y %H:%M:%S.%LZ')]", | |
| "as": "domainFormatted" | |
| } | |
| ] | |
| } | |
| ] | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment