I am trying to make SVG responsive, so when window is re-sized, my svg will resize as well and fill parent div as it is when viewed first time.
Here is the fiddle - http://jsfiddle.net/nhe613kt/321/
HTML
<div id="myConta">
<div id="myContaList"></div>
</div>
JS
$(window).resize(OwResize);
function OwResize() {
$("#myConta").height(window.innerHeight - (window.innerHeight / 40));
}
var sideRectW = window.innerWidth / 20,
sideRectH = window.innerHeight / 20,
width = window.innerWidth - (window.innerWidth / 50),
height = window.innerHeight - (window.innerHeight / 40),
boxW = (width - sideRectW) / 4,
boxH = (height - sideRectH) / 4,
boxSize = boxW + boxH,
xPos1 = sideRectW,
xPos2 = boxW + sideRectW,
xPos3 = (boxW * 2) + sideRectW,
xPos4 = (boxW * 3) + sideRectW,
yPos1 = 0,
yPos2 = boxH,
yPos3 = boxH * 2,
yPos4 = boxH * 3;
var CreateRect = function (x, y, boxColor, boxId) {
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("id", "rectBox" + boxId)
.attr("width", boxW)
.attr("height", boxH)
.attr("fill", boxColor)
.attr("class", "hover_group")
.attr("preserveAspectRatio", "xMaxYMid meet")
.attr("viewBox", "0 0 " + $("#myConta").width() + $("#myConta").height())
.attr("onclick", "alert('haha');");
};
var CreateRectWithLength = function (x, y, w, h, boxColor) {
svgContainer.append("rect")
.attr("x", x)
.attr("y", y)
.attr("width", w)
.attr("height", h)
.attr("fill", boxColor);
};
var CreateText = function (x, y, text, textColor, size) {
svgContainer.append("text")
.attr("x", x)
.attr("y", y)
.attr("fill", textColor)
.attr("font-size", size)
.text(text);
};
var CreateText90 = function (x, y, text, textColor, size) {
svgContainer.append("text")
.attr("x", x)
.attr("y", y)
.attr("fill", textColor)
.attr("font-size", size)
.attr("transform", "rotate(-90," + x + 20 + ", " + y + ")")
.text(text);
};
var svgContainer = d3.select("#myConta")
.append("svg")
.attr("id", "myContasvg")
.attr("width", width)
.attr("height", height)
.attr("fill", "#2E2E2E")
.attr("float", "right")
.append("g");
CreateRectWithLength(0, 0, sideRectW, window.innerHeight, "Black");
CreateRectWithLength(0, height - sideRectH, width, sideRectH, "Black");
CreateText90(0, yPos3, "Sales", "white", 16);
CreateText(xPos3, height - sideRectH / 5, "Profit", "white", 16);
CreateText(sideRectW / 2, yPos1 + (boxH / 2), "3", "white", 12);
CreateText(sideRectW / 2, yPos2 + (boxH / 2), "2", "white", 12);
CreateText(sideRectW / 2, yPos3 + (boxH / 2), "1", "white", 12);
CreateText(sideRectW / 2, yPos4 + (boxH / 2), "0", "white", 12);
CreateText(xPos1 + (boxW / 2), height - sideRectH / 2, "0", "white", 12);
CreateText(xPos2 + (boxW / 2), height - sideRectH / 2, "1", "white", 12);
CreateText(xPos3 + (boxW / 2), height - sideRectH / 2, "2", "white", 12);
CreateText(xPos4 + (boxW / 2), height - sideRectH / 2, "3", "white", 12);
CreateRect(xPos1, yPos1, "#C0FC3E", 03);
CreateRect(xPos1, yPos2, "#60FC60", 02);
CreateRect(xPos1, yPos3, "#64FE2E", 01);
CreateRect(xPos1, yPos4, "#00FF00", 00);
CreateRect(xPos2, yPos1, "#F6FF33", 13);
CreateRect(xPos2, yPos2, "#AFFC3B", 12);
CreateRect(xPos2, yPos3, "#00FF00", 11);
CreateRect(xPos2, yPos4, "#64FE2E", 10);
CreateRect(xPos3, yPos1, "#FDB500", 23);
CreateRect(xPos3, yPos2, "#8DB723", 22);
CreateRect(xPos3, yPos3, "#AFFC3B", 21);
CreateRect(xPos3, yPos4, "#60FC60", 20);
CreateRect(xPos4, yPos1, "red", 33);
CreateRect(xPos4, yPos2, "#FDB500", 32);
CreateRect(xPos4, yPos3, "#F6FF33", 31);
CreateRect(xPos4, yPos4, "#C0FC3E", 30);
var rectContainer = d3.selectAll("#rectBox33");
var rectX = rectContainer.attr("x");
console.log(rectX);
Please Note
This is not exact what I am working on, but I tried to make it as close to working example as I could.
What I don't want
I want svg to resize and fill parent div automatically on window size.
I don't know if this is what you were after, but how is this?
Demo fiddle
You need to apply the viewBox and preserveAspectRatio attributes to your SVG. Also if you want the SVG to scale with its parent <div> then you should not set the width and height to fixed values. Instead leave them unset so that the default to the value "100%".
Related
I have an xp system and I have the percentage of how far someone is to leveling up, and I created a rectangle. but I don't know how to change the width depending on the percentage...
I have the percentage easily, but I need a way to code that percentage 0 = start point of 259, and 100 percent is 630
const rectX = 259;
const rectY = 182;
const rectWidth = 630;
const rectHeight = 38;
const cornerRadius = 20;
ctx.lineJoin = 'round';
ctx.lineWidth = cornerRadius;
ctx.strokeStyle = '#FF1700';
ctx.fillStyle = '#FF1700';
ctx.strokeRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
ctx.fillRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
You just need to multiply the width of the rectangle with percentage / 100. Here is how it would look with your sample code.
const rectX = 259;
const rectY = 182;
const rectWidth = 630;
const rectHeight = 38;
const cornerRadius = 20;
const scale = Math.min(1, Math.max(0, percentage / 100));
ctx.lineJoin = 'round';
ctx.lineWidth = cornerRadius;
ctx.strokeStyle = '#FF1700';
ctx.fillStyle = '#FF1700';
ctx.strokeRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth - cornerRadius, rectHeight - cornerRadius);
ctx.fillRect(rectX + (cornerRadius / 2), rectY + (cornerRadius / 2), rectWidth * scale - cornerRadius , rectHeight - cornerRadius);
When percentage is 100% scale will be 1 and the width of the rectangle will result in 630. When percentage is 50% scale will be 0.5 and the width will be 315 and so on.
So from what I understand you have two numbers, starts at at 259 and ends at 630, and 50% would mean the midpoint between these two numbers, 100% would mean 630 and 0% would mean 259, is that correct?
If that's the case, then you need this thing called LERP (linear interpolation),
const lerp = (start: number, end: number, percentage: number): number => {
return (((end - start) * percentage) / 100) + start;
};
OR if you want javascript, simply remove the type definitions,
The way it works is by normalizing your start to 0,
Now using this is quite simple: console.log(lerp(259, 630, 50))
which returns: 444.5
I am attempting to wrap called data into two lines of text on an svg. Right now it is displaying the text over six lines. Can anyone help with this.
function wrap(text, width, content) {
text.each(function () {
var text = d3.select(this),
words = content.split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1, // ems
x = text.attr("x"),
y = text.attr("y"),
dy = 0, //parseFloat(text.attr("dy")),
tspan = text.text(null)
.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(''));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan")
.attr("x", x)
.attr("y", y)
.attr("dy", ++lineNumber * lineHeight + dy + "em")
.text(word);
}
}
});
}
Thermometer.prototype.drawTick = function(t, label, labelColor, textOffset, width, tubeWidth, lineColor, scale, svg) {
svg.append("line")
.attr("id", label + "Line")
.attr("x1", width / 2 - tubeWidth / 2)
.attr("x2", width / 2 + tubeWidth / 2)
.attr("y1", scale(t))
.attr("y2", scale(t))
.style("stroke", lineColor)
.style("stroke-width", "2px")
.style("shape-rendering", "crispEdges");
if (label) {
svg.append("text")
.attr("x", width / 2 + tubeWidth / 2 + 15)
.attr("y", scale(t))
.attr("dy", ".5em")
.text(label)
.style("fill", labelColor)
.style("stroke", "black")
.style("font-size", "14px")
.call(wrap,30,label)
}
};
return Thermometer;
the link to my fiddle is here
https://jsfiddle.net/corcorancr/sxs5n2cw/1/
The drawTick() function is calling another function called wrap() to plot the text. As might be suggested by that name, wrap() is splitting the input text into words and wrapping it onto a new line if it gets wider than the width you pass in.
The width value is the "30" in the following line:
.call(wrap,30,label)
Try changing it to something bigger so that it doesn't wrap so soon. 180 seems to be about the right value.
https://jsfiddle.net/sxs5n2cw/4/
I've created a scatter plot in d3. The problem is that the y axis label does not appear in firefox and chrome (works fine in IE). I've tried doing things like making the svg width 100%, but for some reason the label always gets cut off.
<div id="TimeSeriesChartDiv" style="display: inline; float: right; width: 650px;
height: 415px">
</div>
//Width and height
var w = 600;
var h = 300;
var padding = 30;
var margin = { top: 20, right: 20, bottom: 30, left: 20 };
var df = d3.time.format("%b-%y");
//Create scale functions
var xScale = d3.time.scale()
.domain([d3.min(dataset, function (d) { return d[0]; }), d3.max(dataset, function (d) { return d[0]; })])
.range([padding, w - padding * 2])
.nice(5);
var yScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) { return d[1]; })])
.range([h - padding, padding]);
//Define X axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(5)
.tickFormat(df);
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5);
//Create SVG element
var svg = d3.select("#TimeSeriesChartDiv")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
//Create X axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(20," + (h - padding) + ")")
.call(xAxis);
//Create Y axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + 50 + ",0)")
.call(yAxis);
svg.append("text")
.attr("class", "axislabel")
.attr("text-anchor", "end")
.attr("x", w / 2)
.attr("y", h + 8)
.text("Date");
svg.append("text")//-->>this is the text that gets cut off
.attr("class", "axislabel")
.attr("text-anchor", "end")
.attr("x", -100)
.attr("y", -15)
//.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.text(unit);
Any ideas would be much appreciated. Thanks
You are using negative coordinates for your text, which means they get drawn outside the SVG. It seems that IE9 doesn't seem to clip thing to the SVG area, other browsers do. The best solution is to add enough padding to your graph so that your text can be drawn inside the SVG. Disabling the clipping does not seem to be supported in all browsers.
Thanks Jan -- with additional help from:
http://www.d3noob.org/2012/12/adding-axis-labels-to-d3js-graph.html
this worked:
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("class", "axislabel")
.attr("text-anchor", "middle")
.attr("x", 0 - (h / 2))
.attr("y",0)//any negative value here wouldnt display in ff or chrome
.attr("dy", "1em")
.text(unit);
I want to draw arrows on my Highcharts chart and came up with this so far. Looks nice but has problems:
higher stroke-width gives a longer arrow.
rotating the arrow will require complicated calculation like here.
If I could use a SVG marker on a Highcharts path like in this SVG tutorial drawing arrows would become much easier
My code:
renderer.path(['M', 200, 0, 'L', 200, 200,'L', 225, 200,'L',200,250,'L', 175, 200,'L', 200, 200])
.attr({
'stroke-width': 5,
stroke: 'red',fill:'red'
})
.add();
renderer.path(['M', 400, 0, 'L', 400, 200,'L', 425, 200,'L',400,250,'L', 375, 200,'L', 400, 200])
.attr({
'stroke-width': 50,
stroke: 'red',fill:'red'
})
.add();
I managed to draw arrows without using SVG marker. The arrow points exactly to the right spot, regardless of the rotation. It can even take into account the radius of the start and end point.
See fiddle
function drawArrow(startX, startY, startRadius, endX, endY, endRadius, width) {
var angle = Math.PI + Math.atan((endX - startX) / (endY - startY)),
arrowLength = 3 * width,
arrowWidth = 1.5 * width,
path = [],
startArrowX,
startArrowY,
margin = 5;
if (endY >= startY) {
//correct for circle radius
startX -= ((startRadius + margin) * Math.sin(angle));
startY -= ((startRadius + margin) * Math.cos(angle));
endX += ((endRadius + margin) * Math.sin(angle));
endY += ((endRadius + margin) * Math.cos(angle));
//correct for arrow head length
endX += (arrowLength * Math.sin(angle));
endY += (arrowLength * Math.cos(angle));
//draw arrow head
path.push('M', endX, endY);
path.push(
'L',
endX - arrowWidth * Math.cos(angle),
endY + arrowWidth * Math.sin(angle));
path.push(
endX - arrowLength * Math.sin(angle),
endY - arrowLength * Math.cos(angle));
path.push(
endX + arrowWidth * Math.cos(angle),
endY - arrowWidth * Math.sin(angle), 'Z');
} else {
//correct for circle radius
startX += ((startRadius + margin) * Math.sin(angle));
startY += ((startRadius + margin) * Math.cos(angle));
endX -= ((endRadius + margin) * Math.sin(angle));
endY -= ((endRadius + margin) * Math.cos(angle));
//correct for arrow head length
endX -= (arrowLength * Math.sin(angle));
endY -= (arrowLength * Math.cos(angle));
//draw arrow head
path.push('M', endX, endY);
path.push(
'L',
endX + arrowWidth * Math.cos(angle),
endY - arrowWidth * Math.sin(angle));
path.push(
endX + arrowLength * Math.sin(angle),
endY + arrowLength * Math.cos(angle));
path.push(
endX - arrowWidth * Math.cos(angle),
endY + arrowWidth * Math.sin(angle), 'Z');
}
renderer.path(path)
.attr({
'stroke-width': 1,
stroke: '#989898',
fill: '#989898'
}).add();
renderer.path(['M', startX, startY, 'L', endX, endY])
.attr({
'stroke-width': width,
stroke: '#989898'
}).add();
I know raphael can create paths, but not lines. I know d3 can create both.
I would like to create a box and whisker chart, similar to this one, but horizontal instead of vertical. I have json data in the form:
{
"lowestValue":"53",
"lowerQuartile":"63",
"medianValue":"73",
"upperQuartile":"80",
"highestValue":"99",
"targetValue":"80"
},
...
How can I create a (or several) box and whisker plot(s) with d34raphael or with pure raphael, so that it will display properly in IE7/IE8?
Here is a picture of the end goal:
The path is such a similar primitive that it seems like it would be easy to recreate such a graph using raw Raphael (which seems increasingly to be my preference these days). Consider such a utility function as this:
function whisker( paper, x, y, width, height, data )
{
var x1 = x + data.lowestValue * width / 100, x2 = x + data.highestValue * width / 100;
var outer_range = paper.path( [ "M", x1, y + height * 0.25, "L", x1, y + height * 0.75, "M", x1, y + height / 2, "L", x2, y + height / 2, "M", x2, y + height / 4, "L", x2, y + height * 0.75 ] ).attr( { fill : 'none', stroke: 'gray' } );
var inner_range = paper.rect( x + ( width * data.lowerQuartile / 100 ), y, width * ( data.upperQuartile - data.lowerQuartile ) / 100, height, 0 ).attr( { fill: 'lightgray', stroke: 'black' } );
var median = paper.path( [ "M", x + width * data.medianValue / 100, y, "L", x + width * data.medianValue / 100, y + height ] ).attr( { fill: 'none', stroke: 'black' } );;
var target = paper.circle( x + ( width * data.targetValue / 100 ), y + height / 2, height / 4 ).attr( { fill: 'black' } );
}
The sixth parameter is simply your json data. You would need to increment the y value for each whisker, of course. Here's the code in action on my website.