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.
Related
Could some one please help with d3 ticks alignment?
I want the x axis ticks label in the screenshot below to be aligned to bottom location (-2.00) in this case. I have tried quite a few options, but could not figure out.
Any help is appreciated! Thanks
My code is given at the bottom.
const xScale = d3
.scaleLinear()
.domain([
data && data[0] ? Math.min(...data[0].items.map(d => d.hour)) : 0,
data && data[0] ? Math.max(...data[0].items.map(d => d.hour)) : 0
])
.range([0, width]);
const yScale = d3
.scaleLinear()
.domain([
data && data[0] ? getDataYMin() - 2 * (Math.ceil(Math.abs(getDataYMin())/10)) : 0,
data && data[0] ? getDataYMax() + 4 * (Math.ceil(getDataYMax()/10)): 0
]).range([height, 0]);
const svgEl = d3.select(svgRef.current);
svgEl.selectAll("*").remove(); // Clear svg content before adding new elements
const svg = svgEl
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Add X grid lines with labels
const xAxis = d3.axisBottom(xScale).ticks(24).tickSize(-height + margin.bottom).tickPadding(10);
const xAxisGroup = svg.append("g").attr("transform", `translate(0, ${height - margin.bottom})`).call(xAxis);
xAxisGroup.select(".domain").remove();
xAxisGroup.selectAll(".tick:first-of-type line").attr("class", "axis_bar").attr("stroke", "black");
xAxisGroup.selectAll(".tick:not(:first-of-type) line").attr("class", "axis_y_tick").attr("stroke", "rgba(155, 155, 155, 0.5)").style("stroke-dasharray", "5 5");
xAxisGroup.selectAll("text").attr("opacity", 0.5).attr("color", "black").attr("font-size", "0.75rem");
// Add Y grid lines with labels
const yAxis = d3.axisLeft(yScale).ticks(10).tickSize(-width).tickFormat(d3.format(".2f"));
const yAxisGroup = svg.append("g").call(yAxis);
yAxisGroup.select(".domain").remove();
yAxisGroup.selectAll(".tick:first-of-type line").attr("class", "axis_bar").attr("stroke", "black");
yAxisGroup.selectAll(".tick:not(:first-of-type) line").attr("class", "axis_y_tick").attr("stroke", "rgba(155, 155, 155, 0.5)").style("stroke-dasharray", "5 5");
yAxisGroup.selectAll("text").attr("opacity", 0.5).attr("color", "black").attr("font-size", "0.75rem");
The dimensions are defined constants and are passed through property to the chart component (this is a React application)
const marginValue: Margin = {
top: 30,
right: 30,
bottom: 30,
left: 60
};
const dimensionsInput : Dimensions = {
width: 1040,
height: 400,
margin: marginValue
};
I'm using Victory Tooltips inside a Victory PieChart. The tooltip text (actually label) is quite huge.
I'm unable to break the text in an automatic manner and so the width of the tooltip overflows the container and I can't see the whole text. How do I add a max-width to the tooltip? Is it possible to break the text to multiple lines?
<VictoryPie
colorScale = { colors } // Some colors
responsive = { true }
data = { data } // Some data
innerRadius = { 100 }
labelRadius = { 100 }
labelComponent = {
<VictoryTooltip
orientation = "top"
cornerRadius = { 8 }
flyoutStyle = {{ fill: "#151515", stroke : '#313131' }}
pointerOrientation = "bottom"
flyoutPadding = { 20 }
text = { (data) => `top \n ${data.datum?.label}` }
style = { [
{fill : 'orange', stroke : 'blue', strokeWidth : '0.1%', fontSize : '20px'},
{fill : 'green', stroke : 'red', fontSize : '14px', dy : '20'}
] }
/>
}
/>
It shows up like so
How do I show the text in a smaller container?
The only way I found was to add a line break ${'\n'} or creating a CustomFlyout using svg (its on the Victory page docs) https://formidable.com/open-source/victory/guides/tooltips/#tooltips-with-victoryvoronoicontainer
I have a scatter plot graph in my reactJS app using d3 and svg. I need to display horizontal lines on y-axis below the labels. Currently, the horizontal lines are appearing beside the labels.
here's my code:
const yAxis = d3.axisLeft(yScale).tickFormat(function(d) {
if (d != minYFloor)
return d + " yds";
else return "";
})
.tickSize(-width - 20, 0, 0);
tickSize function in above code renders horizontal lines in the graph like this:
This is my css:
.axisY line {
fill: none;
stroke: #fff;
opacity: 0.3;
shape-rendering: crispEdges;
}
While I need something like this:
How do I go about achieving that?
Here is a basic example of how you can translate the ticks in the axis.
Let's suppose this running snippet:
var svg = d3.select("svg");
var scale = d3.scalePoint()
.domain([0, 50, 100, 150, 200])
.range([140, 10]);
var axis = d3.axisLeft(scale)
.tickFormat(function(d) {
return d + " yds"
})
.tickSize(-250);
var g = svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(50,0)")
.call(axis);
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
As you can see, it's a common y axis, just like yours.
To translate the ticks we first assign that axis'group a specific class (or ID):
.attr("class", "axis")
Then, we select the texts with the ticks class, and move them:
svg.selectAll(".axis .tick text")
.style("text-anchor", "start")
.attr("transform", "translate(4,-6)")
Here is the demo:
var svg = d3.select("svg");
var scale = d3.scalePoint()
.domain([0, 50, 100, 150, 200])
.range([140, 10]);
var axis = d3.axisLeft(scale)
.tickFormat(function(d) {
return d + " yds"
})
.tickSize(-250);
var g = svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(50,0)")
.call(axis);
svg.selectAll(".axis .tick text")
.style("text-anchor", "start")
.attr("transform", "translate(4,-6)")
.axis path {
stroke: none;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>
Here I'm using magic numbers, change them accordingly.
Finally, there is an important advice: unlike what your question's title suggest, move the labels, not the lines. The lines are the actual indicator of the position to the users seeing the datavis.
i'm using chartjs and i've added the plugin for adding text inside this chart. My problem is that when i define text like this:
text: ${sectorsCounter}\ Sectors it didnt show the Sectors under the sectorsCounter. My desired output is like this:
The plugin i use for adding text is:
Chart.pluginService.register({
afterUpdate(chart) {
let helpers;
let centerConfig;
let globalConfig;
let ctx;
let fontStyle;
let fontFamily;
let fontSize;
if (chart.config.options.elements.center) {
helpers = Chart.helpers;
centerConfig = chart.config.options.elements.center;
globalConfig = Chart.defaults.global;
ctx = chart.chart.ctx;
fontStyle = helpers.getValueOrDefault(
centerConfig.fontStyle, globalConfig.defaultFontStyle
);
fontFamily = helpers.getValueOrDefault(
centerConfig.fontFamily, globalConfig.defaultFontFamily
);
if (centerConfig.fontSize) {
fontSize = centerConfig.fontSize;
} else {
ctx.save();
fontSize = helpers.getValueOrDefault(centerConfig.minFontSize, 1);
ctx.restore();
}
const newChart = chart;
newChart.center = {
font: helpers.fontString(fontSize, fontStyle, fontFamily),
fillStyle: helpers.getValueOrDefault(
centerConfig.fontColor, globalConfig.defaultFontColor
)
};
}
},
afterDraw(chart) {
if (chart.center) {
const centerConfig = chart.config.options.elements.center;
const ctx = chart.chart.ctx;
ctx.save();
ctx.font = chart.center.font;
ctx.fillStyle = chart.center.fillStyle;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const centerX = (chart.chartArea.left + chart.chartArea.right) / 2;
const centerY = (chart.chartArea.top + chart.chartArea.bottom) / 2;
ctx.fillText(centerConfig.text, centerX, centerY);
ctx.restore();
}
},
});
And when i the call of Doughnut chart is:
<Doughnut
data={sectorsData}
width={250}
height={250}
options={{
legend: {
display: false
},
maintainAspectRatio: false,
responsive: true,
cutoutPercentage: 75,
elements: {
center: {
text: `${sectorsCounter}\ Sectors`,
fontColor: '#000000',
fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
fontStyle: 'normal',
minFontSize: 25,
maxFontSize: 25,
}
},
Well, I see you haven't found your desired solution yet. SO, here's the solution...
add the following code after const centerY = (chart.chartArea.top + chart.chartArea.bottom)/2
in your plugin
let helpers = Chart.helpers;
let fontSize = helpers.getValueOrDefault(centerConfig.minFontSize, 1);
let text = centerConfig.text.split('\ ');
ctx.fillText(text[0], centerX, centerY - fontSize / 2);
ctx.fillText(text[1], centerX, centerY + fontSize / 2);
See Escape notation:
let stringTemplateA = `-a
a-`;
let stringTemplateB = `-b\r\nb-`;
console.log(stringTemplateA);
console.log(stringTemplateB);
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();