mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-10 01:25:21 +00:00
Add primitive charting capabilities
This commit is contained in:
parent
8c8cd6a21d
commit
eba897d4ec
@ -386,6 +386,39 @@
|
||||
text-align: center;
|
||||
margin-top: 5em;
|
||||
}
|
||||
|
||||
#chart
|
||||
{
|
||||
background-color: var(--element-background-color);
|
||||
filter: drop-shadow(.2rem .2rem .2rem var(--shadow-color));
|
||||
display: none;
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
/* This is for charts (uPlot) */
|
||||
.u-wrap {position: relative;user-select: none;}
|
||||
.u-over, .u-under, .u-axis {position: absolute;}
|
||||
.u-under {overflow: hidden;}
|
||||
.uplot canvas {display: block;position: relative;width: 100%;height: 100%;}
|
||||
.u-legend {margin: auto;text-align: center; margin-top: 1em; font-family: Liberation Mono, DejaVu Sans Mono, MonoLisa, Consolas, monospace;}
|
||||
.u-inline {display: block;}
|
||||
.u-inline * {display: inline-block;}
|
||||
.u-inline tr {margin-right: 16px;}
|
||||
.u-legend th {font-weight: 600;}
|
||||
.u-legend th > * {vertical-align: middle;display: inline-block;}
|
||||
.u-legend td { min-width: 13em; }
|
||||
.u-legend .u-marker {width: 1em;height: 1em;margin-right: 4px;background-clip: padding-box !important;}
|
||||
.u-inline.u-live th::after {content: ":";vertical-align: middle;}
|
||||
.u-inline:not(.u-live) .u-value {display: none;}
|
||||
.u-series > * {padding: 4px;}
|
||||
.u-series th {cursor: pointer;}
|
||||
.u-legend .u-off > * {opacity: 0.3;}
|
||||
.u-select {background: rgba(0,0,0,0.07);position: absolute;pointer-events: none;}
|
||||
.u-cursor-x, .u-cursor-y {position: absolute;left: 0;top: 0;pointer-events: none;will-change: transform;z-index: 100;}
|
||||
.u-hz .u-cursor-x, .u-vt .u-cursor-y {height: 100%;border-right: 1px dashed #607D8B;}
|
||||
.u-hz .u-cursor-y, .u-vt .u-cursor-x {width: 100%;border-bottom: 1px dashed #607D8B;}
|
||||
.u-cursor-pt {position: absolute;top: 0;left: 0;border-radius: 50%;border: 0 solid;pointer-events: none;will-change: transform;z-index: 100;/*this has to be !important since we set inline "background" shorthand */background-clip: padding-box !important;}
|
||||
.u-axis.u-off, .u-select.u-off, .u-cursor-x.u-off, .u-cursor-y.u-off, .u-cursor-pt.u-off {display: none;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@ -410,6 +443,7 @@
|
||||
<table class="monospace-table shadow" id="data-table"></table>
|
||||
<pre class="monospace-table shadow" id="data-unparsed"></pre>
|
||||
</div>
|
||||
<div id="chart"></div>
|
||||
<svg id="graph" fill="none"></svg>
|
||||
<p id="error" class="monospace shadow">
|
||||
</p>
|
||||
@ -530,8 +564,13 @@
|
||||
if (status === 200) {
|
||||
let json;
|
||||
try { json = JSON.parse(response); } catch (e) {}
|
||||
|
||||
if (json !== undefined && json.statistics !== undefined) {
|
||||
renderResult(json);
|
||||
} else if (Array.isArray(json) && json.length == 2 &&
|
||||
Array.isArray(json[0]) && Array.isArray(json[1]) && json[0].length > 1 && json[0].length == json[1].length) {
|
||||
/// If user requested FORMAT JSONCompactColumns, we will render it as a chart.
|
||||
renderChart(json);
|
||||
} else {
|
||||
renderUnparsedResult(response);
|
||||
}
|
||||
@ -578,30 +617,27 @@
|
||||
}
|
||||
}
|
||||
|
||||
function clearElement(id)
|
||||
{
|
||||
let elem = document.getElementById(id);
|
||||
while (elem.firstChild) {
|
||||
elem.removeChild(elem.lastChild);
|
||||
}
|
||||
elem.style.display = 'none';
|
||||
}
|
||||
|
||||
function clear()
|
||||
{
|
||||
let table = document.getElementById('data-table');
|
||||
while (table.firstChild) {
|
||||
table.removeChild(table.lastChild);
|
||||
}
|
||||
|
||||
let graph = document.getElementById('graph');
|
||||
while (graph.firstChild) {
|
||||
graph.removeChild(graph.lastChild);
|
||||
}
|
||||
graph.style.display = 'none';
|
||||
|
||||
document.getElementById('data-unparsed').innerText = '';
|
||||
document.getElementById('data-unparsed').style.display = 'none';
|
||||
|
||||
document.getElementById('error').innerText = '';
|
||||
document.getElementById('error').style.display = 'none';
|
||||
clearElement('data-table');
|
||||
clearElement('graph');
|
||||
clearElement('chart');
|
||||
clearElement('data-unparsed');
|
||||
clearElement('error');
|
||||
clearElement('hourglass');
|
||||
|
||||
document.getElementById('check-mark').innerText = '';
|
||||
document.getElementById('hourglass').innerText = '';
|
||||
document.getElementById('stats').innerText = '';
|
||||
|
||||
document.getElementById('hourglass').style.display = 'none';
|
||||
document.getElementById('check-mark').style.display = 'none';
|
||||
|
||||
document.getElementById('logo-container').style.display = 'block';
|
||||
}
|
||||
|
||||
@ -738,6 +774,7 @@
|
||||
}
|
||||
let table = document.getElementById('data-table');
|
||||
table.appendChild(tbody);
|
||||
table.style.display = 'table';
|
||||
}
|
||||
|
||||
function renderTable(response)
|
||||
@ -792,6 +829,7 @@
|
||||
let table = document.getElementById('data-table');
|
||||
table.appendChild(thead);
|
||||
table.appendChild(tbody);
|
||||
table.style.display = 'table';
|
||||
}
|
||||
|
||||
/// A function to render raw data when non-default format is specified.
|
||||
@ -873,16 +911,80 @@
|
||||
svg.style.height = graph.graph().height;
|
||||
}
|
||||
|
||||
function setColorTheme(theme) {
|
||||
window.localStorage.setItem('theme', theme);
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
let load_uplot_promise;
|
||||
function loadUplot() {
|
||||
if (load_uplot_promise) { return load_uplot_promise; }
|
||||
load_uplot_promise = loadJS('https://cdn.jsdelivr.net/npm/uplot@1.6.21/dist/uPlot.iife.min.js',
|
||||
'sha384-TwdJPnTsKP6pnvFZZKda0WJCXpjcHCa7MYHmjrYDu6rsEsb/UnFdoL0phS5ODqTA');
|
||||
return load_uplot_promise;
|
||||
}
|
||||
|
||||
let uplot;
|
||||
async function renderChart(json)
|
||||
{
|
||||
await loadUplot();
|
||||
clear();
|
||||
|
||||
let chart = document.getElementById('chart');
|
||||
chart.style.display = 'block';
|
||||
|
||||
let paths = uPlot.paths.stepped({align: 1});
|
||||
|
||||
const [line_color, fill_color, grid_color, axes_color] = theme == 'light'
|
||||
? ["#F80", "#FED", "#c7d0d9", "#2c3235"]
|
||||
: ["#888", "#045", "#2c3235", "#c7d0d9"];
|
||||
|
||||
const opts = {
|
||||
width: chart.clientWidth,
|
||||
height: chart.clientHeight,
|
||||
scales: { x: { time: json[0][0] > 1000000000 && json[0][0] < 2000000000 } },
|
||||
axes: [ { stroke: axes_color,
|
||||
grid: { width: 1 / devicePixelRatio, stroke: grid_color },
|
||||
ticks: { width: 1 / devicePixelRatio, stroke: grid_color } },
|
||||
{ stroke: axes_color,
|
||||
grid: { width: 1 / devicePixelRatio, stroke: grid_color },
|
||||
ticks: { width: 1 / devicePixelRatio, stroke: grid_color } } ],
|
||||
series: [ { label: "x" },
|
||||
{ label: "y", stroke: line_color, fill: fill_color,
|
||||
drawStyle: 0, lineInterpolation: 1, paths } ],
|
||||
padding: [ null, null, null, (Math.ceil(Math.log10(Math.max(...json[1]))) + Math.floor(Math.log10(Math.max(...json[1])) / 3)) * 6 ],
|
||||
};
|
||||
|
||||
uplot = new uPlot(opts, json, chart);
|
||||
}
|
||||
|
||||
function resizeChart() {
|
||||
if (uplot) {
|
||||
let chart = document.getElementById('chart');
|
||||
uplot.setSize({ width: chart.clientWidth, height: chart.clientHeight });
|
||||
}
|
||||
}
|
||||
|
||||
function redrawChart() {
|
||||
if (uplot && document.getElementById('chart').style.display == 'block') {
|
||||
renderChart(uplot.data);
|
||||
}
|
||||
}
|
||||
|
||||
new ResizeObserver(resizeChart).observe(document.getElementById('chart'));
|
||||
|
||||
/// First we check if theme is set via the 'theme' GET parameter, if not, we check localStorage, otherwise we check OS preference.
|
||||
let theme = current_url.searchParams.get('theme');
|
||||
if (['dark', 'light'].indexOf(theme) === -1) {
|
||||
theme = window.localStorage.getItem('theme');
|
||||
}
|
||||
if (!theme) {
|
||||
theme = 'light';
|
||||
}
|
||||
|
||||
function setColorTheme(new_theme, update_preference) {
|
||||
theme = new_theme;
|
||||
if (update_preference) {
|
||||
window.localStorage.setItem('theme', theme);
|
||||
}
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
redrawChart();
|
||||
}
|
||||
|
||||
if (theme) {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
@ -890,26 +992,21 @@
|
||||
/// Obtain system-level user preference
|
||||
const media_query_list = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
if (media_query_list.matches) {
|
||||
/// Set without saving to localstorage
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
setColorTheme('dark');
|
||||
}
|
||||
|
||||
/// There is a rumor that on some computers, the theme is changing automatically on day/night.
|
||||
media_query_list.addEventListener('change', function(e) {
|
||||
if (e.matches) {
|
||||
document.documentElement.setAttribute('data-theme', 'dark');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', 'light');
|
||||
}
|
||||
setColorTheme(e.matches ? 'dark' : 'light');
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('toggle-light').onclick = function() {
|
||||
setColorTheme('light');
|
||||
setColorTheme('light', true);
|
||||
}
|
||||
|
||||
document.getElementById('toggle-dark').onclick = function() {
|
||||
setColorTheme('dark');
|
||||
setColorTheme('dark', true);
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user