python/whylogs/viz/html/templates/index-hbs-cdn-all-in-jupyter-differenced-chart.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Profile Visualizer | whylogs</title>
<link rel="icon" href="images/whylabs-favicon.png" type="image/png" sizes="16x16" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Asap:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js"
integrity="sha512-RNLkV3d+aLtfcpEyFG8jRbnWHxUqVZozacROI4J2F1sTaDqo1dPQYs01OMi1t1w9Y2FdbSCDSQ2ZVdAC8bzgAg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<style type="text/css">
:root {
/** Branded colors */
--brandSecondary900: #4f595b;
--secondaryLight1000: #313b3d;
/** Purpose colors */
--tealBackground: #eaf2f3;
}
/* RESET STYLE */
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: border-box;
overflow-y: hidden;
}
/* Screen on smaller screens */
.no-responsive {
display: none;
position: fixed;
top: 0;
left: 0;
z-index: 1031;
width: 100vw;
height: 100vh;
background-color: var(--tealBackground);
display: flex;
align-items: center;
justify-content: center;
}
.desktop-content {
display: flex;
flex-direction: column;
}
.no-responsive__content {
max-width: 600px;
width: 100%;
padding: 0 24px;
}
.no-responsive__title {
font-size: 96px;
font-weight: 300;
color: var(--brandSecondary900);
line-height: 1.167;
}
.no-responsive__text {
margin: 0;
font-size: 16px;
font-weight: 400;
color: var(--brandSecondary900);
line-height: 1.5;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
left: 0;
}
.circle-color {
display: inline-block;
padding: 5px;
border-radius: 50px;
}
.colors-for-distingushing-charts {
padding-right: 10px;
}
.display-flex{
display: flex;
}
.flex-direction-column {
flex-direction: column;
}
.align-items-flex-end {
align-items: flex-end;
}
.chart-box-title {
width: 98%;
justify-content: space-between;
margin: 10px;
margin-top: 15px;
bottom: 0;
}
.chart-box-title p{
margin-bottom: 0;
font-family: Asap;
font-weight: bold;
font-size: 18px;
line-height: 16px;
color: #4F595B;
}
.bar.positive {
fill: #44C0E7;
}
.bar.negative {
fill: #F5843C;
}
.error-message {
display: flex;
justify-content: center;
align-items: center;
color: rgb(255, 114, 71);
font-size: 30px;
font-weight: 900;
}
@media screen and (min-width: 500px) {
.desktop-content {
display: block;
}
.no-responsive {
display: none;
}
}
</style>
</head>
<body id="generated-html"></body>
<script id="entry-template" type="text/x-handlebars-template">
{{{{raw}}}}
<div class="desktop-content">
{{#each this}}
<div class="chart-box" id="chart-box">
<div class="chart-box-title display-flex">
<p>{{@key}}</p>
<div class="display-flex">
<div class="colors-for-distingushing-charts">
<div class="circle-color" style="background: #44C0E7;"></div>
<text alignment-baseline="middle" style="font-size: 15px;">Target</text>
</div>
<div class="colors-for-distingushing-charts">
<div class="circle-color" style="background: #F5843C"></div>
<text alignment-baseline="middle" style="font-size: 15px;">Reference</text>
</div>
</div></div>
<div class="svg-container">
{{{getDoubleHistogramChart this}}}
</div>
</div>
{{/each}}
</div>
<div class="no-responsive">
<div class="no-responsive__content">
<h1 class="no-responsive__title">Hold on! :)</h1>
<p class="no-responsive__text">
It looks like your current screen size or device is not yet supported by the WhyLabs Sandbox. The Sandbox is
best experienced on a desktop computer. Please try maximizing this window or switching to another device. We
are working on adding support for a larger variety of devices.
</p>
</div>
</div>
{{{{/raw}}}}
</script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.7.0/d3.min.js" integrity="sha512-cd6CHE+XWDQ33ElJqsi0MdNte3S+bQY819f7p3NUHgwQQLXSKjE4cPZTeGNI+vaxZynk1wVU3hoHmow3m089wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
function registerHandlebarHelperFunctions() {
//helper fun
const findAndDeleteUndefined = (axisData) => {
const undefinedAxisIndex = axisData.findIndex((axis) => axis === undefined)
if (undefinedAxisIndex > 0) {
return [...axisData.slice(0, undefinedAxisIndex), ...axisData.slice(undefinedAxisIndex + 1)]
}
return axisData
}
const filterAndSortChartData = (overlappedHistogramData, histogramData) => {
const filteredData = overlappedHistogramData
.map((d) =>
histogramData
.filter((ref) => d.axisX === ref.axisX )[0])
.sort((a, b) => {
if (+a.axisY < +b.axisY) {
return 1;
}
if (+a.axisY > +b.axisY) {
return -1;
}
return 0;
})
.slice(0, 20)
return findAndDeleteUndefined(filteredData)
}
class GenerateChartParams {
constructor(height, width, data, referenceData, bottomMargin=30, topMargin=5) {
this.MARGIN = {
TOP: topMargin,
RIGHT: 5,
BOTTOM: bottomMargin,
LEFT: 55,
};
this.SVG_WIDTH = width;
this.SVG_HEIGHT = height;
this.CHART_WIDTH = this.SVG_WIDTH - this.MARGIN.LEFT - this.MARGIN.RIGHT;
this.CHART_HEIGHT = this.SVG_HEIGHT - this.MARGIN.TOP - this.MARGIN.BOTTOM;
this.svgEl = d3.create("svg")
.attr("preserveAspectRatio", "xMinYMin meet")
.attr("viewBox", `0 -10 ${$(window).width()} ${$(window).height()-30}`)
.classed("svg-content-responsive", true)
this.maxYValue = d3.max(data, (d) => Math.abs(d.axisY));
this.xScale = d3
.scaleBand()
.domain(filterAndSortChartData(data, referenceData).map((sortedCounts) => sortedCounts && sortedCounts.axisX))
.rangeRound([this.MARGIN.LEFT, this.SVG_WIDTH])
.padding([0.1]);
this.yScale = d3
.scaleLinear()
.domain([0, this.maxYValue])
.range([this.CHART_HEIGHT, 0]);
}
}
function chartData(column) {
const data = [];
if (column.frequentItems) {
Object.entries(column.frequentItems).forEach(([key, {value, estimate}], index) => {
data.push({
axisY: estimate,
axisX: value,
});
});
} else {
$(document).ready(() =>
$(".desktop-content").html(`
<p style="height: ${$(window).height()}px" class="error-message">
Something went wrong. Please try again.
</p>
`)
)
}
return data
}
function generatePositiveNegativeChart(histogramData, overlappedHistogramData) {
const data = filterAndSortChartData(chartData(histogramData), chartData(overlappedHistogramData)).map((axis, index) => {
if (axis) {
const findIndex = chartData(histogramData).findIndex((value) => value.axisX === axis.axisX)
const difference = axis.axisY - chartData(histogramData)[findIndex].axisY
return [difference]
}
return 0;
}).flat()
let yFormat,
xFormat;
const sizes = new GenerateChartParams($(window).height()-60, $(window).width(), chartData(histogramData), chartData(overlappedHistogramData))
let {
MARGIN,
SVG_WIDTH,
SVG_HEIGHT,
CHART_WIDTH,
CHART_HEIGHT,
xScale,
svgEl
} = sizes
const rectColors = ["bar positive", "bar negative"]
const maxY = Math.abs(d3.max(data));
const minY = Math.abs(d3.min(data));
let positiveY = Math.ceil(maxY) % 1 ? maxY + 2*(maxY/(maxY*10)) : maxY + 2*(maxY/(maxY/10)),
negativeY = Math.ceil(minY) % 1 ? minY + 2*(minY/(minY*10)) : minY + 2*(minY/(minY/10));
const yScale = d3.scaleLinear()
.range([CHART_HEIGHT,0])
.domain([-negativeY*1.2, positiveY*1.2 || 0])
.nice()
const xAxis = d3.axisBottom(xScale).ticks(SVG_WIDTH / 80, xFormat).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(CHART_HEIGHT / 30, yFormat);
yFormat = yScale.tickFormat(100, yFormat);
svgEl.append("g")
.attr("transform", `translate(${MARGIN.LEFT}, 0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line")
.attr("x2", CHART_WIDTH )
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", - MARGIN.LEFT)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start"));
svgEl.append("text")
.attr("transform",
"translate(" + (CHART_WIDTH/2) + " ," +
(CHART_HEIGHT + MARGIN.TOP + 30) + ")")
.style("text-anchor", "middle")
.text("Values")
.style("font-size", "15")
.style("opacity", "0.6")
svgEl.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 0)
.attr("x", 0 - (SVG_HEIGHT / 2))
.attr("dy", "1em")
.style("text-anchor", "middle")
.text("Counts")
.style("font-size", "15")
.style("opacity", "0.6")
svgEl
.append("g")
.attr("transform", `translate(0,${SVG_HEIGHT - MARGIN.BOTTOM - 7})`)
.call(xAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick>line").remove())
.call(g => g.append("text")
.attr("x", SVG_WIDTH - MARGIN.RIGHT)
.attr("y", 27)
.attr("fill", "currentColor")
.attr("text-anchor", "end"));
svgEl.selectAll(".bar")
.data(data)
.enter()
.append("rect")
.attr("class", function(d) { return d < 0 ? rectColors[0] : rectColors[1]; })
.attr("y", function(d) { return yScale(Math.max(0, d)); })
.attr("x", function(d, i) { return xScale(filterAndSortChartData(chartData(histogramData), chartData(overlappedHistogramData))[i]?.axisX) })
.attr("height", function(d) { return Math.abs(yScale(d) - yScale(0)); })
.attr("width", xScale.bandwidth())
.style("opacity", "0.8")
return svgEl._groups[0][0].outerHTML;
}
const profileFromCSVfile = {{{reference_profile_from_whylogs}}}
Handlebars.registerHelper("getDoubleHistogramChart",(column,key) => {
const columnKey = key.data.key
try {
if (profileFromCSVfile) {
return generatePositiveNegativeChart(
column,
profileFromCSVfile[columnKey]
)
}
} catch (err) {
$(document).ready(() =>
$(".desktop-content").html(`
<p style="height: ${$(window).height()}px" class="error-message">
Something went wrong. Please try again.
</p>
`)
)
}
});
}
function initWebsiteScripts() {
$(".svg-container").css("height", $(window).height() - 32)
}
function initHandlebarsTemplate() {
// Replace this context with JSON from .py file
const context = {{{profile_from_whylogs}}};
// Config handlebars and pass data to HBS template
const source = document.getElementById("entry-template").innerHTML;
const template = Handlebars.compile(source);
const html = template(context);
const target = document.getElementById("generated-html");
target.innerHTML = html;
}
// Invoke functions -- keep in mind invokation order
registerHandlebarHelperFunctions();
initHandlebarsTemplate();
initWebsiteScripts();
</script>
</html>