Skip to content

Commit

Permalink
[update]: big number aggregation
Browse files Browse the repository at this point in the history
  • Loading branch information
helloklyn committed Aug 24, 2021
1 parent f27ac25 commit 2ebab4f
Show file tree
Hide file tree
Showing 2 changed files with 366 additions and 0 deletions.
254 changes: 254 additions & 0 deletions preview/big-number-aggregation/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
const inputDimensionBreakdownNameLookML = "_breakdown";
const { tidy, select, distinct, arrange, desc } = Tidy;

const visObject = {
options: {
bar_color: {
type: "string",
display: "color",
label: "1. Choose Primary Color",
default: "#3259F9",
},
is_human_readable: {
type: "boolean",
label: "2. Toggle for Readable Number",
default: false,
},
is_percentage_number: {
type: "boolean",
label: "3. Toggle for Percentage Number",
default: false,
},
aggregation_type: {
type: "string",
display: "select",
label: "4. Select Aggregation Type",
default: "sum",
values: [
{ sum: "sum" },
{ average: "average" },
{ min: "min" },
{ max: "max" },
{ median: "median" },
],
},
},
create: function (element, config) {
element.innerHTML = `
<style>
.highcharts-figure #container{
height: 90%;
width: 90%;
position: absolute;
// fully responsiveness
}
.highcharts-figure #container:hover {
}
.highcharts-figure #container .highcharts-container {
// border-radius: 20px;
// filter: drop-shadow(2px 2px 2px #999999);
}
.highcharts-title {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
color: "#181818";
font-size: 24px !important;
margin-bottom: 10px;
}
.highcharts-metrics-value-latest {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
color: "#181818";
font-size: 50px;
font-weight: 600;
line-height: 52px;
}
.highcharts-subtitle {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
color: "#181818";
font-size: 14px;
}
.highcharts-subtitle text {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
color: "#181818";
font-size: 14px;
fill: #999999 !important;
}
.highcharts-axis-title {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
}
.highcharts-axis-labels .highcharts-yaxis-labels {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
fill: #999999 !important;
}
.highcharts-axis-labels .highcharts-xaxis-labels {
font-family: "Circular Spotify Text", Helvetica, Arial, sans-serif;
fill: #999999 !important;
}
.highcharts-metrics-by {
font-size: 14px;
font-style: italic;
fill: #999999 !important;
}
.highcharts-metrics-value-prefix {
font-size: 14px;
font-style: Italic;
}
</style>
<figure class="highcharts-figure">
<div id="container"></div>
</figure>
`;
},
updateAsync: function (data, element, config, queryResponse, details, done) {
this.clearErrors();
var errorMessage = `
Instructions🧭
This viz package requires
1 dimension in named _breakdown in .lkml
1 measure
Please contact Hong Wu(@hongkuiw) if you still facing errors
`;

if (
queryResponse.fields.dimensions.length == 0 ||
(queryResponse.fields.dimensions.length == 1 &&
queryResponse.fields.measures.length > 1)
) {
console.error(errorMessage);
return;
}

dataInput = queryResponse.data;
var dataRecords = generateDataRecords(dataInput);
var highchartsFigureHeight = document.getElementById("container").offsetHeight;
var pointHeightResponsive = parseInt((highchartsFigureHeight / dataRecords.length) * 0.45);

var viewName = queryResponse.fields.dimensions.length > 0 ? queryResponse.fields.dimensions[0].view : queryResponse.fields.measures[0].view;
var dimensionName = viewName + "." + inputDimensionBreakdownNameLookML;
dimensionMetaInfoValue = getFieldMetaInfoValue(queryResponse, dimensionName);
breakdownName = dimensionMetaInfoValue[0].label_short;
breakdownDescription = dimensionMetaInfoValue[0].description;
var measureName = queryResponse.fields.measures[0].name;
measureMetaInfoValue = getFieldMetaInfoValue(queryResponse, measureName);
metricsTitle = measureMetaInfoValue[0].label_short;
chartTitle = metricsTitle;
dataRecordsSortDescending = tidy(dataRecords, arrange((a, b) => b.measureName - a.measureName));
var numberBreakdowns = dataRecordsSortDescending.length;
var dataHighCharts = generateHighChartsDataSeries(dataRecordsSortDescending);
var dataBreakdowns = [];
var dataSeries = [];
dataHighCharts.forEach((d) => {
dataBreakdowns.push(d[0]);
dataSeries.push(d[1]);
});

metricsValueAggregated = calculateAggregatedValue(dataSeries, config.aggregation_type);

Highcharts.chart("container", {
chart: {
zoomType: "x",
panning: "true",
panKey: "shift",
type: "bar",
events: {
load: function () {
this.title.on("mouseover", (e) => {
myLabel = this.renderer
.label(
measureMetaInfoValue[0]["description"],
e.x,
e.y,
"rectangle"
)
.css({ color: "#FFFFFF" })
.attr({
fill: "#181818",
"font-family": "Circular Spotify Text, Helvetica, Arial, sans-serif",
})
.add()
.toFront();
});
this.title.on("mouseout", (e) => {
if (myLabel) {
myLabel.destroy();
}
});
},
},
},
title: {
text:
chartTitle +
'<br> <p class="highcharts-metrics-by">by ' +
breakdownName +
"</p>" +
"<br>" +
"<br>" +
"<br>" +
"<p class='highcharts-metrics-value-prefix'>" + translateAggregationType(config.aggregation_type) + ": </p>" +
'<p class="highcharts-metrics-value-latest">' + humanReadableNumber(percentageNumber(metricsValueAggregated, config.is_percentage_number),config.is_human_readable) +
"</p>",
align: "left",
},
subtitle: {
},
xAxis: {
categories: dataBreakdowns,
title: {
text: undefined,
},
},
yAxis: {
title: {
text: null,
},
labels: {
overflow: "justify",
enable: false,
},
},
tooltip: {
valuePrefix: metricsTitle + ": ",
},
plotOptions: {
bar: {
dataLabels: {
enabled: true,
formatter: function() {
return humanReadableNumber(percentageNumber(parseFloat(this.y), config.is_percentage_number),config.is_human_readable)
}
},
color: config.bar_color
},
},
legend: {
enabled: false,
},
credits: {
enabled: false,
},
exporting: {
enabled: false
},
series: [
{
pointWidth: pointHeightResponsive,
name: breakdownName,
data: dataSeries,
},
],
});
done();
},
};

looker.plugins.visualizations.add(visObject);
112 changes: 112 additions & 0 deletions preview/big-number-aggregation/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
var generateDataRecords = (dataIndexFormat) => {
//HELP: convert looker response data into common records format
//NOTE: used by metrics-widget__big-number
var dataRecords = []
dataIndexFormat.forEach(d=>{
obj = {}
var headerName = Object.keys(d);
headerName.forEach(h=>{
obj[h] = d[h].value
});
dataRecords.push(obj)
})
return dataRecords
}

function generateHighChartsDataSeries(dataRecordsInput) {
//HELP: convert DataRecords into HighChart DataSeries without Header/Column Name
//NOTE: used by metrics-widget__big-number
dataHighCharts = []
dataRecordsInput.forEach(function(d) {
var rowValueOnly = []
var columnNames = Object.keys(d);
// console.log(columnNames);
columnNames.forEach(function(c) {
rowValueOnly.push(d[c])
})
dataHighCharts.push(rowValueOnly)
});
return dataHighCharts;
}

function getFieldMetaInfoValue(queryResponse, fieldName) {
//HELP: look up meta info of looker fields
// @queryResponse: looker Response
// @fieldName: viewName.fieldTechicalName
// @return: array of metainfo of this field
const queryResponseFieldsDimensions = queryResponse.fields.dimensions
const queryResponseFieldsMeasures = queryResponse.fields.measures

f_dimension = queryResponseFieldsDimensions.filter(d=>{
return d.name == fieldName
})

f_measure = queryResponseFieldsMeasures.filter(d=>{
return d.name == fieldName
})

var f = f_dimension.length == 0 ? f_measure : f_dimension

return f
}

function calculateAggregatedValue(inputArray1D, aggregationType) {
// HELP
// @aggregationType - String: sum, average, min, max, median
switch (aggregationType) {
case "sum":
return d3.sum(inputArray1D);
break;
case "average":
return d3.mean(inputArray1D);
break;
case "min":
return d3.min(inputArray1D);
break;
case "max":
return d3.max(inputArray1D);
break;
case "median":
return d3.median(inputArray1D);
break;
case "major-between":
return ( d3.quantile(inputArray1D, 0.25) + " ~ " + d3.quantile(inputArray1D, 0.75));
break;
}
}

function humanReadableNumber(value, is_human_readable) {
if (is_human_readable == true) {
return numeral(value).format("0.00a")
} else {
return value
}
}

function percentageNumber(value, is_percentage_number) {
if (is_percentage_number == true) {
return numeral(value).format("0.00%")
} else {
return value
}
}

function translateAggregationType(aggregationType) {
switch (aggregationType) {
case "sum":
return "Total";
break;
case "average":
return "Average";
break;
case "max":
return "Max";
break;
case "min":
return "Min";
break;
case "median":
return "Median";
break;
}
}

0 comments on commit 2ebab4f

Please sign in to comment.