I am building a charting tool in ember using cytoscape js and I can render chart data however I do not know how to set each node to display with a image that has other images/buttons that function within it. Basically I want it to look like this:
In the image there are two buttons (I will most likely add icons as well) and also there are labels that exist within the node which I don't know how to do either.
Here is the code I currently have.
Template:
<div class="container" >
<div id="cy"></div>
</div>
Component JS:
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
map: Ember.computed('model.map_data', function()
{
if(this.get('model.map_data')){
return JSON.parse(this.get('model.map_data').data)
} else {
return {};
}
}),
cytoscape_data: Ember.computed('model.sub_apps.[]',function() {
var ret = {
nodes: [],
edges: []
};
var red = 50;//replace with threshold
var green = 25;//replace with threshold
var _this = this;
this.get("model").map_data.forEach(function(node) {
var y= 0;
var x = 0;
var color = 'green';
if(node.value >= red ){
color = 'red';
}else {
if(node.value > green){
color = 'orange';
}
}
var position = _this.get("map")["app" + node.id];
if(position){
x = parseInt(position.split(',')[0]);
y = parseInt(position.split(',')[1]);
}
ret["nodes"].push({
data: {
id: node.id,
label: node.name,
node_type: 'app',
tooltip: node.description,
color: color
},
position: {
x: x,
y: y
}
});
if(node.relations) {
node.relations.forEach(function(parent) {
ret["edges"].push({
data: {
source: node.id,
target: parent.app_to_id
}
});
});
}
});
return ret;
}),
didInsertElement: function() {
this._super();
var cy = cytoscape({
container: Ember.$('#cy')[0],
elements: this.get("cytoscape_data"),
zoom: 1,
pan: { x: 0, y: 0 },
fit: true,
randomize: false,
layout: {
name: 'preset'
},
style: [
{
selector: 'node',
style: {
'content': 'data(label)',
'text-opacity': 0.8,
'text-valign': 'center',
'text-halign': 'right',
'width': '200px',
'height': '200px',
'border-color': 'green',
'border-width': 3,
'border-opacity': 0.5,
'background-image': 'url(../assets/images/base_node_image.svg)'
// 'background-color': 'data(color)'
}
},
{
selector: 'edge',
style: {
'width': 6,
'border-color': 'green',
'target-arrow-shape': 'triangle',
'target-arrow-color': 'red',
'opacity': 1,
'curve-style': 'bezier'
}
},
{
selector: ':selected',
style: {
'background-color': 'orange',
'opacity': 1
}
},
{
selector: '.faded',
style: {
'opacity': 0.0,
'text-opacity': 0
}
},
],
});
Ember.run.scheduleOnce('afterRender', this, function(){
cy;
});
cy.on('click', 'node', function(evt){
var node = evt.target;
console.log( 'clicked ' + node.data('label') );
});
},
});
The chart this code renders looks like this:
I can display a background-image however it displays in a circle which I dont know how to get rid of. The color of the circle is determined by some logic above which was a test to see if it works and that is fine (going to use that for one of the icons on the node later). I can also display the label for the node but I don't know how to display that within the node itself.
Any help is appreciated, thanks!
It is not so trivial to achieve what you want if not impossible. You say "set each node to display with a image that has other images/buttons that function within it."; this means you need to render html into a canvas; because what cytoscape puts as drawing area is an HTML canvas.
See #maxfranz's (author of cytoscape.js) for a relevant question; where he basically says "It's not possible to render HTML in a canvas, nor would you probably want to for performance".
This means putting html buttons, URLs might not be what you desire. See also MDN Web Docs for further explanation.
That said; I think you can still manage to achieve what you want; but with a different approach. You can make use of cytoscape's compound nodes. You can define the images and buttons as simple nodes and define compound nodes as surrounding containers. I have created a working example for you at the follwoing github repository.
The final result I got is as follows:
I hope this helps.
Related
Ok so I build sync charts with test data. Everything works perfectly. The only problem I have is that I want to export all of them in one PDF but I only have the option to do it seperatly. So I build in a button that gets the charts in an array and exports it to PDF but it does not want to work.
This is the JS i have that builds the charts
function clickModal(test) {
document.getElementById("myModal").style.display = "block";
// Call the AJAX function that start the chart procedures
getAjaxData(test);
}
// HIGHCHART FUNCTIONALITY STARTS HERE
['mousemove', 'touchmove', 'touchstart'].forEach(function (eventType) {
document.getElementById('container').addEventListener(
eventType,
function (e) {
var chart,
point,
i,
event;
for (i = 0; i < Highcharts.charts.length; i = i + 1) {
chart = Highcharts.charts[i];
// Find coordinates within the chart
event = chart.pointer.normalize(e);
// Get the hovered point
point = chart.series[0].searchPoint(event, true);
if (point) {
point.highlight(e);
}
}
}
);
});
/**
* Override the reset function, we don't need to hide the tooltips and
* crosshairs.
*/
Highcharts.Pointer.prototype.reset = function () {
return undefined;
};
/**
* Highlight a point by showing tooltip, setting hover state and draw crosshair
*/
Highcharts.Point.prototype.highlight = function (event) {
event = this.series.chart.pointer.normalize(event);
this.onMouseOver(); // Show the hover marker
this.series.chart.tooltip.refresh(this); // Show the tooltip
this.series.chart.xAxis[0].drawCrosshair(event, this); // Show the crosshair
};
/**
* Synchronize zooming through the setExtremes event handler.
*/
function syncExtremes(e) {
var thisChart = this.chart;
if (e.trigger !== 'syncExtremes') { // Prevent feedback loop
Highcharts.each(Highcharts.charts, function (chart) {
if (chart !== thisChart) {
if (chart.xAxis[0].setExtremes) { // It is null while updating
chart.xAxis[0].setExtremes(
e.min,
e.max,
undefined,
false,
{ trigger: 'syncExtremes' }
);
}
}
});
}
}
function getAjaxData(test){
$.getJSON('datasync.php', function(chartData, tstSuccess) {
// Display the results of the getJSON call to data.php
document.getElementById("json1").innerHTML = JSON.stringify(tstSuccess, undefined, 2);
document.getElementById("json").innerHTML = JSON.stringify(chartData, undefined, 2);
var charts1 = {}; // global variable
var index = 0;
// Loop through each dataset in the returned JSON
chartData.datasets.forEach(function (dataset, i) {
// Add X values
dataset.data = Highcharts.map(dataset.data, function (val, j) {
return [chartData.xData[j], val];
});
// Create a child div for each dataset that is returned
var chartDiv = document.createElement('div');
chartDiv.className = 'chart';
document.getElementById('container').appendChild(chartDiv);
charts1[index] = Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
exporting: {
buttons: {
contextButton: {
menuItems: [
'downloadPDF',
'viewData'
]
}
}
},
title: {
text: dataset.name,
align: 'left',
margin: 0,
x: 30
},
credits: {
enabled: false
},
legend: {
enabled: false
},
xAxis: {
crosshair: true,
events: {
setExtremes: syncExtremes
},
labels: {
//format: '{value} km'
format: ' '
}
},
yAxis: {
title: {
text: null
}
},
tooltip: {
positioner: function () {
return {
// right aligned
x: this.chart.chartWidth - this.label.width - 40,
y: 10 // align to title
};
},
borderWidth: 0,
backgroundColor: 'none',
pointFormat: '{point.y}',
headerFormat: '',
shadow: false,
style: {
fontSize: '18px'
},
valueDecimals: dataset.valueDecimals
},
series: [{
data: dataset.data,
name: dataset.name,
type: dataset.type,
color: Highcharts.getOptions().colors[i],
fillOpacity: 0.3,
tooltip: {
valueSuffix: ' ' + dataset.unit
}
}]
});
});
index++;
});
}
$('#export-pdf').click(function() {
Highcharts.exportCharts([charts1[0], charts1[1]], {
type: 'application/pdf'
});
});
And this is my HTML
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<div id="container">
<button id="export-png">Export to PNG</button>
</div>
</div>
</div>
So im using PHP to build up JSON from an array ( will use SQL to build from database later just want to get the functionality working )
Then I use the highcharts library along with JS to display the charts
Ok so I figured out the problem. I brought in this piece of code
Highcharts.getSVG = function(charts) {
var svgArr = [],
top = 0,
width = 0;
$.each(charts, function(i, chart) {
var svg = chart.getSVG();
svg = svg.replace('<svg', '<g transform="translate(0,' + top + ')" ');
svg = svg.replace('</svg>', '</g>');
svg = svg.replace('-9000000000', '-999'); // Bug in v4.2.6
top += chart.chartHeight;
width = Math.max(width, chart.chartWidth);
svgArr.push(svg);
});
return '<svg height="'+ top +'" width="' + width + '" version="1.1" xmlns="http://www.w3.org/2000/svg">' + svgArr.join('') + '</svg>';
};
/**
* Create a global exportCharts method that takes an array of charts as an argument,
* and exporting options as the second argument
*/
Highcharts.exportCharts = function(charts, options) {
// Merge the options
options = Highcharts.merge(Highcharts.getOptions().exporting, options);
// Post to export server
Highcharts.post(options.url, {
filename: options.filename || 'chart',
type: options.type,
width: options.width,
svg: Highcharts.getSVG(charts)
});
};
I changed the charts so that it does not created an array of chart objects
Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
instead of
charts1[index] = Highcharts.chart(chartDiv, {
chart: {
marginLeft: 40, // Keep all charts left aligned
spacingTop: 20,
spacingBottom: 20
},
And in the buttons function I just used the highcharts.charts instead of the array
$('#export-pdf').click(function() {
Highcharts.exportCharts(Highcharts.charts, {
type: 'application/pdf'
});
});
What would be the best approach to extend the show() and hide() functions in Highcharts?
I want to add a boolean-like show(effectLinkedSeries) and hide(effectLinkedSeries), so I can control when a linkedSeries gets removed or added along with its "parent". Here is a demo of linkedSeries functionality. A property called "linkedTo" sets up the functionality:
series: [{
name: 'Temperature',
data: averages,
zIndex: 1,
marker: {
fillColor: 'white',
lineWidth: 2,
lineColor: Highcharts.getOptions().colors[0]
}
}, {
name: 'Range',
data: ranges,
type: 'arearange',
lineWidth: 0,
linkedTo: ':previous',
color: Highcharts.getOptions().colors[0],
fillOpacity: 0.3,
zIndex: 0
}]
I could modify the source directly but I'd rather try to extend the library, instead of hacking it.
Figured out I can make use of the Highcharts.Series.prototype to accomplish this
Highcharts.Series.prototype.setVisible = function (vis, redraw, effectLinkedSeries) {
var series = this,
chart = series.chart,
legendItem = series.legendItem,
showOrHide,
ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
oldVisibility = series.visible;
// if called without an argument, toggle visibility
series.visible = vis = series.userOptions.visible = vis === Highcharts.UNDEFINED ? !oldVisibility : vis;
showOrHide = vis ? 'show' : 'hide';
// show or hide elements
Highcharts.each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {
if (series[key]) {
series[key][showOrHide]();
}
});
// hide tooltip (#1361)
if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
series.onMouseOut();
}
if (legendItem) {
chart.legend.colorizeItem(series, vis);
}
// rescale or adapt to resized chart
series.isDirty = true;
// in a stack, all other series are affected
if (series.options.stacking) {
Highcharts.each(chart.series, function (otherSeries) {
if (otherSeries.options.stacking && otherSeries.visible) {
otherSeries.isDirty = true;
}
});
}
// show or hide linked series
Highcharts.each(series.linkedSeries, function (otherSeries) {
if (effectLinkedSeries === true) {
otherSeries.setVisible(vis, false);
}
});
if (ignoreHiddenSeries) {
chart.isDirtyBox = true;
}
if (redraw !== false) {
chart.redraw();
}
Highcharts.fireEvent(series, showOrHide);
}; // end Highcharts.Series.prototype.setVisible
Highcharts.Series.prototype.show = function (effectLinkedSeries) {
this.setVisible(true,undefined,effectLinkedSeries);
};
Highcharts.Series.prototype.hide = function (effectLinkedSeries) {
this.setVisible(false,undefined,effectLinkedSeries);
};
I'm new with jointjs and I try to constraint a rectangle with ports to a line.
I tried to reproduce tutorial, that works with a basic.Circle, with a basic.Rect but not with devs.Model
Could someone explian me why and how to solve this problem?
Many thanks in advance!
Here is my code :
var width=400, height=1000;
var ConstraintElementView = joint.dia.ElementView.extend({
pointermove: function(evt, x, y) {
joint.dia.ElementView.prototype.pointermove.apply(this, [evt, 100, y]);
}
});
var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({ el: $('#myholder'), width: width, height: height, gridSize: 1, model: graph, elementView: ConstraintElementView});
var m1 = new joint.shapes.devs.Model({
position: { x: 20, y: 20 },
size: { width: 90, height: 90 },
inPorts: [''],
outPorts: [''],
attrs: {
'.label': { text: 'Model', 'ref-x': .4, 'ref-y': .2 },
rect: { fill: '#2ECC71' },
'.inPorts circle': { fill: '#16A085' },
'.outPorts circle': { fill: '#E74C3C' }
}
});
var m2=m1.clone();
m2.translate(0,300);
var earth = new joint.shapes.basic.Circle({
position: { x: 100, y: 20 },
size: { width: 20, height: 20 },
attrs: { text: { text: 'earth' }, circle: { fill: '#2ECC71' } },
name: 'earth'
});
graph.addCell([m1, m2, earth]);
Why does it not work?
devs.Model is not rendered via ContraintElementView to the paper.
devs.Model uses devs.ModelView for rendering, basic.Circle and basic.Rect use ContraintElementView.
JointJS dia.Paper searches for a view defined in the same namespace as the model first. If found, it uses it. It uses one from the paper elementView option otherwise. i.e. joint.shapes.devs.ModelView found for devs.Model but no view found for basic.Circle (no joint.shapes.basic.RectView is defined)
How to make it work?
define elementView paper option as a function. In that case paper don't search the namespace and uses the result of the function first.
Note that in order to render ports devs.ModelView is still required.
i.e.
var paper = new joint.dia.Paper({
elementView: function(model) {
if (model instanceof joint.shapes.devs.Model) {
// extend the ModelView with the constraining method.
return joint.shapes.devs.ModelView.extend({
pointermove: function(evt, x, y) {
joint.dia.ElementView.prototype.pointermove.apply(this, [evt, 100, y]);
}
});
}
return ConstraintElementView;
}
});
http://jsfiddle.net/kumilingus/0bjqg4ow/
What is the recommended way to do that?
JointJS v0.9.7+
not to use custom views that restrict elements movement
use restrictTranslate paper option instead.
i.e.
var paper = new joint.dia.Paper({
restrictTranslate: function(elementView) {
// returns an area the elementView can move around.
return { x: 100, y: 0, width: 0, height: 1000 }
};
});
http://jsfiddle.net/kumilingus/atbcujxr/
I think this could help you :
http://jointjs.com/demos/devs
I am stuck in a bit of a problem. You people might have seen an animated line which acts like a scanner in many apps. Well I ned something similar to that but I need it in a graph.
What I actually need is that I need to plt a vertical line which moves from one point to other automatically.
Let me give you a bit more explaination:
1. I have a button
2. I press the button and graph area appears.
3. On the graph area, a vertical line scrolls through the area as if it is scanning the area.
I am able to plot the line but it is coming out to be a little tilted. The logic behind that is provided below:
for(i=0;i<frequencyArray.length;i++){
myTestArray2.push([i,outFrequencyArray[i]]);
}
plot.setData([
{data:myTestArray2,lines:{fill:false,lineWidth:3},shadowSize:10}
]);
function setUpflot(){
// setup plot
//console.log("setUpflot");
var options = {
// series : { shadowSize: 0, splines: {show:true,lineWidth:1}},
series : { },
yaxis : { ticks: 5, tickColor:"rgba(148,129,151,0.5)", min: minGraphY, max:maxGraphY,show: true},
xaxis : { tickLength:0, show: false },
grid : { borderWidth:0,markings:[
{yaxis: { from: 200.0, to: 240.0 },color: "rgba(140,2,28,0.5)"}
]}
};
I put this together in response to a comment yesterday.
Fiddle here.
Produces:
plot = $.plot($("#placeholder"),
[ { data: someData} ], {
series: {
lines: { show: true }
},
crosshair: { mode: "x" }, // turn crosshair on
grid: { hoverable: true, autoHighlight: false },
yaxis: { min: -1.2, max: 1.2 }
});
crossHairPos = plot.getAxes().xaxis.min;
direction = 1;
setCrossHair = function(){
if (direction == 1){
crossHairPos += 0.5;
}
else
{
crossHairPos -= 0.5;
}
if (crossHairPos < plot.getAxes().xaxis.min){
direction = 1;
crossHairPos = plot.getAxes().xaxis.min;
}
else if (crossHairPos > plot.getAxes().xaxis.max)
{
direction = 0;
crossHairPos = plot.getAxes().xaxis.max;
}
plot.setCrosshair({x: crossHairPos})
setTimeout(setCrossHair,100);
}
// kick it off
setTimeout(setCrossHair,100);
var frequencyIndex = 0; //dynamic values stored intialised with 0.
var outFrequencyArray = [];
for(i=0;i<totalPoints;i++){
outFrequencyArray.push(minGraphY-1);
}
opd=Math.tan(Math.PI/2);
outFrequencyArray.splice(frequencyIndex,0,opd);
frequencyIndex++;
for(i=0;i<frequencyArray.length;i++){
myTestArray2.push([i,outFrequencyArray[i]]);
}
plot.setData([
{data:myTestArray2,lines:{fill:false,lineWidth:3},shadowSize:10}
]);
function setUpflot(){
// setup plot
//console.log("setUpflot");
var options = {
// series : { shadowSize: 0, splines: {show:true,lineWidth:1}},
series : { },
yaxis : { ticks: 5, tickColor:"rgba(148,129,151,0.5)", min: minGraphY, max:maxGraphY,show: true},
xaxis : { tickLength:0, show: false },
grid : { borderWidth:0,markings:[
{yaxis: { from: 200.0, to: 240.0 },color: "rgba(140,2,28,0.5)"}
]}
};
I'm trying to update an existing data series with a new data array and invoke the redraw function when done. While this works perfectly, I'm not quite satisfied as I'd like to have a sort of grow/shrink transition. I have seen an example by Highcharts (fiddle around with the existing data set then click on the button "Set new data to selected series") but I can't replicate this behavior.
This is what code that I've written:
var series, newSeriesThreshold = this.chart.series.length * 2;
for (var i = 0; i < data.length; i += 2) {
series = {
name: this.data[i].title,
data: this.data[i].data,
color: this.data[i].color
};
if (i >= newSeriesThreshold) {
this.chart.addSeries(series, false);
} else {
var currentSeries = this.chart.series[i / 2];
currentSeries.setData(series.data, false);
}
}
this.chart.redraw();
These are the options when creating the chart:
var config = {
chart: {
renderTo: $(this.container).attr('id'),
type: this.settings.type,
animation: {
duration: 500,
easing: 'swing'
}
},
title: {
text: null
},
legend: {
enabled: this.settings.legend.show
},
tooltip: {
formatter: function() {
return this.x.toFixed(0) + ": <b>" + this.y.toString().toCurrency(0) + '</b>';
}
},
xAxis: {
title: {
text: this.settings.xaxis.title,
style: {
color: '#666'
}
}
},
yAxis: {
title: {
text: this.settings.yaxis.title,
style: {
color: '#666'
}
}
},
series: series,
plotOptions: {
column: {
color: '#FF7400'
}
},
credits: {
enabled: false
}
};
This yields an immediate update without transitioning effects. Any ideas what I might be doing wrong?
I have solved this problem by destroying and creating again the chart.
Here is the link on highcharts forum that helps me : http://forum.highcharts.com/highcharts-usage/animation-on-redraw-t8636/#p93199
The answer comes from the highcharts support team.
$(document).ready(function() {
var chartOptions = {
// Your chart options
series: [
{name: 'Serie 1' , color: '#303AFF', data: [1,1,1,1,1,1,1}
]
};
var chart = new Highcharts.Chart(chartOptions);
$("#my_button").click(function(){
chart.destroy();
chartOptions.series[0].data = [10,5,2,10,5,2,10];
chart = new Highcharts.Chart(chartOptions);
});
});
This remain a mystery. I managed to make the axis update which suggests that there is some kind of animation going on, but it's applied only to the axis, and not the columns.
In the end, I've settled with this behavior.
This might help:
var mychart = $("#mychart").highcharts();
var height = mychart.renderTo.clientHeight;
var width = mychart.renderTo.clientWidth;
mychart.setSize(width, height);
to update all charts
$(Highcharts.charts).each(function(i,chart){
var height = chart.renderTo.clientHeight;
var width = chart.renderTo.clientWidth;
chart.setSize(width, height);
});