Controls an animation using a progress bar - d3.js

I am using these libraries
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
I am making an animation that I try to synchronize with a progress bar. My animation would last a total of 15 seconds, and would have more elements.
For this example, I am making an animation where I have a rectangle and a circle.
var rectangle = vis.append("rect")
var circle=vis.append("circle");
For each element I define its animation properties in a json specifying a starting point (.begin) and an end point (.destiny), also a delay (.delay) then the transition is generated with the definition of those properties for each of them. an example of this json is this:
circleValues={
"delay":4000,
"duration":3000,
"begin": {
"cx": 250,
"r":20,
"fill": "blue"
},
"destiny": {
"cx": 0,
"fill": "orane"
}
}
So for the case of the circle, I will generate an animation that will start with these properties:
//circleValues.begin
"begin": {
"cx": 250,
"r":20,
"fill": "blue"
}
and a transition will be generated where in the end it will end with these properties:
//circleValues.destiny
"destiny": {
"cx": 0,
"fill": "orange"
}
The total animation will be in the course of 15 seconds, in the case of the circle and according to the properties that I defined, it means that it trasntion will start in 4 seconds, for that it will have a delay of 4000 (circleValues.delay) and will last (circleValues.duration) seconds.
Then I have a function called initAnimation() where I execute this transition for each element in the style:
function initAnimation(percentage){
//circle properties
circle.styles(circleValues.begin)
.transition()
.delay(circleValues.delay)
.duration(circleValues.duration)
.ease(d3.easeLinear())
.styles(circleValues.destiny)
}
I want to control the animation using a progress bar in this case a progressbar that I define using:
//15 seconds, min value is 0 seconds, max value is 15 seconds, step=1
<input id="progressbar" type="range" min="0" max="15" step="1" value="0" oninput="setPercentValue(+event.target.value)">
and I make the progress bar automatically move every second with the following code, I also have 2 buttons to stop animation and resume animation.
var duration=15000; //15 seconds duration animation
var second=0; // current value of progress var
var interval; // instance of setInterval
function pause(){
clearInterval(interval);
}
function play(){
moveProgressBar();
}
function moveProgressBar(){
interval= setInterval(()=>{
console.log(second);
second++;
document.getElementById("progressbar").value=second;
if(second==15){
clearInterval(interval);
}
},1000)
}
and I execute all my code with the invocation of these 2 lines.
initAnimation(0);
moveProgressBar();
The problem is that I don't know how to make it so that according to the selected time of my progress bar, it corresponds to the current moment in which each element of my animation should be. I would like that depending on the value you select when moving the progress bar, it corresponds to the behaviors that I defined for each of my elements, even if I pause or resume the animation. This is beyond my knowledge at d3.js.
How can I do it?
d3.select("#visualization").append('svg');
var vis = d3.select("svg").attr("width", 800).attr("height", 150).style("border", "1px solid red");
var duration=15000; //15 seconds duration animation
var second=0; // current value of progress var
var interval; // instance of setInterval
function pause(){
clearInterval(interval);
}
function play(){
moveProgressBar();
}
function moveProgressBar(){
interval= setInterval(()=>{
second++;
document.getElementById("progressbar").value=second;
if(second==15){
clearInterval(interval);
}
},1000)
}
function setPercentValue(percentage) {
second=percentage;
rectangle.interrupt();
circle.interrupt();
initAnimation(percentage);
}
var rectValues={
"delay":2000,
"duration":5000,
"begin": {
"x": 0,
"y": 0,
"height": 70,
"width": 100,
"opacity": 1,
"fill": "red"
},
"destiny": {
"x": 250,
"y": 1,
"height": 100,
"width": 120,
"opacity": 0.8,
"fill": "green"
}
}
var rectangle = vis.append("rect")
var circle=vis.append("circle");
var circleValues={
"delay":4000,
"duration":3000,
"begin": {
"cx": 250,
"r":20,
"fill": "blue"
},
"destiny": {
"cx": 0,
"fill": "orange"
}
}
function initAnimation(percentage){
//rectangle properties
rectangle.styles(rectValues.begin)
.transition()
.delay(rectValues.delay)
.duration(rectValues.duration)
.ease(t => d3.easeLinear(percentage + t * (1 - percentage)))
.styles(rectValues.destiny)
//circle properties
circle.styles(circleValues.begin)
.transition()
.delay(circleValues.delay)
.duration(circleValues.duration)
.ease(t => d3.easeLinear(percentage + t * (1 - percentage)))
.styles(circleValues.destiny)
}
initAnimation(0);
moveProgressBar();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
<div id="visualization"></div>
<!--<input type="range" min="0" max="1" step="0.1" value="0" oninput="setPercentValue(+event.target.value)">-->
<input id="progressbar" type="range" min="0" max="15" step="1" value="0" oninput="setPercentValue(+event.target.value)">
<button onclick="pause()">
pause
</button>
<button onclick="play()">
play
</button>

As you've specified in your post, your animations are composed of a delay followed by a transition.
If you want to display the result of such animation at some fixed time T (as determined by the progressbar value), you need to evaluate the following conditions:
if T is less than the delay, then you are still waiting for the transition to start. The shape that you're animating still has all attributes set to the begin values and you only need to make the delay shorter
if T is more than the delay, but less then delay+transition duration, then the shape is already in the transition phase. In this case, you need to determine how far in translation you are at time T and interpolate the attribute values (so the values will be something between begin and destiny), also the transition duration needs to be shortened
if T is more than the delay, then the transition is already completed. In this case, the attribute values will be set to destiny values
This logic is encapsulated in the function interpolateValuesAtTime below. This function, in addition to calculating new delay,duration,begin and destiny values, it also creates a new easing function, which calculates how the easing should continue from time T onward (details about how the modified easing functions works are described in detail on this page)
You can test it in the snippet below.
Here are few notes regarding its functionality:
after clicking on the progressbar, the animation will be shown at given time (the animation will be paused, press play to start it)
also, after pressing play, the animation starts from the time corresponding to the progress bar (and since the progress bar counts only whole seconds, that implies that if you pause & play the animation, it may seem to jump back a little)
const progressbar = document.getElementById('progressbar');
d3.select("#visualization").append('svg');
const vis = d3.select("svg").attr("width", 800).attr("height", 150).style("border", "1px solid red");
const rectangle = vis.append("rect")
const circle = vis.append("circle");
let second = 0;
let interval;
const rectValues = {
"delay": 2000,
"duration": 5000,
"begin": {
"x": 0,
"y": 0,
"height": 70,
"width": 100,
"opacity": 1,
"fill": "red"
},
"destiny": {
"x": 250,
"y": 1,
"height": 100,
"width": 120,
"opacity": 0.8,
"fill": "green"
}
};
const circleValues = {
"delay": 4000,
"duration": 3000,
"begin": {
"cx": 250,
"r": 20,
"fill": "blue"
},
"destiny": {
"cx": 0,
"fill": "orange"
}
}
function resumedEasingFunction(easingFunction, progress) {
return function (xAfterResume) {
const xOriginal = d3.scaleLinear()
.domain([0, 1])
.range([progress, 1])
(xAfterResume);
return d3.scaleLinear()
.domain([easingFunction(progress), 1])
.range([0, 1])
(easingFunction(xOriginal));
};
}
function interpolateValuesAtTime(values, easingFunction, time) {
const interpolatedValues = JSON.parse(JSON.stringify(values));;
let progress;
if (values.delay >= time) {
//the initial delay has not yet elapsed
interpolatedValues.delay = values.delay - time;
progress = 0;
} else if (values.delay + values.duration >= time) {
//the animation is running
interpolatedValues.delay = 0;
interpolatedValues.duration = values.delay + values.duration - time;
progress = (values.duration - interpolatedValues.duration) / values.duration;
for (let key in values.begin) {
const startValue = values.begin[key];
if (key in values.destiny) {
const endValue = values.destiny[key];
const interpolator = d3.interpolate(startValue, endValue);
interpolatedValues.begin[key] = interpolator(progress);
}
}
} else {
//the animation has already ended
interpolatedValues.delay = 0;
interpolatedValues.duration = 0;
progress = 1;
for (let key in values.destiny) {
interpolatedValues.begin[key] = values.destiny[key];
}
}
interpolatedValues.easingFunction = resumedEasingFunction(easingFunction, progress);
return interpolatedValues;
}
function play() {
startAnimation(progressbar.value * 1000);
startMovingProgressbar();
}
function pause() {
stopMovingProgressBar();
pauseAnimation();
}
function updateTimeBasedOnProgressbar() {
stopMovingProgressBar();
second = progressbar.value;
startAnimation(1000 * second);
pause();
}
function stopMovingProgressBar(){
clearInterval(interval);
}
function startMovingProgressbar() {
clearInterval(interval);
interval = setInterval(() => {
second++;
progressbar.value = second;
if (second == 15) {
clearInterval(interval);
}
}, 1000)
}
function pauseAnimation() {
pauseShape(rectangle);
pauseShape(circle);
}
function pauseShape(shape) {
shape.transition()
.duration(0)
}
function startAnimation(miliseconds) {
//rectangle properties
startShapeAnimation(rectangle, rectValues, miliseconds);
//circle properties
startShapeAnimation(circle, circleValues, miliseconds);
}
function startShapeAnimation(shape, shapeValues, miliseconds) {
const valuesAtTime = interpolateValuesAtTime(shapeValues, d3.easeLinear, miliseconds);
shape.attrs(valuesAtTime.begin)
.transition()
.duration(0)
.attrs(valuesAtTime.begin)
.transition()
.delay(valuesAtTime.delay)
.duration(valuesAtTime.duration)
.ease(valuesAtTime.easingFunction)
.attrs(valuesAtTime.destiny);
}
updateTimeBasedOnProgressbar();
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v1.min.js"></script>
</head>
<body>
<div id="visualization"></div>
<input id="progressbar" type="range" min="0" max="15" step="1" value="0" oninput="updateTimeBasedOnProgressbar()">
<button onclick="pause()">pause</button>
<button onclick="play()">play</button>
</script>
</body>
</html>

Related

NVD3 - show tick lines only on axes

TLDR: I have an NVD3 graph that shows tick lines all across the axis, but I would like to change it so it only displays on the axis lines if possible.
Here is a live example:
var app = angular.module('plunker', ['nvd3']);
app.controller('MainCtrl', function($scope) {
$scope.options = {
chart: {
type: 'lineChart',
height: 450,
margin : {
top: 20,
right: 20,
bottom: 80,
left: 55
},
x: function(d){ return d.x; },
y: function(d){ return d.y; },
useInteractiveGuideline: true,
xAxis: {
axisLabel: 'Timeline',
tickFormat: function(d) {
return d3.time.format('%B %d')(new Date(d))
},
ticks: 6,
showMaxMin: false
},
yAxis: {
axisLabel: 'Molecular density (kg/m^3)',
tickFormat: function(d){
return d3.format('.02f')(d);
},
axisLabelDistance: -10,
showMaxMin: false
}
}
};
$scope.data = [{"key":"K7 molecules","values":[{"x":1435708800000,"y":8},{"x":1435795200000,"y":9},{"x":1435881600000,"y":8},{"x":1435968000000,"y":8},{"x":1436054400000,"y":9},{"x":1436140800000,"y":9},{"x":1436227200000,"y":8},{"x":1436313600000,"y":8},{"x":1436400000000,"y":9},{"x":1436486400000,"y":9},{"x":1436572800000,"y":7},{"x":1436659200000,"y":8}],"area":true,"color":"#0CB3EE"},{"key":"N41 type C molecules","values":[{"x":1435708800000,"y":8},{"x":1435795200000,"y":7},{"x":1435881600000,"y":8},{"x":1435968000000,"y":9},{"x":1436054400000,"y":7},{"x":1436140800000,"y":9},{"x":1436227200000,"y":8},{"x":1436313600000,"y":9},{"x":1436400000000,"y":9},{"x":1436486400000,"y":9},{"x":1436572800000,"y":9},{"x":1436659200000,"y":8}],"area":true,"color":"#383838"}];
});
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>Angular-nvD3 Line Chart</title>
<script>document.write('<base href="' + document.location + '" />');</script>
<link rel="stylesheet" href="style.css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.min.css"/>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.1/nv.d3.min.js"></script>
<script src="https://rawgit.com/krispo/angular-nvd3/v1.0.1/dist/angular-nvd3.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<nvd3 options="options" data="data" class="with-3d-shadow with-transitions"></nvd3>
</body>
</html>
Is there any way I could make the tick lines appear just on the axes line only? To make it clear, this is what it looks like:
I used a different library to generate the following plot, and I would like the tick lines to appear just on the axis lines like this example instead:
It appears that there is no real way to do this with NVD3 as it does not provide a way to show tick marks on the axis. However, we could add our own tick marks by fetching the chart SVG and then modifying it.
I've attached an example that adds tick marks to X-Axis, and it is basically slightly modified based on this jsfiddle here: http://jsfiddle.net/3r88bgjw
var data;
data = [{
values: [],
}, ];
var i, x;
var prevVal = 3000;
var tickCount = 2000;
for (i = 0; i < tickCount; i++) {
x = 1425096000 + i * 10 * 60; // data points every ten minutes
if (Math.random() < 0.8) { // add some gaps
prevVal += (Math.random() - 0.5) * 500;
if (prevVal <= 0) {
prevVal = Math.random() * 100;
}
data[0].values.push({
x: x * 1000,
y: prevVal
});
}
}
var chart;
nv.addGraph(function() {
chart = nv.models.historicalBarChart();
chart.xScale(d3.time.scale()) // use a time scale instead of plain numbers in order to get nice round default values in the axis
.color(['#68c'])
.useInteractiveGuideline(true) // check out the css that turns the guideline into this nice thing
.margin({
"left": 80,
"right": 50,
"top": 20,
"bottom": 30,
})
.noData("There is no data to display.")
.duration(0);
var tickMultiFormat = d3.time.format.multi([
["%-I:%M%p", function(d) {
return d.getMinutes();
}], // not the beginning of the hour
["%-I%p", function(d) {
return d.getHours();
}], // not midnight
["%b %-d", function(d) {
return d.getDate() != 1;
}], // not the first of the month
["%b %-d", function(d) {
return d.getMonth();
}], // not Jan 1st
["%Y", function() {
return true;
}]
]);
chart.xAxis
.showMaxMin(false)
.tickPadding(10)
.tickFormat(function(d) {
return tickMultiFormat(new Date(d));
});
chart.yAxis
.tickFormat(d3.format(",.0f"));
var svgElem = d3.select('#chart svg');
svgElem
.datum(data)
.transition()
.call(chart);
// make our own x-axis tick marks because NVD3 doesn't provide any
var tickY2 = chart.yAxis.scale().range()[1];
var lineElems = svgElem
.select('.nv-x.nv-axis.nvd3-svg')
.select('.nvd3.nv-wrap.nv-axis')
.select('g')
.selectAll('.tick')
.data(chart.xScale().ticks())
.append('line')
.attr('class', 'x-axis-tick-mark')
.attr('x2', 0)
.attr('y1', tickY2 + 7)
.attr('y2', tickY2)
.attr('stroke-width', 3);
// set up the tooltip to display full dates
var tsFormat = d3.time.format('%b %-d, %Y %I:%M%p');
var contentGenerator = chart.interactiveLayer.tooltip.contentGenerator();
var tooltip = chart.interactiveLayer.tooltip;
tooltip.contentGenerator(function(d) {
d.value = d.series[0].data.x;
return contentGenerator(d);
});
tooltip.headerFormatter(function(d) {
return tsFormat(new Date(d));
});
return chart;
});
<div>Try resizing the panel to see the various types of time labels.</div>
<br>
<div id="chart">
<svg></svg>
</div>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.4/nv.d3.min.js"></script>

svg.js, click event does not fire when animation is running

I try to bind a click event on symbol in a svg.
When a element spinns in the symbol, the click event is not fired, stops/pauses the animation, the click event fires.
How can i fix this, that the click events get fired every time i click on it, regardless if the animations run or not.
.as-console-wrapper{
display: none!important;
}
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Schematic</title>
<script src="https://cdn.jsdelivr.net/npm/#svgdotjs/svg.js#3.0/dist/svg.min.js"></script>
<!--<script src="assets/js/vue.min.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.common.dev.min.js"></script>
<script>
const color = {
blue: "#007bff",
indigo: "#6610f2",
purple: "#6f42c1",
pink: "#e83e8c",
red: "#dc3545",
orange: "#fd7e14",
yellow: "#ffc107",
green: "#28a745",
teal: "#20c997",
cyan: "#17a2b8",
white: "#fff",
gray: "#6c757d",
"gray-dark": "#343a40",
primary: "#007bff",
secondary: "#6c757d",
success: "#28a745",
info: "#17a2b8",
warning: "#ffc107",
danger: "#dc3545",
light: "#f8f9fa",
dark: "#343a40"
};
let baseColor = {
background: "#000",
"fill-opacity": 0.5,
stroke: color.white,
"stroke-width": 1,
"stroke-opacity": 0.5
};
let pipeColor = {
fill: color.blue,
opacity: 1
};
let pumpColor = {
fill: color.gray,
"fill-opacity": 0.8
}
document.addEventListener("DOMContentLoaded", () => {
// parent drawing
let draw = SVG().addTo('#schematic').size("100%", 500);
let pumpGroup = draw.symbol();
pumpGroup.click(function () {
alert("Pump clicked!");
});
let height = 50;
let radius = 30;
let chase = pumpGroup.rect(80, height).attr(baseColor).move(0, 0); // 520, 370
let motor1 = pumpGroup.circle(radius).attr(pumpColor).move(45, (height / 2) - radius / 2); // 525, 380
let motor2 = pumpGroup.circle(radius).attr(pumpColor).move(5, (height / 2) - radius / 2); // 565, 380
let fan1 = pumpGroup.image("https://cdn0.iconfinder.com/data/icons/screw/512/fan-ventilator-propeller-rotor-motion-512.png").scale(0.05).move(940, 240); //.animate().rotate(360, 256 + 940, 256 + 240).loop();
let fan2 = pumpGroup.image("https://cdn0.iconfinder.com/data/icons/screw/512/fan-ventilator-propeller-rotor-motion-512.png").scale(0.05).move(140, 240);
//
// 1 = slave, 2 = master
let fant1Runner = fan1.animate(1500).ease("-").rotate(360, 256 + 940, 256 + 240).loop().timeline().pause();
let fant2Runner = fan2.animate(1500).ease("-").rotate(360, 256 + 140, 256 + 240).loop().timeline().pause();
setInterval(() => {
fant2Runner.play();
fant1Runner.play();
setTimeout(() => {
fant2Runner.pause();
fant1Runner.pause();
}, 2500)
}, 5000);
draw.use(pumpGroup).move(10, 10).click(() => {
alert("Clicked on pump!");
});
});
</script>
</head>
<body>
<div id="app">
<div id="schematic"></div>
</div>
</body>
</html>
For demsonstation i create a minimal snippet.
The fans start spinning after 5sec, run then for 2.5 sec and stops.
As written above, when the fans spin, no click fires.
Thanks for any advise.

c3js Multi color design

I've implementing barchart to denote my result set. I am trying to create this design using c3js. I referred the documentation but have not getting the shaded design. Please someone guide me to solve this.
My Code Snippet is,
var chart = c3.generate({
bindto: '#chart',
size : {
width: 600,
height: 200
},
data: {
columns: [
["data1", 40, 20, 20, 20],
["data2", 20, 10, 30, 30]
],
type: 'bar',
groups: [
['data1', 'data2']
],
order: null,
labels: {
format: function(v, id, i, j) {
return v;
}
}
},
bar: {
space: 0.2,
width: {
ratio: 0.2 // this makes bar width 50% of length between ticks
}
},
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.3.0/c3.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.3.0/c3.min.js"></script>
<div id="chart"></div>
My requirement is to design like below image in bars in barchart,
Thanks
The solution in the snippet introduces some d3 SVG manipulation which puts a region over the bars of each targeted series. The region has a hatched grey line and a semi-transparent fill. The function to do this is written to be portable i.e. outside of the C3 construtor params, and is invoked by the onrendered c3 event.
The doClone() function duplicates nodes in place which is useful to know, and the creation of the pattern and use as a colour via 'url(#patternid)' is also a potentially useful technique.
Also available at https://codepen.io/JEE42/pen/yRWbvq
function doClone(node){ // clone the given node
return d3.select(node.parentNode.insertBefore(node.cloneNode(true), node.nextSibling));
}
function hatchBars(hatchTargets){ // Place a hatching pattern over the target bars.
for (var i = 0; i < hatchTargets.length; i = i + 1){
d3.select('.c3-bars-' + hatchTargets[i]).each(function(d, i){
d3.select(this).selectAll('path').each(function(d, i){
var node = d3.select(this).node();
var daClone = doClone(node);
daClone
.style('fill', 'url(#hash4_4)')
.style('stroke', 'url(#hash4_4)');
});
})
}
}
c3.chart.internal.fn.afterInit = function () {
d3.select('defs')
.append('pattern')
.attr('id', "hash4_4") // use id to get handle in a moment
.attr('width', 14)
.attr('height', 14)
.attr('patternUnits', "userSpaceOnUse")
.attr('patternTransform', "rotate(45 0 0 )")
.append("rect")
.attr('width', 14)
.attr('height', 14)
.attr('fill', '#00000000') // transparent background
d3.select('#hash4_4') // get the pattenn
.append('line') // add a line
.attr('y2', 14)
.style('stroke', "#00000044") // semi-transparent bars
.attr('stroke-width', 14)
};
//
// Standard C3 chart render with one twist which is the onrendered event call at the end.
//
var chart = c3.generate({
bindto: '#chart',
data: {
columns: [
['data1', -30, 200, 200, 400, -150, 250],
['data2', 130, 100, -100, 200, -150, 50],
],
type: 'bar',
groups: [
['data1', 'data2']
]
},
grid: {
y: {
lines: [{value:0}]
}
},
onrendered: function () { // execute after drawn
hatchBars(['data2']); // Place a hatching pattern over the target bars.
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/c3/0.6.7/c3.min.js"></script>
<div class='chart-wrapper'>
<div class='chat' id="chart"></div>
</div>

How to change text in multiple lines / places of a Textbox in FabricJS?

I need to replace text of particular selection when the input value has been changed.
On the initial rendering, I get an object of selections and fields.
Secondly, inputs get values of fields.
Assume I'm changing a value of the Line input, since this field controls two lines, both of those green texts should be replaced with the new one.
http://jsfiddle.net/hkvmLwfu/
Tnx
////////////////////////////////////////////////////////////////////////
/////////////// THIS FUNCTION NEEDS TO BE DEVELOPED ////////////////////
////////////////////////////////////////////////////////////////////////
function replaceTextBySelection(fieldId, fieldValue, canvas, text){
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
var canvas = new fabric.Canvas('paper');
canvas.setHeight(300);
canvas.setWidth(500);
var text = new fabric.Textbox('Sample Line 1 Line 2 Line 3', {
left: 50,
top: 10,
fontFamily: 'arial',
fill: '#333',
fontSize: 50
});
canvas.add(text);
canvas.renderAll();
const fields = {
FIELD_1: {
value: "Sample",
color: '#F00'
},
FIELD_2: {
value: "Line",
color: '#0F0'
}
}
selections = [
{
rowId: 0,
offset: 0,
length: 6,
field: "FIELD_1"
},
{
rowId: 1,
offset: 0,
length: 4,
field: "FIELD_2"
},
{
rowId: 2,
offset: 0,
length: 4,
field: "FIELD_2"
}
]
selections.map((obj)=>{
text.setSelectionStart(obj.offset);
text.setSelectionEnd(obj.offset + obj.length);
text.setSelectionStyles();
for (let i = text.selectionStart; i < text.selectionEnd; i++) {
text.insertCharStyleObject(obj.rowId, i, {
textBackgroundColor: fields[obj.field].color
})
}
canvas.renderAll();
return obj;
});
$('#FIELD_1').val( fields['FIELD_1'].value );
$('#FIELD_2').val( fields['FIELD_2'].value );
$("input").keyup(function(t){
replaceTextBySelection(this.id, this.value, canvas, text);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.19/fabric.min.js"></script>
<canvas id="paper" width="400" height="400" style="border:1px solid #ccc"></canvas>
<input type="text" id="FIELD_1" />
<br/>
<input type="text" id="FIELD_2" />
Your example complicates things because the selections object reasons about rows, but the text doesn't actually contain rows, only spaces that get wrapped to a new line.
It gets easier if you define your text instead as
var text = new fabric.Textbox('Sample\nLine 1\nLine 2\nLine 3', {
left: 50,
top: 10,
fontFamily: 'arial',
fill: '#333',
fontSize: 50
});
Using this text I have created a functional example: http://jsfiddle.net/hkvmLwfu/12/. I'm not sure this is entirely how you want it, but you should be able to pick it up from here.
Just use \n where you want to break the line. It will work like <br/>.

what is the proper way to create a Date-Time axis for the dc.js bubble chart?

Struggling my way through dc.js bubble charts.
My objective is to have the X axis be a date/time line.
I have the following code, which works fine with a linear X, but shows nothing with the date/time:
http://jsfiddle.net/eugene_goldberg/LL41xxho/13/
<body>
<div class="container">
<div class="row">
<div id="bubble-chart" class="dc-chart">
<div class="clearfix"></div>
</div>
</div>
</div>
</body>
var data = [
{date: "12/27/2012", label: "a1", x: 2, y: 190, bubble: 5},
{date: "12/28/2012", label: "a2", x: 2, y: 10, bubble: 5},
{date: "12/29/2012", label: "a3", x: 95, y: 300, bubble: 10},
{date: "01/04/2013", label: "a9", x: 51, y: 90, bubble: 1},
];
var ndx = crossfilter(data);
var parseDate = d3.time.format("%m/%d/%Y").parse;
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var dateDim = ndx.dimension(function(d) {return d.date;});
var xDim = ndx.dimension(function(d) {return d.x;});
var dateGroup = dateDim.group().reduce(
function(p, v) {
++p.count;
p.label = v.label;
p.bubble = v.bubble;
p.x = v.x;
p.y = v.y;
return p;
},
function(p, v) {
--p.count;
p.bubble = 0;
p.label = "";
p.x = 0;
p.y = 0;
return p;
}, function() {
return { count: 0, x: 0, y:0, label: "" };
});
var minDate = dateDim.bottom(1)[0].date;
var maxDate = dateDim.top(1)[0].date;
var xRange = [-10, d3.max(dateGroup.all(), function(d) { return d.value.x + d.value.bubble*2; }) ],
yRange = [-10, d3.max(dateGroup.all(), function(d) { return d.value.y + d.value.bubble*2; }) ];
var bubbleChart = dc.bubbleChart("#bubble-chart");
//debugger;
bubbleChart
.dimension(dateDim)
.group(dateGroup)
// .x(d3.time.scale()
// .domain([minDate, maxDate])
// .nice(d3.time.day)
//.range(xRange))
// )
.x(d3.scale.linear().domain(xRange))
.y(d3.scale.linear().domain(yRange))
.width(400)
.height(400)
.yAxisPadding(50)
.xAxisPadding(50)
.xAxisLabel('X')
.yAxisLabel('Y')
.label(function (p) {
return p.value.label;
})
.renderLabel(true)
.title(function (p) {
return [
"x: " + p.value.x,
"y: " + p.value.y,
"Bubble: " + p.value.bubble,
]
.join("\n");
})
.renderTitle(true)
.renderHorizontalGridLines(true) // (optional) render horizontal grid lines, :default=false
.renderVerticalGridLines(true)
.maxBubbleRelativeSize(0.3)
.keyAccessor(function (p) {
return p.value.x;
})
.valueAccessor(function (p) {
return p.value.y;
})
.radiusValueAccessor(function (p) {
return p.value.bubble;
});
dc.renderAll();
I have looked through same similar discussions here, but did not find an answer.
In my fiddle, I have lines 51 through 55 commented out, which is where my attempt at the time axis is. The line right below has .x as d3.linear, and works fine.
How do I do this properly to have my time range be my .x?
It can sure be frustrating when nothing comes up in a dc.js chart, but here I think you just need to use a date when you mean a date.
I commented out the keyAccessor so that the chart uses the default, the key which is of Date type, and the chart draws okay.
http://jsfiddle.net/gordonwoodhull/fwebkwqo/4/
(x is of of course not of Date type.)

Resources