Search code examples
vue.jsvis.js-timeline

VisJS Timeline in VueJS: Items with images get misplaced


I'm trying to incorporate a Vis Timeline in my Vue app. I've used the Custom styling example as a starting point (because I will in future work on the styling). I've adapted it in my Vue app like this (cutting the css code for now):

<template>
    <span id="visualization">
    </span>
</template>

<script>
import { Timeline } from "vis-timeline/peer";
import "vis-timeline/styles/vis-timeline-graph2d.css";

export default {
    name: 'VisTimelineChronik',
    data() {
        return {
            itemset: [
                {start: new Date(2010,7,23), content: '<div>Conversation</div><img src="'+require("@/assets/fullscreen.svg")+'" style="width:32px; height:32px;">'},
                {start: new Date(2010,7,23,23,0,0), content: '<div>Mail from boss</div><img src="'+require("@/assets/fullscreen.svg")+'" style="width:32px; height:32px;">'},
                {start: new Date(2010,7,24,16,0,0), content: 'Report'},
                {start: new Date(2010,7,26), end: new Date(2010,8,2), content: 'Traject A'},
                {start: new Date(2010,7,28), content: '<div>Memo</div><img src="'+require("@/assets/fullscreen.svg")+'" style="width:48px; height:48px;">'},
                {start: new Date(2010,7,29), content: '<div>Phone call</div><img src="'+require("@/assets/fullscreen.svg")+'" style="width:32px; height:32px;">'},
                {start: new Date(2010,7,31), end: new Date(2010,8,3), content: 'Traject B'},
                {start: new Date(2010,8,4,12,0,0), content: '<div>Report</div><img src="'+require("@/assets/fullscreen.svg")+'" style="width:32px; height:32px;">'}
            ],
            options: {
                editable: false,
                margin: {
                    item: 20,
                    axis: 40,
                }
            }
        }
    },
    mounted() {
        const container = document.getElementById("visualization");
        const timeline = new Timeline(container, this.itemset, this.options);
    }
}
</script>

I replaced the images sources with an asset of my vue app, so they can be displayed. This works, but the items get misplaced:

enter image description here

I'm running the Vue development server (via vue serve) and interestingly the misplacement vanishes, when I change the code and the dev server automatically loads the new code. But when I reload the site in the browser the items are misplaced again.

I can select the misplaced items and drag them up, which moves the item up in a better position (no longer overlapping other items or the axis). The timeline will also increase its vertical size in this case if necessary.

Note: In the image the timeline is rather small, but that is only for the image. The timeline can resize itself in vertical direction and I originally had it about twice the size in horizontal direction. Still the same.

Why does this misplacement happen and how can I prevent it?

Used versions:

+-- @vue/cli-plugin-babel@5.0.8
+-- @vue/cli-plugin-router@5.0.8
+-- @vue/cli-plugin-vuex@5.0.8
+-- @vue/cli-service@5.0.8
+-- vue-router@4.1.5
+-- vue-select@4.0.0-beta.5
+-- vue@3.2.39
`-- vuex@4.0.2
+-- vis-timeline@7.7.2

Solution

  • The problem occurs when the icon has finished loading after the timeline has been drawn. Here is what happens:

    • The itemset is passed to Timeline
    • Timeline creates items from the content property, including the img which src has not been loaded
    • Timeline calculates the position for each item so they do not overlap, using the dimensions of each item as it is right now (with the empty img)
    • according to the calculations, icons are positioned and the dimension of the timeline is set
    • the images' src has finally loaded and blamo, items are much larger and you get the overlap

    When the site does a hot-reload after you change sources, the image is loaded in cache, so there is no load time and Timeline can calculate the width correctly, so there is no overlap now.


    From what I can see, there are two ways you can solve it:

    • after the icon has loaded, call timeline.redraw() and timeline.fit() to recalculate the dimension of the items and then adjust the dimension of the timeline
    • set a fixed width and height of the img

    The first option means that you have to work with the onload event, and I am pretty sure that you don't want to go down that route, so I am going to skip the details (let me know if you do want to use it).

    The second option is probably the cleaner solution, as you want to limit image dimensions anyway. You just have to add a CSS rule like:

    .vis-item-content img{
      width: 80px;
      height: 80px;
    }
    

    Now the items have a fixed size and position is calculated correctly.


    Here is a playground where you can see how different loading mechanisms and dimensions change the behavior.