Vis Network + Vuetify Tabs - Networks won't fill screen - css

I am having some problems getting some Vis JS Networks to fill the available space under the App Bar correctly in a '' element. The graph element will fill the space, but the 100% I height I have had to apply to both '.v-tabs-items' and '.v-window-item' seems to ignore the padding and creates a scroll-bar, I have also set 100% width and there is no horizontal scroll.
It also doesn't adjust properly when resizing the window, requiring a tab switch to readjust to the correct size.
If I remove the 100% Height and Width then the tab content is constrained to a small rectange.
I have simplified the relevant code and it is below, there are two parts, the App.Vue file which contains the main screen and also the GraphView.Vue file which contains the Component that is used to draw the graph.
App.Vue
<template>
<v-app>
<v-app-bar app>
<v-toolbar-title class="headline text-uppercase">
<span>Test</span>
</v-toolbar-title>
<template v-slot:extension>
<v-tabs v-model="graphTab">
<v-tab v-for="(graph, title) in getGraphData()" :key="title">
{{ title }}
<span style="display: none"> {{ graph }}</span>
</v-tab>
</v-tabs>
</template>
</v-app-bar>
<v-content>
<v-container fluid fill-height>
<v-tabs-items v-model="graphTab">
<v-tab-item v-for="(graph, title) in getGraphData()" :key="title">
<GraphView
:nodes="graph.nodes"
:edges="graph.edges"
:options="defaultVisOptions"
></GraphView>
</v-tab-item>
</v-tabs-items>
</v-container>
</v-content>
</v-app>
</template>
<script>
import GraphView from "./components/GraphView";
import { DataSet } from "vis-network";
const defaultVisOptions = {
autoResize: true,
height: "100%",
width: "100%",
edges: {
arrows: "to"
},
nodes: {
shape: "circle"
}
};
export default {
name: "App",
components: {
GraphView
},
data: () => ({
defaultVisOptions: defaultVisOptions,
graphTab: null
}),
methods: {
getGraphData: function() {
return {
"Graph 1": {
nodes: new DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" }
]),
edges: new DataSet([
{ from: 1, to: 3 },
{ from: 1, to: 2 },
{ from: 2, to: 4 },
{ from: 2, to: 5 }
])
},
"Graph 2": {
nodes: new DataSet([
{ id: 1, label: "Node 1" },
{ id: 2, label: "Node 2" },
{ id: 3, label: "Node 3" },
{ id: 4, label: "Node 4" },
{ id: 5, label: "Node 5" }
]),
edges: new DataSet([
{ from: 1, to: 2 },
{ from: 2, to: 3 },
{ from: 3, to: 4 },
{ from: 4, to: 5 }
])
}
};
}
}
};
</script>
<style scoped>
.v-tabs-items {
height: 100%;
width: 100%;
}
.v-window-item {
height: 100%;
width: 100%;
}
</style>
GraphView.Vue
<template>
<div class="graphContainer" ref="graphContainer"></div>
</template>
<script>
import { Network } from "vis-network";
export default {
name: "GraphView",
props: ["nodes", "edges", "options"],
mounted() {
this.network = new Network(
this.$refs.graphContainer,
{ nodes: this.$props.nodes, edges: this.$props.edges },
this.$props.options
);
}
};
</script>
<style scoped>
.graphContainer {
width: 100%;
height: 100%;
}
</style>
As explained above, the expected result is that the Graphs fill the available space without creating scroll bars but this doesn't happen.

I didn't test it with your code but the container should be styled like:
<div style="position: relative; width: 100%; height: 100%;">
<div
style="position: absolute; top: 0px; right: 0px; bottom: 0px; left: 0px;"
ref="graphContainer"
/>
</div>
This way it will fill all available space (you may need to set position: relative on the parent) without stretching it any further.

Related

Using chartjs, how to define relative width and height of canvas?

Using Chart.js version 4.2.0, I try to display a chart on a maximized window on Windows 11 where height of chart is 100% of screen's height and width of chart is 80% of screen's width.
I tried with following code
<body>
<div height="100%" width="80%">
<canvas id="canvas">
But this simple attributes don't do the job.
Finally, I have found this solution
<body>
<div>
<canvas id="canvas">
</canvas>
</div>
, options:
{ responsive: true
, maintainAspectRatio: true
, aspectRatio: 1.65
var ctx = document.getElementById("canvas").getContext("2d");
var myChart = new Chart(ctx, config);
myChart.canvas.parentNode.style.width = '80%';
This works correctly but is a little tricky because width (not height) is set dynamically in Javascript code and aspectRatio must be manually fixed in options.
Is there a simple solution to define width to 80% of screen and height to 100% of screen ?
My current screen size are 1920x1080.
What I obtain is
When I suppress aspectRatio in options, I obtains following chart
It seems that the standard way for a chart.js plot to fill a certain area with a size set in css, is to do these:
include the canvas in a div that doesn't contain anything else
set the size in the style of the div, not the canvas (that is already done in your code)
have at least one size in absolute units, (that is not both in %) - in your case
<div style="height:100vh; width:80vw">
<canvas id="myChart"></canvas>
</div>
See also this comment from the docs.
set chart options maintainAspectRatio: false and responsive: true - the latter for the chart to be redrawn when you resize the window.
Here's a code snippet doing these, including a plugin I wrote that displays the current sizes of the canvas, div and window
const plugin = {
id: 'canvasSizeMonitor',
currentWidth: 0,
currentHeight: 0,
resizing: false,
displaySizes(chart){
const canvas = chart.canvas,
div = canvas.parentElement;
document.querySelector('#sizes').innerText+=
`div: ${div.offsetWidth}x${div.offsetHeight}\n`+
`canvas: ${canvas.offsetWidth}x${canvas.offsetHeight}\n`+
`window:${window.innerWidth}x${window.innerHeight}\n`+
`0.8 * ${window.innerWidth} = ${Math.round(0.8*window.innerWidth)}\n`+
'---------\n'//`aspRatio: ${chart.options.aspectRatio.toFixed(3).replace(/[.]?0*$/, '')}\n\n`;
},
afterRender(chart){
if(!plugin.resizing &&
(chart.canvas.offsetWidth !== plugin.currentWidth ||
chart.canvas.offsetHeight !== plugin.currentHeight)){
plugin.resizing = true;
setTimeout(
function(){
plugin.resizing = false;
plugin.currentWidth = chart.canvas.offsetWidth;
plugin.currentHeight = chart.canvas.offsetHeight;
plugin.displaySizes(chart);
}, 500
)
}
}
};
chart = new Chart(document.getElementById("myChart"), {
type: 'line',
data: {
datasets: Array.from({length: 8}, (_, i)=>({
label: `k = ${i+1}`,
data: Array.from({length: 100}, (_, j)=>({
x: j/50, y: Math.exp(-j/10)*Math.cos((i+1)*j*Math.PI/100)
}))
}))
},
options: {
parsing: {
xAxisKey: 'x',
yAxisKey: 'y'
},
pointStyle: false,
borderWidth: 1,
responsive: true,
maintainAspectRatio: false,
//aspectRatio: 1,
scales: {
x: {
type: 'linear',
grid: {
drawOnChartArea: true,
lineWidth: 1
},
border:{
color: '#000',
},
ticks: {
display: true,
color: '#000',
padding: 10
},
title: {
display: true,
text: 'x',
align: 'end'
}
},
y: {
type: 'linear',
ticks: {
padding: 10,
color: '#000',
},
grid: {
drawOnChartArea: true,
lineWidth: 1
},
border:{
color: '#000',
},
title: {
display: true,
text: 'f[k](x)',
align: 'end'
}
}
},
plugins:{
legend:{
position: 'right'
}
},
animation: {duration: 0}
},
plugins: [plugin]
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.1.2/chart.umd.js"
integrity="sha512-t41WshQCxr9T3SWH3DBZoDnAT9gfVLtQS+NKO60fdAwScoB37rXtdxT/oKe986G0BFnP4mtGzXxuYpHrMoMJLA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<body style="margin: 0">
<pre id="sizes" style="position: absolute; top: 0; right: 10px; text-align: right;background-color:rgba(255, 200, 100,0.4)"></pre>
<div style="height:100vh; width:80vw; padding:0; margin: 0; background: red">
<canvas style="background: #ddd" id="myChart"></canvas>
</div>
</body>

Vue.js: CSS styles (partly) shown although they are removed from the DOM successfully

I'm trying to create a simple accordion menu with Vue.js. When I open an item, the header should be colored blue and the child element should be visible.
defaultState openedState
When I click several times on the same item, however, I see strange CSS side effects. The child element is shown and hidden correctly. However, the blue color sometimes remains in the item text or shows also on the child element. In the DOM tree, the classes adding the blue color are, however, successfully removed.
issue0 issue1 issue2
My code looks as follows.
AccordionMenu.vue
<script setup lang="ts">
import AccordionMenuItem from "./AccordionMenuItem.vue";
</script>
<script lang="ts">
export default {
data() {
return {
selectedItem: null,
items: [
{
id: 1,
title: "Title 1",
content: "Content 1",
},
{
id: 2,
title: "Title 2",
content: "Content 2",
},
{
id: 3,
title: "Title 3",
content: "Content 3",
},
{
id: 4,
title: "Title 4",
content: "Content 4",
},
{
id: 5,
title: "Title 5",
content: "Content 5",
},
{
id: 6,
title: "Title 6",
content: "Content 6",
},
],
};
},
methods: {
onItemClick(item: any) {
if (item == this.selectedItem) {
this.selectedItem = null;
} else {
this.selectedItem = item;
}
},
isItemSelected(item: any) {
if (item == this.selectedItem) {
return true;
}
return false;
},
},
};
</script>
<template>
<div class="accordionMenu">
<AccordionMenuItem
v-for="item in items"
:key="item.id"
:title="item.title"
:is-selected="isItemSelected(item)"
#click="onItemClick(item)"
>
{{ item.content }}
</AccordionMenuItem>
</div>
</template>
<style scoped>
.accordionMenu {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background-color: var(--color-background-medium);
color: var(--color-font-white);
}
</style>
AccordionMenuItem.vue
<script lang="ts">
export default {
props: {
title: String,
isSelected: Boolean,
},
computed: {
accordionMenuItemClasses() {
let classes = "accordionMenuItem";
if (this.isSelected) classes += " activeAccordionMenuItem";
return classes;
},
},
};
</script>
<template>
<div v-bind:class="accordionMenuItemClasses">
<div class="accordionMenuItemTitle">{{ title }}</div>
<div class="accordionMenuItemContent"><slot></slot></div>
</div>
</template>
<style scoped>
.accordionMenuItem {
width: 100%;
}
.accordionMenuItemTitle {
background-color: var(--color-background-medium);
}
.accordionMenuItemTitle:hover {
cursor: pointer;
}
.accordionMenuItemContent {
display: none;
}
.activeAccordionMenuItem .accordionMenuItemTitle {
background-color: var(--color-active);
}
.activeAccordionMenuItem .accordionMenuItemContent {
display: block;
}
</style>
How is it possible that the blue color remains in such strange positions, although the class containing the blue color is removed successfully from the element and does not show up in the DOM anymore?
I also tried removing the title container and adding a new title and content container, because setting the display property seems to work correctly. But also in this approach, I had the same strange side effects showing up.

Vuedraggable how to swap item between 2 draggable

Hi all, i have a problem in vuedraggable. When i drag "item 2" to "item 3" => I want "item 3" will swap with "item 2".
Please help me.
Assuming you have multiple lists and each list has its own <draggable> element, then you will need to assign a new method handleMove(event) to your <draggable> elements. The event parameter is important because event.draggedContext contains the index of the item that you are trying to move (index) and also the index of the other item which is currently overlapping with your grabbed item (futureIndex). It also contains information from which list is your grabbed item (event.from) and where you want to drop it (event.to). Store these 4 variables somewhere and use them when you are done dragging (function handleDragEnd()).
Inside handleDragEnd() simply swap those 2 items and Vue will update the HTML template.
LIVE DEMO HERE
<template>
<div class="row">
<div class="col-3">
<h3>My LEGO Bionicles</h3>
<draggable
class="list-group"
data-list="list1"
:list="list1"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
<div class="col-3">
<h3>Favourite LEGO Bionicle</h3>
<draggable
class="list-group"
data-list="list2"
:list="list2"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'two-lists-swap',
display: 'Swapping between 2 lists',
order: 1,
components: {
draggable,
},
data() {
return {
list1: [
{ name: 'TOA Mata Nui', id: 1, style: { background: 'gold' } },
{
name: 'TOA Tahu',
id: 2,
style: { background: 'red', color: 'yellow' },
},
{ name: 'TOA Kopaka', id: 3, style: { background: 'white' } },
{
name: 'TOA Anakin',
id: 4,
style: { background: 'black', color: 'yellow' },
},
],
list2: [
{
name: 'TOA Gali',
id: 5,
style: { background: 'blue', color: 'yellow' },
},
{
name: 'TOA Lewa',
id: 6,
style: { background: 'green', color: 'yellow' },
},
{
name: 'TOA Pohatu',
id: 7,
style: { background: 'brown', color: 'white' },
},
],
};
},
methods: {
handleDragEndItem() {
if (this.originalList === this.futureList) {
this.movingItem = this[this.futureList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _list = Object.assign([], this[this.futureList]);
_list[this.futureIndex] = this.movingItem;
_list[this.originalIndex] = this.futureItem;
this[this.futureList] = _list;
}
} else {
this.movingItem = this[this.originalList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _listFrom = Object.assign([], this[this.originalList]);
let _listTo = Object.assign([], this[this.futureList]);
_listTo[this.futureIndex] = this.movingItem;
_listFrom[this.originalIndex] = this.futureItem;
this[this.originalList] = _listFrom;
this[this.futureList] = _listTo;
}
}
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
this.$toast.show('dragEnd');
},
handleMoveItem(event) {
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
const { index, futureIndex } = event.draggedContext;
this.originalIndex = index;
this.futureIndex = futureIndex;
this.originalList = event.from.getAttribute('data-list');
this.futureList = event.to.getAttribute('data-list');
if (this[this.futureList][this.futureIndex]) {
event.to.children[this.futureIndex].style.border = '2px solid orange';
}
return false; // disable sort
},
},
};
</script>
<style>
.list-group-item {
padding: 5px 10px;
cursor: grab;
}
</style>

How can I ensure my Highcharts don't break my flexbox layout?

I basically have two graphs and a sidebar.
Here's a fiddle demonstrating it:
https://jsfiddle.net/NibblyPig/b54qjavu/
Without min-width: 0 in the CSS the layout is badly broken with it stretching off the screen somehow even though that shouldn't be possible with a parent width of 100%.
Without calling the chart reflow method at the bottom of the javascript, the charts overflow off the screen.
Reflow kinda fixes it but despite both divs having flex-grow:1 they end up different sizes, again despite both having flex-grow: 1
If you resize the window itself, i.e. restore/maximise it the layout breaks even more, with the left div taking something like 20% of the width.
Any idea how I can fix this?
Pasted JS fiddle code below:
<div id='parent'>
<div id='columns'>
<div id='leftcolumn'>
<div id='chart1'>
</div>
<div id='chart2'>
</div>
</div>
<div id='rightcolumn'>
This is the right column.
</div>
</div>
</div>
CSS:
#parent {
display: flex;
width: 100%;
flex-direction: row;
height: 100vh;
min-width: 0;
}
#columns {
display: flex;
width: 100%;
flex-direction: row;
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
#leftcolumn {
display:flex;
flex-grow: 1;
flex-direction: row;
flex-shrink: 1;
min-width: 0;
}
#rightcolumn: {
flex-grow: 0;
display: flex;
width: 200px;
min-width: 0;
}
JS:
$(function () {
var myChart = Highcharts.chart('chart1', {
chart: {
type: 'bar'
},
title: {
text: 'Monthly Statistics'
},
xAxis: {
categories: ['ABC', 'DEF']
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'This Month',
data: [4, 1]
}, {
name: 'Average',
data: [3, 2]
}]
});
var myChart2 = Highcharts.chart('chart2', {
chart: {
type: 'pie'
},
title: {
text: 'ABCDEF'
},
plotOptions: {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
format: '<b>{point.name}</b>: {point.percentage:.1f} %'
}
}
},
series: [{
name: 'Brands',
colorByPoint: true,
data: [{
name: 'AXD',
y: 67.34,
sliced: true,
selected: true
}, {
name: 'ERT',
y: 11.88
}, {
name: 'ASD',
y: 20.78
}]
}]
});
});
I've fixed this by applying flex-basis: 50% to the parent container and a call to resize upon first loading the page with window.setTimeout(function () { myChart.reflow(); myChart2.reflow(); });
Here is the updated fiddle:
https://jsfiddle.net/NibblyPig/b54qjavu/2/

Highcharts pie wrong legend colors

[![enter image description here][1]][1]
The Slice color and the Legend color of the Pie-Chart do not match when the color is set using className. Doing this for (some) other charts works.
As you can see in the following code snippet, the pie slice and the Legend color for Chrome do not match.
// Build the chart
Highcharts.chart('container-donut', {
chart: {
type: 'pie'
},
plotOptions: {
pie: {
cursor: 'pointer',
showInLegend: true,
}
},
series: [{
name: 'browsers',
data: [
{
name: 'Chrome',
y: 60 ,
className: 'MyCustomColor'
},
{ name: 'Internet Explorer', y: 5 },
{ name: 'Firefox', y: 5 },
{ name: 'Edge', y: 5 },
{ name: 'Safari', y: 5 },
{ name: 'Other', y: 5 }
]
}]
});
.MyCustomColor {
fill: green;
}
<script src="https://code.highcharts.com/modules/export-data.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="container-donut" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>
<div id="container-column" style="min-width: 310px; height: 400px; max-width: 600px; margin: 0 auto"></div>
Your css is not accessing the legend element, which is a <rect> inside of the <g class="MyCustomColor">
Changing your css to this should solve your issue:
.MyCustomColor, .MyCustomColor rect {
fill: green;
}

Resources