Fixes pie chart not correctly rendered

This commit is contained in:
Matteo Biscosi 2026-05-25 10:52:46 +00:00
parent 648e668d23
commit 2e1f18915d
7 changed files with 58 additions and 46 deletions

View file

@ -3,11 +3,12 @@
-->
<template>
<div class="donut-charts-wrapper" :style="gridStyle">
<div :style="gridStyle">
<PieChart
v-for="chart in charts"
:key="chart.name"
:chart="chart"
:style="style"
/>
</div>
</template>
@ -22,6 +23,8 @@ const props = defineProps({
charts_per_row: { type: Number, default: null },
});
const style = "max-height:400px"
const charts = computed(() => {
if (props.context?.charts) return props.context.charts;
if (props.chart) return [props.chart];
@ -33,8 +36,7 @@ const gridStyle = computed(() => {
return {
display: "grid",
gridTemplateColumns: `repeat(${perRow}, 1fr)`,
gap: "10px",
width: "100%",
gap: "10px"
};
});
</script>

View file

@ -2,7 +2,7 @@
(C) 2026 - ntop.org
-->
<template>
<div ref="container" class="pie-container" :class="{ 'layout-column': legend_below }">
<div ref="container" class="pie-container" :class="{ 'layout-column': legend_below }" :style="props.style">
<!-- Title -->
<div v-if="chart.title" class="pie-title"><strong>{{ chart.title }}</strong></div>
@ -18,7 +18,7 @@
<!-- Legend -->
<div v-if="!loading && items.length && !no_data" class="pie-legend-wrap">
<div class="pie-legend">
<div class="pie-legend justify-content-center">
<div v-for="(it, i) in items" :key="i" class="legend-item" :class="{ clickable: !!it.url }"
@click="it.url && (window.location.href = it.url)">
<span class="legend-dot" :style="{ background: it.color }"></span>
@ -51,7 +51,7 @@ import NoData from '../components/no-data.vue'
const d3 = d3v7;
const _i18n = (t) => (typeof i18n === "function" ? i18n(t) : t);
const props = defineProps({ chart: { type: Object, required: true }, hideLoading: Boolean });
const props = defineProps({ chart: { type: Object, required: true }, hideLoading: Boolean, style: String });
const { name, update_url, url_params, refresh, unit, label, custom_fetch } = props.chart;
const formatted_label = label ? (i18n(label) || label) : null;
const container = ref(null);
@ -133,7 +133,7 @@ async function load() {
if (!has_loaded.value) loading.value = true;
const { update_url, url_params, custom_fetch } = props.chart;
emit("update-requested");
try {
let data;
@ -315,9 +315,11 @@ watch(() => props.chart.url_params, () => {
}
.pie-wrapper {
flex: 0 0 auto;
/* width drives height via aspect-ratio — no height: 100% which caused circular sizing */
width: clamp(100px, 45%, 200px);
flex: 0 0 auto;
/* prende il minore tra 45% della larghezza e il 100% dell'altezza disponibile */
width: min(clamp(100px, 45%, 100%), 100%);
height: 100%;
aspect-ratio: 1 / 1;
overflow: hidden;
}

View file

@ -8,7 +8,8 @@
@select_option="changeCriteria">
</SelectSearch>
</div>
<div v-if="props.context.isEnterpriseXL && props.context.hasClickHouseSupport" class="w-100 d-flex align-items-center button-group">
<div v-if="props.context.isEnterpriseXL && props.context.hasClickHouseSupport"
class="w-100 d-flex align-items-center button-group">
<!--
<CustomSwitch v-model:value="toggle_slider" :change_label_side="true" :label="toggle_slider_label" style=""
class="me-1" icon="fa-calendar-days" :title="toggle_slider_label" @change_value="saveSwitch">
@ -16,8 +17,8 @@
-->
<div class="w-100 position-relative">
<Transition name="add-effect" mode="out-in">
<DateTimeRangePicker class="dontprint" id="as-date-time-picker"
:round_time="true" :custom_time_interval_list="time_preset_list" min_time_interval_id="live"
<DateTimeRangePicker class="dontprint" id="as-date-time-picker" :round_time="true"
:custom_time_interval_list="time_preset_list" min_time_interval_id="live"
:custom_change_select_time="changeTime" @epoch_change="setTimeInterval">
</DateTimeRangePicker>
</Transition>
@ -34,20 +35,18 @@
<div class="m-2 mb-3">
<Transition name="add-effect" mode="out-in">
<div class="position-relative">
<div class="mb-4 d-flex flex-column" style="height: 60vh;">
<div class="mb-4 d-flex flex-column" :style="{ height: show_pie ? '360px' : '60vh' }">
<Loading :isLoading="loading"></Loading>
<Sankey v-if="show_sankey" ref="sankey_chart" :no_data_message="no_data_message"
:sankey_data="sankey_data" @node_click="onNodeClick" @autorefresh_toggle="onAutoRefreshToggle">
</Sankey>
<Pie v-if="show_pie" :key="reRenderPie" ref="pie_chart" :no_data_message="no_data_message"
:pie_data="pie_data" @autorefresh_toggle="onAutoRefreshToggle">>
</Pie>
<PieChart v-if="show_pie" ref="pie_chart" :key="reRenderPie" :chart="pieChartInfo" />
</div>
</div>
</Transition>
<Transition name="add-effect" v-if="props.context.isEnterpriseXL && props.context.hasClickHouseSupport" mode="out-in">
<div class="position-relative" :key="reRenderTable"
style="min-height: 614px;">
<Transition name="add-effect" v-if="props.context.isEnterpriseXL && props.context.hasClickHouseSupport"
mode="out-in">
<div class="position-relative" :key="reRenderTable" style="min-height: 614px;">
<TableWithConfig ref="table_as_stats" :table_id="table_id" :csrf="props.context.csrf"
:showLoading="true" :f_map_columns="mapTableColumns" :f_sort_rows="columnsSorting"
:get_extra_params_obj="getExtraParameters" @custom_event="onTableCustomEvent">
@ -65,7 +64,7 @@ import { ref, onMounted, onBeforeMount, computed } from "vue";
import { default as NoteList } from "./note-list.vue";
import { default as Loading } from "./loading.vue"
import { default as Sankey } from "./sankey.vue";
import { default as Pie } from "./pie-test.vue";
import { default as PieChart } from "./charts/pie-chart.vue";
import { default as sortingFunctions } from "../utilities/sorting-utils.js";
import { default as TableWithConfig } from "./table-with-config.vue";
import { default as SelectSearch } from "./select-search.vue";
@ -126,9 +125,23 @@ const note_list = [
/* ************************************** */
const updatePieData = async () => {
loading.value = true;
const data = await getPieData();
loading.value = false;
return data
}
const pieChartInfo = {
custom_fetch: updatePieData,
unit: "bytes"
}
/* ************************************** */
onMounted(() => {
updateSankeyData();
updatePieData();
})
/* ************************************** */
@ -184,7 +197,7 @@ const onAutoRefreshToggle = (enabled) => {
if (enabled) {
intervalId = setInterval(() => {
updateSankeyData()
updatePieData()
pie_chart.value?.update();
}, 10000 /* 10 sec refresh */)
} else {
clearInterval(intervalId);
@ -212,7 +225,7 @@ const changeCriteria = async (opt) => {
checkComponentsToShow();
ntopng_url_manager.set_key_to_url(opt.key, `${opt.value}`);
updateSankeyData();
updatePieData();
pie_chart.value?.update();
}
/* ************************************** */
@ -237,7 +250,7 @@ function setTimeInterval(epoch_interval) {
}
main_epoch_interval.value = epoch_interval;
updateSankeyData();
updatePieData();
pie_chart.value?.update();
reloadTable()
}
@ -249,18 +262,6 @@ function saveSwitch() {
/* ************************************** */
const updatePieData = async () => {
if (show_pie.value) {
reRenderPie.value = !reRenderPie.value
loading.value = true;
const data = await getPieData();
pie_data.value = data;
loading.value = false;
}
}
/* ************************************** */
const getPieData = async () => {
const url_request = getPieUrl();
let graph = await ntopng_utility.http_request(url_request);
@ -388,7 +389,7 @@ function columnsSorting(col, r0, r1) {
} else if (col.id == "customer") {
return sortingFunctions.sortByName(r0.customer.name, r1.customer.name, col.sort);
} else if (col.id == "as") {
return sortingFunctions.sortByName(r0.as.name, r1.as.name, col.sort);
return sortingFunctions.sortByNumber(r0.asn.name, r1.asn.name, col.sort);
} else if (col.id == "dst_as") {
return sortingFunctions.sortByName(r0.dst_as.name, r1.dst_as.name, col.sort);
} else if (col.id == "src_as") {

@ -1 +1 @@
Subproject commit a565a45fb166f8fe946c05ecaaf22ad38b8fe22d
Subproject commit 3450d2fe25d5e29a37b44ebeac9516ae72c31a7c

View file

@ -805,7 +805,7 @@ if (ifstats.type ~= "zmq") then
end
print [[ <tr>]]
print [[<td colspan=6>]]
print [[<td colspan=6><div class="row"><div class="row-4">]]
template.render("pages/vue_page.template", {
vue_page_name = "MultiPieChart",
@ -814,7 +814,7 @@ template.render("pages/vue_page.template", {
}),
})
print [[</td></tr>]]
print [[</div></div></td></tr>]]
if (ifstats.zmqRecvStats ~= nil and table.len(ifstats.zmqRecvStats) > 0) then
print("<tr><th colspan=7 nowrap>" .. i18n("if_stats_overview.zmq_rx_statistics") .. "</th></tr>\n")
local tot_flows = (ifstats.zmqRecvStats.flows or 0) + (ifstats.zmqRecvStats.dropped_flows or 0)

View file

@ -15,7 +15,12 @@ local values_table = {}
-- ##########################################
local function update_section(sections, section_ref, value, formatter)
local section_label = formatter(section_ref)
local section_label
if section_ref == "others" then
section_label = i18n(section_ref)
else
section_label = formatter(section_ref)
end
local index = values_table[section_ref].index
local previous_value = values_table[section_ref].value
local new_value = previous_value + value
@ -27,16 +32,18 @@ end
-- ##########################################
local function create_section(sections, section_ref, value, formatter)
local section_label = formatter(section_ref)
local section_label
if section_ref == "others" then
section_label = i18n(section_ref)
else
section_label = formatter(section_ref)
end
local index = #sections + 1
sections[index] = {
label = section_label .. " (" .. bytesToSize(value) .. ")",
value = value,
url = "#",
}
if sections_ref == nil then
return
end
values_table[section_ref] = {
index = index,
value = value,

View file

@ -95,7 +95,7 @@ if criteria_as == "user_traffic_breakdown" then
}
end
local sections = flow_pie.generatePie(queries, 10000, not isEmptyString(epoch_begin))
local sections = flow_pie.generatePie(queries, 8, not isEmptyString(epoch_begin))
table.sort(sections, function(a, b)
return a.value > b.value