diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js index b8e287fc85..04a5d7c294 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisFactory.js @@ -54,7 +54,9 @@ export const axisFactory = settings => { labelFunction: axis.labelFunction, component: { bottom: component.bottom, - left: component.left + left: component.left, + top: component.top, + right: component.right }, size: component.size, decorate: component.decorate, @@ -73,6 +75,8 @@ export const axisFactory = settings => { const defaultComponent = () => ({ bottom: fc.axisBottom, left: fc.axisLeft, + top: fc.axisTop, + right: fc.axisRight, decorate: () => {} }); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js b/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js index 68b3b4f987..0692f246a3 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/axisSplitter.js @@ -8,8 +8,10 @@ */ import {splitterLabels} from "./splitterLabels"; -export const axisSplitter = (settings, sourceData) => { +export const axisSplitter = (settings, sourceData, splitFn = dataSplitFunction) => { let color; + let data; + let altData; // splitMainValues is an array of main-value names to put into the alt-axis const splitMainValues = settings.splitMainValues || []; @@ -21,8 +23,8 @@ export const axisSplitter = (settings, sourceData) => { const haveSplit = settings["mainValues"].some(m => altValue(m.name)); // Split the data into main and alt displays - const data = haveSplit ? sourceData.map(d => d.filter(v => !altValue(v.key))) : sourceData; - const altData = haveSplit ? sourceData.map(d => d.filter(v => altValue(v.key))) : null; + data = haveSplit ? splitFn(sourceData, key => !altValue(key)) : sourceData; + altData = haveSplit ? splitFn(sourceData, altValue) : null; // Renderer to show the special controls for moving between axes const splitter = selection => { @@ -54,8 +56,39 @@ export const axisSplitter = (settings, sourceData) => { }; splitter.haveSplit = () => haveSplit; - splitter.data = () => data; - splitter.altData = () => altData; + + splitter.data = (...args) => { + if (!args.length) { + return data; + } + data = args[0]; + return splitter; + }; + splitter.altData = (...args) => { + if (!args.length) { + return altData; + } + altData = args[0]; + return splitter; + }; return splitter; }; + +export const dataSplitFunction = (sourceData, isIncludedFn) => { + return sourceData.map(d => d.filter(v => isIncludedFn(v.key))); +}; + +export const dataBlankFunction = (sourceData, isIncludedFn) => { + return sourceData.map(series => { + if (!isIncludedFn(series.key)) { + // Blank this data + return series.map(v => Object.assign({}, v, {mainValue: null})); + } + return series; + }); +}; + +export const groupedBlankFunction = (sourceData, isIncludedFn) => { + return sourceData.map(group => dataBlankFunction(group, isIncludedFn)); +}; diff --git a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js index 8b717e2d9d..49a1eb21e9 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js @@ -80,7 +80,7 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { .style("-ms-grid-column", 5) .style("grid-row", 3) .style("-ms-grid-row", 3) - .style("width", "1em") + .style("width", altAxis.size || "1em") .style("display", "flex") .style("align-items", "center") .style("justify-content", "center") @@ -89,7 +89,9 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { .style("transform", "rotate(-90deg)"); const y2Scale = altAxis.scale.domain(altAxis.domain); - const yAxisComponent = fc.axisRight(y2Scale); + const yAxisComponent = altAxis.component.right(y2Scale); + yAxisComponent.tickFormat(altAxis.tickFormatFunction); + if (altAxis.decorate) yAxisComponent.decorate(altAxis.decorate); // Render the axis y2AxisDataJoin(container, ["right"]) diff --git a/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js b/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js new file mode 100644 index 0000000000..79db504dca --- /dev/null +++ b/packages/perspective-viewer-d3fc/src/js/axis/domainMatchOrigins.js @@ -0,0 +1,16 @@ +export default (domain1, domain2) => { + if (!isMatchable(domain1) || !isMatchable(domain2)) return; + + const ratio1 = originRatio(domain1); + const ratio2 = originRatio(domain2); + + if (ratio1 > ratio2) { + domain2[0] = adjustLowerBound(domain2, ratio1); + } else { + domain1[0] = adjustLowerBound(domain1, ratio2); + } +}; + +const isMatchable = domain => domain.length === 2 && !isNaN(domain[0]) && !isNaN(domain[1]) && domain[0] !== domain[1]; +const originRatio = domain => (0 - domain[0]) / (domain[1] - domain[0]); +const adjustLowerBound = (domain, ratio) => (ratio * domain[1]) / (ratio - 1); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js index 1cc1c18128..4d1bfd5e6d 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js @@ -10,7 +10,7 @@ import * as d3 from "d3"; import * as fc from "d3fc"; import minBandwidth from "./minBandwidth"; import {flattenArray} from "./flatten"; -import {multiAxisBottom, multiAxisLeft} from "../d3fc/axis/multi-axis"; +import {multiAxisBottom, multiAxisLeft, multiAxisTop, multiAxisRight} from "../d3fc/axis/multi-axis"; import {getChartContainer} from "../plugin/root"; export const scale = () => minBandwidth(d3.scaleBand()).padding(0.5); @@ -69,8 +69,8 @@ export const component = settings => { const tickSizeInner = multiLevel ? groupTickLayout.map(l => l.size) : groupTickLayout[0].size; const tickSizeOuter = groupTickLayout.reduce((s, v) => s + v.size, 0); - const createAxis = scale => { - const axis = pickAxis(multiLevel)(scale); + const createAxis = base => scale => { + const axis = base(scale); if (multiLevel) { axis.groups(levelGroups) @@ -87,9 +87,12 @@ export const component = settings => { hideOverlappingLabels(s, rotation); }; + const axisSet = getAxisSet(multiLevel); return { - bottom: createAxis, - left: createAxis, + bottom: createAxis(axisSet.bottom), + left: createAxis(axisSet.left), + right: createAxis(axisSet.right), + top: createAxis(axisSet.top), size: `${tickSizeOuter + 10}px`, decorate }; @@ -102,6 +105,24 @@ export const component = settings => { return orient === "horizontal" ? fc.axisOrdinalBottom : fc.axisOrdinalLeft; }; + const getAxisSet = multiLevel => { + if (multiLevel) { + return { + bottom: multiAxisBottom, + left: multiAxisLeft, + top: multiAxisTop, + right: multiAxisRight + }; + } else { + return { + bottom: fc.axisOrdinalBottom, + left: fc.axisOrdinalLeft, + top: fc.axisOrdinalTop, + right: fc.axisOrdinalRight + }; + } + }; + const axisGroups = domain => { const groups = []; domain.forEach(tick => { diff --git a/packages/perspective-viewer-d3fc/src/js/charts/area.js b/packages/perspective-viewer-d3fc/src/js/charts/area.js index d4b50d2be7..b1afa85ce3 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/area.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/area.js @@ -9,6 +9,7 @@ import * as fc from "d3fc"; import {axisFactory} from "../axis/axisFactory"; import {chartSvgFactory} from "../axis/chartFactory"; +import {axisSplitter} from "../axis/axisSplitter"; import {AXIS_TYPES} from "../axis/axisType"; import {areaSeries} from "../series/areaSeries"; import {seriesColors} from "../series/seriesColors"; @@ -35,15 +36,25 @@ function areaChart(container, settings) { .excludeType(AXIS_TYPES.linear) .settingName("crossValues") .valueName("crossValue")(data); - const yAxis = axisFactory(settings) + const yAxisFactory = axisFactory(settings) .settingName("mainValues") .valueName("mainValue") .excludeType(AXIS_TYPES.ordinal) .orient("vertical") .include([0]) - .paddingStrategy(hardLimitZeroPadding())(data); + .paddingStrategy(hardLimitZeroPadding()); - const chart = chartSvgFactory(xAxis, yAxis).plotArea(withGridLines(series).orient("vertical")); + // Check whether we've split some values into a second y-axis + const splitter = axisSplitter(settings, data).color(color); + + const yAxis1 = yAxisFactory(splitter.data()); + + // No grid lines if splitting y-axis + const plotSeries = splitter.haveSplit() ? series : withGridLines(series).orient("vertical"); + + const chart = chartSvgFactory(xAxis, yAxis1) + .axisSplitter(splitter) + .plotArea(plotSeries); chart.yNice && chart.yNice(); @@ -55,12 +66,20 @@ function areaChart(container, settings) { const toolTip = nearbyTip() .settings(settings) .xScale(xAxis.scale) - .yScale(yAxis.scale) + .yScale(yAxis1.scale) .color(color) .data(data); + if (splitter.haveSplit()) { + // Create the y-axis data for the alt-axis + const yAxis2 = yAxisFactory(splitter.altData()); + chart.altAxis(yAxis2); + // Give the tooltip the information (i.e. 2 datasets with different scales) + toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); + } + // render - container.datum(data).call(zoomChart); + container.datum(splitter.data()).call(zoomChart); container.call(toolTip); container.call(legend); } diff --git a/packages/perspective-viewer-d3fc/src/js/charts/column.js b/packages/perspective-viewer-d3fc/src/js/charts/column.js index c00fb9c95c..7c9576bad3 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/column.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/column.js @@ -9,6 +9,8 @@ import * as fc from "d3fc"; import {axisFactory} from "../axis/axisFactory"; import {chartSvgFactory} from "../axis/chartFactory"; +import domainMatchOrigins from "../axis/domainMatchOrigins"; +import {axisSplitter, dataBlankFunction, groupedBlankFunction} from "../axis/axisSplitter"; import {AXIS_TYPES} from "../axis/axisType"; import {barSeries} from "../series/barSeries"; import {seriesColors} from "../series/seriesColors"; @@ -37,15 +39,26 @@ function columnChart(container, settings) { .excludeType(AXIS_TYPES.linear) .settingName("crossValues") .valueName("crossValue")(data); - const yAxis = axisFactory(settings) + const yAxisFactory = axisFactory(settings) .settingName("mainValues") .valueName("mainValue") .excludeType(AXIS_TYPES.ordinal) .orient("vertical") .include([0]) - .paddingStrategy(hardLimitZeroPadding())(data); + .paddingStrategy(hardLimitZeroPadding()); - const chart = chartSvgFactory(xAxis, yAxis).plotArea(withGridLines(series).orient("vertical")); + // Check whether we've split some values into a second y-axis + const blankFunction = settings.mainValues.length > 1 ? groupedBlankFunction : dataBlankFunction; + const splitter = axisSplitter(settings, data, blankFunction).color(color); + + const yAxis1 = yAxisFactory(splitter.data()); + + // No grid lines if splitting y-axis + const plotSeries = splitter.haveSplit() ? series : withGridLines(series).orient("vertical"); + + const chart = chartSvgFactory(xAxis, yAxis1) + .axisSplitter(splitter) + .plotArea(plotSeries); if (chart.xPaddingInner) { chart.xPaddingInner(0.5); @@ -59,8 +72,16 @@ function columnChart(container, settings) { .settings(settings) .xScale(xAxis.scale); + if (splitter.haveSplit()) { + // Create the y-axis data for the alt-axis + const yAxis2 = yAxisFactory(splitter.altData()); + + domainMatchOrigins(yAxis1.domain, yAxis2.domain); + chart.yDomain(yAxis1.domain).altAxis(yAxis2); + } + // render - container.datum(data).call(zoomChart); + container.datum(splitter.data()).call(zoomChart); container.call(legend); } columnChart.plugin = { diff --git a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js index e8f9869374..8bd615197b 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js @@ -10,6 +10,7 @@ import * as fc from "d3fc"; import {axisFactory} from "../axis/axisFactory"; import {AXIS_TYPES} from "../axis/axisType"; import {chartSvgFactory} from "../axis/chartFactory"; +import {axisSplitter} from "../axis/axisSplitter"; import {seriesColors} from "../series/seriesColors"; import {categoryPointSeries, symbolType} from "../series/categoryPointSeries"; import {groupData} from "../data/groupData"; @@ -43,13 +44,23 @@ function yScatter(container, settings) { .excludeType(AXIS_TYPES.linear) .settingName("crossValues") .valueName("crossValue")(data); - const yAxis = axisFactory(settings) + const yAxisFactory = axisFactory(settings) .settingName("mainValues") .valueName("mainValue") .orient("vertical") - .paddingStrategy(paddingStrategy)(data); + .paddingStrategy(paddingStrategy); - const chart = chartSvgFactory(xAxis, yAxis).plotArea(withGridLines(series).orient("vertical")); + // Check whether we've split some values into a second y-axis + const splitter = axisSplitter(settings, data).color(color); + + const yAxis1 = yAxisFactory(splitter.data()); + + // No grid lines if splitting y-axis + const plotSeries = splitter.haveSplit() ? series : withGridLines(series).orient("vertical"); + + const chart = chartSvgFactory(xAxis, yAxis1) + .axisSplitter(splitter) + .plotArea(plotSeries); chart.yNice && chart.yNice(); @@ -61,12 +72,20 @@ function yScatter(container, settings) { const toolTip = nearbyTip() .settings(settings) .xScale(xAxis.scale) - .yScale(yAxis.scale) + .yScale(yAxis1.scale) .color(color) .data(data); + if (splitter.haveSplit()) { + // Create the y-axis data for the alt-axis + const yAxis2 = yAxisFactory(splitter.altData()); + chart.altAxis(yAxis2); + // Give the tooltip the information (i.e. 2 datasets with different scales) + toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); + } + // render - container.datum(data).call(zoomChart); + container.datum(splitter.data()).call(zoomChart); container.call(toolTip); if (legend) { container.call(legend); diff --git a/packages/perspective-viewer-d3fc/test/results/results.json b/packages/perspective-viewer-d3fc/test/results/results.json index 58c2a30163..4e687057c2 100644 --- a/packages/perspective-viewer-d3fc/test/results/results.json +++ b/packages/perspective-viewer-d3fc/test/results/results.json @@ -13,19 +13,19 @@ "line.html/highlights invalid filter.": "b3c75abfed5dc9e4f90909598e973f59", "line.html/sorts by an alpha column.": "ba630a497a7995723198fce64a9e87db", "line.html/displays visible columns.": "cac30d35fa3c8d3da7cb5a4e11cd775b", - "bar.html/shows a grid without any settings applied.": "1c9db86cfe87a5a920495a75b0d52910", + "bar.html/shows a grid without any settings applied.": "8d566389f8a7fc93e617bffec4c343d8", "bar.html/pivots by a row.": "41120594ece3603976abcca83d6eacf7", "bar.html/pivots by two rows.": "1d21d9c320c04de2a70210387723e87e", - "bar.html/pivots by a column.": "130b801eb0222c48be7a9da2dbb70be4", + "bar.html/pivots by a column.": "ce9b123b9621c2441b59f1d529646224", "bar.html/pivots by a row and a column.": "87365707d748dad0dc44e91d29b7ccae", "bar.html/pivots by two rows and two columns.": "0ac0108921a7b8da8d46bc29dd893d03", - "bar.html/sorts by a hidden column.": "eb16734ba512437bcd20ce9ce9030d41", - "bar.html/sorts by a numeric column.": "b3c00d901ab92a5e6b9bdf97e28ad612", - "bar.html/filters by a numeric column.": "cbf9e3a4c4c7679f269144c4363ba92b", - "bar.html/filters by a datetime column.": "5a048c88cf2ccbb851406b100638b1f8", - "bar.html/highlights invalid filter.": "8afeaa16360f9970f93c51e10f6257e5", - "bar.html/sorts by an alpha column.": "4fa47ac2e7207c9eb43ddd9d64696c8e", - "bar.html/displays visible columns.": "d6a8f925c6de67c3a3afebaf9eb70551", + "bar.html/sorts by a hidden column.": "ff9afc43959ea8c911f8bb268c58fae6", + "bar.html/sorts by a numeric column.": "c5de18d51d2586779f5d71d6ba3239d9", + "bar.html/filters by a numeric column.": "d1b0fb04b3cd1420a06854543da36089", + "bar.html/filters by a datetime column.": "1fff8a67abbf84fd0c7e92f3b32b1733", + "bar.html/highlights invalid filter.": "b96910062ad5af4e84e4d673b883d876", + "bar.html/sorts by an alpha column.": "a92f7635e38dc25417eedebd0e596f16", + "bar.html/displays visible columns.": "a725dd645c8fd767973ef7af9d7bac4d", "scatter.html/shows a grid without any settings applied.": "08720a4e1eba0f7299d784b24766ee5e", "scatter.html/pivots by a row.": "3efb7dd9cfc78e2f6df7521a94ef0c2d", "scatter.html/pivots by two rows.": "81d42b2966158525a6e9b57fe6eed90d", @@ -51,45 +51,45 @@ "bar-x.html/highlights invalid filter.": "b1438f7ae28e09faa9ef164e2e129ae7", "bar-x.html/sorts by an alpha column.": "153fdf95263563aed0727454ec94e2c8", "bar-x.html/displays visible columns.": "cb7cfd8e052b02dfe0d55ed9e0174404", - "bar-themed.html/shows a grid without any settings applied.": "4c9570007a5cff45fcc020a83d208c29", + "bar-themed.html/shows a grid without any settings applied.": "1cfe10eb48794d7c25948fd34e9f84ee", "bar-themed.html/pivots by a row.": "6dd0dec013a19586a4e81bf3d651b622", "bar-themed.html/pivots by two rows.": "113af9ac70d703788062c8d81c32f43b", - "bar-themed.html/pivots by a column.": "b1d2f7eaefa016699282001d8bfe278d", + "bar-themed.html/pivots by a column.": "0f49bb43851757bc18ac0a3918be5854", "bar-themed.html/pivots by a row and a column.": "bd93007e15638abf24b85434806d0426", "bar-themed.html/pivots by two rows and two columns.": "ed9b903cc5148faeda6d778dc228b397", - "bar-themed.html/sorts by a hidden column.": "025323f0bda16b0ec8433b9065eb4d4c", - "bar-themed.html/sorts by a numeric column.": "5e5262d4160aa1c1839625c5eaf525f9", - "bar-themed.html/filters by a numeric column.": "98c69cb7251af248014ed732c3cbad14", - "bar-themed.html/filters by a datetime column.": "e8635e1f0769cd520565e08ff543e60f", - "bar-themed.html/highlights invalid filter.": "3bce2bc2e290fa5cfd95dfdc3455b659", - "bar-themed.html/sorts by an alpha column.": "9f1020eb0c24645c556b0f5c2ae49363", - "bar-themed.html/displays visible columns.": "a6a7a49573ad97ef8c4e99df6d40b3cc", + "bar-themed.html/sorts by a hidden column.": "ed74c235e2ec9adb0bb341e1587f0dc4", + "bar-themed.html/sorts by a numeric column.": "4702d28e5182025267ad7089a5ffaf4d", + "bar-themed.html/filters by a numeric column.": "da497d54dc02250ae8c732758cf3bf88", + "bar-themed.html/filters by a datetime column.": "3b5c5ffd71d91c0cb990b21e132e228c", + "bar-themed.html/highlights invalid filter.": "3c7dfad60715fb4739a570972ad2ecd0", + "bar-themed.html/sorts by an alpha column.": "64ca6ea592e984da003201c3f35950c1", + "bar-themed.html/displays visible columns.": "123056996835bcf3c3640e4dd9eaf743", "yscatter.html/shows a grid without any settings applied.": "e1a7d89696ad75fd84ff26707ea67935", "yscatter.html/pivots by a row.": "58796fe685033d94d101fa0e9706ae49", "yscatter.html/pivots by two rows.": "93487d41dacf2c89c3eddf95c7ee7e3c", "yscatter.html/pivots by a column.": "3e3637b60e5524587eb3d92f3a85abb6", "yscatter.html/pivots by a row and a column.": "3030ccc8e4f3440bb97f1a0397bfd05d", "yscatter.html/pivots by two rows and two columns.": "678377950d54bdb930f11888f526e27b", - "yscatter.html/sorts by a hidden column.": "37684ecd5bbf47929e0b05f46d35b8ee", + "yscatter.html/sorts by a hidden column.": "a0a892731663664059896e80073eb2bc", "yscatter.html/sorts by a numeric column.": "8a90511bc126a99af9dffe1bb0a53359", "yscatter.html/filters by a numeric column.": "17c8f2802fd63aa863b52144fb8b777b", "yscatter.html/filters by a datetime column.": "c850b5d983193a8a01060e4fdbfa8723", "yscatter.html/highlights invalid filter.": "aab5fe2737db9de572a7731618476cfd", "yscatter.html/sorts by an alpha column.": "121c53d718cb0169c6955fd1b87e4ea4", - "yscatter.html/displays visible columns.": "e8e7ea8f1727c7fce490423bfd9ebfff", + "yscatter.html/displays visible columns.": "283397b18dd372448a2c55a12f4ddfac", "area.html/shows a grid without any settings applied.": "d97c3b605ee41a3ffd72be024c622b35", "area.html/pivots by a row.": "96308cf375891e9d300c81bb4d8c99cd", "area.html/pivots by two rows.": "2a5f9d3da087091fd214190ab142905c", "area.html/pivots by a column.": "ce1638975b61d012f34a33a972758d11", "area.html/pivots by a row and a column.": "e69fccc2dba97cdb90bd9809e1fdd392", "area.html/pivots by two rows and two columns.": "83e070476878a3ff42f67e178a790f1d", - "area.html/sorts by a hidden column.": "49d93d31b65fea1dc02bbf1f23b44586", + "area.html/sorts by a hidden column.": "f18821c143917036b7335b6f0c574ed0", "area.html/sorts by a numeric column.": "2db75fb488cba9ebf80d6f6523c43d9f", "area.html/filters by a numeric column.": "2e886ef9e5ec325fca75c4abc7dc5daa", "area.html/filters by a datetime column.": "85b93c12b92a10e742ca15cd2e1ea380", "area.html/highlights invalid filter.": "69cbf32414377d407e25034df9591e44", "area.html/sorts by an alpha column.": "10cb34f57c6dec026ad341fec18ee7e1", - "area.html/displays visible columns.": "db4cbf9a75992a64d9b1b7c4d00657c5", + "area.html/displays visible columns.": "9364f8607d874164a0b8cbd8722ac9c0", "heatmap.html/shows a grid without any settings applied.": "b8748cd81fc40b5a11149e07e18c1c2e", "heatmap.html/pivots by a row.": "7c17828d42c09ae38bcd4119032430a1", "heatmap.html/pivots by two rows.": "f454e4e4af58f883d25133196d14232f", @@ -120,5 +120,5 @@ "sunburst.html/highlights invalid filter.": "e48566c24b1655a202d1d227424f71c5", "sunburst.html/sorts by an alpha column.": "dca6a09d9b0c4b4a3fd6cdb91fa1eb1a", "sunburst.html/displays visible columns.": "92769650f8ccaf823d070f3bd12aa73b", - "__GIT_COMMIT__": "71ddec3d6f108f3d3ba120339f8b40758b73728b" + "__GIT_COMMIT__": "f32e66bae59552df4f4835d05f6fc883d5dc99b6" } \ No newline at end of file