Constructing an inline SVG diagram using JavaScript with JSON - javascript

I'm currently working on a project that involves building a dynamic diagram and am looking at using SVGs for this.
Based on this, I wish to see whether what I am after is actually possible with SVGs.
1) Using JavaScript and json, is it possible to build an SVG diagram within a HTML page dynamically, based on my json data?
Basically, I want to construct say a rectangle box within the page, where I can have say 1 to 10 svg lines as inputs into the rectangle box, on the left hand side. To determine how many actual lines appear, will come from my json object.
So as mentioned above, is this possible and if so, how might this be setup to draw this inline svg diagram?
2) Again, using JavaScript, is it possible to place hyperlink labels on these svg lines, again based on info within the json object?
I started playing with static inline SVG but unsure how to construct this using javascript instead, to cover off my two points above, i.e.:
<body>
<h1>My SVG Test</h1><hr/>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<text x="465" y="90" fill="red">Heading One</text>
<image x="100" y="110" width="50%" height="50%"
xlink:href="http://my-image.com/myimg.jpg" />
<line x1="25" y1="80" x2="350" y2="80" style="stroke: #000000; stroke-width: 2;"/>
</svg>
</body>

You can use Snap to easily create what you are looking for. In the below snippet,I added all the required attributes and type of the element and used .el() to create an svg element
The syntax for el is
Paper.el(name,attr)
Name is the element that you want to create, for example line,circle,path etc and attr is the attributes that you want to add. In the data below i have created two lines and one circle by specifying their attributes . I have also added fill and stroke
var data = [
{
type:"line",
attrs:{
x1:0,
x2:0,
y1:0,
y2:100,
stroke:"black",
"stroke-width":"5"
}
},
{
type:"line",
attrs:{
x1:100,
x2:100,
y1:0,
y2:100,
stroke:"black",
"stroke-width":"5"
}
},
{
type:"circle",
attrs:{
cx:50,
cy:50,
r:40,
fill:"orange",
}
}
]
var s = Snap("svg");
for(var x=0;x<data.length;x++){
s.el(data[x].type).attr(data[x].attrs);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<svg width="500" height="500" viewbox="0 0 100 100"></svg>
Adding a link is a bit hard and complex because you have to append a child text element to the a. So i have changed my code a bit.
In the following snippet i have added a new key to the json data called parent. And then set parent:true to those elements who have child elements and run another loop to append the child element to the parent
var data = [{
type: "line",
parent: false, //is false because it has no child elements
attrs: {
x1: 0,
x2: 0,
y1: 0,
y2: 100,
stroke: "black",
"stroke-width": "5"
}
}, {
type: "line",
parent: false,
attrs: {
x1: 100,
x2: 100,
y1: 0,
y2: 100,
stroke: "black",
"stroke-width": "5"
}
}, {
type: "circle",
parent: false,
attrs: {
cx: 50,
cy: 50,
r: 40,
fill: "orange",
}
}, {
type: "a",
parent: true, // Is true because this has child elements
attrs: {
x: 10,
y: 50,
"xlink:href": "http://snapsvg.io/docs/"
},
childs: [{
type: "text",
attrs: {
x: 10,
y: 50,
text: "Snap is Cool",
}
}]
}, {
type: "image",
parent: false,
attrs: {
"xlink:href": "http://i.imgur.com/5NK0H1e.jpg",
x: 0,
y: 0,
width: 50,
height: 50
}
}]
var s = Snap("svg");
for (var x = 0; x < data.length; x++) {
var p = s.el(data[x].type).attr(data[x].attrs);
if (data[x].parent) { //check if it is true
for (var y = 0; y < data[x].childs.length; y++) {
var c = s.el(data[x].childs[y].type).attr(data[x].childs[y].attrs);
p.append(c);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js?wmode=transparent"></script>
<svg width="500" height="500" viewbox="0 0 100 100"></svg>
To add more child elements the following code should be used
var data = [{
type: "g"
}, {
type: "image",
appendTo: 0,
attrs: {
"xlink:href": "http://lorempixel.com/500/500/",
x: 0,
y: 0,
width: 50,
height: 50
}
}, {
appendTo: 0,
type: "a",
parent: true,
attrs: {
x: 5,
y: 5,
target: "_blank",
"xlink:href": "http://snapsvg.io/docs/"
}
}, {
appendTo: 2,
type: "circle",
attrs: {
cx: 5,
cy: 5,
r: 2.5,
fill: "orange"
}
}]
var s = Snap("svg");
var elems = [];
for (var x = 0; x < data.length; x++) {
var e = s.el(data[x].type).attr(data[x].attrs);
if (data[x]["appendTo"] !== undefined) {
var p = elems[data[x].appendTo];
p.append(e);
}
elems.push(e);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.3.0/snap.svg-min.js"></script>
<div class="box">
<svg width="100%" height="100%" viewBox="0 0 100 100"></svg>
</div>

Related

makes shapes with loop in Plotly

So far i got this code `
shapes: [
{
type: "line",
xref: "x",
yref: "paper",
x0: data[i],
y0: 0,
x1: data[i],
y1: 1,
line: {
color: "red",
width: 2,
dash: "longdash",
},
},
],
I want to set up a vertical line in my plot on a specific position and this works fine. Now how can i do more of the exact same line ? cause my array of data will be updated after a button. If there is only one point in my array i get a line , but if a try to set up a second line it just wont work. Can i somehow make a for loop to plot me those lines ? i just want to plot more than one vertical lane at different places.
You can construct shapes as an array containing multiple dictionaries: something in the form [{shape 1 info}, ..., {shape N info}]. To do that you can loop through your data array, and use data[i] as the x-coordinates of each line in your shapes array.
Here is some sample code and the codepen.
var data = [1,2,3,5]
shapes = []
for (let i = 0; i < data.length; i++) {
shapes.push({
type: "line",
xref: "x",
yref: "paper",
x0: data[i],
y0: 0,
x1: data[i],
y1: 1,
line: {
color: "red",
width: 2,
dash: "longdash",
},
})
}
var trace1 = {
x: [2, 6],
y: [1, 2],
mode: 'markers+lines'
};
var layout = {
title: 'Vertical Line Annotations',
xaxis: {
range: [0, 7]
},
yaxis: {
range: [0, 6]
},
shapes: shapes
};
var plotdata = [trace1];
Plotly.newPlot('myDiv', plotdata, layout);

JavaScript - Chart.js tooltip shows wrong x-axis value

I have a chart with two different datasets, but sometimes they have the same x, y coordinates. But when I hover a shared point, it sometimes shows the wrong date. The y value is correct, but it's the x value that is not showing correctly.
Try hovering a shared point here on codepen.
In the image below you can see I'm hovering { y: 56.04, x: April 05, 2014 }, but is shows the xLabel value of 58.28, which is April 15, 2012. Also, you can see in the chart that both 57.05 and 58.28 have April 15, 2012 as x-value, but they are not on the same y position!
The code is too long to share on stackoverflow, but I made this codepen so you can view, fork and edit it there.
Update
I updated the pen and fixed points that have the same date. I also added type: 'time' thanks to #Oluwafemi Sule.
Here my edited pen.
But now, the dates on the x-axis are weird. They don't say March 06, 2011 anymore, but they say Q1 2011. And the tooltips still bug.
I solved my problem by using tooltip callback like this:
options: {
tooltips: {
callbacks: {
title: function(tooltipItems, data) {
return data.datasets[tooltipItems[0].datasetIndex].data[tooltipItems[0].index].x;
}
}
}
}
Now my tooltips getting their title directly from the corresponding dataset.
Chartjs Version : 2.9.3
You defined a custom scale for a category cartesian axis in your chart configuration. Set the type for your xAxes to 'category'. This may not be neccessary as ChartJS picks this up by default.
options: {
scales: {
xAxes: [{
type: 'category',
....
Also, the second data set isn't formatted properly. You should supply the data points in {x: xval, y: yval} format.
References:
http://www.chartjs.org/docs/latest/axes/cartesian/time.html#time-cartesian-axis
I belive that you need pass the labels.
labels: ["09:00", "09:30", "09:50", "10:10", "10:30", "10:50", "11:10"],
//////////////////////////////////////////////
var chartPluginLineaHorizontal = {
afterDraw: function (chartobj, chartobjDos) {
if (chartobj.options.lineaHorizontal) {
var ctx = chartobj.chart.ctx;
var valorY = chartobj.scales["y-axis-0"].getPixelForValue(chartobj.options.lineaHorizontal);
ctx.beginPath();
ctx.moveTo(0, valorY);
ctx.lineTo(chartobj.chart.width, valorY);
ctx.strokeStyle = "red";
ctx.stroke();
}
}
}
Chart.pluginService.register(chartPluginLineaHorizontal);
var chartPluginLineaHorizontalDos = {
afterDraw: function (chartobj) {
if (chartobj.options.lineaHorizontal) {
var ctx = chartobj.chart.ctx;
var valorY = chartobj.scales["y-axis-0"].getPixelForValue(chartobj.options.lineaHorizontalDos);
ctx.beginPath();
ctx.moveTo(0, valorY);
ctx.lineTo(chartobj.chart.width, valorY);
ctx.strokeStyle = "red";
ctx.stroke();
}
}
}
Chart.pluginService.register(chartPluginLineaHorizontalDos);
// Define a plugin to provide data labels
Chart.plugins.register({
afterDatasetsDraw: function (chartobj) {
var ctx = chartobj.chart.ctx;
chartobj.data.datasets.forEach(function (dataset, i) {
//debugger
var meta = chartobj.getDatasetMeta(i);
if (!meta.hidden) {
meta.data.forEach(function (element, index) {
// Draw the text in black, with the specified font
ctx.fillStyle = 'rgb(0, 0, 0)';
var fontSize = 16;
var fontStyle = 'inherit';
var fontFamily = 'sans-serif';
ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
// Just naively convert to string for now
var dataString = dataset.data[index].y.toString();
// Make sure alignment settings are correct
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var padding = 5;
var position = element.tooltipPosition();
ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);
});
}
});
}
});
var ctx = document.getElementById('myChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: ["09:00", "09:30", "09:50", "10:10", "10:30", "10:50", "11:10"],
datasets: [{
label: "My First dataset",
data: [
{
x: "09:30",
y: 127
},
{
x: "09:30",
y: 140
},
{
x: "09:50",
y: 135
},
{
x: "10:10",
y: 122
}, {
x: "10:30",
y: 135
}, {
x: "10:50",
y: 135
}],
backgroundColor: "rgba(0,255,51,0.5)",
borderColor: "rgba(0,255,51,0.5)",
fill: false
},
{
label: "My second dataset",
data: [
{
x: "09:50",
y: 95
},
{
x: "10:10",
y: 140
},
{
x: "10:30",
y: 130
},
{
x: "10:50",
y: 150
},
{
x: "11:10",
y: 143
}],
backgroundColor: "rgba(0,98,31,0.5)",
borderColor: "rgba(0,98,31,0.5)",
fill: false
}]
},
options: {
lineaHorizontal: 140,
lineaHorizontalDos: 100,
elements: {
line: {
tension: 0
}
}
}
})

Joint.js add custom ports with path class. for custom elements

What I am trying to do is make a element with custom class for ports and path so that I can add an element with custom path and my own markup for ports.This way when I create an element I will pass dynamic path for its shape just like elements of path class behave and as I have also extended from PortsModelInterface I will also have my own markup for ports.
This whole effort is to make svg scalable for zomming. Previously I was using html custom element with my custom ports which was working fine but html of custom elements wasn't scaling on zooming
var graph = new joint.dia.
var paper = new joint.dia.Paper({
el: $('#paper'),
width: 800,
height: 600,
gridSize: 1,
model: graph,
snapLinks: true,
embeddingMode: true
});
joint.shapes.custom1={};
joint.shapes.custom1.Element = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {
markup: '<g class="rotatable"><g class="scalable"><rect class = "myrect"/></g><g class="inPorts"/><g class="outPorts"/></g>',
portMarkup: '<g class="port<%= id %>"><circle class="port-body"/></g>',
defaults: joint.util.deepSupplement({
type: 'html.Element',
size: { width: 200, height: 110 },
inPorts: [],
outPorts: [],
attrs: {
'.': { magnet: true},
rect: {
stroke: 'none', 'fill-opacity': 0, width: 300, height: 210,
},
circle: {
r: 6, //circle radius
magnet: true,
left:0,
stroke: 'gray'
},
'.inPorts circle': { fill: 'gray', magnet: 'passive', type: 'input', y: 0},
'.outPorts circle': { fill: 'gray', type: 'output' }
}
}, joint.shapes.basic.Generic.prototype.defaults),
getPortAttrs: function (portName, index, total, selector, type) {
var attrs = {};
var portClass = 'port' + index;
var portSelector = selector + '>.' + portClass;
var portCircleSelector = portSelector + '>circle';
attrs[portCircleSelector] = { port: { id: portName || _.uniqueId(type), type: type } };
attrs[portSelector] = { ref: 'rect', 'ref-x': (index + 1) * (0.55 / total)};
if (selector === '.outPorts') {
attrs[portSelector]['ref-dy'] = 15;
}
return attrs;
}
}));
joint.shapes.custom1.Atomic = joint.shapes.custom1.Element.extend({
markup: '<g class="rotatable"><g class="scalable"><path/></g><text/></g>',
defaults: joint.util.deepSupplement({
type: 'basic.Path',
size: { width: 60, height: 60 },
attrs: {
'path': { fill: '#FFFFFF', stroke: 'black' },
'text': { 'font-size': 14, text: '', 'text-anchor': 'middle', 'ref-x': .5, 'ref-dy': 20, ref: 'path', 'y-alignment': 'middle', fill: 'black', 'font-family': 'Arial, helvetica, sans-serif' }
}
}, joint.shapes.basic.Generic.prototype.defaults)
});
var a2 = new joint.shapes.custom1.Atomic({
position: { x: 50, y: 260 },
size: { width: 100, height: 100 },
attrs: {
path: { d: 'M 30 0 L 60 30 30 60 0 30 z' },
text: {
text: 'Diamond',
'ref-y': .5 // basic.Path text is originally positioned under the element
}
},
inPorts: ['in'],
outPorts: ['out']
});
graph.addCells([a2])
The element is added in graph but some how the ports don't show up.
I don't have proper concept of adding classes so please any help will be greatly appreciated. Thanks.
Fiddle example
I suggest to define an element with custom markup for the shape and ports. Both markups should contain an SVG path, so you can set an arbitrary path data d via model.attr() on them.
joint.shapes.devs.GenericModel = joint.shapes.devs.Model.extend({
markup: '<g class="rotatable"><g class="scalable"><path class="body"/></g><text class="label"/><g class="inPorts"/><g class="outPorts"/></g>',
portMarkup: '<g class="port port<%= id %>"><path class="port-body"/><text class="port-label"/></g>',
defaults: joint.util.deepSupplement({
type: 'devs.GenericModel'
}, joint.shapes.devs.Model.prototype.defaults)
});
Tell the paper to use devs.ModelView for rendering.
joint.shapes.devs.GenericModelView = joint.shapes.devs.ModelView;
Now you can set or change d attribute for the shape and ports anytime you wish.
var model = new joint.shapes.devs.GenericModel({
attrs: {
'.body': { d: 'M 0 0 0 50 50 50 z'},
'.port-body': { d: 'M 0 0 10 0 10 10 0 10 z'}
}
});
model.attr('.body/d', 'M 25 0 50 50 0 50 z');
JS Fiddle: http://jsfiddle.net/kumilingus/kge023bc/

How to create a custom shape in JointJs, which consists out of 2 or more basic shapes?

I want to create a shape in Joint JS.
Which I could initialize and lets say have rectangle and circle as one shape.
As far as I know, only path method is something near.
I have just come to an answer. I needed to extend the joint.dia.Element class. Here is how the code looks, which draws a rectangle with two circles(extension):
joint.shapes.basic.myShape = joint.dia.Element.extend({
markup: '<g class="rotatable"><g class="scalable"><rect class="outer"/><circle class="inner"/><circle class="inner1"/></g></g>',
defaults: joint.util.deepSupplement({
type: "basic",
size: {
width: 20,
height: 20
},
attrs: {
".outer": {
stroke: 'black',
'fill-opacity': 1,
width: 10,
height: 15
},
".inner": {
transform: "translate(10, 10)",
r: 8,
fill: "#000000"
},
".inner1": {
transform: "translate(10, 10)",
r: 6,
fill: "#fff"
}
}
}, joint.dia.Element.prototype.defaults)
});
initializing (to stencil, but directly to graph is possible too by using,
graph.addCell):
var r1 = new joint.shapes.basic.myShape({
size: {
width: 60,
height: 60
}
});
stencilData.load([r1], 'basic');

Dynamically Add Data Points To Rickshaw Graph

So I'm using the Rickshaw graphing library and I was wondering how to dynamically add points to a graph.
I have a graph instantiated like this:
#seriesData = [ [], [], [] ]
random = new Rickshaw.Fixtures.RandomData(150)
for (var i = 0; i < 50; i++) {
random.addData(self.seriesData)
}
#graph = new Rickshaw.Graph(
element: document.getElementById("chart")
width: 550
height: 300
renderer: 'area'
series: [
{
color: "#c05020"
data: self.seriesData[0]
name: 'One'
}, {
color: "#30c020"
data: self.seriesData[1]
name: 'Two'
}, {
color: "#2791d7"
data: self.seriesData[2]
name: 'Three'
}
]
)
#graph.render()
hoverDetail = new Rickshaw.Graph.HoverDetail(
graph: self.graph
)
legend = new Rickshaw.Graph.Legend(
graph: self.graph
element: document.getElementById('legend')
)
shelving = new Rickshaw.Graph.Behavior.Series.Toggle(
graph: self.graph
legend: legend
)
axes = new Rickshaw.Graph.Axis.Time(
graph: self.graph
)
axes.render()
And I have data coming in through socket.io like this:
app.on('data',
(one, two, three) =>
// Dynamically add data points to graph
)
And I was wondering how to append these three points to the graph. I can't find any good documentation for this library. I know it's built on top of d3.js, but I'm not sure how to incorporate these methods into my graph.
Any help would be appreciated.
I envision two scenario that could solve your question:
Using the fixed Window Series for Streaming Data
leveraging the fact that arrays in javascript are passed by reference. A demo is available here
.
var data = [
{
data: [ { x: 0, y: 120 }, { x: 1, y: 890 }, { x: 2, y: 38 }, { x: 3, y: 70 }, { x: 4, y: 32 } ],
color: "#c05020"
}, {
data: [ { x: 0, y: 80 }, { x: 1, y: 200 }, { x: 2, y: 100 }, { x: 3, y: 520 }, { x: 4, y: 133 } ],
color: "#30c020"
}
];
var graph = new Rickshaw.Graph( {
element: document.getElementById("chart"),
renderer: 'line',
height: 300,
width: 800,
series: data
} );
var y_ticks = new Rickshaw.Graph.Axis.Y( {
graph: graph,
orientation: 'left',
tickFormat: Rickshaw.Fixtures.Number.formatKMBT,
element: document.getElementById('y_axis'),
} );
graph.render();
$('button#add').click(function() {
data.push({
data: [ { x: 0, y: 200 }, { x: 1, y: 390 }, { x: 2, y: 1000 }, { x: 3, y: 200 }, { x: 4, y: 230 } ],
color: "#6060c0"
});
graph.update();
});

Categories