Price: ${d.price}
VillaBility Score: ${d.villaBilityScore}`) .style(“left”, (event.pageX + 5) + “px”) .style(“top”, (event.pageY – 28) + “px”); }) .on(“mouseout”, function(d) { d3.select(this) .attr(“opacity”, 0.6); // Reset opacity tooltip.transition().duration(500).style(“opacity”, 0); }); const tooltip = d3.select(“body”).append(“div”) .attr(“class”, “tooltip”) .style(“position”, “absolute”) .style(“text-align”, “center”) .style(“padding”, “8px”) .style(“background-color”, “rgba(0, 0, 0, 0.7)”) .style(“color”, “#fff”) .style(“border-radius”, “5px”) .style(“opacity”, 0); svg.append(“text”) .attr(“x”, (width / 2)) .attr(“y”, 0 – (margin.top / 2)) .attr(“text-anchor”, “middle”) .style(“font-size”, “16px”) .style(“font-weight”, “bold”) .text(“Ventura, CA Real Estate Data – Price vs VillaBility Score”); svg.append(“text”) .attr(“x”, (width / 2)) .attr(“y”, height – 10) .attr(“text-anchor”, “middle”) .style(“font-size”, “12px”) .style(“font-weight”, “bold”) .text(“VillaBility Score”); svg.append(“text”) .attr(“transform”, “rotate(-90)”) .attr(“x”, 0 – (height / 2)) .attr(“y”, 10) .attr(“text-anchor”, “middle”) .style(“font-size”, “12px”) .style(“font-weight”, “bold”) .text(“Price ($)”); }) .catch(error => console.error(‘Error loading the JSON data:’, error));
Ventura
// I - Website Link References for San Francisco Real Estate Data Visualizations
// Set the website link references for the visualization
const websiteLinks = {
homepage: "https://www.villaterras.com",
sanFranciscoPage: "https://www.villaterras.com/san-francisco",
dataPage: "https://www.villaterras.com/data",
contactPage: "https://www.villaterras.com/contact",
blog: "https://www.villaterras.com/blog"
};
// Adding the links to the DOM for reference and easy navigation
document.getElementById("homepage-link").href = websiteLinks.homepage;
document.getElementById("san-francisco-link").href = websiteLinks.sanFranciscoPage;
document.getElementById("data-link").href = websiteLinks.dataPage;
document.getElementById("contact-link").href = websiteLinks.contactPage;
document.getElementById("blog-link").href = websiteLinks.blog;
//SScatter plot visualization with additional filtering for affordability and price ranges
fetch('ventura_real_estate_data.json')
.then(response => response.json())
.then(data => {
const width = 1200, height = 700;
const margin = { top: 20, right: 30, bottom: 50, left: 100 };
const svg = d3.select("#scatter-plot-chart")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// X-axis: VillaBility Score
const x = d3.scaleLinear()
.domain([0, 100])
.range([0, width - margin.left - margin.right]);
// Y-axis: Price
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, '')))])
.nice()
.range([height - margin.top - margin.bottom, 0]);
svg.append("g")
.attr("transform", `translate(0,${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
// Generate the circles for the scatter plot
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => x(d.villaBilityScore))
.attr("cy", d => y(parseInt(d.price.replace(/[^\d.-]/g, ''))))
.attr("r", 8)
.attr("fill", "green")
.attr("opacity", 0.6)
.on("mouseover", function(event, d) {
d3.select(this)
.attr("opacity", 1); // Highlight on hover
tooltip.transition().duration(200).style("opacity", .9);
tooltip.html(`<strong>${d.title}</strong><br>Price: ${d.price}<br>VillaBility Score: ${d.villaBilityScore}<br>Location: ${d.location}`)
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.attr("opacity", 0.6); // Reset opacity
tooltip.transition().duration(500).style("opacity", 0);
});
// Tooltip to display details on hover
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("background-color", "rgba(0, 0, 0, 0.7)")
.style("color", "#fff")
.style("border-radius", "5px")
.style("opacity", 0);
// Title and axis labels
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Ventura, CA Real Estate Data - Price vs VillaBility Score");
svg.append("text")
.attr("x", (width / 2))
.attr("y", height - 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("VillaBility Score");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("Price ($)");
// Create a filter for the price range
const priceFilter = d3.sliderBottom()
.min(d3.min(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))))
.max(d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))))
.step(1000)
.width(300)
.ticks(5)
.default([d3.min(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))), d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, '')))])
.on('onchange', val => updateChart(val));
// Append the slider to the chart for filtering
svg.append('g')
.attr('transform', 'translate(20, 650)')
.call(priceFilter);
// Function to update the chart based on filtered data
function updateChart([minPrice, maxPrice]) {
const filteredData = data.filter(d => parseInt(d.price.replace(/[^\d.-]/g, '')) >= minPrice && parseInt(d.price.replace(/[^\d.-]/g, '')) <= maxPrice);
const circles = svg.selectAll("circle")
.data(filteredData);
circles.enter().append("circle")
.merge(circles)
.transition().duration(500)
.attr("cx", d => x(d.villaBilityScore))
.attr("cy", d => y(parseInt(d.price.replace(/[^\d.-]/g, ''))))
.attr("r", 8)
.attr("fill", "green")
.attr("opacity", 0.6);
circles.exit().remove();
}
})
.catch(error => console.error('Error loading the JSON data:', error));
// Scatter plot with data filter and interactive tooltips for real estate data
fetch('ventura_real_estate_data.json')
.then(response => response.json())
.then(data => {
const width = 1200, height = 700;
const margin = { top: 20, right: 30, bottom: 50, left: 100 };
const svg = d3.select("#scatter-plot")
.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// X-axis: VillaBility Score
const x = d3.scaleLinear()
.domain([0, 100]) // VillaBility Score
.range([0, width - margin.left - margin.right]);
// Y-axis: Price
const y = d3.scaleLinear()
.domain([0, d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, '')))]) // Price range
.nice()
.range([height - margin.top - margin.bottom, 0]);
// Append X and Y axis to the SVG container
svg.append("g")
.attr("transform", `translate(0,${height - margin.top - margin.bottom})`)
.call(d3.axisBottom(x));
svg.append("g")
.call(d3.axisLeft(y));
// Generate circles for scatter plot
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => x(d.villaBilityScore))
.attr("cy", d => y(parseInt(d.price.replace(/[^\d.-]/g, ''))))
.attr("r", 8)
.attr("fill", "green")
.attr("opacity", 0.6)
.on("mouseover", function(event, d) {
d3.select(this)
.attr("opacity", 1); // Highlight on hover
tooltip.transition().duration(200).style("opacity", .9);
tooltip.html(`<strong>${d.title}</strong><br>Price: ${d.price}<br>VillaBility Score: ${d.villaBilityScore}`)
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function(d) {
d3.select(this)
.attr("opacity", 0.6); // Reset opacity
tooltip.transition().duration(500).style("opacity", 0);
});
// Tooltip to show information on hover
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("background-color", "rgba(0, 0, 0, 0.7)")
.style("color", "#fff")
.style("border-radius", "5px")
.style("opacity", 0);
// Title and axis labels
svg.append("text")
.attr("x", (width / 2))
.attr("y", 0 - (margin.top / 2))
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Ventura, CA Real Estate Data - Price vs VillaBility Score");
svg.append("text")
.attr("x", (width / 2))
.attr("y", height - 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("VillaBility Score");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", 0 - (height / 2))
.attr("y", 10)
.attr("text-anchor", "middle")
.style("font-size", "12px")
.style("font-weight", "bold")
.text("Price ($)");
// Add dynamic filtering for data based on price range
const priceFilter = d3.sliderBottom()
.min(d3.min(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))))
.max(d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))))
.step(1000)
.width(300)
.ticks(5)
.default([d3.min(data, d => parseInt(d.price.replace(/[^\d.-]/g, ''))), d3.max(data, d => parseInt(d.price.replace(/[^\d.-]/g, '')))])
.on('onchange', val => updateChart(val));
// Append filter slider to the chart
svg.append('g')
.attr('transform', 'translate(20, 650)')
.call(priceFilter);
// Function to update the chart based on filtered data
function updateChart([minPrice, maxPrice]) {
const filteredData = data.filter(d => parseInt(d.price.replace(/[^\d.-]/g, '')) >= minPrice && parseInt(d.price.replace(/[^\d.-]/g, '')) <= maxPrice);
const circles = svg.selectAll("circle")
.data(filteredData);
circles.enter().append("circle")
.merge(circles)
.transition().duration(500)
.attr("cx", d => x(d.villaBilityScore))
.attr("cy", d => y(parseInt(d.price.replace(/[^\d.-]/g, ''))))
.attr("r", 8)
.attr("fill", "green")
.attr("opacity", 0.6);
circles.exit().remove();
}
})
.catch(error => console.error('Error loading the JSON data:', error));