How to draw line graph on timeline using visjs? - vis.js

Is it possible to draw line graph on timeline, using visjs?
I want to get something like here http://almende.github.io/chap-links-library/js/timeline/examples/example19_bar_graph.html but instead of bar graph, I need line graph.
Essentially the question is, is it possible to get timeline and graph2d on the same canvas like in example below?

With the help of the demo of syncing to timelines here and some experimenting of my own, the following seems to do something similar. You could probably tweak it a bit more to get what you want.
The console warnings of "WARNING: infinite loop in redraw?" when zooming on the graph2d part appear to be a bug in vis.js in this scenario, as it doesn't occur when zooming on the timeline - in any case, it doesn't affect the functionality
// create a couple of HTML items in various ways
var item1 = document.createElement('div');
item1.appendChild(document.createTextNode('item 1'));
var item2 = document.createElement('div');
item2.innerHTML = '<span>item 2</span>';
var item3 = document.createElement('div');
var span3 = document.createElement('span');
span3.className = 'large';
span3.appendChild(document.createTextNode('item 3'));
item3.appendChild(span3);
var item4 = 'item <span class="large">4</span>';
var item5 = document.createElement('div');
item5.appendChild(document.createTextNode('item 5'));
item5.appendChild(document.createElement('br'));
var img5 = document.createElement('img');
img5.src = 'https://d30y9cdsu7xlg0.cloudfront.net/png/511-200.png';
img5.style.width = '48px';
img5.style.height = '48px';
item5.appendChild(img5);
var item6 = 'item6<br><img src="https://lasindias.com/wp-content/uploads/2013/11/Dominio-Publico.png" style="width: 48px; height: 48px;">';
var item7 = 'item7<br>click here';
// create data and a Timeline
var graph_container = document.getElementById('visualization-top-row');
var event_container = document.getElementById('visualization-bottom-row');
var items_graph = [
{x: '2013-04-20', y: 10},
{x: '2013-04-14', y: 25},
{x: '2013-04-18', y: 30},
{x: '2013-04-16', y: 10},
{x: '2013-04-25', y: 15},
{x: '2013-04-27', y: 30},
{x: '2013-04-21', y: 30}
];
var items_bottom_row = new vis.DataSet([
{id: 1, content: item1, start: '2013-04-20', group: 0},
{id: 2, content: item2, start: '2013-04-14', group: 0},
{id: 3, content: item3, start: '2013-04-18', group: 0},
{id: 4, content: item4, start: '2013-04-16', end: '2013-04-19', group: 0},
{id: 5, content: item5, start: '2013-04-25', group: 0},
{id: 6, content: item6, start: '2013-04-27', group: 0},
{id: 7, content: item7, start: '2013-04-21', group: 0}
]);
var groupsBottomRow = new vis.DataSet();
groupsBottomRow.add({id: 0, content: "Cool and ze Gang"});
var dataset_graph = new vis.DataSet(items_graph);
var options2 = {
start: '2013-04-12',
end: '2013-04-22',
height: '100%',
};
var graph2d = new vis.Graph2d(graph_container, dataset_graph, options2);
var timeline = new vis.Timeline(event_container);
timeline.setGroups(groupsBottomRow);
timeline.setOptions(options2);
timeline.setItems(items_bottom_row);
function onChangeGraph(range) {
if (!range.byUser) {
return;
}
timeline.setOptions({
start: range.start,
end: range.end,
height: '100%',
});
}
function onChangeTimeline(range) {
if (!range.byUser) {
return;
}
graph2d.setOptions({
start: range.start,
end: range.end,
height: '100%'
});
}
// graph2d.on('rangechanged', onChangeGraph);
// timeline.on('rangechanged', onChangeTimeline);
graph2d.on('rangechange', onChangeGraph);
timeline.on('rangechange', onChangeTimeline);
graph2d.on('_change', function() {
visLabelSameWidth();
});
$(window).resize(function(){
visLabelSameWidth();
});
// Vis same width label.
function visLabelSameWidth() {
var ylabel_width = $("#visualization-bottom-row .vis-labelset .vis-label").width() + "px";
//$("#visualization-top-row")[0].childNodes[0].childNodes[2].style.left = ylabel_width;
var w1 = $("#visualization-top-row .vis-content .vis-data-axis").width();
var w2 = $("#visualization-bottom-row .vis-labelset .vis-label").width();
$("#visualization-top-row")[0].childNodes[0].childNodes[2].style.display = 'none';
if (w2 > w1) {
$("#visualization-top-row .vis-content")[1].style.width = ylabel_width;
}
else {
$("#visualization-bottom-row .vis-labelset .vis-label").width(w1+"px");
}
}
body, html {
font-family: arial, sans-serif;
font-size: 11pt;
}
span {
color: red;
}
span.large {
font-size: 200%;
}
#visualization-bottom-row, #visualization-top-row {
height: 100%;
}
.outer-top-row {
height: 200px;
}
.outer-bottom-row {
height: 300px;
}
#visualization-top-row .vis-panel.vis-bottom {
display: none;
}
#visualization-top-row .vis-timeline{
border-bottom: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.17.0/vis-timeline-graph2d.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.17.0/vis-timeline-graph2d.min.css" rel="stylesheet"/>
<div class="outer-top-row">
<div id="visualization-top-row"></div>
</div>
<div class="outer-bottom-row">
<div id="visualization-bottom-row"></div>
</div>

Sadly what you have described is not possible. The closest solution I have found is to have a both a graph2d and timeline on the same page and listen to the drag events on each and update the viewport on the other such that they are both showing the same timespan and zoom level.

Related

OpenLayers cluster get incorrect style

I want to paint several clusters red, but when zoom the red flags disappear. I code on vue3 optionAPI.
script.js
import View from 'ol/View'
import Map from 'ol/Map'
import TileLayer from 'ol/layer/Tile'
import OSM from 'ol/source/OSM'
import VectorLayer from 'ol/layer/Vector'
import Point from 'ol/geom/Point';
import LineString from 'ol/geom/LineString';
import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature'
import {
Circle as CircleStyle,
Fill,
Stroke,
Style,
Text,
} from 'ol/style';
import Overlay from 'ol/Overlay';
import Cluster from "ol/source/Cluster"
import { useGeographic } from 'ol/proj';
import 'ol/ol.css'
import './style.css'
import axios from 'axios'
export default {
name: 'MapContainer',
components: {},`your text`
props: {},
data() {
return {
place: [43.984506, 56.305298],
data: [],
featuresPoints: [],
featuresLines: [],
}
},
methods: {
async getData() {
axios("https://someRestApiLink.com").then(res => {
this.data = res.data;
this.createFeachers();
this.renderMap();
});
},
setCircleStyle(feature) {
const size = feature.get('features').length;
let style = styleCache[size];
if (!style) {
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: '#fff',
}),
fill: new Fill({
color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
}
return style;
},
createFeachers() {
for (let item of this.data) {
let coords = JSON.parse(item.coords);
if (coords.length === 1) {
let feature = new Feature(new Point(coords[0].reverse()));
feature.mydata = item;
this.featuresPoints.push(feature);
} else {
let rightCoords = coords.map(el => el.reverse());
let isValidFeacture = true;
for (let i = 0; i < rightCoords.length - 1; i++) {
if (Math.abs(rightCoords[i][0] - rightCoords[i + 1][0]) > .01) {
isValidFeacture = false;
break
}
}
if (!isValidFeacture) continue;
let feature = new Feature({
geometry: new LineString(rightCoords)
});
feature.setStyle(new Style({
stroke: new Stroke({
color: '#0000ff',
width: 3
})
}))
feature.mydata = item
this.featuresLines.push(feature);
}
}
},
createMap() {
return new Map({
target: this.$refs['map-root'],
view: new View({
zoom: 12,
center: this.place
}),
layers: [
new TileLayer({
source: new OSM()
}),
this.createLineLayer(),
this.createPointLayer(),
],
});
},
createPointLayer() {
const styleCache = {};
let cluster = new Cluster({
distance: 15,
minDistance: 6,
source: new VectorSource({
features: this.featuresPoints,
})
});
const mainCluster = new VectorLayer({
source: cluster,
style: function (feature) {
function calculateFired(cluster, length) {
let pointList = cluster.values_.features;
let countFired = 0;
for (let point of pointList) {
if (point.mydata.status === "Просрочен") {
countFired++;
}
}
return countFired === length ? "full" : countFired > 0 ? "several" : "none";
}
const size = feature.get('features').length;
let style = styleCache[size];
if (!style) {
let hasFired = calculateFired(feature, size);
style = new Style({
image: new CircleStyle({
radius: 10,
stroke: new Stroke({
color: hasFired === "none" ? '#fff' : "#f00",
// color: '#fff',
}),
fill: new Fill({
color: hasFired === "full" ? "#f00" : '#3399CC',
// color: '#3399CC',
}),
}),
text: new Text({
text: size.toString(),
fill: new Fill({
color: '#fff',
}),
}),
});
styleCache[size] = style;
// console.log(style);
}
return style;
},
});
mainCluster.mydata = this.featuresPoints.map(el => el.mydata);
return mainCluster
},
createLineLayer() {
return new VectorLayer({
source: new VectorSource({
features: this.featuresLines,
}),
})
},
renderMap() {
useGeographic();
const createPopUp = this.createPopUp;
var container = document.getElementById("popup");
var content = document.getElementById("popup-content");
const map = this.createMap();
const overlay = new Overlay({
element: container,
autoPan: true
});
map.on('click', function (e) {
let pixel = map.getEventPixel(e.originalEvent);
if (document.getElementsByClassName('popup-content').length != 0) {
document.getElementsByClassName('popup-content')[0].style.display = 'none'
}
map.forEachFeatureAtPixel(pixel, function (feature) {
let data = feature.mydata ?? feature.values_.features.map(el => el.mydata);
let coodinate = e.coordinate;
content.innerHTML = createPopUp(data);
overlay.setPosition(coodinate);
map.addOverlay(overlay);
});
});
},
createPopUp(data) {
let content = "";
if (data.length) {
let sortedData = data.sort((a, b)=> {
if (a.time.split(".").reverse().join("-") > b.time.split(".").reverse().join("-")) return 1;
if (a.time.split(".").reverse().join("-") < b.time.split(".").reverse().join("-")) return -1;
return 0;
});
for (let el of sortedData) {
content += `
<div class="popup-content__valueBlock">
<div class="popup-content__valueBlock__organization">
${el.organization}:
</div>
<div class="popup-content__valueBlock__aimOfWorks">
Тип: ${el.aim_of_works}
</div>
<div class="popup-content__valueBlock__stripSurface">
Работы ведутся над: ${el.strip_surface}
</div>
<div class="popup-content__valueBlock__status">
${el.status} - ${el.finish_date}
</div>
</div>
`;
}
} else {
content = `
<div class="popup-content__valueBlock">
<div class="popup-content__valueBlock__organization">
${data.organization}:
</div>
<div class="popup-content__valueBlock__aimOfWorks">
Тип: ${data.aim_of_works}
</div>
<div class="popup-content__valueBlock__stripSurface">
Работы ведутся над: ${data.strip_surface}
</div>
<div class="popup-content__valueBlock__status">
${data.status} - ${data.time}
</div>
</div>
`;
}
return `
<div class="popup-content">
<span class="close" onclick="closePopup()">x</span>
<span class="count">Количество выбранных ордеров: ${data.length ?? 1}</span>
${content}
</div>
`;
},
},
mounted() {
this.getData();
},
}
I checked ol_uid of cluster when I rezoomed, and I was so surprised by changed this property at the same cluster. Also I tried to rerender map at every time when I changed zoom, but this not work too.
I think that I mb do something wrong on creating or rendering map or clusters.

How to visualize a vaadin web component

Follow the vaadin guidence of web component to create a meter:
#Tag("dw-meter")
#NpmPackage(value = "echarts", version = "5.2.2")
#JsModule("../node_modules/echarts/dist/echarts.js")
#JsModule("./dwmeter.webcomponent.js")
public class DwMeter extends Div {
}
Integrate the meter into a demo application:
DwMeter meter = new DwMeter();
meter.setWidth("100px");
meter.setHeight("100px");
add(meter);
Application is executed successfully, but the meter is not displayed.
Trace the web page, Tag <dw-meter> and <canvas> are generated correctly :
Changed Tag <dw-meter> to <div>, the meter is visible:
My question is how to visualize an user-defined vaadin web component? e.g. <dw-meter>
attached dwmeter.webcomponent.js:
import * as ECharts from "echarts";
class dwMeter extends HTMLElement {
constructor() {
super();
}
init(o) {
// Shadow root
const shadowRoot = this.attachShadow({mode: 'open'});
// container
var container = document.createElement('div');
shadowRoot.appendChild(container);
// Garantee all elements are rendered
setTimeout(function() {
var myChart = ECharts.init(o); //container
myChart.setOption(o.options());
}, 0);
}
options() {
const gaugeData = [
{
value: 0.25,
name: 'pressure',
title: {
offsetCenter: ['0%', '90%']
}
}
];
var option = {
series: [
{
type: 'gauge',
min: 0,
max: 0.25,
splitNumber: 5,
progress: {
show: false,
width: 5
},
axisLine: {
lineStyle: {
width: 5,
color: [[1, 'rgba(36,177, 76)']]
}
},
axisTick: {
show: false
},
splitLine: {
length: -10,
lineStyle: {
width: 5,
color: 'rgba(36,177, 76)'
}
},
axisLabel: {
distance: -20,
color: '#999',
fontSize: 10
},
anchor: {
show: true,
showAbove: true,
size: 12,
itemStyle: {
borderWidth:0,
color: 'rgba(36,177, 76)',
}
},
pointer: {
icon: 'path://M2.9,0.7L2.9,0.7c1.4,0,2.6,1.2,2.6,2.6v115c0,1.4-1.2,2.6-2.6,2.6l0,0c-1.4,0-2.6-1.2-2.6-2.6V3.3C0.3,1.9,1.4,0.7,2.9,0.7z',
width: 5,
length: '60%',
offsetCenter: [0, '8%'],
itemStyle: {
color: 'rgba(36,177, 76)'
}
},
title: {
color: 'rgba(36,177, 76)',
fontSize: 14,
fontWeight: 800,
fontFamily: 'Arial',
offsetCenter: [0, '100%']
},
detail: {
valueAnimation: true,
fontSize: 12,
offsetCenter: [0, '55%'],
show: true
},
data:gaugeData
}
]
};
return option;
}
connectedCallback() {
this.init(this);
}
disconnectedCallback() {
}
attributeChangedCallback() {
}
clone(origin, target) {
var target = target || {};
for(var prop in origin) {
target[prop] = origin[prop];
}
}
}
window.customElements.define('dw-meter', dwMeter);
The reason that nothing is shown when you're using the <dw-meter> custom element is that it has a shadow root, while the actual content (rendered by the ECharts library) is outside that shadow root. Whenever an element has a shadow root, then the content of the shadow root will be rendered and the content outside the shadow root will be rendered in the location of a <slot> element inside the shadow root. If there is no <slot>, then it won't be shown anywhere at all.
If you want to use the shadow root to encapsulate styles, then you would at the very least need to change ECharts.init(o) to instead do ECharts.init(container). There might also be other things that you need to change to make it work properly, but that depends on exactly how ECharts is implemented. The o parameter that I assume you're passing from the server is most likely redundant since this is already a reference to the top-level element.

HTML Canvas How can I get the rectangle to move backwards?

My code below animates a rectangle to grow right as soon as I start it. How can I make it simultaneously grow left? I have tried (line.x -= line.dx) and a few other variations and it does not seem to work.
const line = {
x: 600,
y: 100,
width: 1,
height: 3,
dx: 1,
dy: 1,
}
let totalLineWidth = line.x + line.width;
const drawLine = () => {
ctx.fillRect(line.x, line.y , line.width, line.height)
}
const update = () => {
drawLine()
line.x += line.dx
requestAnimationFrame(update)
}
update()
At the same time as you draw a new bit to the right, draw a new bit to the left.
The new bit to the left goes an equal amount to the left as the new bit to the right. So that's (600 - (left.x - 600) - and of course update the 600 if you ever change the start position.
Run this snippet in full page and click the button to start the drawing.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const line = {
x: 600,
y: 100,
width: 1,
height: 3,
dx: 1,
dy: 1,
}
let totalLineWidth = line.x + line.width;
const drawLine = () => {
ctx.fillRect(line.x, line.y, line.width, line.height);
ctx.fillRect(600 - (line.x - 600), line.y, line.width, line.height);
}
const update = () => {
drawLine()
line.x += line.dx
requestAnimationFrame(update)
}
//removed just for demo so we can wait until user clicks the button
//update()
canvas {
background-color: pink;
/* just for demo so we can see where the canvas is */
}
<button onclick="update(); this.style.display = 'none';">Click me to start</button>
<br>
<canvas width=1200 height=200></canvas>

Change font size and font color in Chartjs Angular 5

Font color in chartjs is light gray, then when you want to print from page, it does not appear.
I change the font color of chartjs in options attribute, but it does not work.
How can I change the font color in chartjs angular
public options:any = {
legend: {
labels: {
// This more specific font property overrides the global property
fontColor: 'red',
fontSize: '30'
}
}
};
in template :
<canvas baseChart
height=100
[datasets]="barChartData"
[labels]="barChartLabels"
[options]="barChartOptions"
[legend]="barChartLegend"
[colors]="chartColors"
[chartType]="barChartType"
[options]="options"
>
</canvas>
I use chartjs like following in ts file.
This is my complete ts file:
import { Component, Input, OnInit } from '#angular/core';
import { Test } from './models/test.model';
#Component({
selector: 'app-customer-report-test',
templateUrl: './customer-report-test.component.html',
styleUrls: ['./customer-report-test.component.css']
})
export class CustomerReportTestComponent implements OnInit {
#Input('test') test: Test = new Test();
public barChartOptions:any = {
scaleShowVerticalLines: false,
responsive: true
};
public barChartLabels:string[];
public barChartType:string = 'bar';
public barChartLegend:boolean = true;
public barChartData:any[];
backgroundColorList: string[];
public chartColors: any[] = [
{
backgroundColor: this.backgroundColorList
}];
public options:any;
constructor() { }
//----------------------------------------------------------------------------
ngOnInit() {
//set Label
this.barChartLabels = [];
for(let i=1; i<= this.test.data_array.length; i++){
this.barChartLabels.push('' + i);
}
//set data chart
this.barChartData = [{data: this.test.data_array, label: this.test.test_type[1]}]
this.test.test_type[1]}, {data: [20,20, 20, 20],type: "line",label: ['0', '1', '2', '3'] ,fill:'none'}]
// set color to line according to state_array
this.backgroundColorList = [];
if(this.test.state_array.length != 0){
for(let i=0; i<this.test.data_array.length; i++){
if(this.test.state_array[i] == 0){
this.backgroundColorList.push('#069ed6');
}else if(this.test.state_array[i] == 1){
this.backgroundColorList.push('#F5482D');
}else if(this.test.state_array[i] == 2){
this.backgroundColorList.push('#CAC409');
}
}
}
else{
for(let d of this.test.data_array){
this.backgroundColorList.push('#069ed6');
}
}
this.chartColors = [
{
backgroundColor: this.backgroundColorList
}];
this.options = {
responsive: true,
title: {
display: true,
text: 'Custom Chart Title'
},
legend: {
display: true,
labels: {
fontColor: 'red'
}
}
};
}
}
for changing the color of numbers and lines in coordinate plane,we can do:
for example in xAxes:
xAxes: [{
gridLines: {
display: true,
color: "red" // this here
},
ticks: {
fontColor: "red", // this here
}
}],
and font and color of labels:
legend: {
display: true,
labels:{
fontSize: 10,
fontColor: 'red',
}
},
DEMO.
You may try to edit the source code.
1. Go to the link /node_modules/chart.js/src/core/core.js in your node modules folder.
2. edit the following code i.e the core.js file. change the
defaultFontColor: '#0000ff'
to any color you want. I have implemented this in my code for pie chart. and it worked.
`
defaults._set('global', {
responsive: true,
responsiveAnimationDuration: 0,
maintainAspectRatio: true,
events: ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'],
hover: {
onHover: null,
mode: 'nearest',
intersect: true,
animationDuration: 400
},
onClick: null,
defaultColor: 'rgba(0,0,0,0.1)',
defaultFontColor: '#0000ff',
defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
defaultFontSize: 12,
defaultFontStyle: 'normal',
showLines: true,
// Element defaults defined in element extensions
elements: {},
// Layout options such as padding
layout: {
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0
}
}
});
module.exports = function() {
// Occupy the global variable of Chart, and create a simple base class
var Chart = function(item, config) {
this.construct(item, config);
return this;
};
Chart.Chart = Chart;
return Chart;
};`

Kendo Diagram Shapes Centered

I'm following the example from the Telerik web site for Basic Usage with the exception of using a Model, children Heirarchy. I just want to be able to list shapes with text boxes and be able to connect them and get the connections later. So far, I'm able to list the shapes and the text boxes, but for some reason all the shapes get centered to the origin of the diagram. I'd like to be able to list the shapes in some order, without connections, then connect them later on on the diagram. Here is the code I have so far:
var data = [{
firstName: "Antonio",
lastName: "Moreno",
title: "Team Lead",
colorScheme: "#1696d3"
},
{
firstName: "Alfredo",
lastName: "Morales",
title: "Team Lead",
colorScheme: "#1696d3"
}];
function visualTemplate(options) {
var dataviz = kendo.dataviz;
var g = new dataviz.diagram.Group();
var dataItem = options.dataItem;
g.append(new dataviz.diagram.Rectangle({
width: 210,
height: 75,
stroke: {
width: 0
},
fill: {
gradient: {
type: "linear",
stops: [{
color: dataItem.colorScheme,
offset: 0,
opacity: 0.5
}, {
color: dataItem.colorScheme,
offset: 1,
opacity: 1
}]
}
}
}));
g.append(new dataviz.diagram.TextBlock({
text: dataItem.firstName + " " + dataItem.lastName,
x: 85,
y: 20,
fill: "#fff"
}));
g.append(new dataviz.diagram.TextBlock({
text: dataItem.title,
x: 85,
y: 40,
fill: "#fff"
}));
return g;
}
function createDiagram() {
$("#diagram").kendoDiagram({
dataSource: new kendo.data.HierarchicalDataSource({
data: data,
}),
shapeDefaults: {
visual: visualTemplate
},
});
var diagram = $("#diagram").getKendoDiagram();
diagram.bringIntoView(diagram.shapes);
}
$(document).ready(createDiagram);
I've made some sample: http://dojo.telerik.com/UbECE that makes rectangles one next to another.
I am following this example from API Documentation http://docs.telerik.com/kendo-ui/api/javascript/dataviz/ui/diagram#configuration-shapeDefaults.visual
They are using:
$("#diagram").getKendoDiagram().layout();
which works.
Your function will became:
function createDiagram() {
$("#diagram").kendoDiagram({
dataSource : data,
shapeDefaults: {
visual: visualTemplate
},
});
$("#diagram").getKendoDiagram().layout();
}

Resources