I am trying to make a dynamic pie chart, which changes on change of slider.
I have setup everything But i'am not able to introduce states in it as I am new to react. I just want that when someone slides slider, the pie chart also changes according to value of slider. Can anyone help me?
<!DOCTYPE html>
<html>
<head>
<title>React Example without Babel </title>
<script src="https://npmcdn.com/react#15.3.1/dist/react.min.js"></script>
<script src="https://npmcdn.com/react-dom#15.3.1/dist/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0-alpha1/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet">
<style>
.pie{
fill:red;
}
</style>
</head>
<body>
<div class="container">
<div id="app"></div>
<div class="col-lg-5" id="slider"></div>
<div class="col-lg-6" id="pie_chart"></div>
</div>
<script type="text/jsx">
class Header extends React.Component{
render(){
return (<div><h1 className='page-header'>{this.props.text}</h1></div>);
}
}
ReactDOM.render(<Header text='Dynamic Pie Chart'/>,document.getElementById('app'));
class Slider extends React.Component{
render(){
return (<div><input type='range' min='1' max='100' /></div>);
}
}
ReactDOM.render(<Slider />,document.getElementById('slider'));
var PropTypes = React.PropTypes;
var PieChart = React.createClass({
displayName: 'PieChart',
propTypes: {
className: PropTypes.string,
size: PropTypes.number,
slices: PropTypes.arrayOf(PropTypes.shape({
color: PropTypes.string.isRequired, // hex color
value: PropTypes.number.isRequired })).isRequired },
getDefaultProps: function getDefaultProps() {
return {
size: 200 };
},
_renderPaths: function _renderPaths() {
var radCircumference = Math.PI * 2;
var center = this.props.size / 2;
var radius = center - 1; // padding to prevent clipping
var total = this.props.slices.reduce(function (totalValue, slice) {
return totalValue + slice.value;
}, 0);
var radSegment = 0;
var lastX = radius;
var lastY = 0;
return this.props.slices.map(function (slice, index) {
var color = slice.color;
var value = slice.value;
// Should we just draw a circle?
if (value === total) {
return React.createElement('circle', {
r: radius,
cx: radius,
cy: radius,
fill: color,
key: index
});
}
if (value === 0) {
return;
}
var valuePercentage = value / total;
// Should the arc go the long way round?
var longArc = valuePercentage <= 0.5 ? 0 : 1;
radSegment += valuePercentage * radCircumference;
var nextX = Math.cos(radSegment) * radius;
var nextY = Math.sin(radSegment) * radius;
// d is a string that describes the path of the slice.
// The weirdly placed minus signs [eg, (-(lastY))] are due to the fact
// that our calculations are for a graph with positive Y values going up,
// but on the screen positive Y values go down.
var d = ['M ' + center + ',' + center, 'l ' + lastX + ',' + -lastY, 'a' + radius + ',' + radius, '0', '' + longArc + ',0', '' + (nextX - lastX) + ',' + -(nextY - lastY), 'z'].join(' ');
lastX = nextX;
lastY = nextY;
return React.createElement('path', { d: d, fill: color, key: index });
});
},
/**
* #return {Object}
*/
render: function render() {
var size = this.props.size;
var center = size / 2;
return React.createElement(
'svg',
{ viewBox: '0 0 ' + size + ' ' + size },
React.createElement(
'g',
{ transform: 'rotate(-90 ' + center + ' ' + center + ')' },
this._renderPaths()
)
);
}
});
var slices = [
{ color: '#468966', value: 180 },
{ color: '#FFF0A5', value: 180 },
];
ReactDOM.render(<PieChart slices={slices} />, document.getElementById('pie_chart'));
</script>
</body>
</html>
Hi Please check the code its working now. You have to pass your slide change value to the state. I have changed few code. Please check.
<!DOCTYPE html>
<html>
<head>
<title>React Example without Babel </title>
<script src="https://npmcdn.com/react#15.3.1/dist/react.min.js"></script>
<script src="https://npmcdn.com/react-dom#15.3.1/dist/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0-alpha1/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet">
<style>
.pie{
fill:red;
}
</style>
</head>
<body>
<div class="container">
<div id="app"></div>
<div class="col-lg-5" id="slider"></div>
<div class="col-lg-6" id="pie_chart"></div>
</div>
<script type="text/jsx">
class Header extends React.Component{
render(){
return (<div><h1 className='page-header'>{this.props.text}</h1></div>);
}
}
ReactDOM.render(<Header text='Dynamic Pie Chart'/>,document.getElementById('app'));
class Slider extends React.Component{
render(){
return (<div><input type='range' min='1' max='100' onChange = {this.props.sliderEvent} /></div>);
}
}
var PropTypes = React.PropTypes;
var PieChart = React.createClass({
displayName: 'PieChart',
propTypes: {
className: PropTypes.string,
size: PropTypes.number,
slices: PropTypes.arrayOf(PropTypes.shape({
color: PropTypes.string.isRequired, // hex color
value: PropTypes.number.isRequired })).isRequired },
getDefaultProps: function getDefaultProps() {
return {
size: 200 };
},
_renderPaths: function _renderPaths() {
var radCircumference = Math.PI * 2;
var center = this.props.size / 2;
var radius = center - 1; // padding to prevent clipping
var total = this.props.slices.reduce(function (totalValue, slice) {
return totalValue + slice.value;
}, 0);
var radSegment = 0;
var lastX = radius;
var lastY = 0;
return this.props.slices.map(function (slice, index) {
var color = slice.color;
var value = slice.value;
// Should we just draw a circle?
if (value === total) {
return React.createElement('circle', {
r: radius,
cx: radius,
cy: radius,
fill: color,
key: index
});
}
if (value === 0) {
return;
}
var valuePercentage = value / total;
// Should the arc go the long way round?
var longArc = valuePercentage <= 0.5 ? 0 : 1;
radSegment += valuePercentage * radCircumference;
var nextX = Math.cos(radSegment) * radius;
var nextY = Math.sin(radSegment) * radius;
// d is a string that describes the path of the slice.
// The weirdly placed minus signs [eg, (-(lastY))] are due to the fact
// that our calculations are for a graph with positive Y values going up,
// but on the screen positive Y values go down.
var d = ['M ' + center + ',' + center, 'l ' + lastX + ',' + -lastY, 'a' + radius + ',' + radius, '0', '' + longArc + ',0', '' + (nextX - lastX) + ',' + -(nextY - lastY), 'z'].join(' ');
lastX = nextX;
lastY = nextY;
return React.createElement('path', { d: d, fill: color, key: index });
});
},
/**
* #return {Object}
*/
render: function render() {
var size = this.props.size;
var center = size / 2;
return React.createElement(
'svg',
{ viewBox: '0 0 ' + size + ' ' + size },
React.createElement(
'g',
{ transform: 'rotate(-90 ' + center + ' ' + center + ')' },
this._renderPaths()
)
);
}
});
class App extends React.Component{
constructor(props){
super(props);
this.state = {
slices : [
{ color: '#468966', value: 180 },
{ color: '#FFF0A5', value: 180 },
]
};
//this.handleSliderEvent = this.bind.handleSliderEvent(this);
}
handleSliderEvent(e){
var slices1 = (360 * e.target.value)/100;
var slices2 = 360 - slices1;
var array1 = [
{ color: '#468966', value: slices1 },
{ color: '#FFF0A5', value: slices2 }
];
console.log(array1)
this.setState({slices: array1});
}
render(){
return(
<div>
<Slider sliderEvent = {this.handleSliderEvent.bind(this)}/>
<PieChart slices = {this.state.slices} />
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('pie_chart'));
</script>
</body>
</html>
Go through the this fiddle as well which has working version of your code.
https://jsfiddle.net/oyqzms4c/
Hi Please check the below code. It should work.
<!DOCTYPE html>
<html>
<head>
<script src="https://npmcdn.com/react#15.3.1/dist/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.16/d3.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
<script src="https://unpkg.com/react#15.0.1/dist/react-with-addons.js"></script>
<script src="https://unpkg.com/react-dom#15.0.1/dist/react-dom.js"></script>
<script src="https://npmcdn.com/rd3#0.6.3/build/public/js/react-d3.js"></script>
<link href="http://getbootstrap.com/dist/css/bootstrap.css" rel="stylesheet">
</head>
<body>
<div class="container">
<h1 class="page-header">Dynamic Pie Chart</h1>
<div id="slider" class="col-lg-6"></div>
<div id="container" class="col-lg-6">
</div>
</div>
</div>
</body>
<script type="text/babel">
class Chart extends React.Component{
render() {
var PieChart = rd3.PieChart;
//var pieData = [ {label: "First", value: 50}, {label: "Second", value: 50 }];
return (
<PieChart
data={this.props.data}
width={450}
height={400}
radius={110}
sectorBorderColor="white"
/>
)}
}
class Slider extends React.Component{
render(){
return (<div><input type='range' min='0' max='100' onChange = {this.props.sliderEvent} /></div>);
}
}
class App extends React.Component{
constructor(props){
super(props);
this.state = {
pieData : [
{label: "First", value: 50}, {label: "Second", value: 500 }
]
};
}
handleSliderEvent(e){
var slices1 = (360 * e.target.value)/100;
var slices2 = 360 - slices1;
var array1 = [
{ label: "First", value: slices1 },
{ label: "Second", value: slices2 }
];
this.setState({pieData: array1},function(){
console.log(this.state);
});
}
render(){
return(<div className="row">
<Slider sliderEvent = {this.handleSliderEvent.bind(this)}/>
<div className='col-lg-6'>
<Chart data = {this.state.pieData} />
</div>
</div>)
}
}
ReactDOM.render(<App/>, document.getElementById('container'));
</script>
</html>
When you are passing the state to your component it will be available in component as props. So you have to use that props.
Related
I have created some charts which are showing in front of another object (globe) as shown below:
how objects look like in the A-Frame inspector
but when I am using a camera and move in the scene the order of the objects change and the bar charts are not visible anymore (overlap). Not sure what causing this and how to resolve it.
Here are some screenshots from this issue:
overlay screenshot 1
overlay screenshot 2
overlay screenshot 3
and the code
const globeEntity = document.getElementById('globe');
const getAlt = d => d.MMS / 2500
//const catColor = d3.scaleOrdinal(d3.schemeCategory10.map(col => polished.transparentize(0.2, col)));
// const catColor = d3.scaleOrdinal(d3.schemeCategory10.map(col => polished.transparentize(0.2, col)));
const colorArray = ['#fb0511', '#fa4400', '#f56500', '#eb8100', '#dd9a00', '#cbb100', '#b4c700', '#97db00', '#6fed00', '#05ff34']
//https://colordesigner.io/gradient-generator
var assd = "sjdhdh"
globeEntity.setAttribute('globe', {
pointLat: 'lat',
pointLng: 'long',
pointAltitude: getAlt,
pointRadius: function(d) {
return d.Population / 3000000
},
pointColor: function(d) {
return colorArray[d.Dep]
},
labelLat: 'lat',
labelLng: 'long',
labelAltitude: d => getAlt(d),
labelDotRadius: function(d) {
return d.Population / 3000000
},
labelDotOrientation: () => 'bottom',
labelColor: function(d) {
return colorArray[d.Dep]
},
labelText: 'NAME',
labelSize: 0.05,
labelResolution: 0.15,
onHover: hoverObj => {
let label = '',
desc = '';
if (hoverObj) {
const d = hoverObj.data;
label = `${d.NAME}; Pop.:${new Intl.NumberFormat().format(d.Population)}; Dep:${d.Dep}`;
desc = `Market Share: ${d.MMS}% `
}
document.querySelector('#globe-tooltip').setAttribute('value', label);
document.querySelector('#globe-tooltip-details').setAttribute('value', desc);
// document.querySelector('#textentity').setAttribute('text', desc)
//document.querySelector('#globe-tooltip-details').setAttribute('visible', 'true');
},
onClick: clickObj => {
let label = '',
desc = '';
if (clickObj) {
const d = clickObj.data;
label = `${d.NAME}; Pop.:${new Intl.NumberFormat().format(d.Population)}; Dep:${d.Dep} clicked`;
desc = `Market Share: ${d.MMS}% Clk`
}
document.querySelector('#globe-tooltip').setAttribute('value', label);
document.querySelector('#globe-tooltip-details').setAttribute('value', desc);
}
});
fetch('https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/TAJSON.json?v=1658396524027').then(res => res.json()).then(pt => {
globeEntity.setAttribute('globe', {
pointsData: pt,
labelsData: pt
});
});
// default alpha for bars
var alpha = 0.9
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
function colorPicker(v) {
console.log(v)
if (v <= 30) {
return "#F96B4D";
} else if (v < 50 & v > 30) {
return "yellow"
} else if (v > 50) {
return "#73FA28 ";
}
}
// d3.csv('https://drive.google.com/file/d/1PT6mwPM42mZvhrLYdC8WmwEjwusr0AfZ', function(data){
var X_x = -0.23865 - 10.379
var Y_y = 2.2
var Z_z = -2.21058
d3.csv('https://docs.google.com/spreadsheets/d/e/2PACX-1vS8opHDQ6I1QQbwtS3oxSk4ZNZr5MGsqJnmRMX9xGKcClEhbkCYWP_tsQKF2Y8JWaO6FXkTyqDVNIJt/pub?gid=0&single=true&output=csv', function(data) {
let dataVariable = 'MMS'
// let dataVariable = 'Diff 2008-2012'
data.forEach(function(d) {
d[dataVariable] = +d[dataVariable];
console.log("I am in ")
console.log(d)
});
console.log(data)
let yScale = d3.scale.linear()
.range([3, 5]) //[0, 3]
.domain([0, 70]);
let xScale = d3.scale.linear()
.range([-1, 2]) //[0, 3]
.domain([0, 30]);
console.log(yScale.domain())
let scene = d3.select('a-scene')
console.log(scene)
let bars = scene.selectAll('a-box.bar')
.data(data)
.enter().append('a-box')
.classed('bar', true);
bars.attr({
// src:"https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/Person.jpg",
// normalmap:"#PersonAsset_NRM",
// normalTextureRepeat:"2 2",
position: function(d, i) {
var x = xScale((i * 2) + X_x);
var y = (yScale(d[dataVariable]) / 2) + Y_y - 1.5;
var z = Z_z
return x + " " + y + " " + z
},
width: function(d) {
return 0.07
},
depth: function(d) {
return 0.03
},
height: function(d) {
return d3.max([yScale(d[dataVariable]) - 3, 0.01])
},
opacity: alpha,
material: "shader: standard",
roughness: 0.1,
//repeat: function(d) {return (1,getRandomArbitrary(1,8))},
// scale:"1 1 1",
color: function(d) {
return colorPicker(d['MMS']); // call the color picker to get the fill.
}
});
let text = scene.selectAll('a-text .desc')
.data(data)
.enter().append('a-text')
.classed('desc', true);
text.attr({
position: function(d, i) {
var x = xScale((i * 2) + X_x)
var y = 2.2
var z = Z_z
return x + " " + y + " " + z
},
value: function(d) {
return d['AREA_NAME'].replace(' ', '\n');
},
color: '#faf443',
align: 'center',
baseline: 'top',
width: 1,
})
let numFormat = d3.format("s")
let numText = scene.selectAll('a-text .num')
.data(data)
.enter().append('a-text')
.classed('num', true);
numText.attr({
position: function(d, i) {
var x = xScale((i * 2) + X_x)
var y = yScale(d[dataVariable]) + Y_y - 2.8
var z = Z_z
return x + " " + y + " " + z
},
value: function(d) {
return numFormat(d[dataVariable]);
},
color: 'red',
align: 'center',
width: 1.5,
})
scene.append('a-text')
.attr({
position: '5 5 0',
value: 'MMS (each person is equal to 50k cutomers!)',
color: 'green',
align: 'center',
width: 5,
})
});
<meta charset="utf-8">
<title>A-Frame 3D Globe Component Example</title>
<meta name="description" content="Example for 3D Globe component."></meta>
<!-- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions-->
<!-- from https://r105.threejsfundamentals.org/threejs/lessons/threejs-load-gltf.html-->
<script src="https://r105.threejsfundamentals.org/threejs/resources/threejs/r105/three.min.js"></script>
<script src="https://r105.threejsfundamentals.org/threejs/resources/threejs/r105/js/controls/OrbitControls.js"></script>
<script src="https://r105.threejsfundamentals.org/threejs/resources/threejs/r105/js/loaders/GLTFLoader.js"></script>
<script src="https://r105.threejsfundamentals.org/threejs/../3rdparty/dat.gui.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="//unpkg.com/polished#3.5.2/dist/polished.js"></script>
<script src="//unpkg.com/aframe"></script>
<script src="//unpkg.com/aframe-extras/dist/aframe-extras.min.js"></script>
<script src="https://cdn.rawgit.com/tizzle/aframe-orbit-controls-component/v0.1.14/dist/aframe-orbit-controls-component.min.js"></script>
<script src="https://cdn.statically.io/gh/vasturiano/aframe-globe-component/c23c2a7e/dist/aframe-globe-component.min.js"></script>
<script src="https://unpkg.com/aframe-look-at-component#0.8.0/dist/aframe-look-at-component.min.js"></script>
</head>
<body>
<a-scene>
<a-assets>
<img id="skyTexture" src="https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/clear-sunny-sky.jpg?v=1657244930844">
<a-asset-item id="Person1" src="https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/scenePerson1.gltf"></a-asset-item>
<img id="skyNight" src="https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/Solarsystemscope_texture_2k_stars_milky_way.jpg?v=1658390858400">
</a-assets>
<a-entity position="0 0 0" movement-controls="fly: true; speed: 0.5">
<a-entity cursor="rayOrigin: mouse; mouseCursorStylesEnabled: true;" raycaster="objects: [globe]; interval: 100"></a-entity>
<a-entity laser-controls="hand: left" raycaster="objects: [globe]; interval: 100; lineColor: yellow; lineOpacity: 1;showLine:true "></a-entity>
<a-entity laser-controls="hand: right" raycaster="objects: [globe]; interval: 100; lineColor: red; lineOpacity: 1;showLine:true "></a-entity>
<a-entity id="globe" scale="0.1 0.1 0.1" globe="
globe-image-url: https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/8k_earth_daymap.jpg;
bump-image-url: https://upload.wikimedia.org/wikipedia/commons/f/fb/Solarsystemscope_texture_8k_earth_normal_map.tif" rotation="48.202939304356164 179.81249076149652 0.6153566719705041" position="0.43949 -0.25848 -12.17506"></a-entity>
<!-- 0 -175 0 -->
<!-- https://www.h-schmidt.net/map/download/world_shaded_43k.jpg -->
<a-camera id="cam" look-controls wasd-controls="acceleration:10; ">
<!--wsAxis:y;wsInverted:true -->
<a-cursor></a-cursor>
<a-text id="globe-tooltip" position="0 -0.4 -1" width="2" align="center" color="lavender"></a-text>
<a-text id="globe-tooltip-details" position="0 -0.5 -1" width="1" align="center" color="lavender"></a-text>
</a-camera>
</a-entity>
<a-sky src="#skyNight"></a-sky>
<a-entity id="moon" class="collidable" position="13.202 14.175 2.96" scale="0.05 0.05 0.05" globe="globe-image-url: https://cdn.glitch.global/c153e3cf-7430-444d-9897-4e97f1ef8d35/2k_moon.jpg?v=1658444439177;">
</a-entity>
<a-text id="moontooltip" position="10.5605 3.75316 -1.37718" rotation="0 -59.99999999999999 -0.5" width="22" align="center" color="lavender" text="value: 3D vis of MMS (hight), \n
Dep (Color),\n Pop (radious). \n
and TAs, \nPOC" look-at="#cam" opacity="0.8" scale="1 1 1"></a-text>
</a-scene>
and the order of objects in the scene,
You have two movement systems:
the movement-controls at the rig which has the entire globe as a child object
the wasd-controls attached to the camera which is also a child of the rig.
So when You try moving the camera, You also independantly move the globe with the movement controls, positioning the bar chart inside the globe.
Here's a glitch with the wasd-controls removed
I have a requirement, where I need to assign a different color to each path segments. I approached the problem by generating a dynamic data-driven linear Gradientas following
//-----******PROOF OF CONCEPT*******---
const color = ["red", "green", "blue", "magenta"];
const svgns = 'http://www.w3.org/2000/svg';
const svgVan = document.querySelector('svg');
const lg = document.createElementNS(svgns, 'linearGradient');
lg.setAttribute('id', 'linear1');
lg.setAttribute('x1', '0%');
lg.setAttribute('y1', '0%');
lg.setAttribute('x2', '100%');
lg.setAttribute('y2', '0%');
svgVan.appendChild(lg);
color.forEach(
(a, i) => {
const stop1 = document.createElementNS(svgns, 'stop');
stop1.setAttribute('offset', i / 4);
stop1.setAttribute('stop-color', color[i]);
lg.appendChild(stop1)
const stop2 = document.createElementNS(svgns, 'stop');
stop2.setAttribute('offset', (i + 1) / 4);
stop2.setAttribute('stop-color', color[i]);
lg.appendChild(stop2)
}
)
const rect = document.createElementNS(svgns, 'rect')
rect.setAttribute('x', '100');
rect.setAttribute('y', '100');
rect.setAttribute('width', '600');
rect.setAttribute('height', '200');
rect.setAttribute('fill', 'url(#linear1)')
rect.setAttribute('stroke', 'black');
svgVan.appendChild(rect);
//-----******Application*******---
const array = [
{ x: 0, y: 80 },
{ x: 50, y: 20 },
{ x: 100, y: 50 },
{ x: 150, y: 30 },
{ x: 200, y: 40 },
{ x: 250, y: 90 },
{ x: 300, y: null },
{ x: 350, y: null },
{ x: 400, y: 20 },
{ x: 450, y: 70 },
{ x: 500, y: 60 },
];
var result = array.reduce((acc, curr, index) => acc + curr.y, 0);
for (let i = 0; i < array.length; i++) {
if (i == 0) {
array[i].z = array[i].y
} else {
array[i].z = array[i].y + array[i - 1].z
}
}
array.forEach(
(a) => {
a.pct = a.z / result
}
)
const lnr = document.createElementNS(svgns, 'linearGradient')
lnr.setAttribute('id', 'linearTest')
lnr.setAttribute('x1', '0%')
lnr.setAttribute('y1', '0%')
lnr.setAttribute('x2', '100%')
lnr.setAttribute('y2', '0%')
svgVan.appendChild(lnr);
const colorName = ["Blue", "Brown", "Crimson", "DarkCyan", "DarkMagenta", "DarkOliveGreen", "DarkOrchid", "DarkOrange", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", "DarkSlateGrey"]
array.forEach(
(a, i, r) => {
const stop1 = document.createElementNS(svgns, 'stop');
stop1.setAttribute('offset', (i == 0) ? 0 : r[i - 1].pct);
stop1.setAttribute('stop-color', colorName[i]);
lnr.appendChild(stop1)
const stop2 = document.createElementNS(svgns, 'stop');
stop2.setAttribute('offset', a.pct);
stop2.setAttribute('stop-color', colorName[i]);
lnr.appendChild(stop2);
}
)
const pathVal = 'M' + array.filter((a) => a.y !== null).map((a, i) => a.x + ',' + a.y).join(' L')
const path = document.createElementNS(svgns, 'path');
path.setAttribute('d', pathVal);
path.setAttribute('fill', 'none')
path.setAttribute('stroke', 'url(#linearTest)')
path.setAttribute('stroke-width', '3px')
path.style.setProperty('transform', 'translate(100px, 400px)')
svgVan.appendChild(path);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
<body>
<div id="container" class="svg-container"></div>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 720">
</svg>
<!--d3 script-->
<script src="index.js"></script>
</body>
</html>
However, the code is not assigning colors as desired to the path segments and I am having a hard time detecting where it is going wrong.
Path Segments are as following
Like Robert says; It is easier to draw the lines.
For a smoother effect you need to calc gradients, and calc the direction of the gradients depending on line slope.
<svg-rainbow-line
points="0,80-50,20-100,50-150,30-200,40-250,90-300,0-350,0-400,20-450,70-500,60"
colors="red,orange,yellow,green,blue,purple"
width="20"></svg-rainbow-line>
<script>
customElements.define("svg-rainbow-line", class extends HTMLElement {
connectedCallback() {
let xaxis = [], yaxis = []; // find viewPort boundaries
let w = Number(this.getAttribute("width")||5); // stroke-width and padding
let colors = (this.getAttribute("colors")||"red,yellow,blue").split(",");
this.innerHTML = `<svg style="background:grey">${
this
.getAttribute("points")
.split("-")
.map(xy => xy.split(","))
.map(([x2, y2,stopcolor="not implemented"], idx, arr) => {
xaxis.push(~~x2); yaxis.push(~~y2);// force integers
if (idx) {
let [x1, y1,startcolor=colors.shift()] = arr[idx - 1]; // previous point
// calc gradient here
colors.push(startcolor);// cycle colors
return `<line stroke="${startcolor}" stroke-width="${w}"
stroke-linecap="round"
x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}"/>`;
}
return "";
}).join("")}</svg>`;
xaxis.sort((a, b) => a - b); yaxis.sort((a, b) => a - b); // sort by number!
let vb = `${xaxis.at(0)-w} ${yaxis.at(0)-w} ${xaxis.at(-1)+w*2} ${yaxis.at(-1)+w*2}`;
this.querySelector("svg").setAttribute("viewBox", vb);
}
})
</script>
Good day Guys,
I am trying to use a spinner that shows on the entire page when i click on Submit button. The below are the codes snippets.
This is the JS code
<script type="text/javascript"
src="#Url.Content("~/Scripts/spin.js")"></script>
<script type="text/javascript">
$(function () {
$("#searchbtn").click(function () {
$("#loading").fadeIn();
var opts = {
lines: 12, // The number of lines to draw
length: 7, // The length of each line
width: 4, // The line thickness
radius: 10, // The radius of the inner circle
color: '#000', // #rgb or #rrggbb
speed: 1, // Rounds per second
trail: 60, // Afterglow percentage
shadow: false, // Whether to render a shadow
hwaccel: false // Whether to use hardware acceleration
};
var target = document.getElementById('loading');
//var spinner = new Spinner(opts).spin(target);
var spinner = new Spin.Spinner(opts).spin(target);
});
});
This is the CSS below
#loading {
display: none;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.8);
z-index: 1000;
}
#loadingcontent {
display: table;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#loadingspinner {
display: table-cell;
vertical-align: middle;
width: 100%;
text-align: center;
font-size: larger;
padding-top: 80px;
}
Below is the DIV that holds the searching.
<div id="loading">
<div id="loadingcontent">
<p id="loadingspinner">
Searching things...
</p>
</div>
</div>
<div class="col-md-12">
<p>
#using (Html.BeginForm("AllLoanProcessed", "Transactions", new { area = "Transactions" }, FormMethod.Get))
{
<b>Search By:</b>
#Html.RadioButton("searchBy", "Account_Number", true) <text>Account Number</text>
#Html.RadioButton("searchBy", "Surname") <text> Surname </text> <br />
#Html.TextBox("search", null, new { placeholder = "Search Value", #class = "form-control" })
<br />
<input type="submit" value="Search" id="searchbtn" class="btn btn-primary btn-block" />
}
</p>
</div>
The issue is that, when i click on the search button, The spin does not load.
Am I missing something? OR any one has any other spinner method that works that will cover the whole page when running.
EDIT:
The below is the spin.js file content.
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function (t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var defaults = {
lines: 12,
length: 7,
width: 5,
radius: 10,
scale: 1.0,
corners: 1,
color: '#000',
fadeColor: 'transparent',
animation: 'spinner-line-fade-default',
rotate: 0,
direction: 1,
speed: 1,
zIndex: 2e9,
className: 'spinner',
top: '50%',
left: '50%',
shadow: '0 0 1px transparent',
position: 'absolute',
};
var Spinner = /** #class */ (function () {
function Spinner(opts) {
if (opts === void 0) { opts = {}; }
this.opts = __assign(__assign({}, defaults), opts);
}
/**
* Adds the spinner to the given target element. If this instance is already
* spinning, it is automatically removed from its previous target by calling
* stop() internally.
*/
Spinner.prototype.spin = function (target) {
this.stop();
this.el = document.createElement('div');
this.el.className = this.opts.className;
this.el.setAttribute('role', 'progressbar');
css(this.el, {
position: this.opts.position,
width: 0,
zIndex: this.opts.zIndex,
left: this.opts.left,
top: this.opts.top,
transform: "scale(" + this.opts.scale + ")",
});
if (target) {
target.insertBefore(this.el, target.firstChild || null);
}
drawLines(this.el, this.opts);
return this;
};
/**
* Stops and removes the Spinner.
* Stopped spinners may be reused by calling spin() again.
*/
Spinner.prototype.stop = function () {
if (this.el) {
if (typeof requestAnimationFrame !== 'undefined') {
cancelAnimationFrame(this.animateId);
}
else {
clearTimeout(this.animateId);
}
if (this.el.parentNode) {
this.el.parentNode.removeChild(this.el);
}
this.el = undefined;
}
return this;
};
return Spinner;
}());
export { Spinner };
/**
* Sets multiple style properties at once.
*/
function css(el, props) {
for (var prop in props) {
el.style[prop] = props[prop];
}
return el;
}
/**
* Returns the line color from the given string or array.
*/
function getColor(color, idx) {
return typeof color == 'string' ? color : color[idx % color.length];
}
/**
* Internal method that draws the individual lines.
*/
function drawLines(el, opts) {
var borderRadius = (Math.round(opts.corners * opts.width * 500) / 1000) +
'px';
var shadow = 'none';
if (opts.shadow === true) {
shadow = '0 2px 4px #000'; // default shadow
}
else if (typeof opts.shadow === 'string') {
shadow = opts.shadow;
}
var shadows = parseBoxShadow(shadow);
for (var i = 0; i < opts.lines; i++) {
var degrees = ~~(360 / opts.lines * i + opts.rotate);
var backgroundLine = css(document.createElement('div'), {
position: 'absolute',
top: -opts.width / 2 + "px",
width: (opts.length + opts.width) + 'px',
height: opts.width + 'px',
background: getColor(opts.fadeColor, i),
borderRadius: borderRadius,
transformOrigin: 'left',
transform: "rotate(" + degrees + "deg) translateX(" + opts.radius +
"px)",
});
var delay = i * opts.direction / opts.lines / opts.speed;
delay -= 1 / opts.speed; // so initial animation state will include trail
var line = css(document.createElement('div'), {
width: '100%',
height: '100%',
background: getColor(opts.color, i),
borderRadius: borderRadius,
boxShadow: normalizeShadow(shadows, degrees),
animation: 1 / opts.speed + "s linear " + delay + "s infinite " +
opts.animation,
});
backgroundLine.appendChild(line);
el.appendChild(backgroundLine);
}
}
function parseBoxShadow(boxShadow) {
var regex = /^\s*([a-zA-Z]+\s+)?(-?\d+(\.\d+)?)([a-zA-Z]*)\s+(-?\d+(\.\d+)?)
([a-zA-Z]*)(.*)$/;
var shadows = [];
for (var _i = 0, _a = boxShadow.split(','); _i < _a.length; _i++) {
var shadow = _a[_i];
var matches = shadow.match(regex);
if (matches === null) {
continue; // invalid syntax
}
var x = +matches[2];
var y = +matches[5];
var xUnits = matches[4];
var yUnits = matches[7];
if (x === 0 && !xUnits) {
xUnits = yUnits;
}
if (y === 0 && !yUnits) {
yUnits = xUnits;
}
if (xUnits !== yUnits) {
continue; // units must match to use as coordinates
}
shadows.push({
prefix: matches[1] || '',
x: x,
y: y,
xUnits: xUnits,
yUnits: yUnits,
end: matches[8],
});
}
return shadows;
}
/**
* Modify box-shadow x/y offsets to counteract rotation
*/
function normalizeShadow(shadows, degrees) {
var normalized = [];
for (var _i = 0, shadows_1 = shadows; _i < shadows_1.length; _i++) {
var shadow = shadows_1[_i];
var xy = convertOffset(shadow.x, shadow.y, degrees);
normalized.push(shadow.prefix + xy[0] + shadow.xUnits + ' ' + xy[1] +
shadow.yUnits + shadow.end);
}
return normalized.join(', ');
}
function convertOffset(x, y, degrees) {
var radians = degrees * Math.PI / 180;
var sin = Math.sin(radians);
var cos = Math.cos(radians);
return [
Math.round((x * cos + y * sin) * 1000) / 1000,
Math.round((-x * sin + y * cos) * 1000) / 1000,
];
}
I have quite a few used-defined svg markers (glyphs) (big thanks to the SO user with the name rioV8 for his help on this - and not only this -...) and ideally I would like these glyphs to get their shape from the feature properties structure.
//create feature properties
var p = {
"id": i,
"popup": "Dot_" + i,
"year": parseInt(data[i].year),
"glyphName": "square",
"size": 500 // Fixed size circle radius=~13
};
These user-defined glyphs extend L.circleMarker and for simplicity let's say that their shapes can be square or diamond. Currently, I am extending L.Class and am passing glyphName in the constructor: (feel free to criticise that, If it doesnt look nice to you)
var glyph = L.Class.extend({
initialize: function(glyphName) {
glyphName === "square"? this.type = MarkerSquare:
glyphName === "diamond"? this.type = MarkerDiamond:
this.type = L.circleMarker;
},
});
and when I need to plot the glyphs I have something like:
L.geoJson(myDots[i], {
pointToLayer: function(feature, latlng) {
var p = latlng;
var myGlyph = new glyph('diamond')
return new myGlyph.type(p, style(feature));
},
onEachFeature: onEachDot
}).addTo(map);
Can I have the shape determined by the feature properties please? Eventually, what i am trying to achieve is to merge these two lines
var myGlyph = new glyph('diamond')
return new myGlyph.type(p, style(feature));
to something like
return new myGlyph.type(p, style(feature));
That will enable me to plot different shapes, and those shapes will be determined by the input data used to populate features properties. In a similar manner that these properties are used for color or size they could now be used to set the shape.
Thanks! (Full code below)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chart</title>
<style>
html,
body {
height: 100%;
margin: 0;
}
#map {
width: 600px;
height: 600px;
}
</style>
<script src='https://d3js.org/d3.v4.min.js' type='text/javascript'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet.js"></script>
</head>
<body>
<div id="map"></div>
<script>
L.Canvas.include({
_updateMarkerDiamond: function(layer) {
if (!this._drawing || layer._empty()) {
return;
}
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 6);
this._drawnLayers[layer._leaflet_id] = layer;
ctx.beginPath();
ctx.moveTo(p.x - r, p.y);
ctx.lineTo(p.x, p.y - r);
ctx.lineTo(p.x + r, p.y);
ctx.lineTo(p.x, p.y + r);
ctx.lineTo(p.x - r, p.y);
ctx.closePath();
this._fillStroke(ctx, layer);
}
});
var MarkerDiamond = L.CircleMarker.extend({
_updatePath: function() {
this._renderer._updateMarkerDiamond(this);
}
});
L.Canvas.include({
_updateMarkerSquare: function(layer) {
if (!this._drawing || layer._empty()) {
return;
}
var p = layer._point,
ctx = this._ctx,
r = Math.max(Math.round(layer._radius), 5);
this._drawnLayers[layer._leaflet_id] = layer;
ctx.beginPath();
ctx.moveTo(p.x - r, p.y - r);
ctx.lineTo(p.x + r, p.y - r);
ctx.lineTo(p.x + r, p.y + r);
ctx.lineTo(p.x - r, p.y + r);
ctx.lineTo(p.x - r, p.y - r);
ctx.closePath();
this._fillStroke(ctx, layer);
}
});
var MarkerSquare = L.CircleMarker.extend({
_updatePath: function() {
this._renderer._updateMarkerSquare(this);
}
});
var glyph = L.Class.extend({
initialize: function(glyphName) {
glyphName === "square"? this.type = MarkerSquare:
glyphName === "diamond"? this.type = MarkerDiamond:
this.type = L.circleMarker;
},
});
var data = [];
var NumOfPoints = 100;
for (let i = 0; i < NumOfPoints; i++) {
data.push({
num: i,
x: Math.random() * 60,
y: Math.random() * 60,
year: Math.floor(100 * Math.random())
})
}
renderChart(data);
function make_dots(data) {
var arr = [];
var nest = d3.nest()
.key(function(d) {
return Math.floor(d.year / 10);
})
.entries(data);
for (var k = 0; k < nest.length; ++k) {
arr[k] = helper(nest[k].values);
}
return arr;
}
function helper(data) {
dots = {
type: "FeatureCollection",
features: []
};
for (var i = 0; i < data.length; ++i) {
x = data[i].x;
y = data[i].y;
var g = {
"type": "Point",
"coordinates": [x, y]
};
//create feature properties
var p = {
"id": i,
"popup": "Dot_" + i,
"year": parseInt(data[i].year),
//"glyphName": "square",
"size": 500 // Fixed size circle radius=~13
};
//create features with proper geojson structure
dots.features.push({
"geometry": g,
"type": "Feature",
"properties": p
});
}
return dots;
}
//create color ramp
function getColor(y) {
return y > 90 ? '#6068F0' :
y > 80 ? '#6B64DC' :
y > 70 ? '#7660C9' :
y > 60 ? '#815CB6' :
y > 50 ? '#8C58A3' :
y > 40 ? '#985490' :
y > 30 ? '#A3507C' :
y > 20 ? '#AE4C69' :
y > 10 ? '#B94856' :
y > 0 ? '#C44443' :
'#D04030';
}
//calculate radius so that resulting circles will be proportional by area
function getRadius(y) {
r = Math.sqrt(y / Math.PI)
return r;
}
var myRenderer;
//create style, with fillColor picked from color ramp
function style(feature) {
return {
radius: getRadius(feature.properties.size),
fillColor: getColor(feature.properties.year),
color: "#000",
weight: 0,
opacity: 1,
fillOpacity: 0.9,
renderer: myRenderer
};
}
//create highlight style, with darker color and larger radius
function highlightStyle(feature) {
return {
radius: getRadius(feature.properties.size) + 1.5,
fillColor: "#FFCE00",
color: "#FFCE00",
weight: 1,
opacity: 1,
fillOpacity: 0.9,
};
}
//attach styles and popups to the marker layer
function highlightDot(e) {
var layer = e.target;
dotStyleHighlight = highlightStyle(layer.feature);
layer.setStyle(dotStyleHighlight);
if (!L.Browser.ie && !L.Browser.opera) {
layer.bringToFront();
}
}
function resetDotHighlight(e) {
var layer = e.target;
dotStyleDefault = style(layer.feature);
layer.setStyle(dotStyleDefault);
}
function onEachDot(feature, layer) {
layer.on({
mouseover: highlightDot,
mouseout: resetDotHighlight
});
var popup = '<table style="width:110px"><tbody><tr><td><div><b>Marker:</b></div></td><td><div>' + feature.properties.popup +
'</div></td></tr><tr class><td><div><b>Group:</b></div></td><td><div>' + feature.properties.glyphName +
'</div></td></tr><tr><td><div><b>X:</b></div></td><td><div>' + feature.geometry.coordinates[0] +
'</div></td></tr><tr><td><div><b>Y:</b></div></td><td><div>' + feature.geometry.coordinates[1] +
'</div></td></tr></tbody></table>'
layer.bindPopup(popup);
}
function renderChart(data) {
var myDots = make_dots(data);
var minZoom = 0,
maxZoom = 15;
var map = L.map('map', {
minZoom: minZoom,
maxZoom: maxZoom
}).setView([30, 30], 3);
L.tileLayer("http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
continuousWorld: false,
minZoom: 0,
noWrap: true
}).addTo(map);
myRenderer = L.canvas({
padding: 0.5
});
// Define an array to keep layerGroups
var dotlayer = [];
//create marker layer and display it on the map
for (var i = 0; i < myDots.length; i += 1) {
dotlayer[i] = L.geoJson(myDots[i], {
pointToLayer: function(feature, latlng) {
var p = latlng;
var myGlyph = new glyph('diamond')
return new myGlyph.type(p, style(feature));
},
onEachFeature: onEachDot
}).addTo(map);
}
var cl = L.control.layers(null, {}).addTo(map);
for (j = 0; j < dotlayer.length; j += 1) {
var name = "Group " + j + "0-" + j + "9";
cl.addOverlay(dotlayer[j], name);
}
}
</script>
</body>
</html>
You need to make the shape of the marker a property of the marker and merge the render parts of the MarkerDiamond and MarkerSquare into a different marker and decide which render part to draw with an if inside the _updateMarkerXX method based on the property shape.
layer.options.shape contains the shape inside the render routine.
Or do it in the Marker routine
var Marker = L.CircleMarker.extend({
_updatePath: function() {
if (this.options.shape === "square")
this._renderer._updateMarkerSquare(this);
if (this.options.shape === "diamond")
this._renderer._updateMarkerDiamond(this);
}
});
function style(feature) {
return {
radius: getRadius(feature.properties.size),
shape: feature.properties.shape,
fillColor: getColor(feature.properties.year),
color: "#000",
weight: 0,
opacity: 1,
fillOpacity: 0.9,
renderer: myRenderer
};
}
Edit
It might be useful to time the use of Magic Numbers (Enums) instead of strings, because the compare of a number is cheaper than string compare. And Aenaon has about 300K markers, but it might be negligible.
I am having trouble calculating the height and width of slots. I am trying to render images in my Perimeter component. These images have a size 105x160. However, when I console.log the clientWidth and clientHeight, I get 0x24.
I believe my problem is related to this: In vue.js 2, measure the height of a component once slots are rendered but I still can't figure it out. I've tried using $nextTick on both the Perimeter component and the individual slot components.
In my Perimeter component, I have:
<template>
<div class="d-flex">
<slot></slot>
<div class="align-self-center">
<slot name="center-piece"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Perimeter',
mounted() {
this.distributeSlots();
},
updated() {
this.distributeSlots();
},
computed: {
centerRadius() {
return this.$slots['center-piece'][0].elm.clientWidth / 2;
},
},
methods: {
distributeSlots() {
let angle = 0;
const {
clientHeight: componentHeight,
clientWidth: componentWidth,
offsetTop: componentOffsetTop,
offsetLeft: componentOffsetLeft,
} = this.$el;
const componentXCenter = componentWidth / 2;
const componentYCenter = componentHeight / 2;
const slots = this.$slots.default.filter(slot => slot.tag) || [];
const step = (2 * Math.PI) / slots.length;
slots.forEach((slot) => {
slot.context.$nextTick(() => {
const { height, width } = slot.elm.getBoundingClientRect();
console.log(`height ${height}, width ${width}`);
const distanceFromCenterX = (this.centerRadius + componentXCenter) * Math.cos(angle);
const distanceFromCenterY = (this.centerRadius + componentYCenter) * Math.sin(angle);
const x = Math.round((componentXCenter + distanceFromCenterX + componentOffsetLeft) - (width / 2));
const y = Math.round((componentYCenter + distanceFromCenterY + componentOffsetTop) - (height / 2));
slot.elm.style.left = `${x}px`;
slot.elm.style.top = `${y}px`;
angle += step;
});
});
},
},
};
</script>
I also had my distributeSlots() method written without $nextTick:
distributeSlots() {
let angle = 0;
const {
clientHeight: componentHeight,
clientWidth: componentWidth,
offsetTop: componentOffsetTop,
offsetLeft: componentOffsetLeft,
} = this.$el;
const componentXCenter = componentWidth / 2;
const componentYCenter = componentHeight / 2;
const slots = this.$slots.default.filter(slot => slot.tag) || [];
const step = (2 * Math.PI) / slots.length;
slots.forEach((slot) => {
const { height, width } = slot.elm.getBoundingClientRect();
const distanceFromCenterX = (this.centerRadius + componentXCenter) * Math.cos(angle);
const distanceFromCenterY = (this.centerRadius + componentYCenter) * Math.sin(angle);
const x = Math.round((componentXCenter + distanceFromCenterX + componentOffsetLeft) - (width / 2));
const y = Math.round((componentYCenter + distanceFromCenterY + componentOffsetTop) - (height / 2));
slot.elm.style.left = `${x}px`;
slot.elm.style.top = `${y}px`;
angle += step;
});
},
I am passing to the Perimeter component as follows:
<template>
<perimeter>
<div v-for="(book, index) in books.slice(0, 6)" v-if="book.image" :key="book.asin" style="position: absolute">
<router-link :to="{ name: 'books', params: { isbn: book.isbn }}">
<img :src="book.image" />
</router-link>
</div>
<perimeter>
</template>
Even worse, when I console.log(slot.elm) in the forEach function and open up the array in the browser console, I see the correct clientHeight + clientWidth:
Usually in such cases it's a logical mistake, rather than an issue with the framework. So I would go with simplifying your code to the bare minimum that demonstrates your issue.
Assuming you get clientWidth and clientHeight on mounted() or afterwards, as demonstarted below, it should just work.
Avoid any timer hacks, they are the culprit for bugs that are extremely hard to debug.
<template>
<div style="min-height: 100px; min-width: 100px;">
<slot />
</div>
</template>
<script>
export default {
name: 'MyContainer',
data (){
return {
width: 0,
height: 0,
}
},
mounted (){
this.width = this.$slots["default"][0].elm.clientWidth
this.height = this.$slots["default"][0].elm.clientHeight
console.log(this.width, this.height) // => 100 100 (or more)
},
}
</script>
<style scoped lang="scss">
</style>
You can use a trick and put the code in a setTimeout method to execute it in another thread with a tiny delay:
setTimeout(() => {
// Put your code here
...
}, 80)