How to enable responsive design for Fabric.js - responsive-design

Is there a way to make a Fabric.js canvas resize with the browser to enable the same result on any device? I'm talking about responsive design.
Has anyone a code example?

This jsFiddle is a working solution. It is inspired by this github issue.
These are the required things:
A div that surrounds the canvas that is controlled by fabric.
<div class="fabric-canvas-wrapper">
<canvas id="theCanvas"></canvas>
</div>
And a window resize handler that triggers the calculation and setting of the new canvas dimension and the zoom.
window.onresize = resizeCanvas() {
const outerCanvasContainer = document.getElementById('fabric-canvas-wrapper');
const ratio = canvas.getWidth() / canvas.getHeight();
const containerWidth = outerCanvasContainer.clientWidth;
const scale = containerWidth / canvas.getWidth();
const zoom = canvas.getZoom() * scale;
canvas.setDimensions({width: containerWidth, height: containerWidth / ratio});
canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
}
That's all. It seems to work perfectly. Please let me know if any issues come up with this solution.
I have used fabric.js in version 3.6.2.

Basically you need to get the device screen's width and height. Afterwards just resize the canvas accordingly in your Javascript. Example:
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
var height = (window.innerHeight > 0) ? window.innerHeight : screen.height;
var canvas = document.getElementById("canvas");
canvas.width = width;
canvas.height = height;
You might have screens with varying resolution ratios though, what I usually do in this case is calculate your original width's and the device's width ratio and then adjust both width and height accordingly with that value. Example:
var originalWidth = 960; //Example value
var width = (window.innerWidth > 0) ? window.innerWidth : screen.width;
var widthRatio = originalWidth / width;
canvas.width *= widthRatio;
canvas.height *= widthRatio;
This usually works fine for me on any device, hope this helps.

I know it's been a while but i found an easier way to do it using the zoom feature.
This code is triggered when the window resize is completed. It allows me to determine based on the new dimension, the zoom factor needed to resize everything in the canvas.
function rescale_canvas_if_needed()
{
var optimal_dimensions = get_optimal_canvas_dimensions();
var scaleFactor=optimal_dimensions[0]/wpd.canvas_w;
if(scaleFactor != 1) {
wpd_editor.canvas.setWidth(optimal_dimensions[0]);
wpd_editor.canvas.setHeight(optimal_dimensions[1]);
wpd_editor.canvas.setZoom(scaleFactor);
wpd_editor.canvas.calcOffset();
wpd_editor.canvas.renderAll();
}
}
$( window ).resize(function() {
clearTimeout(resizeId);
resizeId = setTimeout(handle_resize, 500);
});
function handle_resize()
{
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}

i thought this one was the answer I needed https://stackoverflow.com/a/29445765/1815624
it seemed so close but after some adjustments I got what I wanted it down scales when needed and maxes out at a scale of 1.
The canvas size you desire should be set as width/height var optimal_dimensions = [1000,1000];
this needs some more changes to handle height as well so when a user rotates there phone to landscape it may become to big to see it all on the screen
the new edited code will be at 1000 pixels in width and height for this demo
update: added detection for height and width to scale to window...
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="1000" height="1000"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.0.0/fabric.min.js"></script>
<script>
var canvas = new fabric.Canvas('canvas');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.item(0).set({
borderColor: 'gray',
cornerColor: 'black',
cornerSize: 12,
transparentCorners: true
});
canvas.setActiveObject(canvas.item(0));
canvas.renderAll();
function rescale_canvas_if_needed(){
var optimal_dimensions = [400,400];
var scaleFactorX=window.innerWidth/optimal_dimensions[0];
var scaleFactorY=window.innerHeight/optimal_dimensions[1];
if(scaleFactorX < scaleFactorY && scaleFactorX < 1) {
canvas.setWidth(optimal_dimensions[0] *scaleFactorX);
canvas.setHeight(optimal_dimensions[1] *scaleFactorX);
canvas.setZoom(scaleFactorX);
} else if(scaleFactorX > scaleFactorY && scaleFactorY < 1){
canvas.setWidth(optimal_dimensions[0] *scaleFactorY);
canvas.setHeight(optimal_dimensions[1] *scaleFactorY);
canvas.setZoom(scaleFactorY);
}else {
canvas.setWidth(optimal_dimensions[0] );
canvas.setHeight(optimal_dimensions[1] );
canvas.setZoom(1);
}
canvas.calcOffset();
canvas.renderAll();
}
function handle_resize(){
$(".canvas-container").hide();
rescale_canvas_if_needed();
$(".canvas-container").show();
}
var resizeId = null;
$(function() {
$(window).resize(function() {
if(resizeId != null)
clearTimeout(resizeId);
resizeId = setTimeout(handle_resize, 500);
});
console.log( "ready!" );
/* auto size it right away... */
resizeId = setTimeout(handle_resize, 500);
});
</script>
hope this helps someone...good day.

Resize the canvas depending on the size of an element with jQuery.
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.setHeight(jQuery('#image').height());
canvas.setWidth(jQuery('#image').width());
canvas.renderAll();
}
resizeCanvas();

Related

How to create and set a setMapTypeId using react-google-maps

I was looking for a way to create my own mars map in a website, using google maps.
I found this example in google map api
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 0, lng: 0},
zoom: 1,
streetViewControl: false,
mapTypeControlOptions: {
mapTypeIds: ['moon']
}
});
var moonMapType = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
return '//mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw' +
'/' + zoom + '/' + normalizedCoord.x + '/' +
(bound - normalizedCoord.y - 1) + '.jpg';
},
tileSize: new google.maps.Size(256, 256),
maxZoom: 9,
minZoom: 0,
radius: 1738000,
name: 'Moon'
});
map.mapTypes.set('moon', moonMapType);
map.setMapTypeId('moon');
}
// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
var y = coord.y;
var x = coord.x;
// tile range in one direction range is dependent on zoom level
// 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
var tileRange = 1 << zoom;
// don't repeat across y-axis (vertically)
if (y < 0 || y >= tileRange) {
return null;
}
// repeat across x-axis
if (x < 0 || x >= tileRange) {
x = (x % tileRange + tileRange) % tileRange;
}
return {x: x, y: y};
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap">
</script>
https://jsfiddle.net/dobleuber/319kgLh4/
It works perfect, but I would like to create the same thing with react using react-google-maps.
I looked out in the react-google-maps code but I only see getters no setters for the map props:
getMapTypeId, getStreetView, ect.
Is there any way to achieve this without modify the react-google-maps code?
Thanks in advance
use props mapTypeId="moon" in react-google-maps
I've found a better way to solve this that preserve the changes on re-render, leaving it here to anyone who comes here.
there is an onLoad function that exposes a map instance, we can use this to set mapTypeId instead of passing it as an option. In this way, if the user changes the map type later, it will preserve the changes on re-render.
<GoogleMap
onLoad={(map) => {
map.setMapTypeId('moon');
}}
/>

Aligning the background box with text on canvas

I am using the following code:
function measureText(text, font) {
const span = document.createElement('span');
span.appendChild(document.createTextNode(text));
Object.assign(span.style, {
font: font,
margin: '0',
padding: '0',
border: '0',
whiteSpace: 'nowrap'
});
document.body.appendChild(span);
const {width, height} = span.getBoundingClientRect();
span.remove();
return {width, height};
}
function drawTextBG(ctx, txt, font, x, y) {
ctx.save();
ctx.font = font;
ctx.textAlign = "center";
ctx.textBaseline = 'center';
ctx.fillStyle = '#FFFFFF';
var dimen = measureText(txt, font)
var height = dimen['height']
var width = dimen['width']
ctx.fillRect(x - width/2, y-height/2, width, parseInt(font, 10));
ctx.fillStyle = '#000000';
ctx.fillText(txt, x, y);
ctx.restore();
}
I get the following image where the white back ground is not perfectly capturing the text. How do I modify my code to get the text within the white background aligned perfectly.
I want to dynamically create the height of the background box also.
A working sandbox code is here
I think setting padding:"5%" could do the job, I just test it[here] (https://codesandbox.io/s/fervent-sanderson-bwl8h?fontsize=14) and looks nice.

Using D3 javascript diagram without sending id of the element

I'm trying use D3 diagram component in my Angular project. By default the svg tag used by D3 needs id, but I want to use the element itself instead of the id.
here is my angular directive code:
'use strict';
angular.module('mbCharts').directive('mbHumidity', [
'mbWebMetricsService', '$window', '$timeout', 'd3Service',
function (mbWebMetricsService ,$window, $timeout, d3Service) {
return {
//We restrict its use to an element
//as usually <bars-chart> is semantically
//more understandable
restrict: 'E',
//this is important,
//we don't want to overwrite our directive declaration
//in the HTML mark-up
replace: false,
//our data source would be an array
//passed thru chart-data attribute
template: "<svg width='97%' height='250' onclick='gauge1.update(NewValue());'></svg>",
link: function (scope, elem, attrs) {
var gauge1 = loadLiquidFillGauge(elem[0], 0);
var config1 = liquidFillGaugeDefaultSettings();
config1.circleColor = "#FF7777";
config1.textColor = "#FF4444";
config1.waveTextColor = "#FFAAAA";
config1.waveColor = "#FFDDDD";
config1.circleThickness = 0.2;
config1.textVertPosition = 0.2;
config1.waveAnimateTime = 1800;
scope.$on('mbWebMetricsService-received-data-event', function (evt, data) {
var val = Math.round(data[scope.metric]);
gauge1.update(val);
});
}
}
}
]);
and here is the code for loadLiquidFillGauge method:
function liquidFillGaugeDefaultSettings(){
return {
minValue: 0, // The gauge minimum value.
maxValue: 100, // The gauge maximum value.
circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
circleColor: "#178BCA", // The color of the outer circle.
waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
waveCount: 1, // The number of full waves per width of the wave circle.
waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
waveAnimate: true, // Controls if the wave scrolls or is static.
waveColor: "#178BCA", // The color of the fill wave.
waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
displayPercent: true, // If true, a % symbol is displayed after the value.
textColor: "#045681", // The color of the value text when the wave does not overlap it.
waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
};
}
function loadLiquidFillGauge(element, value, config) {
if(config == null) config = liquidFillGaugeDefaultSettings();
var gauge = d3.select(element);
var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
var locationX = parseInt(gauge.style("width"))/2 - radius;
var locationY = parseInt(gauge.style("height"))/2 - radius;
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
var waveHeightScale;
if(config.waveHeightScaling){
waveHeightScale = d3.scale.linear()
.range([0,config.waveHeight,0])
.domain([0,50,100]);
} else {
waveHeightScale = d3.scale.linear()
.range([config.waveHeight,config.waveHeight])
.domain([0,100]);
}
var textPixels = (config.textSize*radius/2);
var textFinalValue = parseFloat(value).toFixed(2);
var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
var percentText = config.displayPercent?"%":"";
var circleThickness = config.circleThickness * radius;
var circleFillGap = config.circleFillGap * radius;
var fillCircleMargin = circleThickness + circleFillGap;
var fillCircleRadius = radius - fillCircleMargin;
var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
var waveLength = fillCircleRadius*2/config.waveCount;
var waveClipCount = 1+config.waveCount;
var waveClipWidth = waveLength*waveClipCount;
// Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
var textRounder = function(value){ return Math.round(value); };
if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
textRounder = function(value){ return parseFloat(value).toFixed(1); };
}
if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
textRounder = function(value){ return parseFloat(value).toFixed(2); };
}
// Data for building the clip wave area.
var data = [];
for(var i = 0; i <= 40*waveClipCount; i++){
data.push({x: i/(40*waveClipCount), y: (i/(40))});
}
// Scales for drawing the outer circle.
var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);
// Scales for controlling the size of the clipping path.
var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
// Scales for controlling the position of the clipping path.
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
.domain([0,1]);
var waveAnimateScale = d3.scale.linear()
.range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
.domain([0,1]);
// Scale for controlling the position of the text within the gauge.
var textRiseScaleY = d3.scale.linear()
.range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
.domain([0,1]);
// Center the gauge within the parent SVG.
var gaugeGroup = gauge.append("g")
.attr('transform','translate('+locationX+','+locationY+')');
// Draw the outer circle.
var gaugeCircleArc = d3.svg.arc()
.startAngle(gaugeCircleX(0))
.endAngle(gaugeCircleX(1))
.outerRadius(gaugeCircleY(radius))
.innerRadius(gaugeCircleY(radius-circleThickness));
gaugeGroup.append("path")
.attr("d", gaugeCircleArc)
.style("fill", config.circleColor)
.attr('transform','translate('+radius+','+radius+')');
// Text where the wave does not overlap.
var text1 = gaugeGroup.append("text")
.text(textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.textColor)
.attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
// The clipping wave area.
var clipArea = d3.svg.area()
.x(function(d) { return waveScaleX(d.x); } )
.y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
.y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
var waveGroup = gaugeGroup.append("defs")
.append("clipPath")
.attr("id", "clipWave");
var wave = waveGroup.append("path")
.datum(data)
.attr("d", clipArea)
.attr("T", 0);
// The inner circle with the clipping wave attached.
var fillCircleGroup = gaugeGroup.append("g")
.attr("clip-path", "url(#clipWave)");
fillCircleGroup.append("circle")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", fillCircleRadius)
.style("fill", config.waveColor);
// Text where the wave does overlap.
var text2 = fillCircleGroup.append("text")
.text(textRounder(textStartValue) + percentText)
.attr("class", "liquidFillGaugeText")
.attr("text-anchor", "middle")
.attr("font-size", textPixels + "px")
.style("fill", config.waveTextColor)
.attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
// Make the value count up.
if(config.valueCountUp){
var textTween = function(){
var i = d3.interpolate(this.textContent, textFinalValue);
return function(t) { this.textContent = textRounder(i(t)) + percentText; }
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
}
// Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
if(config.waveRise){
waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
.transition()
.duration(config.waveRiseTime)
.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
.each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
} else {
waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
}
if(config.waveAnimate) animateWave();
function animateWave() {
wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
wave.transition()
.duration(config.waveAnimateTime * (1-wave.attr('T')))
.ease('linear')
.attr('transform','translate('+waveAnimateScale(1)+',0)')
.attr('T', 1)
.each('end', function(){
wave.attr('T', 0);
animateWave(config.waveAnimateTime);
});
}
function GaugeUpdater(){
this.update = function(value){
var newFinalValue = parseFloat(value).toFixed(2);
var textRounderUpdater = function(value){ return Math.round(value); };
if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
}
if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
}
var textTween = function(){
var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
};
text1.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
text2.transition()
.duration(config.waveRiseTime)
.tween("text", textTween);
var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
var waveRiseScale = d3.scale.linear()
// The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
// such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
// circle at 100%.
.range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
.domain([0,1]);
var newHeight = waveRiseScale(fillPercent);
var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
var newClipArea;
if(config.waveHeightScaling){
newClipArea = d3.svg.area()
.x(function(d) { return waveScaleX(d.x); } )
.y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
.y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
} else {
newClipArea = clipArea;
}
var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
wave.transition()
.duration(0)
.transition()
.duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
.ease('linear')
.attr('d', newClipArea)
.attr('transform','translate('+newWavePosition+',0)')
.attr('T','1')
.each("end", function(){
if(config.waveAnimate){
wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
animateWave(config.waveAnimateTime);
}
});
waveGroup.transition()
.duration(config.waveRiseTime)
.attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
}
}
return new GaugeUpdater();
}
Do it like this to select the SVG within the element:
var gauge = d3.select(element).select("svg");

D3 area graph animation

I have an area graph ( see js fiddle https://jsfiddle.net/o7df3tyn/ ) I want to animate this area graph. I tried the approach in this
question , but this doesnt seem to help because I have more line graphs in the the same svg element
var numberOfDays = 30;
var vis = d3.select('#visualisation'),
WIDTH = 1000,
HEIGHT = 400,
MARGINS = {
top: 20,
right: 20,
bottom: 20,
left: 50
};
var drawArea = function (data) {
var areaData = data;
// var areaData = data.data;
var xRange = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([0, numberOfDays + 1]),
yRange = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([_.min(areaData), _.max(areaData)]);
var area = d3.svg.area()
.interpolate("monotone")
.x(function(d) {
return xRange(areaData.indexOf(d));
})
.y0(HEIGHT)
.y1(function(d) {
return yRange(d);
});
var path = vis.append("path")
.datum(areaData)
.attr("fill", 'lightgrey')
.attr("d", area);
};
var data = [1088,978,1282,755,908,1341,616,727,1281,247,1188,11204,556,15967,623,681,605,7267,4719,9665,5719,5907,3520,1286,1368,3243,2451,1674,1357,7414,2726]
drawArea(data);
So I cant use the curtain approach.
I want to animate the area from bottom.
Any ideas / explanations ?
Just in case anyone else stuck in the same problem, #thatOneGuy nailed the exact problem. My updated fiddle is here https://jsfiddle.net/sahils/o7df3tyn/14/
https://jsfiddle.net/DavidGuan/o7df3tyn/2/
vis.append("clipPath")
.attr("id", "rectClip")
.append("rect")
.attr("width", 0)
.attr("height", HEIGHT);
You can have a try now.
Remember add clip-path attr to the svg elements you want to hide
In this case
var path = vis.append("path")
.datum(areaData)
.attr("fill", 'lightgrey')
.attr("d", area)
.attr("clip-path", "url(#rectClip)")
Update:
If we want to grow the area from bottom:
vis.append("clipPath")
.attr("id", "rectClip")
.append("rect")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.attr("transform", "translate(0," + HEIGHT + ")")
d3.select("#rectClip rect")
.transition().duration(6000)
.attr("transform", "translate(0," + 0 + ")")
The other answer is okay but this doesn't animate the graph.
Here is how I would do it.
I would add an animation tween to the path so it tweens from 0 to the point on the path.
Something like so :
//create an array of 0's the same size as your current array :
var startData = areaData.map(function(datum) {
return 0;
});
//use this and tween between startData and data
var path = vis.append("path")
.datum(startdata1)
.attr("fill", 'lightgrey')
.attr("d", area)
.transition()
.duration(1500)
.attrTween('d', function() {
var interpolator = d3.interpolateArray(startData, areaData );
return function(t) {
return area(interpolator(t));
}
});
The reason why yours wasn't working was because of this line :
.x(function(d) {
return xRange(areaData.indexOf(d));
})
d at this point is a value between 0 and the current piece of data, so areaData.indexOf(d) will not work.
Just change this :
.x(function(d,i) {
return xRange(i);
})
This will increment along the x axis :)
Updated fiddle : https://jsfiddle.net/thatOneGuy/o7df3tyn/17/

ng-grid - Keep overall width the same when altering column widths

I have an ng-grid with 6 columns in it, and as a default set up each column is 100px, so the grid itself is 600px. The columns are resizable but I want to keep the overall grid width the same, to ensure that there are no horizontal scroll bars. So, for example, if I change the second columns width to 150px I would want the overall width to stay at 600px (so maybe an adjacent cell will change size to 50px) - this way I don't get a scroll bar.
Does anybody know if there is a plugin that can do this/help me accomplish this?
I've included a plunker: http://plnkr.co/edit/4LRHQPg7w2eDMBafvy6b?p=preview
In this example, I would want to keep the table width at 600px, so if I expand "Field 2" you will see "Field 4" go off the edge of the viewable area for the grid and a horizontal scroll bar appear. The behaviour I want is for a different column (probably the adjacent column - "Field 3") to shrink in size automatically, so that the grid stays at 600px and the horizontal scroll bar doesn't appear.
After a lot of time searching the web for an answer, I started with Paul Witherspoon idea of watching the isColumnResizing property and after reading about ng-grid plugins I came up with this plugin solution:
Plunker: http://plnkr.co/edit/Aoyt73oYydIB3JmnYi9O?p=preview
Plugin code:
function anchorLastColumn () {
var self = this;
self.grid = null;
self.scope = null;
self.services = null;
self.init = function (scope, grid, services) {
self.grid = grid;
self.scope = scope;
self.services = services;
self.scope.$watch('isColumnResizing', function (newValue, oldValue) {
if (newValue === false && oldValue === true) { //on stop resizing
var gridWidth = self.grid.rootDim.outerWidth;
var viewportH = self.scope.viewportDimHeight();
var maxHeight = self.grid.maxCanvasHt;
if(maxHeight > viewportH) { // remove vertical scrollbar width
gridWidth -= self.services.DomUtilityService.ScrollW;
}
var cols = self.scope.columns;
var col = null, i = cols.length;
while(col == null && i-- > 0) {
if(cols[i].visible) {
col = cols[i]; // last column VISIBLE
}
}
var sum = 0;
for(var i = 0; i < cols.length - 1; i++) {
if(cols[i].visible) {
sum += cols[i].width;
}
}
if(sum + col.minWidth <= gridWidth) {
col.width = gridWidth - sum; // the last gets the remaining
}
}
});
}
}
and in the controller
$scope.gridOptions = {
data: 'myData',
enableColumnResize: true,
plugins: [new anchorLastColumn()],
columnDefs: $scope.columnDefs
};
It is not a perfect solution but works for me and I hope that it will help others.
I found a way to do this using a watch on the isColumnResizing property:
$scope.$watch('gridOptions.$gridScope.isColumnResizing', function (newValue, oldValue) {
if (newValue === false && oldValue === true) { //on stop resizing
$scope.ColResizeHandler($scope.gridOptions.$gridScope.columns);
}
}, true);
then I was able to resize the columns in the resize handler I created:
$scope.ColResizeHandler = function (columns) {
var origWidth;
var col1 = undefined;
var col2 = undefined;
var widthcol2;
var found = false;
var widthDiff = 0;
angular.forEach(columns, function (value) {
if (col2 == undefined && value.visible) {
if (found) {
origWidth += value.width;
col2 = value;
colSizeLimits(col2, widthDiff);
found = false;
}
if (value.origWidth != undefined && value.origWidth != value.width && col2 == undefined) {
found = true;
col1 = value;
widthDiff = value.width - value.origWidth;
origWidth = value.origWidth;
}
}
});
if (col2 == undefined) {
//this was the last visible column - don't allow resizing
col1.width = origWidth;
}
else {
//ensure limits haven't been blown to cope with reizing
if (col1.width + col2.width != origWidth) {
var diff = (col1.width + col2.width) - origWidth;
colSizeLimits(col1, diff);
}
}
col1.origWidth = col1.width;
col2.origWidth = col2.width;
}
There are 2 issues with this.
1 - if you resize and drag the column sizer outside of the grid (i.e. all the way over and out of the ng-grid viewable area) the isColumnResizing watch doesn't execute when you stop dragging and release the resizer. (I think this may be a bug in ng-grid because it does actually resize the column to where you have dragged the resizer, even if it is outside the grids viewable area, it just doesn't fire the watch code).
2 - if you avoid this issue and just drag within the viewable grid area then the columns will resize but only after you finish dragging the resizer, so the ui looks a little funny (i.e. if I expand a column then the adjacent column will not shrink until I click off the resizer and stop dragging).
I'll be working on these issues and will post any updates/fixes I find.

Resources