Remove transitions in scatter plot in NVD3 - javascript

I have created a scatter chart in NVD3.js with dynamic data, but I cannot manage to prevent points from moving across the screen when the data changes (see 'demo snippet' at the end of this question). I would like these points to appear and disappear instantaneously, without any movement at all. Is this possible?
What I have tried
Played around with chart's settings:
chart.duration(0): no effect (from How to remove NVD3 chart resize/update delay)
chart.duration(-1): idem
chart.transitionDuration(0): shown in examples on nvd3.org but found out that function no longer exists (see: transitionDuration function does not exist in nvd3.js).
chartElement.transition().duration(0): no effect
Simply reinitialize chart everytime the data changes (not elegant, I know). This did not work because the chart also animates when it initializes.
Even uglier: disable all D3 animations as explained in Disabling all D3 animations (for testing), but also no success.
I have found out that someone else had a similar problem with NVD3's pie chart (https://github.com/novus/nvd3/issues/1474). It turned out that duration is not even used anywhere in the pie chart model. This does not seem to apply to the scatter chart (see https://github.com/novus/nvd3/blob/master/src/models/scatterChart.js#L96-L101, line 96 until 101):
chart.update = function() {
if (duration === 0)
container.call(chart);
else
container.transition().duration(duration).call(chart);
};
More details
I am using:
D3.js v3.4.11
NVD3.js v1.8.2
In case that this turns out the be a bug then I will try to write and commit a bug fix. Nevertheless, it would be nice to have a quick workaround.
Demo snippet
Run the snippet and move the slider to see the problem.
var chart;
var dataset; // All data points
var subset; // Datapoints that are close to
function randomPoint() {
return {
x: Math.random(),
y: Math.random(),
time: Math.random()
}
}
// Calculate the subset
function updateSubset(time) {
var upperBound = time + 0.1;
var lowerBound = time - 0.1;
subset[0].values = dataset[0].values.filter(function(d) {
return lowerBound < d.time && d.time < upperBound;
});
}
subset = [{
key: "Foobar",
values: []
}];
for (var i = 0; i < 100; i++) {
subset[0].values.push(randomPoint());
}
// Make a deep copy of the full dataset
dataset = jQuery.extend(true, {}, subset);
chart = nv.models.scatterChart()
.forceX([0, 1])
.forceY([0, 1])
.pointRange([150, 150])
.duration(0); // This doesn't seem to work?
updateSubset(0.5);
d3.select("#chart svg")
.datum(subset)
.call(chart);
// Recalculate subset and update chart when the slider is changed
$("#time").on("input", function() {
var time = Number($("#time").val());
updateSubset(time);
chart.update();
});
#time {
width: 100%;
}
#chart {
width: 100%;
height: 400px;
}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.2/nv.d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
Time control:
</div>
<div>
<input id="time" type="range" min="0" max="1" value="0.5" step="0.01" />
</div>
<div>
Points around that time:
</div>
<div id="chart">
<svg></svg>
</div>

Related

What I code do I add to change this bar chart in real-time in JavaScript?

I'm trying to automatically update this bar chart. I've looked on websites for what code to add, and I don't know how to apply it to what I have.
Here is the page the bar chart is on: https://lifespringchurch.com/spiritual-gifts-survey/
(The bar chart is at the very bottom of the page.)
After a person is done with the survey, their final tallies should be shown in the bar graph.
<html>
<head>
<script src="https://cdn.anychart.com/releases/8.0.0/js/anychart-base.min.js"></script>
</head>
<body>
<div id="container" style="width: 75%; height: 75%"></div>
<script>
anychart.onDocumentReady(function() {
// set the data
var data = {
header: ["Gift", "Value"],
rows: [
["Leadership", document.getElementById("ninja_forms_field_464").value],
["Administration", document.getElementById("ninja_forms_field_465").value],
["Teaching", document.getElementById("ninja_forms_field_467").value],
["Knowledge", document.getElementById("ninja_forms_field_468").value],
["Wisdom", document.getElementById("ninja_forms_field_469").value],
["Prophecy", document.getElementById("ninja_forms_field_470").value],
["Discernment", document.getElementById("ninja_forms_field_471").value],
["Exhortation", document.getElementById("ninja_forms_field_472").value],
["Shepherding", document.getElementById("ninja_forms_field_473").value],
["Faith", document.getElementById("ninja_forms_field_474").value],
["Evangelism", document.getElementById("ninja_forms_field_475").value],
["Apostleship", document.getElementById("ninja_forms_field_476").value],
["Service", document.getElementById("ninja_forms_field_477").value],
["Mercy", document.getElementById("ninja_forms_field_478").value],
["Giving", document.getElementById("ninja_forms_field_479").value],
["Hospitality", document.getElementById("ninja_forms_field_480").value]
]};
// create the chart
var chart = anychart.column();
// add the data
chart.data(data);
// draw
chart.container("giftgraph");
chart.draw();
});
</script>
</body>
</html>
From what I understand I think this code might work.
Any Chart does have some documentation on how to update a chart.
Here is the link: https://docs.anychart.com/Working_with_Data/Data_Sets
for(var i = 0; i < data.rows.length; i++){
data.rows(i, [data.rows[i][0], data.rows[i][1]]);
}
Cheers, Oliver
I went back and there were a few errors with the code so I went back fixed them.
So here is the final code: https://codepen.io/olifire/pen/rNpwpeP
function update(){
// Some code to check if the form is filled
for(var i = 0; i < data.rows.length; i++){
data.rows[i] = [data.rows[i][0], data.rows[i][1]];
// Sets new data
chart.data(data);
// Redraws chart
chart.draw();
}
}
Cheers, Oliver

Can you remove specific grid lines and point labels using highcharts without css?

Currently I'm using css to remove my y-axis grid lines and labels like in the below image.
CSS:
.highcharts-yaxis-grid path:nth-child(1),.highcharts-yaxis-grid path:nth-child(2),
.highcharts-yaxis-grid path:nth-child(4),.highcharts-yaxis-grid path:nth-child(5){ stroke:transparent !important; }
.highcharts-yaxis-labels text:nth-child(1),.highcharts-yaxis-labels text:nth-child(2),
.highcharts-yaxis-labels text:nth-child(4),.highcharts-yaxis-labels text:nth-child(5){ fill:transparent !important; }
Whenever I download the chart as an image they reappear which makes sense as it doesn't see the css file I'm using.
I've tried to search for an answer on highcharts or anything on the net and haven't had luck, so here I am presenting this question.
For example in the load event you can remove specific labels and gridLines:
chart: {
events: {
load: function() {
var chart = this,
yAxis = chart.yAxis[0],
gridLines = yAxis.gridGroup.element.children,
ticks = yAxis.ticks,
tickPositions = yAxis.tickPositions;
gridLines[2].remove();
ticks[tickPositions[2]].label.element.remove();
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/5m0s7th2/
API Reference: https://api.highcharts.com/highcharts/chart.events.load
Using the accepted answer I did this in a loop to avoid losing my middle gridline and tick val.
events: {
load: function() {
var chart = this,
yAxis = chart.yAxis[0],
gridLines = yAxis.gridGroup.element.children,
ticks = yAxis.ticks,
tickPositions = yAxis.tickPositions;
var i = 4;
while(i > -1){
if(i !== 2){
gridLines[i].remove(); // the whole array element is removed from gridLines, so we're doing this backwards
ticks[tickPositions[i]].label.element.remove();
}
i--;
}
}
}
It became a problem when I was starting from 0 using either the accepted answer or my loop. Hope this helps anyone who may have a ton of gridlines.

Chart.js drag points on linear chart

I have a simple linear chart built with Chart.js library.
And i want to allow user to drag points on chart for dynamically change data of it. I tied chartjs-plugin-draggable but it works for me only with annotations. I need graph exactly like this:
https://www.rgraph.net/canvas/docs/adjusting-line.html
But use new graph library in project is not good solution :(
Also i tried to play with dot event's.
UPDATE:
With angular i created something like this.
Maybe if there is no way to add drag&drop to points, there will be a hack to put "sliders" with absolute position on graph on points positions. I didn't find any info too :(
In case anyone is looking for a solution that doesn't require the use of plugins, it's pretty straightforward to do it in vanilla chart.js.
Here's a simple working example - just click and drag a data point
// some data to be plotted
var x_data = [1500,1600,1700,1750,1800,1850,1900,1950,1999,2050];
var y_data_1 = [86,114,106,106,107,111,133,221,783,2478];
var y_data_2 = [2000,700,200,100,100,100,100,50,25,0];
// globals
var activePoint = null;
var canvas = null;
// draw a line chart on the canvas context
window.onload = function () {
// Draw a line chart with two data sets
var ctx = document.getElementById("canvas").getContext("2d");
canvas = document.getElementById("canvas");
window.myChart = Chart.Line(ctx, {
data: {
labels: x_data,
datasets: [
{
data: y_data_1,
label: "Data 1",
borderColor: "#3e95cd",
fill: false
},
{
data: y_data_2,
label: "Data 2",
borderColor: "#cd953e",
fill: false
}
]
},
options: {
animation: {
duration: 0
},
tooltips: {
mode: 'nearest'
}
}
});
// set pointer event handlers for canvas element
canvas.onpointerdown = down_handler;
canvas.onpointerup = up_handler;
canvas.onpointermove = null;
};
function down_handler(event) {
// check for data point near event location
const points = window.myChart.getElementAtEvent(event, {intersect: false});
if (points.length > 0) {
// grab nearest point, start dragging
activePoint = points[0];
canvas.onpointermove = move_handler;
};
};
function up_handler(event) {
// release grabbed point, stop dragging
activePoint = null;
canvas.onpointermove = null;
};
function move_handler(event)
{
// locate grabbed point in chart data
if (activePoint != null) {
var data = activePoint._chart.data;
var datasetIndex = activePoint._datasetIndex;
// read mouse position
const helpers = Chart.helpers;
var position = helpers.getRelativePosition(event, myChart);
// convert mouse position to chart y axis value
var chartArea = window.myChart.chartArea;
var yAxis = window.myChart.scales["y-axis-0"];
var yValue = map(position.y, chartArea.bottom, chartArea.top, yAxis.min, yAxis.max);
// update y value of active data point
data.datasets[datasetIndex].data[activePoint._index] = yValue;
window.myChart.update();
};
};
// map value to other coordinate system
function map(value, start1, stop1, start2, stop2) {
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1))
};
body {
font-family: Helvetica Neue, Arial, sans-serif;
text-align: center;
}
.wrapper {
max-width: 800px;
margin: 50px auto;
}
h1 {
font-weight: 200;
font-size: 3em;
margin: 0 0 0.1em 0;
}
h2 {
font-weight: 200;
font-size: 0.9em;
margin: 0 0 50px;
color: #555;
}
a {
margin-top: 50px;
display: block;
color: #3e95cd;
}
<!DOCTYPE html>
<html>
<!-- HEAD element: load the stylesheet and the chart.js library -->
<head>
<title>Draggable Points</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js#2.9.3/dist/Chart.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<!-- BODY element: create a canvas and render a chart on it -->
<body>
<!-- canvas element in a container -->
<div class="wrapper">
<canvas id="canvas" width="1600" height="900"></canvas>
</div>
<!-- call external script to create and render a chart on the canvas -->
<script src="script.js"></script>
</body>
</html>
Update: My previous answer got deleted because it only featured a link to a plugin solving the issue, however here comes the explanation to what it does:
The general procedure on how to achieve the desired behaviour is to
Intercept a mousedown (and check if it's a dragging gesture) on a given chart
Check if the mousedown was over a data point using the getElementAtEvent function
On mousemove, translate the new Y-Pixel value into a data coordinate using the axis.getValueForPixel function
Synchronously update the chart data using chart.update(0)
as pointed out in this Chart.js issue.
In order to intercept the mousedown, mousemove and mouseup events (the dragging gesture), event listeners for said events need to be created. In order to simplify the creation of the listeners one may use the d3 library in this case as follows:
d3.select(chartInstance.chart.canvas).call(
d3.drag().container(chartInstance.chart.canvas)
.on('start', getElement)
.on('drag', updateData)
.on('end', callback)
);
On mousedown (the 'start' event here), a function (getElement) may be called thatfetches the closest chart element to the pointers location and gets the ID of the Y-Scale
function getElement () {
var e = d3.event.sourceEvent
element = chartInstance.getElementAtEvent(e)[0]
scale = element['_yScale'].id
}
On mousemove ('drag'), the chart data is supposed to be updated according to the current Y-Pixel value of the pointer. We can therefore create an updateData function that gets the position of the clicked data point in the charts data array and the according dataset like this
function updateData () {
var e = d3.event.sourceEvent
var datasetIndex = element['_datasetIndex']
var index = element['_index']
var value = chartInstance.scales[scale].getValueForPixel(e.clientY)
chartInstance.data.datasets[datasetIndex].data[index] = value
chartInstance.update(0)
}
And that's it! If you need to store the resulting value after dragging, you may also specify a callback function like this
function callback () {
var datasetIndex = element['_datasetIndex']
var index = element['_index']
var value = chartInstance.data.datasets[datasetIndex].data[index]
// e.g. store value in database
}
Here is a working fiddle of the above code. The functionality is also the core of the Chart.js Plugin dragData, which may be easier to implement in many cases.
Here is how I fixed using both touchscreen or mouse event x,y coordinates for the excellent d3 example above by wrapping event screen coordinates in a more "generic" x,y object.
(Probably d3 has something similar to handle both types of events but lot of reading to find out..)
//Get an class of {points: [{x, y},], type: event.type} clicked or touched
function getEventPoints(event)
{
var retval = {point: [], type: event.type};
//Get x,y of mouse point or touch event
if (event.type.startsWith("touch")) {
//Return x,y of one or more touches
//Note 'changedTouches' has missing iterators and can not be iterated with forEach
for (var i = 0; i < event.changedTouches.length; i++) {
var touch = event.changedTouches.item(i);
retval.point.push({ x: touch.clientX, y: touch.clientY })
}
}
else if (event.type.startsWith("mouse")) {
//Return x,y of mouse event
retval.point.push({ x: event.layerX, y: event.layerY })
}
return retval;
}
.. and here is how I would use it in the above d3 example to store the initial grab point Y. And works for both mouse and touch.
Check the Fiddle
Here how I solved the problem with using d3 and wanting to drag the document on mobile or touch screens. Somehow with the d3 event subscription all Chart area events where already blocked from bubbling up the DOM.
Was not able to figure out if d3 could be configured to pass canvas events on without touching them. So in a protest I just eliminated d3 as it was not much involved other than subscribing events.
Not being a Javascript master this is some fun code that subscribes the events the old way. To prevent chart touches from dragging the screen only when a chart point is grabed each of the handlers just have to return true and the event.preventDefault() is called to keep the event to your self.
//ChartJs event handler attaching events to chart canvas
const chartEventHandler = {
//Call init with a ChartJs Chart instance to apply mouse and touch events to its canvas.
init(chartInstance) {
//Event handler for event types subscribed
var evtHandler =
function myeventHandler(evt) {
var cancel = false;
switch (evt.type) {
case "mousedown":
case "touchstart":
cancel = beginDrag(evt);
break;
case "mousemove":
case "touchmove":
cancel = duringDrag(evt);
break;
case "mouseup":
case "touchend":
cancel = endDrag(evt);
break;
default:
//handleDefault(evt);
}
if (cancel) {
//Prevent the event e from bubbling up the DOM
if (evt.cancelable) {
if (evt.preventDefault) {
evt.preventDefault();
}
if (evt.cancelBubble != null) {
evt.cancelBubble = true;
}
}
}
};
//Events to subscribe to
var events = ['mousedown', 'touchstart', 'mousemove', 'touchmove', 'mouseup', 'touchend'];
//Subscribe events
events.forEach(function (evtName) {
chartInstance.canvas.addEventListener(evtName, evtHandler);
});
}
};
The handler above is initiated like this with an existing Chart.js object:
chartEventHandler.init(chartAcTune);
The beginDrag(evt), duringDrag(evt) and endDrag(evt) have the same basic function as in the d3 example above. Just returns true when wanting to consume the event and not pasing it on for document panning and similar.
Try it in this Fiddle using a touch screen. Unless you touch close to select a chart point the rest of the chart will be transparent to touch/mouse events and allow panning the page.

How to add toolbar to BokehJS plot?

My goal is to add a toolbar to a BokehJS plot. According to the plot tools documention this should be possible by doing (translating the Python example to Javascript):
plot.add_tools(new Bokeh.BoxZoomTool());
plot.add_tools(new Bokeh.ResetTool());
plot.toolbar_location = "right";
I have added these lines to the basic BokehJS example from the documentation, and they don't produce errors/warnings. However, the toolbar does not show up (properly) and the tools don't really seem to work.
I have prepared a minimal JSFiddle to demonstrate the problem: When using the rectangle select tool the plot moves around strangely, which uncovers an unstyled version of the toolbar rendered underneath the plot.
So the question is how can I get a properly working toolbar in BokehJS?
Add bk-root to the root element. <div id="plot" class="mybokehplot bk-root"></div>
Add corresponding css files (bokeh-0.12.0.min.css and bokeh-widgets-0.12.0.min.css).
JSFiddle here:
https://jsfiddle.net/blackmiaool/xzvgrqLj/
Snippet here:
// create some data and a ColumnDataSource
var x = Bokeh.LinAlg.linspace(-0.5, 20.5, 10);
var y = x.map(function(v) {
return v * 0.5 + 3.0;
});
var source = new Bokeh.ColumnDataSource({
data: {
x: x,
y: y
}
});
// create some ranges for the plot
var xdr = new Bokeh.Range1d({
start: -0.5,
end: 20.5
});
var ydr = Bokeh.Range1d(-0.5, 20.5);
// make the plot
var plot = new Bokeh.Plot({
title: "BokehJS Plot",
x_range: xdr,
y_range: ydr,
plot_width: 400,
plot_height: 400,
background_fill_color: "#F2F2F7"
});
// add axes to the plot
var xaxis = new Bokeh.LinearAxis({
axis_line_color: null
});
var yaxis = new Bokeh.LinearAxis({
axis_line_color: null
});
plot.add_layout(xaxis, "below");
plot.add_layout(yaxis, "left");
// add grids to the plot
var xgrid = new Bokeh.Grid({
ticker: xaxis.ticker,
dimension: 0
});
var ygrid = new Bokeh.Grid({
ticker: yaxis.ticker,
dimension: 1
});
plot.add_layout(xgrid);
plot.add_layout(ygrid);
// add a Line glyph
var line = new Bokeh.Line({
x: {
field: "x"
},
y: {
field: "y"
},
line_color: "#666699",
line_width: 2
});
plot.add_glyph(line, source);
// now add the tools
plot.add_tools(new Bokeh.BoxZoomTool());
plot.add_tools(new Bokeh.ResetTool());
plot.toolbar_location = "right";
// add the plot to a document and display it
var doc = new Bokeh.Document();
doc.add_root(plot);
var div = document.getElementById("plot");
Bokeh.embed.add_document_standalone(doc, div);
.mybokehplot {
position: relative;
width: 100%;
height: 100%;
border: 1px dashed #ccc;
}
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.0.min.js"></script>
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-0.12.0.min.css">
<link rel="stylesheet" type="text/css" href="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.0.min.css">
<div id="plot" class="mybokehplot bk-root"></div>
P.S. I found that the edition of bokeh's css files and js files must be same, or you would get lots of bugs.

highstocks - legend position and refresh legend values on mousemove of multiple charts

Here is the scenario. I've multiple highstocks say 10 charts on a single page. Currently I've written 500 lines of code to position the legend, show tooltip and refresh the legend values on mousemove.
No. of legends vary per chart. On mousemove values of all the legends are updated. I need to optimize the code I am using highstocks v1.2.2.
Above screenshot shows 2 charts. Return, Basket, vs Basket Spread are legends and it's values are updated on every mousemove.
Please find this fiddle for example. In my case legends are positioned and updated values on mouse move with hundreds of lines of code. When I move the mouse the legend values of Return and Basket of first chart and the legend values of vs Basket Spread are updated. It's working fine but with lots of javascript code. So I need to optimize it less code or with highstocks built-in feature.
Update
User #wergeld has posted new fiddle. As I've shown in screenshot when cross-hair is being moved over any chart, the legend values of all the charts should be updated.
Is there anyway to implement the same functionality with less code or is there built-in feature available in highstocks ???
Using this as a reference.
Basic example would be to use the events.mouseover methods:
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
var theLegendList = $('#legend');
var theSeriesName = this.series.name;
var theYValue = this.y;
$('li', theLegendList).each(function (l) {
if (this.innerText.split(':')[0] == theSeriesName) {
this.innerText = theSeriesName + ': ' + theYValue;
}
});
}
}
}
}
}
This is assuming I have modded the <li> to be:
$('<li>')
.css('color', serie.color)
.text(serie.name + ': NA')
.click(function () {
toggleSeries(i);
})
.appendTo($legend);
You would then need to handle the mouseout event but I do not know what you want to do there.
Working example.
EDIT:
Here is a version using your reference OHLC chart to put the values in a different legend location when any point in the chart is hovered.
plotOptions: {
series: {
point: {
events: {
mouseOver: function () {
//using the ohlc and volumn data sets created at runtime.
var stockVal = ohlc[this.index][4]; // show close value
var stockVolume = volume[this.index][1];
var theChart = $('#container').highcharts();
var theLegendList = $('#legend');
$('li', theLegendList).each(function (l) {
var legendTitle = theChart.series[l].name;
if (l === 0) {
this.innerText = legendTitle + ': ' + stockVal;
}
if (l === 1) {
this.innerText = legendTitle + ': ' + stockVolume;
}
});
}
}
}
}
}

Categories

Resources