Public
Edited
Nov 17, 2023
Fork of Simple D3
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require('d3@7')
Insert cell
Insert cell
techData = FileAttachment("techdata.csv").csv().then(data => {
return data.map(d => ({
name: d.Name,
latitude: +d.Latitude,
longitude: +d.Longitude,
avg_rating: +d.Average_Rating,
num_ratings: +d.Number_User_Ratings,
address: d.Address
}));
});
Insert cell
Insert cell
plotVars = (
{visualWidth: 930,
visualHeight: 1200,

margin: 25,
mapFrameGeoJSON: JSON.parse(`{
"type" : "Feature",
"geometry" : {
"type" : "LineString",
"coordinates" : [[-122.54644297642132,37.989209933976475],[-121.74157680240731,37.19360698897229]]}
}`),
}
);
Insert cell
projection = d3.geoConicConformal()
.parallels([37 + 4 / 60, 38 + 26 / 60])
.rotate([120 + 30 / 60], 0)
.fitSize([plotVars.visualWidth - 2 * plotVars.margin, plotVars.visualHeight - 2 * plotVars.margin], plotVars.mapFrameGeoJSON)
Insert cell
Insert cell
Insert cell
createVisualization('business-vis')
Insert cell
async function createVisualization(targetDiv) {
let visual = d3.create('svg')
.attr('width', plotVars.visualWidth)
.attr('height', plotVars.visualHeight)
.style('background-color', 'white');

visual.append('image')
.attr('width', plotVars.visualWidth - 2 * plotVars.margin)
.attr('height', plotVars.visualHeight - 2 * plotVars.margin)
.attr('transform', `translate(${plotVars.margin},${plotVars.margin})`)
.attr('xlink:href', await FileAttachment("cs448bmap.png").url());

createDraggableCircles(visual);
plotBusinesses(visual);
createFilterSlider(visual);
document.getElementById('business-vis').innerHTML = ''; // delete previous visualization
document.getElementById('ratingFilter').value = 0; // reset the filter slider
document.getElementById('filterValue').textContent = 0;
document.getElementById(targetDiv).appendChild(visual.node()); // append new visualization
}
Insert cell
Insert cell
// this function handles the code for plotting the business and the associated mouseover events
function plotBusinesses(visual) {

visual.selectAll('.businesses')
.data(techData, d=>d.name)
.join('circle')
.attr('r', 2)
.attr('class', 'businesses')
.style('fill', 'gray')
.attr('cx', d => {
var projectedLocation = projection([d.longitude, d.latitude]);
return projectedLocation[0] + plotVars.margin;
})
.attr('cy', d => {
var projectedLocation = projection([d.longitude, d.latitude]);
return projectedLocation[1] + plotVars.margin;
})

// mouseover events
.on('mouseover', function (event, d) {
var projectedLocation = projection([d.longitude, d.latitude]);

d3.select(this)
.style('fill', 'lightpink');
visual.append('text')
.attr('class', 'nameLabel')
.attr('x', projectedLocation[0] + plotVars.margin - 10)
.attr('y', projectedLocation[1] + plotVars.margin - 10)
.text(d.name)
.style('fill', 'crimson');

if (d.avg_rating != 0) {
visual.append('text')
.attr('class', 'ratingLabel')
.attr('x', projectedLocation[0] + plotVars.margin - 10)
.attr('y', projectedLocation[1] + plotVars.margin + 12)
.style('fill', 'crimson')
.text(d.avg_rating);
}
})
.on('mouseout', function(event, d) {
visual.selectAll('.nameLabel').remove()
visual.selectAll('.ratingLabel').remove()
d3.select(this).style('fill', 'gray')
});
}
Insert cell
Insert cell
// this function creates the two large draggable and resizable circles
function createDraggableCircles(visual) {

// creating circles
const circle1 = visual.append('circle') // create blue circle
.join('circle')
.attr('cx', 250)
.attr('cy', 500)
.attr('r', 150)
.style('fill', 'lightblue')
.attr('opacity', 0.5)
.attr('id', 'circle1')
.attr('class', 'draggable');

const circle2 = visual.append('circle') // create green circle
.join('circle')
.attr('cx', 750)
.attr('cy', 500)
.attr('r', 150)
.style('fill', 'lightgreen')
.attr('opacity', 0.5)
.attr('id', 'circle2')
.attr('class', 'draggable');

// dragging

let isDragging = false;

visual.selectAll('.draggable').call(d3.drag()
.on('start', function() {
isDragging = true;
d3.select(this).attr('stroke', 'orange') // change stroke to orange when dragging
d3.select(this).attr('stroke-width', '3');
})
.on('drag', function(event) {
if (isDragging) {
d3.select(this)
.attr('cx', event.x)
.attr('cy', event.y);
}
})
.on('end', function() {
isDragging = false;
d3.select(this).attr('stroke', null);
}));

// resizing

d3.select('#circleSlider1')
.on('input', function() {
let newRadius = this.value; // get the value of the slider
d3.select('#circle1')
.attr('r', newRadius); // update radius
});

d3.select('#circleSlider2')
.on('input', function() {
let newRadius = this.value; // get the value of the slider
d3.select('#circle2')
.attr('r', newRadius); // update radius
});

}
Insert cell
Insert cell
// this function creates the filter slider for filtering by ratings
function createFilterSlider(visual) {
d3.select('#ratingFilter').on('input', function() {
var filterValue = parseFloat(this.value);
d3.select('#filterValue').text(filterValue);
visual.selectAll('.businesses')
.attr('cx', d => {
var projectedLocation = projection([d.longitude, d.latitude]);
return (d.avg_rating >= filterValue) ? (projectedLocation[0] + plotVars.margin) : -1000; // move point off the map
})
.attr('cy', d => {
var projectedLocation = projection([d.longitude, d.latitude]);
return (d.avg_rating >= filterValue) ? (projectedLocation[1] + plotVars.margin) : -1000; // move point off the map
});
});
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more