问题描述
我正在生成一个包含音乐数据的 d3 极坐标图,并正在按名称过滤搜索。我在自己的组件中有一个文本输入和提交按钮,现在只想按艺术家姓名过滤我的对象。
我尝试了两种方法。一种使用 apollo 客户端缓存并在提交时更新缓存(我不确定我是否正确实施)另一种方法是在 d3 对象中编写事件侦听器以运行函数过滤器并重新渲染图形。我通过使用复选框触发 initvis 函数来通过键签名过滤我的数据并重新渲染页面上的图形(有效),从而执行了类似于重新渲染的操作。 0 是次要的 1 是主要的。截至目前,该图无法重新渲染。 graph.js 中的数据似乎不会随着 apollo 发生变异,而且我尝试过滤的方式也没有给我任何结果。谢谢!
searchbar.js
import styles from './searchbar.module.css';
import { useQuery,gql } from '@apollo/client';
import axios from 'axios';
import { songsVar } from '../lib/apolloClient'
import { setInfo } from '../redux/actions/main.js'
import { tracksVar } from '../pages/index'
const myQuery = gql`
query getsongs ($artist: String) {
songs (filter: { artists: {contains: $artist }}) {
name
artists
key
mode
tempo
releaseYear
}
}
`
export default function Searchbar({ placeholder,initialApolloState }) {
// const songs = songsVar()
// console.log('initialApolloState in searchBar',initialApolloState())
const [searchQuery,setSearchQuery] = useState('')
const { loading,error,data,refetch,client } = useQuery(myQuery,{
variables: {artist: searchQuery}
});
// if (loading) return <p>Loading...</p>;
if (error) return <p>Error :</p>;
const onChange = (e) => {
e.preventDefault();
setSearchQuery(e.target.value);
console.log('songsvar on form change -->',songsVar())
}
const handleClick = async (e) => {
e.preventDefault();
songsVar(searchQuery)
tracksVar(searchQuery)
console.log('data in SearchBar -->',data)
console.log('songsvar data in SearchBar -->',songsVar())
console.log('tracksVar data in SearchBar -->',tracksVar())
}
//GraphQL query
return (
<div>
<div className="field has-addons">
<div className="control">
<input
className="input"
data-testid="searchInput"
type="text"
id="qInput"
placeholder={placeholder}
onChange={onChange}
value={searchQuery}
/>
</div>
<div className="control ">
<button className="button" id="aeriaSearchButton" onClick={handleClick}>
{/* <FontAwesomeIcon icon={faSearch} /> */}S
</button>
</div>
</div>
</div>
);
}
graph.js(反应组件)
import { useQuery,gql } from '@apollo/client';
import axios from 'axios'
import styles from './graph.module.css';
import D3Component from '../lib/d3display';
import { songsVar } from '../lib/apolloClient'
import { tracksVar } from '../pages/index.js'
let vis;
export default function Graph({initialApolloState}) {
const songsQuery = gql`
query allSongs{
songs {
name
artists
key
mode
tempo
releaseYear
}
}`
const { data } = useQuery(songsQuery);
const currSongsVar = songsVar(data)
const currSongsVar2 = tracksVar()
// const [graphData,setGraphData] = useState(currSongsVar2);
const [width,setWidth] = useState(600);
const [height,setHeight] = useState(600);
const [active,setActive] = useState(false);
const [keySig,setKeySig] = useState('Minor');
const [bgColor,setBgColor] = useState('#360071');
const [textColor,setTextColor] = useState('beige');
const [lineColor,setLineColor] = useState('beige');
const [artist,setArtist] = useState('');
const [zoomState,setCurrentZoomState] = useState();
const refElement = useRef(null);
useEffect(handleResizeEvent,[]);
useEffect(initVis,[songsVar(),artist,active]);
useEffect(updateVisOnResize,[width,height]);
function majorOrMinor() {
if (active === false) {
setBgColor('beige')
setActive(true)
setKeySig('Major')
setTextColor('black')
setLineColor('beige')
}
else {
setBgColor('#360071') ;
setActive(false);
setKeySig('Minor');
setTextColor('beige')
setLineColor('#360071')
}
}
function handleResizeEvent() {
let resizeTimer;
const handleResize = () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
setWidth(width);
setHeight(height);
},300);
};
window.addEventListener('resize',handleResize);
return () => {
window.removeEventListener('resize',handleResize);
};
}
function initVis() {
setArtist(tracksVar())
if (currSongsVar && currSongsVar.songs.length) {
songsVar(songsVar())
const d3Props = {
data: currSongsVar.songs,width,height,textColor,backgroundColor: bgColor,lineColor,artist: currSongsVar2,onDatapointClick: setActive
};
vis = new D3Component(refElement.current,d3Props);
}
else console.log('no data')
}
function updateVisOnResize() {
vis && vis.resize(width,height);
}
return (
<div className='react-world'>
{/* <div>{keySig}</div> */}
<input type="checkBox" className={styles.checkBox} id="checkBox" name="major/minor" value="major/minor" checked={active} onChange={majorOrMinor}></input>
<div id="d3Graph" key={new Date().getTime()} ref={refElement} />
</div>
);
}
d3graph.js
import { useState } from 'react';
import * as d3 from 'd3';
import { entries,text,zoomTransform } from 'd3';
export default class D3display {
containerEl;
props;
svg;
constructor(containerEl,props) {
this.containerEl = containerEl;
this.props = props;
const { width,backgroundColor} = props;
this.svg = d3
.select(containerEl)
.append('svg')
.style('background-color',backgroundColor)
.attr('id','d3Canvas')
.attr('width',width + 20 + 20)
.attr('height',height + 20 + 20)
.attr('class','radar' + containerEl);
this.updateDatapoints();
}
updateDatapoints = () => {
//deconstructs variable
const {
svg,props: { data,artist},} = this;
const keysToString = {
8: 'c',3: 'c#',10: 'd',5: 'd#',0: 'e',7: 'f',2: 'f#',9: 'g',4: 'g#',11: 'a',6: 'a#',1: 'b',};
const apiNotesToWheelNotes = {
0: 8,1: 3,2: 10,3: 5,4: 0,5: 7,6: 2,7: 9,8: 4,9: 11,10: 6,11: 1,};
let transform;
const zoomBehavior = d3.zoom()
.scaleExtent([1,5])
.translateExtent([[0,0],height]])
.on('zoom',() => {
console.log('zoomed')
const zoomState = zoomTransform(svg.node())
//set current zoom state
console.log('zoomState -->',zoomState)
transform = d3.event.transform;
console.log('transform',transform);
// svg.attr('transform',transform.toString());
});
svg.call(zoomBehavior);
//EVENT HANDLER FOR CHECKBox
d3.select("#checkBox").on('change',modeChange)
//EVENT HANDLER FOR Searchbar
d3.select('#aeriaSearchButton').on('click',filtergraph)
let radius = 200;
let maxValue = 200;
// let maxValue = Math.max(0,d3.max(data,(i) => d3.max(i.map(o => o.value))))
// All songs/rows by key
let allAxis = data.map((i,j) => i['key']);
const removeDuplicates = (inputArr) => {
let uniqueVals = {};
return inputArr.filter((el) => {
return uniqueVals.hasOwnProperty(el) ? false : (uniqueVals[el] = true);
});
};
//select tempos
let allTempos = data.map((i,j) => i['tempo']);
// numRange creates 0-11 range of notes from data
let numRange = Object.keys(removeDuplicates(allAxis));
let total = numRange.length;
// let radius = Math.min(width / 2,height / 2)
let angleSlice = (Math.PI * 2) / total;
//scale for radius and tempo
let rScale = d3.scaleLinear().range([0,radius]).domain([0,maxValue]);
//mode range will always be constant
// g creates the area to draw on
let g = svg
.append('g')
.attr('transform',`translate(${width / 2},${height / 2})`)
// .attr('transform',"translate(" + transform + ")" + " scale(" + transform + ")");
//circular grid
let axisGrid = g.append('g').attr('class','axisWrapper');
axisGrid
.selectAll('.levels')
.data(d3.range(1,5).reverse())
.join('circle')
.attr('class','gridCircle')
.attr('r',(d,i) => (radius / 4) * d)
.style('fill','#CDCDCD')
.style('stroke','#CDCDCD')
.style('fill-opacity',0.1);
//text indicating bpm
axisGrid
.selectAll('.axisLabel')
.data(d3.range(1,5))
.join('text')
.attr('class','axisLabel')
.attr('x',(d) => (d * radius) / 4)
.attr('y',4 * Math.sin(angleSlice / 20 - Math.PI / 2))
.attr('dy','0.1em')
.style('font-size','10px')
.attr('fill','#737373')
.text((d,i) => {
return (200 * d) / 4 + 'bpm';
});
//create key line points from center
//REMOVE DUPLICATES from DATA pull filter range to be 1[7]
let axis = axisGrid
.selectAll('.axis')
.data(numRange) // [0-11]
.join('g')
.attr('class','axis');
// //append the lines
axis
.append('line')
.attr('x1',0)
.attr('y1',0)
.attr(
'x2',i) =>
rScale(maxValue * 1.1) * Math.cos(angleSlice * i - Math.PI / 2)
)
.attr(
'y2',i) =>
rScale(maxValue * 1.1) * Math.sin(angleSlice * i - Math.PI / 2)
)
.attr('class','line')
.attr('stroke',lineColor)
.attr('stroke-width','2px');
//create key names on each line
axis
.append('text')
.attr('class','legend')
.style('font-size','11px')
.style('fill',textColor)
.attr('text-anchor','middle')
.attr('dy','0.35em')
.attr(
'x',i) =>
rScale(maxValue * 1.25) * Math.cos(angleSlice * i - Math.PI / 2)
)
.attr(
'y',i) =>
rScale(maxValue * 1.25) * Math.sin(angleSlice * i - Math.PI / 2)
)
.text((d) => {
return keysToString[d];
})
.call(wrap,60);
// need tempo range 0-200 x
// need song key to be [0-11]
// songPositions are the dots on the board
let songPositions;
let newData;
function modeChange(){
let circleColor;
let textColor;
//if statements changes mode from minor 0 to major 1
if (d3.select("#checkBox").property("checked")) {
newData = data.filter((el,i) => i <= 20 && el.mode === 1)
circleColor ='#360071'
textColor = 'black'
}
else {
newData = data.filter((el,i) => i <= 20 && el.mode === 0)
circleColor = 'beige'
textColor = 'white'
};
songPositions = g
.selectAll('.songPositions')
.data(newData,(d) => d) // currently selecting less than 20 songs in DB and only songs in Major Keys
.join('g')
.attr('class','.songPositions');
songPositions
.append('circle')
.attr('class','radarCircle')
.attr('r',4)
.attr('cx',function (d,i) {
return (
rScale(d.tempo) *
Math.cos(angleSlice * apiNotesToWheelNotes[d.key] - Math.PI / 2)
);
})
.attr('cy',i) {
return (
rScale(d.tempo) *
Math.sin(angleSlice * apiNotesToWheelNotes[d.key] - Math.PI / 2)
);
})
.style('fill',circleColor)
.style('fill-opacity',0.8)
//append text label to dots
songPositions
.append('text')
.attr('class','songName')
.style('font-size','8px')
.attr('text-anchor','right')
.style('fill',textColor)
.attr('dy','1em')
.attr('dx','1em')
.attr('x',i) {
return (
rScale(d.tempo) *
Math.cos(angleSlice * apiNotesToWheelNotes[d.key] - Math.PI / 2)
);
})
.attr('y',i) {
return (
rScale(d.tempo) *
Math.sin(angleSlice * apiNotesToWheelNotes[d.key] - Math.PI / 2)
);
})
.text((d) => `${d.name} - ${d.artists}`)
.call(wrap,60);
}
modeChange()
function filtergraph(){
//artist is the variable from gql search
// let songPositions;
// let newData;
let circleColor;
let textColor;
//if statements changes mode from minor 0 to major 1
if (d3.select("#checkBox").property("checked")) {
//add artist category/variable
newData = data.filter((el) => el.artists == artist)
circleColor ='#360071'
textColor = 'black'
}
else {
//add artist category/variable
newData = data.filter((el) => {
return el.artists == artist;
})
console.log('newData false',data.filter((el) => {
return el.artists == artist;
}))
circleColor = 'beige'
textColor = 'white'
};
songPositions = g
.selectAll('.songPositions')
.data(newData,0.8)
//append text label to dots
songPositions
.append('text')
.attr('class',60);
}
};
setActiveDatapoint = (d,node) => {
d3.select(node).style('fill','yellow');
this.props.onDatapointClick(d);
};
resize = (width,height) => {
const { svg } = this;
svg.attr('width',width).attr('height',height);
svg
.selectAll('circle')
.attr('cx',() => 0.3 * width)
.attr('cy',() => 0.1 * height);
};
}
function wrap(text,width) {
text.each(function () {
var text = d3.select(this),words = text.text().split(/\s+/).reverse(),word,line = [],lineNumber = 0,lineHeight = 1.4,// ems
x = text.attr('x'),y = text.attr('y'),dy = 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);
}
}
});
} //wrap ```
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)